mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-20 09:03:43 +02:00
chore(i18n): full parity across all 8 locales + drop two-tier check
Backfill 276 missing translations across fr / it / ja / pt-BR so all
8 shipped locales now match en. Most gaps come from features that
landed with en+de+zh translations only:
- login.resetPassword.* (12 × 3): fr, it, ja
- printers.firmwareModal.* (7 × 4): all locales
- settings.spoolbuddy.* (~40 × 3, plus 18 in ja): admin device-
control block (unregister / reboot / shutdown / update /
restart confirms)
- spoolbuddy.settings.* (13 × 4): kiosk backend & auth + diagnostics
- virtualPrinter.archiveNameSource.* (4 × 4): from this release
Also fixes 27 ja and 1 fr placeholder-name mismatches that silently
broke interpolation at runtime — e.g. printers.activeNozzle used
{{side}} while the runtime passes nozzle, and several keys had
{{count}} dropped entirely so the value would never render.
Drop the STRICT / info two-tier machinery from check-i18n-parity.mjs:
en is the reference, every other locale is checked identically,
any drift fails CI. The previous tier was just deferred policy, no
real distinction.
All 8 locales now sit at 4492 leaves. Parity script, i18n test suite
(11 tests), and full frontend build all green.
This commit is contained in:
@@ -161,11 +161,8 @@ export function compareLocales(locales) {
|
||||
return { failed: reports.length > 0, reports };
|
||||
}
|
||||
|
||||
// Strict locales fail CI when they drift from en. Everything else discovered
|
||||
// in the locales directory is reported informationally — promote a locale to
|
||||
// STRICT once its drift is caught up. en is implicitly the reference.
|
||||
const STRICT = ['de', 'zh-CN', 'zh-TW'];
|
||||
|
||||
// en is the reference locale; every other locale discovered in the locales
|
||||
// directory is checked identically and a drift in any of them fails CI.
|
||||
// Skip file IO / process.exit when imported as a library (e.g. from tests).
|
||||
const isMainModule = import.meta.url === url.pathToFileURL(process.argv[1] ?? '').href;
|
||||
if (isMainModule) {
|
||||
@@ -178,46 +175,33 @@ if (isMainModule) {
|
||||
console.error(`No en.ts found in ${localesDir} — cannot run parity check without a reference locale.`);
|
||||
process.exit(1);
|
||||
}
|
||||
const missingStrict = STRICT.filter((c) => !discovered.includes(c));
|
||||
if (missingStrict.length) {
|
||||
console.error(`STRICT locales declared but not found on disk: ${missingStrict.join(', ')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const codes = ['en', ...discovered.filter((c) => c !== 'en')];
|
||||
const locales = Object.fromEntries(
|
||||
codes.map((c) => [c, loadLocale(path.join(localesDir, `${c}.ts`))]),
|
||||
);
|
||||
|
||||
const MAX_REPORT = 20;
|
||||
const strictSet = new Set(STRICT);
|
||||
const printReports = (reports, header) => {
|
||||
if (!reports.length) return;
|
||||
console.error(`\n${header}`);
|
||||
const { reports } = compareLocales(locales);
|
||||
|
||||
if (reports.length) {
|
||||
console.error(`\n=== Locale parity failures (en is the reference) ===`);
|
||||
for (const { label, items } of reports) {
|
||||
console.error(`\n[${label}] (${items.length})`);
|
||||
items.slice(0, MAX_REPORT).forEach((i) => console.error(` ${i}`));
|
||||
if (items.length > MAX_REPORT) console.error(` ... and ${items.length - MAX_REPORT} more`);
|
||||
}
|
||||
};
|
||||
|
||||
// Label prefix is "${code}:" — route reports to strict vs informational.
|
||||
const { reports } = compareLocales(locales);
|
||||
const codeOf = (label) => label.split(':', 1)[0];
|
||||
const strictReports = reports.filter((r) => strictSet.has(codeOf(r.label)));
|
||||
const infoReports = reports.filter((r) => !strictSet.has(codeOf(r.label)));
|
||||
|
||||
printReports(strictReports, '=== STRICT locales (failures below fail CI) ===');
|
||||
printReports(infoReports, '=== INFORMATIONAL locales (drift shown, does not fail CI) ===');
|
||||
}
|
||||
|
||||
console.log('\nLocale leaf counts:');
|
||||
for (const [code, map] of Object.entries(locales)) {
|
||||
const tier = code === 'en' ? 'ref' : strictSet.has(code) ? 'strict' : 'info';
|
||||
const tier = code === 'en' ? 'ref' : 'locale';
|
||||
console.log(` ${code.padEnd(6)} ${String(map.size).padEnd(6)} [${tier}]`);
|
||||
}
|
||||
|
||||
if (strictReports.length > 0) {
|
||||
console.error(`\n❌ i18n parity check failed (strict: ${STRICT.join(', ')}).`);
|
||||
if (reports.length > 0) {
|
||||
console.error(`\n❌ i18n parity check failed.`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`\n✓ Strict locales in parity (en / ${STRICT.join(' / ')}).`);
|
||||
const others = codes.filter((c) => c !== 'en');
|
||||
console.log(`\n✓ All locales in parity with en (${others.join(' / ')}).`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user