feat: add translation key collection utility and update test for completeness

This commit is contained in:
Linus Rath
2026-03-17 20:20:44 +01:00
parent de35d1d8e8
commit 070eeeecee
3 changed files with 31 additions and 10 deletions
+3 -2
View File
@@ -804,6 +804,7 @@ export function EmailViewer({
const t = useTranslations('email_viewer');
const tNotifications = useTranslations('notifications');
const tCommon = useTranslations('common');
const tSmime = useTranslations('smime');
const tFiles = useTranslations('files');
const externalContentPolicy = useSettingsStore((state) => state.externalContentPolicy);
const mailAttachmentAction = useSettingsStore((state) => state.mailAttachmentAction);
@@ -3944,8 +3945,8 @@ export function EmailViewer({
setSmimeUnlockError(null);
}}
onSubmit={handleSmimeUnlockSubmit}
title={tCommon('smime.unlock_key')}
description={tCommon('smime.unlock_key_desc')}
title={tSmime('unlock_key')}
description={tSmime('unlock_key_desc')}
error={smimeUnlockError}
/>
+27 -8
View File
@@ -87,6 +87,22 @@ function extractUsedKeys(filePath: string): string[] {
return [...new Set(keys)];
}
function collectUsedKeysByFile(files: string[]): Map<string, string[]> {
const keyToFiles = new Map<string, string[]>();
for (const filePath of files) {
for (const key of extractUsedKeys(filePath)) {
const existing = keyToFiles.get(key) ?? [];
if (!existing.includes(filePath)) {
existing.push(filePath);
keyToFiles.set(key, existing);
}
}
}
return keyToFiles;
}
const locales = fs
.readdirSync(localesDir)
.filter((entry) => fs.statSync(path.join(localesDir, entry)).isDirectory());
@@ -120,19 +136,22 @@ describe('translations completeness', () => {
describe('translations used in source code exist in en locale', () => {
const srcDirs = ['components', 'app', 'hooks', 'lib', 'stores', 'contexts'].map((d) => path.join(rootDir, d));
const allFiles = srcDirs.flatMap((d) => getSourceFiles(d));
const usedKeys = new Set<string>();
for (const f of allFiles) {
for (const k of extractUsedKeys(f)) {
usedKeys.add(k);
}
}
const usedKeysByFile = collectUsedKeysByFile(allFiles);
const usedKeys = [...usedKeysByFile.keys()].sort();
it('all translation keys referenced in source should exist in en locale', () => {
const missing = [...usedKeys].sort().filter((key) => resolveKey(referenceData, key) === undefined);
const missing = usedKeys.filter((key) => resolveKey(referenceData, key) === undefined);
const details = missing.map((key) => {
const relativeFiles = (usedKeysByFile.get(key) ?? [])
.map((filePath) => path.relative(rootDir, filePath))
.sort();
return `${key}\n used in:\n - ${relativeFiles.join('\n - ')}`;
});
expect(
missing,
`${missing.length} translation key(s) used in source code but missing from en locale:\n${missing.join('\n')}`,
`${missing.length} translation key(s) used in source code but missing from en locale:\n${details.join('\n')}`,
).toEqual([]);
});
});
+1
View File
@@ -27,6 +27,7 @@
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"test:translations": "vitest run lib/__tests__/translations.test.ts",
"prepare": "husky",
"typecheck": "tsc --noEmit"
},