test(i18n): fail on keys a locale catalog defines twice (#2804)

JSON.parse keeps only the last of two equal keys, so a catalog could carry
duplicate entries (one of them silently unused) without any test noticing.
The text catalog test now parses every message and source-text catalog with
the TypeScript JSON parser and lists keys defined more than once.
This commit is contained in:
l0ng-ai
2026-11-12 02:40:39 +08:00
committed by GitHub
parent 684f52b3dd
commit cb010fac2f
+35
View File
@@ -620,6 +620,26 @@ const catalogs = Object.fromEntries(
.map((f) => [f.slice(0, -5), JSON.parse(readFileSync(new URL(f, TEXT_DIR), "utf8")) as Record<string, string>]),
);
/** Keys a JSON file defines more than once (at any depth); `JSON.parse` keeps only the last. */
function duplicateKeys(url: URL): string[] {
const file = ts.parseJsonText(url.pathname, readFileSync(url, "utf8"));
const duplicates: string[] = [];
const visit = (node: ts.Node, path: string) => {
if (ts.isObjectLiteralExpression(node)) {
const seen = new Set<string>();
for (const property of node.properties) {
if (!ts.isPropertyAssignment(property) || !ts.isStringLiteral(property.name)) continue;
const key = path + property.name.text;
if (seen.has(key)) duplicates.push(key);
seen.add(key);
visit(property.initializer, `${key}.`);
}
}
};
file.statements.forEach((statement) => visit(statement.expression, ""));
return duplicates;
}
/** `{name}` templates of a catalog, as `txTemplate` matches them. */
function templatePatterns(catalog: Record<string, string>): RegExp[] {
return Object.keys(catalog)
@@ -658,6 +678,21 @@ it("uses only message keys of the English catalog", () => {
expect(report(scan.keys.filter(({ text }) => !Object.hasOwn(en, text)))).toEqual([]);
});
const CATALOG_DIRS: [prefix: string, dir: URL][] = [
["", new URL("./", import.meta.url)],
["text/", TEXT_DIR],
];
it.each(
CATALOG_DIRS.flatMap(([prefix, dir]) =>
readdirSync(dir)
.filter((f) => f.endsWith(".json"))
.map((f) => ({ name: `${prefix}${f}`, url: new URL(f, dir) })),
),
)("defines each key once in $name", ({ url }) => {
expect(duplicateKeys(url)).toEqual([]);
});
describe.each(Object.keys(catalogs))("source-text catalog %s", (locale) => {
const catalog = catalogs[locale]!;
const patterns = templatePatterns(catalog);