feat(#239): add angular i18n shell namespace bridge
All checks were successful
CI / test-and-quality (push) Successful in 3m43s
CI / test-and-quality (pull_request) Successful in 3m20s

This commit is contained in:
2026-03-02 00:13:12 +00:00
parent f28a390f95
commit 258025ac4e
3 changed files with 48 additions and 5 deletions

View File

@@ -26,13 +26,13 @@ describe('i18n MVP flow smoke (host/player + audio policy)', () => {
host.ngOnInit(); host.ngOnInit();
player.ngOnInit(); player.ngOnInit();
expect(host.copy('host.start_round')).toBe('Start round'); expect(host.copy('game.host.start_round')).toBe('Start round');
expect(player.copy('player.submit_guess')).toBe('Submit guess'); expect(player.copy('game.player.submit_guess')).toBe('Submit guess');
setPreferredLocale('da'); setPreferredLocale('da');
expect(host.copy('host.start_round')).toBe('Start runde'); expect(host.copy('game.host.start_round')).toBe('Start runde');
expect(player.copy('player.submit_guess')).toBe('Send gæt'); expect(player.copy('game.player.submit_guess')).toBe('Send gæt');
player.ngOnDestroy(); player.ngOnDestroy();
host.ngOnDestroy(); host.ngOnDestroy();

View File

@@ -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 () => { it('exposes primary-only audio routing policy to clients', async () => {
vi.stubGlobal('window', { vi.stubGlobal('window', {
location: { search: '' }, location: { search: '' },

View File

@@ -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 { export function t(key: string, locale: string): string {
const normalizedLocale = normalizeLocale(locale); const normalizedLocale = normalizeLocale(locale);
const fallbackLocale = DEFAULT_LOCALE; const fallbackLocale = DEFAULT_LOCALE;
const segments = key.split('.'); const segments = resolveCatalogPath(key).split('.');
let cursor: unknown = lobbyCatalog.frontend.ui; let cursor: unknown = lobbyCatalog.frontend.ui;
for (const segment of segments) { for (const segment of segments) {