system-prompts-and-models-o.../salesflow-saas/frontend/src/i18n/index.tsx
Claude bbef680bc1
feat: Add complete i18n system with Arabic/English translations
Bilingual support infrastructure:
- ar.json: Full Arabic translations (120+ strings)
  - Navigation, hero, pain points, features, pricing, dashboard, marketers
- en.json: Full English translations (120+ strings)
- index.tsx: I18nProvider with context, useI18n hook, LanguageSwitcher component
  - LocalStorage persistence, RTL/LTR auto-switching
  - Nested key resolution (e.g., "dashboard.tabs.overview")
  - Arabic default with instant English toggle

https://claude.ai/code/session_01LsnvBa7HwF5hs99VZbgLGj
2026-04-12 01:30:53 +00:00

92 lines
2.6 KiB
TypeScript

"use client";
import React, { createContext, useContext, useState, useCallback, ReactNode } from "react";
import ar from "./ar.json";
import en from "./en.json";
type Locale = "ar" | "en";
type Translations = typeof ar;
interface I18nContextType {
locale: Locale;
t: (key: string) => string;
switchLocale: (locale: Locale) => void;
dir: "rtl" | "ltr";
isArabic: boolean;
}
const translations: Record<Locale, Translations> = { ar, en };
const I18nContext = createContext<I18nContextType | null>(null);
function getNestedValue(obj: any, path: string): string {
const keys = path.split(".");
let current = obj;
for (const key of keys) {
if (current === undefined || current === null) return path;
current = current[key];
}
return typeof current === "string" ? current : path;
}
export function I18nProvider({ children, defaultLocale = "ar" }: { children: ReactNode; defaultLocale?: Locale }) {
const [locale, setLocale] = useState<Locale>(() => {
if (typeof window !== "undefined") {
return (localStorage.getItem("dealix-locale") as Locale) || defaultLocale;
}
return defaultLocale;
});
const t = useCallback(
(key: string): string => {
return getNestedValue(translations[locale], key);
},
[locale]
);
const switchLocale = useCallback((newLocale: Locale) => {
setLocale(newLocale);
if (typeof window !== "undefined") {
localStorage.setItem("dealix-locale", newLocale);
document.documentElement.dir = newLocale === "ar" ? "rtl" : "ltr";
document.documentElement.lang = newLocale;
}
}, []);
const value: I18nContextType = {
locale,
t,
switchLocale,
dir: locale === "ar" ? "rtl" : "ltr",
isArabic: locale === "ar",
};
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
}
export function useI18n(): I18nContextType {
const context = useContext(I18nContext);
if (!context) {
throw new Error("useI18n must be used within I18nProvider");
}
return context;
}
export function LanguageSwitcher() {
const { locale, switchLocale } = useI18n();
return (
<button
onClick={() => switchLocale(locale === "ar" ? "en" : "ar")}
className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/10 hover:bg-white/20
text-sm font-medium transition-all duration-200 backdrop-blur-sm border border-white/10"
aria-label="Switch language"
>
<span className="text-lg">{locale === "ar" ? "🇬🇧" : "🇸🇦"}</span>
<span>{locale === "ar" ? "English" : "عربي"}</span>
</button>
);
}
export type { Locale, I18nContextType };