Fix invoice period i18n release gate (#245)

Replace unreviewed dynamic invoice-period translation calls with a bounded literal-key contract and focused fallback coverage.
This commit is contained in:
Jeppe B
2026-08-03 07:04:48 +02:00
committed by GitHub
parent 832b362254
commit 0149e06c42
4 changed files with 163 additions and 9 deletions
@@ -25,14 +25,12 @@ import {
import { buildPossibleDuplicateGroups } from "../../utils/possibleDuplicateGroups.js";
import { isUsageOrderAttachedToOrder } from "@/components/displays/department/pos/sync/xlvaskUsageFilters.js";
import { SELFWASH_PERIOD_ALL_LIMIT } from "@/components/displays/department/pos/sync/xlvaskUsagePeriodConstants.js";
import { translateInvoicingPeriodReview } from "../../services/invoicingPeriodTranslation.js";
const period_result = ref<any>(null);
const route = useRoute();
const router = useRouter();
const tr = (key: string, fallback: string) => {
const translated = i18n.global.t(`invoicing_period.review_workspace.${key}`);
return translated === `invoicing_period.review_workspace.${key}` ? fallback : translated;
};
const tr = (key: string, fallback: string) => translateInvoicingPeriodReview(i18n.global, key, fallback);
const DRAFT_STATUS_REFRESH_INTERVAL_MS = 130_000;
let draftStatusRefreshTimer: ReturnType<typeof setInterval> | null = null;
let draftStatusRefreshInFlight = false;
@@ -0,0 +1,142 @@
const REVIEW_WORKSPACE_TRANSLATORS = {
"analytics.title": (composer, params) =>
composer.t("invoicing_period.review_workspace.analytics.title", params),
"detail.next": (composer, params) => composer.t("invoicing_period.review_workspace.detail.next", params),
"detail.open_prompt": (composer, params) =>
composer.t("invoicing_period.review_workspace.detail.open_prompt", params),
"detail.previous": (composer, params) =>
composer.t("invoicing_period.review_workspace.detail.previous", params),
"detail.selected_customer": (composer, params) =>
composer.t("invoicing_period.review_workspace.detail.selected_customer", params),
"headline.aria_label": (composer, params) =>
composer.t("invoicing_period.review_workspace.headline.aria_label", params),
"headline.booked": (composer, params) =>
composer.t("invoicing_period.review_workspace.headline.booked", params),
"headline.not_booked": (composer, params) =>
composer.t("invoicing_period.review_workspace.headline.not_booked", params),
"headline.total": (composer, params) =>
composer.t("invoicing_period.review_workspace.headline.total", params),
"navigation.agreements": (composer, params) =>
composer.t("invoicing_period.review_workspace.navigation.agreements", params),
"navigation.aria_label": (composer, params) =>
composer.t("invoicing_period.review_workspace.navigation.aria_label", params),
"navigation.billing": (composer, params) =>
composer.t("invoicing_period.review_workspace.navigation.billing", params),
"navigation.operations": (composer, params) =>
composer.t("invoicing_period.review_workspace.navigation.operations", params),
"navigation.review": (composer, params) =>
composer.t("invoicing_period.review_workspace.navigation.review", params),
"next_actions.create_invoice": (composer, params) =>
composer.t("invoicing_period.review_workspace.next_actions.create_invoice", params),
"next_actions.none": (composer, params) =>
composer.t("invoicing_period.review_workspace.next_actions.none", params),
"next_actions.resolve_collection_errors": (composer, params) =>
composer.t("invoicing_period.review_workspace.next_actions.resolve_collection_errors", params),
"next_actions.resolve_draft": (composer, params) =>
composer.t("invoicing_period.review_workspace.next_actions.resolve_draft", params),
"next_actions.resolve_manual_flags": (composer, params) =>
composer.t("invoicing_period.review_workspace.next_actions.resolve_manual_flags", params),
"next_actions.review_warnings": (composer, params) =>
composer.t("invoicing_period.review_workspace.next_actions.review_warnings", params),
"next_actions.wait_for_export": (composer, params) =>
composer.t("invoicing_period.review_workspace.next_actions.wait_for_export", params),
"queue.count": (composer, params) => composer.t("invoicing_period.review_workspace.queue.count", params),
"queue.title": (composer, params) => composer.t("invoicing_period.review_workspace.queue.title", params),
"reasons.automatic_flags": (composer, params) =>
composer.t("invoicing_period.review_workspace.reasons.automatic_flags", params),
"reasons.automatic_warnings": (composer, params) =>
composer.t("invoicing_period.review_workspace.reasons.automatic_warnings", params),
"reasons.collection_errors": (composer, params) =>
composer.t("invoicing_period.review_workspace.reasons.collection_errors", params),
"reasons.completed": (composer, params) =>
composer.t("invoicing_period.review_workspace.reasons.completed", params),
"reasons.draft": (composer, params) => composer.t("invoicing_period.review_workspace.reasons.draft", params),
"reasons.draft_blocks_action": (composer, params) =>
composer.t("invoicing_period.review_workspace.reasons.draft_blocks_action", params),
"reasons.export_in_progress": (composer, params) =>
composer.t("invoicing_period.review_workspace.reasons.export_in_progress", params),
"reasons.manual_flags": (composer, params) =>
composer.t("invoicing_period.review_workspace.reasons.manual_flags", params),
"reasons.queued": (composer, params) => composer.t("invoicing_period.review_workspace.reasons.queued", params),
"reasons.ready_to_invoice": (composer, params) =>
composer.t("invoicing_period.review_workspace.reasons.ready_to_invoice", params),
"reasons.requires_action": (composer, params) =>
composer.t("invoicing_period.review_workspace.reasons.requires_action", params),
"reasons.unbooked_transactions": (composer, params) =>
composer.t("invoicing_period.review_workspace.reasons.unbooked_transactions", params),
"severities.blue": (composer, params) =>
composer.t("invoicing_period.review_workspace.severities.blue", params),
"severities.green": (composer, params) =>
composer.t("invoicing_period.review_workspace.severities.green", params),
"severities.red": (composer, params) => composer.t("invoicing_period.review_workspace.severities.red", params),
"severities.yellow": (composer, params) =>
composer.t("invoicing_period.review_workspace.severities.yellow", params),
"sort.customer_name": (composer, params) =>
composer.t("invoicing_period.review_workspace.sort.customer_name", params),
"sort.customer_number": (composer, params) =>
composer.t("invoicing_period.review_workspace.sort.customer_number", params),
"sort.priority": (composer, params) => composer.t("invoicing_period.review_workspace.sort.priority", params),
"sort.total_amount": (composer, params) =>
composer.t("invoicing_period.review_workspace.sort.total_amount", params),
"states.attention": (composer, params) =>
composer.t("invoicing_period.review_workspace.states.attention", params),
"states.blocked": (composer, params) => composer.t("invoicing_period.review_workspace.states.blocked", params),
"states.completed": (composer, params) =>
composer.t("invoicing_period.review_workspace.states.completed", params),
"states.empty_body": (composer, params) =>
composer.t("invoicing_period.review_workspace.states.empty_body", params),
"states.empty_title": (composer, params) =>
composer.t("invoicing_period.review_workspace.states.empty_title", params),
"states.error_body": (composer, params) =>
composer.t("invoicing_period.review_workspace.states.error_body", params),
"states.error_title": (composer, params) =>
composer.t("invoicing_period.review_workspace.states.error_title", params),
"states.filtered_empty_body": (composer, params) =>
composer.t("invoicing_period.review_workspace.states.filtered_empty_body", params),
"states.filtered_empty_title": (composer, params) =>
composer.t("invoicing_period.review_workspace.states.filtered_empty_title", params),
"states.loading_body": (composer, params) =>
composer.t("invoicing_period.review_workspace.states.loading_body", params),
"states.loading_title": (composer, params) =>
composer.t("invoicing_period.review_workspace.states.loading_title", params),
"states.queued": (composer, params) => composer.t("invoicing_period.review_workspace.states.queued", params),
"states.ready": (composer, params) => composer.t("invoicing_period.review_workspace.states.ready", params),
"states.refreshing": (composer, params) =>
composer.t("invoicing_period.review_workspace.states.refreshing", params),
"states.retained_error": (composer, params) =>
composer.t("invoicing_period.review_workspace.states.retained_error", params),
"toolbar.all_departments": (composer, params) =>
composer.t("invoicing_period.review_workspace.toolbar.all_departments", params),
"toolbar.all_invoice_states": (composer, params) =>
composer.t("invoicing_period.review_workspace.toolbar.all_invoice_states", params),
"toolbar.all_severities": (composer, params) =>
composer.t("invoicing_period.review_workspace.toolbar.all_severities", params),
"toolbar.all_states": (composer, params) =>
composer.t("invoicing_period.review_workspace.toolbar.all_states", params),
"toolbar.change_direction": (composer, params) =>
composer.t("invoicing_period.review_workspace.toolbar.change_direction", params),
"toolbar.clear_filters": (composer, params) =>
composer.t("invoicing_period.review_workspace.toolbar.clear_filters", params),
"toolbar.department": (composer, params) =>
composer.t("invoicing_period.review_workspace.toolbar.department", params),
"toolbar.invoice_state": (composer, params) =>
composer.t("invoicing_period.review_workspace.toolbar.invoice_state", params),
"toolbar.reload": (composer, params) => composer.t("invoicing_period.review_workspace.toolbar.reload", params),
"toolbar.retry": (composer, params) => composer.t("invoicing_period.review_workspace.toolbar.retry", params),
"toolbar.search": (composer, params) => composer.t("invoicing_period.review_workspace.toolbar.search", params),
"toolbar.severity": (composer, params) =>
composer.t("invoicing_period.review_workspace.toolbar.severity", params),
"toolbar.sort": (composer, params) => composer.t("invoicing_period.review_workspace.toolbar.sort", params),
"toolbar.workflow": (composer, params) =>
composer.t("invoicing_period.review_workspace.toolbar.workflow", params),
};
export const translateInvoicingPeriodReview = (composer, key, fallback, params = {}) => {
const translate = REVIEW_WORKSPACE_TRANSLATORS[key];
if (typeof translate !== "function" || typeof composer?.t !== "function") {
return fallback;
}
const translated = translate(composer, params);
return translated === `invoicing_period.review_workspace.${key}` ? fallback : translated;
};
@@ -37,13 +37,11 @@ import {
getDuplicateTransactionPlate,
} from "../utils/possibleDuplicateGroups.js";
import { deriveLegacyPeriodCustomerReview } from "../services/invoicingPeriodReview.js";
import { translateInvoicingPeriodReview } from "../services/invoicingPeriodTranslation.js";
const expandedDuplicateGroupKey = ref<string | null>(null);
const tr = (key: string, fallback: string, params: Record<string, any> = {}) => {
const fullKey = `invoicing_period.review_workspace.${key}`;
const translated = i18n.global.t(fullKey, params);
return translated === fullKey ? fallback : translated;
};
const tr = (key: string, fallback: string, params: Record<string, any> = {}) =>
translateInvoicingPeriodReview(i18n.global, key, fallback, params);
const showAnalytics = ref(false);
const onClickCustomer = (customer: any) => {
@@ -3,10 +3,26 @@ import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { buildPeriodCacheKey } from "../../src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/imports/InvoicingBillingPeriodImportPaging.js";
import { deriveLegacyPeriodCustomerReview } from "../../src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/services/invoicingPeriodReview.js";
import { translateInvoicingPeriodReview } from "../../src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/services/invoicingPeriodTranslation.js";
const readProjectFile = (path) => readFileSync(fileURLToPath(new URL(`../../${path}`, import.meta.url)), "utf8");
describe("invoicing period review workspace", () => {
it("translates only the bounded review-workspace key contract", () => {
const calls = [];
const composer = {
t(key, params) {
calls.push([key, params]);
return key === "invoicing_period.review_workspace.queue.count" ? `${params.count} kunder` : key;
},
};
expect(translateInvoicingPeriodReview(composer, "queue.count", "fallback", { count: 3 })).toBe("3 kunder");
expect(translateInvoicingPeriodReview(composer, "queue.unknown", "fallback")).toBe("fallback");
expect(translateInvoicingPeriodReview(composer, "../queue.count", "fallback")).toBe("fallback");
expect(calls).toEqual([["invoicing_period.review_workspace.queue.count", { count: 3 }]]);
});
it("scopes cached pages by every server-side review and sorting input", () => {
const base = {
dateFrom: "2026-08-01",