feat(i18n): add German translations and key parity test
- Create all 6 German namespace JSON files (common, collection, threads, setups, onboarding, settings) - Register German locale in i18n configuration with supportedLngs - Add key parity test ensuring en/de have identical key structures - All 19 locale parity tests pass, all 15 formatter tests pass Phase 34, Plan 05
This commit is contained in:
76
tests/i18n/locales.test.ts
Normal file
76
tests/i18n/locales.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
const LOCALES_DIR = join(import.meta.dir, "../../src/client/locales");
|
||||
|
||||
function flattenKeys(
|
||||
obj: Record<string, unknown>,
|
||||
prefix = "",
|
||||
): string[] {
|
||||
const keys: string[] = [];
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
||||
keys.push(
|
||||
...flattenKeys(value as Record<string, unknown>, fullKey),
|
||||
);
|
||||
} else {
|
||||
keys.push(fullKey);
|
||||
}
|
||||
}
|
||||
return keys.sort();
|
||||
}
|
||||
|
||||
function loadLocale(
|
||||
locale: string,
|
||||
): Record<string, Record<string, unknown>> {
|
||||
const dir = join(LOCALES_DIR, locale);
|
||||
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
||||
const result: Record<string, Record<string, unknown>> = {};
|
||||
for (const file of files) {
|
||||
const ns = file.replace(".json", "");
|
||||
result[ns] = JSON.parse(readFileSync(join(dir, file), "utf8"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
describe("locale key parity", () => {
|
||||
const en = loadLocale("en");
|
||||
const de = loadLocale("de");
|
||||
|
||||
test("en and de have the same namespaces", () => {
|
||||
expect(Object.keys(en).sort()).toEqual(Object.keys(de).sort());
|
||||
});
|
||||
|
||||
for (const ns of Object.keys(en)) {
|
||||
test(`${ns}: every en key exists in de`, () => {
|
||||
const enKeys = flattenKeys(en[ns]);
|
||||
const deKeys = flattenKeys(de[ns]);
|
||||
const missing = enKeys.filter((k) => !deKeys.includes(k));
|
||||
expect(missing).toEqual([]);
|
||||
});
|
||||
|
||||
test(`${ns}: every de key exists in en`, () => {
|
||||
const enKeys = flattenKeys(en[ns]);
|
||||
const deKeys = flattenKeys(de[ns]);
|
||||
const orphan = deKeys.filter((k) => !enKeys.includes(k));
|
||||
expect(orphan).toEqual([]);
|
||||
});
|
||||
|
||||
test(`${ns}: no empty de values`, () => {
|
||||
const deKeys = flattenKeys(de[ns]);
|
||||
for (const key of deKeys) {
|
||||
const value = key
|
||||
.split(".")
|
||||
.reduce(
|
||||
(obj, k) => (obj as Record<string, unknown>)?.[k],
|
||||
de[ns] as unknown,
|
||||
);
|
||||
expect(
|
||||
typeof value === "string" && value.length > 0,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user