213 lines
7.7 KiB
TypeScript
213 lines
7.7 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
const expectedReferencePaths = [
|
|
"admin.pos.reference",
|
|
"bookings.reference",
|
|
"bookings_table.reference",
|
|
"department_dashboard.wash_log.reference",
|
|
"global.payment.reference",
|
|
"objects.columns.reference",
|
|
"pos.bookings.reference",
|
|
"pos.order.reference",
|
|
"tables.bookings.reference",
|
|
"time_booking_flow.reference",
|
|
"user_dashboard.bookings.book.reference",
|
|
];
|
|
|
|
const expectedFuldfValues = {
|
|
"bookings.statuses.completed": "Fuldført",
|
|
"pos.complete": "Fuldfør",
|
|
"pos.completed": "Fuldført",
|
|
"pos.finish_and_register": "Fuldfør og registrer",
|
|
"pos.finish_and_register_new": "Fuldfør og registrer ny",
|
|
"tables.bookings.booking_id_required": "Du skal indtaste booking-id for at fuldføre vask uden vaskecertifikat",
|
|
"tables.bookings.complete_wash_without_certificate": "Fuldfør vask uden vaskecertifikat",
|
|
"tables.bookings.complete_wash_without_certificate_prompt":
|
|
"Indtast booking-id for at fuldføre vask uden vaskecertifikat",
|
|
"tables.bookings.completed": "Fuldført",
|
|
"tables.bookings.wash_completed": "Vasken er fuldført",
|
|
"superuser_invoice_distribution.errors.compare_failed": "Kunne ikke fuldføre sammenligningen",
|
|
} as const;
|
|
|
|
const expectedSelfWashValues = {
|
|
"self_wash.lane_unavailable": "Vaskebanen er ikke tilgængelig",
|
|
"self_wash.vehicle_step_missing_customer_number": "Indtast kundenummer for at fortsætte.",
|
|
"self_wash.vehicle_step_missing_department": "Vent på, at nærmeste afdeling er indlæst, før du fortsætter.",
|
|
"self_wash.vehicle_step_missing_registration": "Indtast registreringsnummer for at fortsætte.",
|
|
"self_wash.vehicle_step_missing_vehicle_type": "Vælg en tilgængelig køretøjstype for at fortsætte.",
|
|
} as const;
|
|
|
|
const removedSelfWashGuidanceText =
|
|
"Maskinvask er ikke tilgængelig for den valgte bane. Vælg manuel vask eller en anden ledig bane.";
|
|
const LINK_TOKEN_PATTERN = /@(?<modifier>\.[\p{L}]+)?:(?:\{'(?<literal>[^']+)'\}|(?<path>[\p{L}\p{N}_.-]+))/gu;
|
|
const EXACT_LINK_PATTERN = /^@(?<modifier>\.[\p{L}]+)?:(?:\{'(?<literal>[^']+)'\}|(?<path>[\p{L}\p{N}_.-]+))$/u;
|
|
|
|
function getValueAtPath(value: unknown, path: string): unknown {
|
|
return path.split(".").reduce<unknown>((current, segment) => {
|
|
if (current && typeof current === "object") {
|
|
return (current as Record<string, unknown>)[segment];
|
|
}
|
|
|
|
return undefined;
|
|
}, value);
|
|
}
|
|
|
|
function collectExactStringPaths(value: unknown, exact: string, prefix = ""): string[] {
|
|
if (value === exact) {
|
|
return [prefix];
|
|
}
|
|
|
|
if (Array.isArray(value)) {
|
|
return value.flatMap((entry, index) => collectExactStringPaths(entry, exact, `${prefix}[${index}]`));
|
|
}
|
|
|
|
if (value && typeof value === "object") {
|
|
return Object.entries(value as Record<string, unknown>).flatMap(([key, entry]) =>
|
|
collectExactStringPaths(entry, exact, prefix ? `${prefix}.${key}` : key)
|
|
);
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
function collectSubstringPaths(value: unknown, substring: string, prefix = ""): string[] {
|
|
if (typeof value === "string") {
|
|
return value.includes(substring) ? [prefix] : [];
|
|
}
|
|
|
|
if (Array.isArray(value)) {
|
|
return value.flatMap((entry, index) => collectSubstringPaths(entry, substring, `${prefix}[${index}]`));
|
|
}
|
|
|
|
if (value && typeof value === "object") {
|
|
return Object.entries(value as Record<string, unknown>).flatMap(([key, entry]) =>
|
|
collectSubstringPaths(entry, substring, prefix ? `${prefix}.${key}` : key)
|
|
);
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
}
|
|
|
|
function mergeMessages(sharedMessages: Record<string, unknown> = {}, localeMessages: Record<string, unknown> = {}) {
|
|
const mergedMessages: Record<string, unknown> = { ...sharedMessages };
|
|
|
|
for (const [key, value] of Object.entries(localeMessages)) {
|
|
if (isPlainObject(value) && isPlainObject(mergedMessages[key])) {
|
|
mergedMessages[key] = mergeMessages(mergedMessages[key] as Record<string, unknown>, value);
|
|
} else {
|
|
mergedMessages[key] = value;
|
|
}
|
|
}
|
|
|
|
return mergedMessages;
|
|
}
|
|
|
|
function applyLinkedModifier(value: string, modifier?: string): string {
|
|
switch (modifier) {
|
|
case ".capitalize":
|
|
return `${value.charAt(0).toLocaleUpperCase()}${value.slice(1)}`;
|
|
case ".upper":
|
|
return value.toLocaleUpperCase();
|
|
case ".lower":
|
|
return value.toLocaleLowerCase();
|
|
default:
|
|
return value;
|
|
}
|
|
}
|
|
|
|
function resolveLinkedString(messages: Record<string, unknown>, value: string, seen = new Set<string>()): string {
|
|
const exactLink = value.trim().match(EXACT_LINK_PATTERN);
|
|
if (exactLink) {
|
|
const target = exactLink.groups?.literal ?? exactLink.groups?.path;
|
|
if (!target || seen.has(target)) {
|
|
return value;
|
|
}
|
|
|
|
const targetValue = getValueAtPath(messages, target);
|
|
const resolved =
|
|
typeof targetValue === "string"
|
|
? resolveLinkedString(messages, targetValue, new Set([...seen, target]))
|
|
: targetValue;
|
|
|
|
return typeof resolved === "string" ? applyLinkedModifier(resolved, exactLink.groups?.modifier) : value;
|
|
}
|
|
|
|
LINK_TOKEN_PATTERN.lastIndex = 0;
|
|
return value.replace(LINK_TOKEN_PATTERN, (token: string, ...args: unknown[]) => {
|
|
const groups = args.at(-1) as { modifier?: string; literal?: string; path?: string } | undefined;
|
|
const target = groups?.literal ?? groups?.path;
|
|
if (!target || seen.has(target)) {
|
|
return token;
|
|
}
|
|
|
|
const targetValue = getValueAtPath(messages, target);
|
|
if (typeof targetValue !== "string") {
|
|
return token;
|
|
}
|
|
|
|
return applyLinkedModifier(
|
|
resolveLinkedString(messages, targetValue, new Set([...seen, target])),
|
|
groups?.modifier
|
|
);
|
|
});
|
|
}
|
|
|
|
function resolveLinkedMessages(value: unknown, messages: Record<string, unknown>): unknown {
|
|
if (typeof value === "string") {
|
|
return resolveLinkedString(messages, value);
|
|
}
|
|
|
|
if (Array.isArray(value)) {
|
|
return value.map((entry) => resolveLinkedMessages(entry, messages));
|
|
}
|
|
|
|
if (isPlainObject(value)) {
|
|
return Object.fromEntries(
|
|
Object.entries(value).map(([key, entry]) => [key, resolveLinkedMessages(entry, messages)])
|
|
);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
test.describe("Danish locale smoke", () => {
|
|
test("@smoke @pr normalizes targeted Danish locale copy in da v2 runtime locale", async ({ request }) => {
|
|
const globalResponse = await request.get("/src/i18n/generated/global-v2.json");
|
|
const localeResponse = await request.get("/src/i18n/generated/da-v2.json");
|
|
expect(globalResponse.ok()).toBe(true);
|
|
expect(localeResponse.ok()).toBe(true);
|
|
|
|
const globalV2 = (await globalResponse.json()) as Record<string, unknown>;
|
|
const localeV2 = (await localeResponse.json()) as Record<string, unknown>;
|
|
const mergedLocale = mergeMessages(
|
|
mergeMessages(
|
|
globalV2.shared as Record<string, unknown>,
|
|
(globalV2.locales as Record<string, unknown>)?.da as Record<string, unknown>
|
|
),
|
|
localeV2
|
|
);
|
|
const locale = resolveLinkedMessages(mergedLocale, mergedLocale) as Record<string, unknown>;
|
|
|
|
for (const path of expectedReferencePaths) {
|
|
expect(getValueAtPath(locale, path), path).toBe("Reference");
|
|
}
|
|
|
|
for (const [path, expectedValue] of Object.entries(expectedFuldfValues)) {
|
|
expect(getValueAtPath(locale, path), path).toBe(expectedValue);
|
|
}
|
|
|
|
for (const [path, expectedValue] of Object.entries(expectedSelfWashValues)) {
|
|
expect(getValueAtPath(locale, path), path).toBe(expectedValue);
|
|
}
|
|
|
|
expect(collectExactStringPaths(locale, "Referanse")).toEqual([]);
|
|
expect(collectExactStringPaths(locale, removedSelfWashGuidanceText)).toEqual([]);
|
|
expect(collectSubstringPaths(locale, "Fullf")).toEqual([]);
|
|
expect(collectSubstringPaths(locale, "fullf")).toEqual([]);
|
|
});
|
|
});
|