First-visit users now always land on the English UI regardless of navigator.language. Arabic remains one click away via the toggle, and the user's explicit pick is persisted to localStorage (encoach-lang) and honoured on every subsequent load. - src/i18n/index.ts: set lng: "en", drop navigator/htmlTag from the detector order so an Arabic-locale browser no longer silently boots the UI in Arabic before the user has chosen. - src/lib/api-client.ts: mirror the same policy for the Accept-Language header sent to the backend, so AI-generated content stays English on first visit instead of echoing the browser locale. Made-with: Cursor
81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
/**
|
|
* EnCoach i18n bootstrap.
|
|
*
|
|
* We keep the translation strings in TypeScript modules (one per locale) so
|
|
* they go through type-checking and tree-shaking, and so non-translators
|
|
* can't ship a broken JSON file that blocks the build.
|
|
*
|
|
* Language selection order (product decision: English is the default UI
|
|
* language at startup; Arabic is opt-in via the language toggle):
|
|
* 1. localStorage key ``encoach-lang`` (explicit user pick, persisted)
|
|
* 2. ``en`` — fallback used on first visit, regardless of browser locale
|
|
*
|
|
* We intentionally do **not** read ``navigator.language`` anymore: an
|
|
* Arabic-locale browser should still land on the English UI the first time
|
|
* a user visits, because the QA team and the product manager both sign off
|
|
* against the English baseline. Users that actively want Arabic flip the
|
|
* toggle once and the preference is remembered from then on.
|
|
*
|
|
* RTL handling: whenever the active language switches to one of RTL_LANGS,
|
|
* we flip ``document.documentElement.dir`` so Tailwind's RTL utilities +
|
|
* Radix primitives pick up the change automatically.
|
|
*/
|
|
|
|
import i18n from "i18next";
|
|
import LanguageDetector from "i18next-browser-languagedetector";
|
|
import { initReactI18next } from "react-i18next";
|
|
|
|
import ar from "./locales/ar";
|
|
import en from "./locales/en";
|
|
|
|
const RTL_LANGS = new Set(["ar", "he", "fa", "ur"]);
|
|
|
|
export const SUPPORTED_LANGS = [
|
|
{ code: "en", label: "English" },
|
|
{ code: "ar", label: "العربية" },
|
|
] as const;
|
|
|
|
export type SupportedLang = (typeof SUPPORTED_LANGS)[number]["code"];
|
|
|
|
export const DEFAULT_LANG: SupportedLang = "en";
|
|
|
|
i18n
|
|
.use(LanguageDetector)
|
|
.use(initReactI18next)
|
|
.init({
|
|
resources: {
|
|
en: { translation: en },
|
|
ar: { translation: ar },
|
|
},
|
|
// Explicit default — english is the baseline UI language at startup.
|
|
lng: DEFAULT_LANG,
|
|
fallbackLng: DEFAULT_LANG,
|
|
supportedLngs: SUPPORTED_LANGS.map((l) => l.code),
|
|
interpolation: { escapeValue: false },
|
|
detection: {
|
|
// Only honour the user's saved pick. Navigator/htmlTag detection was
|
|
// removed so Arabic-locale browsers don't silently boot the UI in
|
|
// Arabic before the user has chosen.
|
|
order: ["localStorage"],
|
|
caches: ["localStorage"],
|
|
lookupLocalStorage: "encoach-lang",
|
|
},
|
|
returnEmptyString: false,
|
|
});
|
|
|
|
export function applyDirectionForLang(lang: string) {
|
|
const base = lang.split("-")[0];
|
|
const dir = RTL_LANGS.has(base) ? "rtl" : "ltr";
|
|
if (typeof document !== "undefined") {
|
|
document.documentElement.dir = dir;
|
|
document.documentElement.lang = base;
|
|
}
|
|
}
|
|
|
|
if (typeof window !== "undefined") {
|
|
applyDirectionForLang(i18n.language || "en");
|
|
i18n.on("languageChanged", applyDirectionForLang);
|
|
}
|
|
|
|
export default i18n;
|