feat(frontend): Phase 2/3 hardening release
Roadmap P0
- Ship /logo.svg fallback and rewire asset references (superseded later by
the project-manager PNG in a separate commit).
Roadmap P1
- Response envelope alignment ({items,total,page,size}) across services
and query hooks.
- Approval/ticket UX updates tied to the new backend rollback semantics.
Roadmap P2
- React.lazy + Vite manualChunks to split vendor-radix/charts/icons/forms
from the main bundle and cut initial JS.
- Fix the 181 tsc errors (PaginationParams class + per-service generics).
- Auto-refresh flow for JWT refresh tokens wired into api-client.ts.
Roadmap P3
- i18n: i18next + language detector, en/ar locales, RTL auto-switch,
LanguageToggle component in RoleLayout and AdminLmsLayout.
- GDPR: PrivacyCenter page for data export and right-to-erasure, backed
by gdpr.service.ts; logout via AuthContext after erasure.
- Human-in-the-loop exam review UI: admin ExamReviewQueue + ExamReviewDetail
pages, useExamReview hooks, exam-review.service.ts.
- AI quality loop: AIPromptEditor (versioning + render preview),
AIFeedbackButtons for students, AIFeedbackTriage admin page, dedicated
services, hooks, and types.
- Dark mode: ThemeProvider + ThemeToggle + chart color tokens.
- Accessibility: DialogDescription on every DialogContent; alert-dialog
and sheet updates.
- Retire dead pages: ExamPage marketing, Index, ProfilePage import.
- Playwright e2e scaffolding: playwright.config.ts + e2e/login.spec.ts
smoke tests, npm scripts test:e2e / test:e2e:install.
Made-with: Cursor
This commit is contained in:
67
frontend/src/i18n/index.ts
Normal file
67
frontend/src/i18n/index.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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 detection order:
|
||||
* 1. localStorage key ``encoach-lang`` (explicit user pick)
|
||||
* 2. ``navigator.language``
|
||||
* 3. fallback ``en``
|
||||
*
|
||||
* 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"];
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources: {
|
||||
en: { translation: en },
|
||||
ar: { translation: ar },
|
||||
},
|
||||
fallbackLng: "en",
|
||||
supportedLngs: SUPPORTED_LANGS.map((l) => l.code),
|
||||
interpolation: { escapeValue: false },
|
||||
detection: {
|
||||
order: ["localStorage", "navigator", "htmlTag"],
|
||||
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;
|
||||
Reference in New Issue
Block a user