chore: tighten validation and runtime logging

* chore: tighten validation and runtime logging

* fix(i18n): localize new settings labels
This commit is contained in:
4gray authored and GitHub committed 2026-05-15 17:43:42 +02:00
1 parent d24c77a143
commit 2d5c4fa4f9
52 files changed
+767 -214

No files matched your search

+15
View File
@@ -43,6 +43,21 @@ for(const f of fs.readdirSync('apps/web/src/assets/i18n').filter(x=>x.endsWith('
}"
```
For a non-mutating CI/agent check, run:
```bash
pnpm run i18n:check
```
The check fails on missing or extra keys against `en.json`. It reports values
that are still identical to English as warnings so untranslated fallback strings
remain visible without blocking key-parity validation. For stricter translation
audits, run:
```bash
node tools/i18n/check-drift.mjs --fail-on-identical
```
## Translation rules
- Preserve `{{interpolation}}` placeholders verbatim.
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env node
import { readdirSync, readFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolve(__dirname, '..', '..');
const I18N_DIR = resolve(REPO_ROOT, 'apps/web/src/assets/i18n');
const EN_LOCALE = 'en.json';
const MAX_EXAMPLES = 5;
const FAIL_ON_IDENTICAL = process.argv.includes('--fail-on-identical');
function readJson(filePath) {
return JSON.parse(readFileSync(filePath, 'utf8'));
}
function collectLeaves(value, prefix = '', leaves = new Map()) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`Expected object at ${prefix || '<root>'}`);
}
for (const [key, entry] of Object.entries(value)) {
const path = prefix ? `${prefix}.${key}` : key;
if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
collectLeaves(entry, path, leaves);
} else {
leaves.set(path, entry);
}
}
return leaves;
}
function listLocaleFiles() {
return readdirSync(I18N_DIR)
.filter((file) => file.endsWith('.json'))
.sort();
}
function formatExamples(values) {
if (values.length === 0) {
return '';
}
const suffix = values.length > MAX_EXAMPLES ? ', ...' : '';
return ` (${values.slice(0, MAX_EXAMPLES).join(', ')}${suffix})`;
}
function formatError(error) {
return error instanceof Error ? error.message : String(error);
}
let enLeaves;
try {
enLeaves = collectLeaves(readJson(resolve(I18N_DIR, EN_LOCALE)));
} catch (error) {
console.error(`FAIL ${EN_LOCALE}: ${formatError(error)}`);
process.exit(1);
}
const enKeys = [...enLeaves.keys()];
let failed = false;
let identicalTotal = 0;
for (const localeFile of listLocaleFiles()) {
if (localeFile === EN_LOCALE) {
continue;
}
try {
const localeLeaves = collectLeaves(
readJson(resolve(I18N_DIR, localeFile))
);
const localeKeys = [...localeLeaves.keys()];
const missing = enKeys.filter((key) => !localeLeaves.has(key));
const extra = localeKeys.filter((key) => !enLeaves.has(key));
const identical = enKeys.filter((key) => {
if (!localeLeaves.has(key)) {
return false;
}
const localeValue = localeLeaves.get(key);
const enValue = enLeaves.get(key);
return (
typeof localeValue === 'string' &&
typeof enValue === 'string' &&
localeValue.trim().length > 0 &&
localeValue === enValue
);
});
identicalTotal += identical.length;
const localeFailed =
missing.length > 0 ||
extra.length > 0 ||
(FAIL_ON_IDENTICAL && identical.length > 0);
failed = failed || localeFailed;
const status = localeFailed ? 'FAIL' : 'ok';
console.log(
`${status} ${localeFile}: missing=${missing.length}${formatExamples(
missing
)} extra=${extra.length}${formatExamples(extra)} identical_en=${
identical.length
}${formatExamples(identical)}`
);
} catch (error) {
failed = true;
console.log(`FAIL ${localeFile}: ${formatError(error)}`);
}
}
if (identicalTotal > 0 && !FAIL_ON_IDENTICAL) {
console.log(
`warn identical_en=${identicalTotal}; pass --fail-on-identical to make English fallback values fatal.`
);
}
if (failed) {
process.exitCode = 1;
}
+3
View File
@@ -19,6 +19,9 @@
"command": "node --test tools/packaging/electron-package-identity.test.mjs",
"cwd": "{workspaceRoot}"
}
},
"lint": {
"command": "eslint \"tools/packaging/**/*.{js,cjs,mjs,ts}\""
}
},
"tags": ["scope:tools", "domain:packaging", "type:tool"]