Merge pull request #419 from opastorello/fix/i18n-ko-parity

fix(i18n): sync ko.json with en.json and add a locale key parity test
This commit is contained in:
Arnas Donauskas 2026-07-29 10:07:13 +03:00 committed by GitHub
commit 0181169a92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 47 additions and 0 deletions

View File

@ -358,6 +358,7 @@
"notes": "메모",
"custom": "커스텀 필드",
"deals": "딜",
"tags": "태그",
"noTags": "태그 없음",
"noDeals": "딜 없음",
"addNotePlaceholder": "메모 추가..."
@ -1500,6 +1501,7 @@
"appearance": "화면 설정",
"whatsapp": "WhatsApp",
"templates": "템플릿",
"quick-replies": "빠른 답장",
"fields": "필드 & 태그",
"deals": "딜 & 통화",
"members": "팀원",

45
src/i18n/messages.test.ts Normal file
View File

@ -0,0 +1,45 @@
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { describe, expect, it } from 'vitest';
// Locale dictionaries are hand-maintained. English is the source of
// truth (src/i18n/request.ts falls back to en.json only when a whole
// locale file is missing — there is no per-key fallback), so a key
// that lands in en.json and not in a translation renders as a raw
// keypath for users on that locale. This guards the parity.
const MESSAGES_DIR = join(process.cwd(), 'messages');
const SOURCE_LOCALE = 'en';
const TRANSLATED_LOCALES = ['ko'];
function loadKeys(locale: string): Set<string> {
const raw = readFileSync(join(MESSAGES_DIR, `${locale}.json`), 'utf8');
const out = new Set<string>();
const walk = (node: unknown, path: string) => {
if (node && typeof node === 'object' && !Array.isArray(node)) {
for (const [k, v] of Object.entries(node)) {
walk(v, path ? `${path}.${k}` : k);
}
return;
}
out.add(path);
};
walk(JSON.parse(raw), '');
return out;
}
describe('message catalogue parity', () => {
const source = loadKeys(SOURCE_LOCALE);
it.each(TRANSLATED_LOCALES)('%s.json covers every en.json key', (locale) => {
const translated = loadKeys(locale);
const missing = [...source].filter((k) => !translated.has(k)).sort();
expect(missing, `${locale}.json is missing these keys`).toEqual([]);
});
it.each(TRANSLATED_LOCALES)('%s.json has no orphaned keys', (locale) => {
const translated = loadKeys(locale);
const orphaned = [...translated].filter((k) => !source.has(k)).sort();
expect(orphaned, `${locale}.json has keys absent from en.json`).toEqual([]);
});
});