[Bug][Frontend] es→en language toggle leaves server-localized content in Spanish (lesson names et al.) — fetch-once surfaces never re-pull on locale change
#611
After #608 shipped, toggling English→Spanish worked flawlessly, but toggling back left parts of the interface in Spanish — notably lesson names.
Root cause (traced end to end)
Backend exonerated: live prod probes with interleaved en/es/en/es/en catalog requests all localize correctly and identically — no shared-state mutation in the translation overlay.
Service worker exonerated for this symptom: the api-lessons-catalog dataGroup is freshness (network-first) and entries are keyed by full URL including contentLocale, so an en URL can never serve a Spanish body.
Playground runner already correct (#275's refetch effect) — verified with a Playwright toggle walk.
The real bug class: fetch-once surfaces. Server-localized content keeps its FETCH-TIME language on screen unless the surface re-pulls on toggle. Confirmed stale: My Journey (the lesson-name trail — zero refetches fired on toggle, reproduced empirically), the playground shell's Reading chapter (fetch keyed on category only), and the geography explorer (countries/facts loaded once at init). The reactive surfaces each hand-rolled the same langChanges$ edge-tracker idiom (4 copies); nothing enforced it for new surfaces, which is why this class keeps recurring (#275 → this).
New @spikersoft/platform-content-locale: ContentLocaleService.locale signal + onContentLocaleChange() edge-triggered hook — the one blessed invalidation idiom.
Fixed the three fetch-once surfaces; migrated the four hand-rolled copies (language-runner, sql-runner, x86, reg-ex) onto the shared helper.
Last-started-wins guards so a slow pre-toggle response can never restore stale-language content.
Unit specs at every layer (incl. previously-untested #275 runner behavior) + a permanent E2E round-trip spec (anonymous-locale-toggle.spec.ts) driving the real nav toggle both directions on the C# playground and My Journey. Full app suite 1873/1873.
Policy documented in the i18n workflow rule (both .cursor/.claude mirrors) and the lib README.
## Symptom
After #608 shipped, toggling English→Spanish worked flawlessly, but toggling back left parts of the interface in Spanish — notably lesson names.
## Root cause (traced end to end)
- **Backend exonerated:** live prod probes with interleaved `en/es/en/es/en` catalog requests all localize correctly and identically — no shared-state mutation in the translation overlay.
- **Service worker exonerated for this symptom:** the `api-lessons-catalog` dataGroup is `freshness` (network-first) and entries are keyed by full URL including `contentLocale`, so an `en` URL can never serve a Spanish body.
- **Playground runner already correct** (#275's refetch effect) — verified with a Playwright toggle walk.
- **The real bug class: fetch-once surfaces.** Server-localized content keeps its FETCH-TIME language on screen unless the surface re-pulls on toggle. Confirmed stale: **My Journey** (the lesson-name trail — zero refetches fired on toggle, reproduced empirically), the playground shell's **Reading chapter** (fetch keyed on category only), and the **geography explorer** (countries/facts loaded once at init). The reactive surfaces each hand-rolled the same `langChanges$` edge-tracker idiom (4 copies); nothing enforced it for new surfaces, which is why this class keeps recurring (#275 → this).
## Fix
spikersoft-angular PR #196:
- New `@spikersoft/platform-content-locale`: `ContentLocaleService.locale` signal + `onContentLocaleChange()` edge-triggered hook — the one blessed invalidation idiom.
- Fixed the three fetch-once surfaces; migrated the four hand-rolled copies (language-runner, sql-runner, x86, reg-ex) onto the shared helper.
- Last-started-wins guards so a slow pre-toggle response can never restore stale-language content.
- Unit specs at every layer (incl. previously-untested #275 runner behavior) + a permanent E2E round-trip spec (`anonymous-locale-toggle.spec.ts`) driving the real nav toggle both directions on the C# playground and My Journey. Full app suite 1873/1873.
- Policy documented in the i18n workflow rule (both `.cursor`/`.claude` mirrors) and the lib README.
Close after PR #196 merges.
Resolved in spikersoft-angular PR #196 (merged to master). New @spikersoft/platform-content-locale primitive (ContentLocaleService signal + onContentLocaleChange edge hook); the three fetch-once surfaces (My Journey trail, playground Reading chapter, geography explorer) now re-pull on every language toggle; the four hand-rolled langChanges$ copies (language-runner, sql-runner, x86, reg-ex) migrated onto the shared helper; last-started-wins guards prevent a slow pre-toggle response from restoring stale-language content. Unit specs at every layer plus the permanent anonymous-locale-toggle.spec.ts e2e round-trip (failed before the fix, green after). Full app suite 1873/1873. Closing.
Resolved in spikersoft-angular PR #196 (merged to `master`). New `@spikersoft/platform-content-locale` primitive (`ContentLocaleService` signal + `onContentLocaleChange` edge hook); the three fetch-once surfaces (My Journey trail, playground Reading chapter, geography explorer) now re-pull on every language toggle; the four hand-rolled `langChanges$` copies (language-runner, sql-runner, x86, reg-ex) migrated onto the shared helper; last-started-wins guards prevent a slow pre-toggle response from restoring stale-language content. Unit specs at every layer plus the permanent `anonymous-locale-toggle.spec.ts` e2e round-trip (failed before the fix, green after). Full app suite 1873/1873. Closing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Symptom
After #608 shipped, toggling English→Spanish worked flawlessly, but toggling back left parts of the interface in Spanish — notably lesson names.
Root cause (traced end to end)
en/es/en/es/encatalog requests all localize correctly and identically — no shared-state mutation in the translation overlay.api-lessons-catalogdataGroup isfreshness(network-first) and entries are keyed by full URL includingcontentLocale, so anenURL can never serve a Spanish body.langChanges$edge-tracker idiom (4 copies); nothing enforced it for new surfaces, which is why this class keeps recurring (#275 → this).Fix
spikersoft-angular PR #196:
@spikersoft/platform-content-locale:ContentLocaleService.localesignal +onContentLocaleChange()edge-triggered hook — the one blessed invalidation idiom.anonymous-locale-toggle.spec.ts) driving the real nav toggle both directions on the C# playground and My Journey. Full app suite 1873/1873..cursor/.claudemirrors) and the lib README.Close after PR #196 merges.
Resolved in spikersoft-angular PR #196 (merged to
master). New@spikersoft/platform-content-localeprimitive (ContentLocaleServicesignal +onContentLocaleChangeedge hook); the three fetch-once surfaces (My Journey trail, playground Reading chapter, geography explorer) now re-pull on every language toggle; the four hand-rolledlangChanges$copies (language-runner, sql-runner, x86, reg-ex) migrated onto the shared helper; last-started-wins guards prevent a slow pre-toggle response from restoring stale-language content. Unit specs at every layer plus the permanentanonymous-locale-toggle.spec.tse2e round-trip (failed before the fix, green after). Full app suite 1873/1873. Closing.