From 258025ac4ee12d75f45bb73cf80bc39bbd06572e Mon Sep 17 00:00:00 2001 From: Asger Geel Weirsoee Date: Mon, 2 Mar 2026 00:13:12 +0000 Subject: [PATCH] feat(#239): add angular i18n shell namespace bridge --- .../src/app/i18n-mvp-flow-smoke.spec.ts | 8 ++--- frontend/angular/src/app/lobby-i18n.spec.ts | 30 +++++++++++++++++++ frontend/angular/src/app/lobby-i18n.ts | 15 +++++++++- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/frontend/angular/src/app/i18n-mvp-flow-smoke.spec.ts b/frontend/angular/src/app/i18n-mvp-flow-smoke.spec.ts index 6e130fb..144fa3d 100644 --- a/frontend/angular/src/app/i18n-mvp-flow-smoke.spec.ts +++ b/frontend/angular/src/app/i18n-mvp-flow-smoke.spec.ts @@ -26,13 +26,13 @@ describe('i18n MVP flow smoke (host/player + audio policy)', () => { host.ngOnInit(); player.ngOnInit(); - expect(host.copy('host.start_round')).toBe('Start round'); - expect(player.copy('player.submit_guess')).toBe('Submit guess'); + expect(host.copy('game.host.start_round')).toBe('Start round'); + expect(player.copy('game.player.submit_guess')).toBe('Submit guess'); setPreferredLocale('da'); - expect(host.copy('host.start_round')).toBe('Start runde'); - expect(player.copy('player.submit_guess')).toBe('Send gæt'); + expect(host.copy('game.host.start_round')).toBe('Start runde'); + expect(player.copy('game.player.submit_guess')).toBe('Send gæt'); player.ngOnDestroy(); host.ngOnDestroy(); diff --git a/frontend/angular/src/app/lobby-i18n.spec.ts b/frontend/angular/src/app/lobby-i18n.spec.ts index d0d17ff..e86d3d5 100644 --- a/frontend/angular/src/app/lobby-i18n.spec.ts +++ b/frontend/angular/src/app/lobby-i18n.spec.ts @@ -93,6 +93,36 @@ describe('lobby i18n locale propagation', () => { } }); + it('resolves baseline shell/game keys from shared namespaces', async () => { + vi.stubGlobal('window', { + location: { search: '' }, + localStorage: storageMock({ 'wpp.locale': 'da' }), + }); + vi.stubGlobal('navigator', { language: 'da-DK' }); + + const i18n = await import('./lobby-i18n'); + + const baselineKeys = [ + 'lobby.shell.title', + 'lobby.shell.host_nav', + 'lobby.shell.player_nav', + 'lobby.shell.language_label', + 'common.refresh', + 'common.session_code', + 'game.host.title', + 'game.host.start_round', + 'game.player.title', + 'game.player.submit_guess', + ] as const; + + for (const key of baselineKeys) { + const value = i18n.t(key, 'da'); + expect(value).toBeTypeOf('string'); + expect(value.length).toBeGreaterThan(0); + expect(value).not.toBe(key); + } + }); + it('exposes primary-only audio routing policy to clients', async () => { vi.stubGlobal('window', { location: { search: '' }, diff --git a/frontend/angular/src/app/lobby-i18n.ts b/frontend/angular/src/app/lobby-i18n.ts index 9011691..777084f 100644 --- a/frontend/angular/src/app/lobby-i18n.ts +++ b/frontend/angular/src/app/lobby-i18n.ts @@ -65,10 +65,23 @@ export function subscribeToLocaleChanges(callback: (locale: SupportedLocale) => }; } +function resolveCatalogPath(key: string): string { + if (key.startsWith('lobby.shell.')) { + return key.replace(/^lobby\.shell\./, 'app.'); + } + if (key.startsWith('game.host.')) { + return key.replace(/^game\.host\./, 'host.'); + } + if (key.startsWith('game.player.')) { + return key.replace(/^game\.player\./, 'player.'); + } + return key; +} + export function t(key: string, locale: string): string { const normalizedLocale = normalizeLocale(locale); const fallbackLocale = DEFAULT_LOCALE; - const segments = key.split('.'); + const segments = resolveCatalogPath(key).split('.'); let cursor: unknown = lobbyCatalog.frontend.ui; for (const segment of segments) {