diff --git a/docs/ISSUE-207-I18N-AUDIO-SMOKE-ARTIFACT.md b/docs/ISSUE-207-I18N-AUDIO-SMOKE-ARTIFACT.md new file mode 100644 index 0000000..092526a --- /dev/null +++ b/docs/ISSUE-207-I18N-AUDIO-SMOKE-ARTIFACT.md @@ -0,0 +1,35 @@ +# Issue #207 Artifact — da/en locale switch + audio routing (primary-only) + +## Scope +Acceptance for `[READY][i18n][P19]`: +1. Verify one MVP host+player flow in both `en` and `da` locale context. +2. Verify controlled fallback to `en` when a locale key is missing. +3. Verify audio-routing policy is primary-only (client/player has no audio output). + +No new feature behavior added — this is verification-only evidence. + +## Smoke/e2e evidence (Angular shell test run) +Executed from `frontend/angular`: + +```bash +npm test -- --reporter=dot src/app/lobby-i18n.spec.ts src/app/features/host/host-shell.component.spec.ts src/app/features/player/player-shell.component.spec.ts +``` + +Evidence covered by the run: +- `lobby-i18n.spec.ts` + - subscriber locale switch updates (`en -> da -> en`) + - controlled fallback where `da` key is removed during test and `en` copy is returned + - `clientHasNoAudioOutput === true` (primary-only audio routing) +- `host-shell.component.spec.ts` + - host actions and route sync in SPA (no reload) +- `player-shell.component.spec.ts` + - player session refresh/sync path and retry behavior + +## Policy evidence +- Shared catalog capability: `shared/i18n/lobby.json` + - `frontend.capabilities.client_has_no_audio_output = true` +- Angular host/player shells bind this capability: + - `HostShellComponent.clientHasNoAudioOutput` + - `PlayerShellComponent.clientHasNoAudioOutput` + +Conclusion: locale switching works in `da`/`en`, fallback resolves to `en` when locale key is intentionally missing, and client audio remains disabled by policy (`primary-only`). diff --git a/frontend/angular/src/app/lobby-i18n.spec.ts b/frontend/angular/src/app/lobby-i18n.spec.ts index 8ba00b2..a9ed37b 100644 --- a/frontend/angular/src/app/lobby-i18n.spec.ts +++ b/frontend/angular/src/app/lobby-i18n.spec.ts @@ -44,4 +44,48 @@ describe('lobby i18n locale propagation', () => { i18n.setPreferredLocale('en'); expect(updates).toEqual(['en', 'da']); }); + + it('falls back to default en translation when da key is intentionally missing', async () => { + vi.stubGlobal('window', { + location: { search: '' }, + localStorage: storageMock({ 'wpp.locale': 'da' }), + }); + vi.stubGlobal('navigator', { language: 'da-DK' }); + + const i18n = await import('./lobby-i18n'); + const catalogModule = await import('../../../../shared/i18n/lobby.json'); + const catalog = catalogModule.default as { + frontend: { + ui: { + common: { + refresh: { + en?: string; + da?: string; + }; + }; + }; + }; + }; + + const originalDa = catalog.frontend.ui.common.refresh.da; + catalog.frontend.ui.common.refresh.da = undefined; + + try { + expect(i18n.t('common.refresh', 'da')).toBe(catalog.frontend.ui.common.refresh.en); + } finally { + catalog.frontend.ui.common.refresh.da = originalDa; + } + }); + + it('exposes primary-only audio routing policy to clients', async () => { + vi.stubGlobal('window', { + location: { search: '' }, + localStorage: storageMock({ 'wpp.locale': 'en' }), + }); + vi.stubGlobal('navigator', { language: 'en-US' }); + + const i18n = await import('./lobby-i18n'); + + expect(i18n.clientHasNoAudioOutput).toBe(true); + }); });