fix(i18n): guard locale key parity and finish the OAuth consent page strings

Every non-en locale now exposes the exact same flat key set as en. Keys that
had drifted out of sync are backfilled with the English source value (tagged
en-fallback) so t() resolves a real string instead of relying on the silent
runtime fallback; no existing translation was touched and no key was removed.

Add a parity test that imports each aggregated locale bundle and asserts its
key set matches en, with a diagnostic listing of any missing/extra keys. This
complements the file-level check in shared/scripts by guarding the merged
export the app actually serves.

Finish internationalising OAuthAuthorizePage: the ~15 remaining hardcoded
English chrome strings now go through oauth.authorize.* keys (English source
in en, en-fallback placeholders elsewhere). Markup and behaviour are unchanged.
This commit is contained in:
Maurice
2026-05-31 16:08:08 +02:00
parent eed9e8ce7c
commit e63a7799fb
57 changed files with 948 additions and 17 deletions
+64
View File
@@ -0,0 +1,64 @@
import { describe, it, expect } from 'vitest'
import type { TranslationStrings } from '@trek/shared/i18n'
import en from '@trek/shared/i18n/en'
import de from '@trek/shared/i18n/de'
import es from '@trek/shared/i18n/es'
import fr from '@trek/shared/i18n/fr'
import hu from '@trek/shared/i18n/hu'
import itIT from '@trek/shared/i18n/it'
import tr from '@trek/shared/i18n/tr'
import ru from '@trek/shared/i18n/ru'
import zh from '@trek/shared/i18n/zh'
import zhTW from '@trek/shared/i18n/zh-TW'
import nl from '@trek/shared/i18n/nl'
import idID from '@trek/shared/i18n/id'
import ar from '@trek/shared/i18n/ar'
import br from '@trek/shared/i18n/br'
import cs from '@trek/shared/i18n/cs'
import pl from '@trek/shared/i18n/pl'
import ja from '@trek/shared/i18n/ja'
import ko from '@trek/shared/i18n/ko'
import uk from '@trek/shared/i18n/uk'
import gr from '@trek/shared/i18n/gr'
// Runtime guard for the aggregated i18n bundles. `t()` resolves keys against the
// active locale's flat dot-key map (see TranslationContext), so a key that is
// present in en but missing in another locale silently falls back to English at
// runtime — easy to ship, hard to notice. This test fails loudly when any locale
// drifts away from the en key set so translators get an explicit, diagnostic list.
//
// The shared package also runs a file-level parity check (shared/scripts), but
// that one only inspects per-domain source files; this one asserts the *merged*
// export each locale actually serves to the app.
const NON_EN_LOCALES: Record<string, TranslationStrings> = {
de, es, fr, hu, it: itIT, tr, ru, zh, 'zh-TW': zhTW, nl, id: idID,
ar, br, cs, pl, ja, ko, uk, gr,
}
const enKeys = new Set(Object.keys(en))
describe('i18n locale key parity', () => {
it('covers every non-en locale', () => {
// Keep the assertion set in lockstep with the supported language list minus en.
expect(Object.keys(NON_EN_LOCALES)).toHaveLength(19)
})
for (const [locale, strings] of Object.entries(NON_EN_LOCALES)) {
it(`${locale} has the exact same key set as en`, () => {
const localeKeys = new Set(Object.keys(strings))
const missing = [...enKeys].filter((k) => !localeKeys.has(k))
const extra = [...localeKeys].filter((k) => !enKeys.has(k))
const diagnostic =
`Locale "${locale}" key drift vs en — ` +
`missing ${missing.length}` +
(missing.length ? ` (${missing.slice(0, 10).join(', ')}${missing.length > 10 ? ', …' : ''})` : '') +
`; extra ${extra.length}` +
(extra.length ? ` (${extra.slice(0, 10).join(', ')}${extra.length > 10 ? ', …' : ''})` : '')
expect(missing, diagnostic).toEqual([])
expect(extra, diagnostic).toEqual([])
})
}
})