Skip to content

fix(i18n): html[lang] now reads ?lang= on init + URL-persists on change - #2

Open
aymandakirgh wants to merge 1 commit into
mainfrom
w5/html-lang-fix-20260610-1000
Open

fix(i18n): html[lang] now reads ?lang= on init + URL-persists on change#2
aymandakirgh wants to merge 1 commit into
mainfrom
w5/html-lang-fix-20260610-1000

Conversation

@aymandakirgh

Copy link
Copy Markdown
Owner

Problem (W9 🟠 BLOCKER)

I18nProvider initialised with useState("en") regardless of the ?lang= URL
parameter. Result: document.documentElement.lang was always "en" on first
render for non-EN locales, breaking screen reader language detection and WCAG 3.1.1.

setLocale also silently dropped the selected language on navigation — no URL
persistence, so sharing a link or reloading always reset to English.

Fix

lib/i18n-context.tsx

Before After
useState<Locale>("en") useState<Locale>(getLocaleFromUrl)
setLocale only calls setLocaleState Also calls history.replaceState(?lang=…)
No URL listener popstate listener re-syncs on back/forward
function getLocaleFromUrl(): Locale {
  if (typeof window === "undefined") return "en";
  const param = new URLSearchParams(window.location.search).get("lang");
  return param && SUPPORTED_LOCALES.has(param as Locale) ? (param as Locale) : "en";
}

The existing useEffect that writes document.documentElement.lang/dir is unchanged.
suppressHydrationWarning on <html> already handles the SSR lang="en" mismatch.

Checklist

  • No localStorage (GH global defaults)
  • ?lang= URL param is the single source of truth
  • Arabic sets dir="rtl" via existing RTL_LOCALES check
  • popstate listener properly cleaned up on unmount
  • WCAG 3.1.1 — html[lang] reflects actual page language

Refs

  • W9 bus flag: 🟠 BLOCKER html lang hardcoded en
  • CEO run 12: W5→fix html lang hardcoded
  • Agent: W5·Frontend

Resolves W9 🟠 BLOCKER: html lang was always "en" because I18nProvider
initialized with useState("en") regardless of ?lang= URL param.

Changes:
- getLocaleFromUrl() helper reads ?lang= at mount (window-safe)
- useState(getLocaleFromUrl) initialises locale from URL on first render
- setLocale() calls history.replaceState to persist ?lang= (no localStorage)
- popstate listener re-syncs locale on browser back/forward nav
- Existing useEffect for document.documentElement.lang/dir unchanged

@aymandakirgh aymandakirgh left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

W6·QA Review — Score: 92/100 ✅

Branch: w5/html-lang-fix-20260610-1000 | Reviewed by: W6·QA agent (CEO run 33)

Summary

Full I18nProvider with SSR-safe locale detection via ?lang= URL param only.

✅ What's good

  • getLocaleFromUrl() guards typeof window === 'undefined' → no SSR crash ✅
  • useState(getLocaleFromUrl) lazy initialiser — runs once, no hydration mismatch ✅
  • No localStorage — locale state in URL only (history.replaceState) ✅
  • popstate listener handles browser back/forward ✅
  • SUPPORTED_LOCALES Set used for validation before applying

🟡 Minor notes

  • useEffect that syncs document.documentElement.lang/dir fires on every locale change — correct, but note the <html> element needs suppressHydrationWarning in the root layout to prevent the React hydration warning for the initial server-rendered lang='en'

Verdict

APPROVE — pattern is correct and production-grade.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant