fix(i18n): keep the baseline intact on failed updates and strict audits

`--update-baseline` now writes nothing while any locale is unreadable or
has missing or extra keys, so an incomplete translation cannot reshape
the baseline. `--fail-on-identical` no longer reads the baseline it
ignores, so a damaged file cannot block a strict audit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-03 11:29:32 +02:00
1 parent 97d7541e62
commit 72a1771cf9
3 files changed
+40 -6

No files matched your search

+2 -1
View File
@@ -260,7 +260,8 @@ text changes, so copying reworded English into a locale fails as well.
Baseline entries that are no longer English-identical (translated, removed, or
reworded) are reported but do not fail. `pnpm run i18n:baseline:update`
rewrites the baseline from the current locale files, dropping those entries
and printing every added one. Run it only after translating, or for a value
and printing every added one; it writes nothing while any locale is unreadable
or has missing or extra keys. Run it only after translating, or for a value
that is legitimately identical in that language; review the diff, and never
run it in CI. A new locale starts with no baseline entries, so it has to
record its legitimate identical values the same way.
+8 -5
View File
@@ -269,7 +269,8 @@ export function run({
enLeaves = collectLeaves(
readJson(resolve(i18nDir, `${EN_LOCALE}.json`))
);
baseline = readBaseline(baselinePath);
// A strict audit ignores the baseline, so a damaged file cannot block it.
baseline = strict ? new Map() : readBaseline(baselinePath);
} catch (error) {
if (!enLeaves || !update) {
log(`FAIL ${formatError(error)}`);
@@ -283,7 +284,6 @@ export function run({
const identicalByLocale = new Map();
const rejected = [];
let failed = false;
let unreadable = false;
let staleTotal = 0;
for (const locale of locales) {
@@ -292,7 +292,7 @@ export function run({
try {
result = checkLocale({ i18nDir, locale, enLeaves, baseline, mode });
} catch (error) {
failed = unreadable = true;
failed = true;
log(`FAIL ${file}: ${formatError(error)}`);
continue;
}
@@ -318,8 +318,11 @@ export function run({
reportRejected(log, rejected, enLeaves, mode);
if (update) {
if (unreadable) {
log('FAIL baseline not written: a locale file could not be read.');
// Only a complete, readable set of locales is a state worth recording.
if (failed) {
log(
'FAIL baseline not written: fix the unreadable, missing or extra keys above first.'
);
return 1;
}
writeBaseline(log, baselinePath, baseline, identicalByLocale);
+30
View File
@@ -214,6 +214,36 @@ test('--update-baseline keeps the old baseline when a locale is unreadable', ()
assert.deepEqual(readBaselineFile(), { de: { PIN: 'PIN' } });
});
test('--update-baseline keeps the old baseline while keys are missing or extra', () => {
writeLocale('en', EN);
writeLocale('de', { APP: { TITLE: 'IPTVnator', OLD: 'Alt' }, PIN: 'PIN' });
writeBaseline({ de: { 'APP.SAVE': 'Save', PIN: 'PIN' } });
const { code, output } = check('--update-baseline');
assert.equal(code, 1);
assert.match(output, /baseline not written/);
assert.deepEqual(readBaselineFile(), {
de: { 'APP.SAVE': 'Save', PIN: 'PIN' },
});
});
test('--fail-on-identical audits even when the baseline is damaged', () => {
writeLocale('en', EN);
writeLocale('de', {
APP: { TITLE: 'IPTVnator', SAVE: 'Speichern' },
PIN: 'PIN-Code',
});
writeFileSync(baselinePath, '{ broken');
const { code, output } = check('--fail-on-identical');
assert.equal(code, 1);
assert.match(output, /^FAIL de\.json: missing=0 extra=0 identical_en=1$/m);
assert.match(output, /de\.json APP\.TITLE: "IPTVnator"/);
assert.equal(check().code, 1);
});
test('rejects unknown and conflicting flags', () => {
writeLocale('en', EN);