feat(i18n): add i18n types, config, provider, context, and custom hook

This commit is contained in:
2025-07-04 00:22:01 +03:30
parent e2d37ac7b2
commit 01658964f7
13 changed files with 312 additions and 23 deletions

View File

@@ -0,0 +1,15 @@
import { type Language } from '@/types/language';
import { createContext } from 'react';
export interface LangaugeContextModel {
language: Language;
changeLanguage: (langauge: Language) => void;
t: (key: string) => string;
}
// The context is used by the LangaugeProvider
export const LangaugeContext = createContext<LangaugeContextModel>({
language: 'fa',
changeLanguage: () => {},
t: () => '',
});

9
src/hooks/useLanguage.ts Normal file
View File

@@ -0,0 +1,9 @@
import {
LangaugeContext,
type LangaugeContextModel,
} from '@/contexts/LangaugeContext';
import { useContext } from 'react';
export const useLangauge = (): LangaugeContextModel => {
return useContext(LangaugeContext);
};

View File

@@ -0,0 +1,17 @@
import { useEffect, useState } from 'react';
export const useLocalStorage = <T extends string>(
key: string,
defaultValue: T,
): [T, (value: T) => void] => {
const localValueOrDefault: T = (localStorage.getItem(key) ??
defaultValue) as T;
const [localValue, setLocalValue] = useState<T>(localValueOrDefault);
useEffect(() => {
localStorage.setItem(key, localValue);
}, [localValue, key]);
return [localValue, setLocalValue];
};

18
src/i18n.ts Normal file
View File

@@ -0,0 +1,18 @@
import i18n from 'i18next';
import i18nBackend from 'i18next-http-backend';
import { initReactI18next } from 'react-i18next';
i18n
.use(i18nBackend)
.use(initReactI18next)
.init({
lng: 'en',
interpolation: {
escapeValue: false,
},
backend: {
loadPath: `${window.location.origin}/i18n/{{lng}}.json`,
},
});
export default i18n;

View File

@@ -2,9 +2,12 @@ import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
import { LanguageProvider } from './providers/LanguageProvider';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
<LanguageProvider>
<App />
</LanguageProvider>
</StrictMode>,
);

View File

@@ -0,0 +1,47 @@
import type { Language } from '@/types/language';
import { changeLanguage } from 'i18next';
import { useLayoutEffect, type JSX, type PropsWithChildren } from 'react';
import { initReactI18next, useTranslation } from 'react-i18next';
import { useLocalStorage } from '@/hooks/useLocalStorage';
import { LangaugeContext } from '@/contexts/LangaugeContext';
import i18n from 'i18next';
import i18nBackend from 'i18next-http-backend';
i18n
.use(i18nBackend)
.use(initReactI18next)
.init({
lng: 'en',
interpolation: {
escapeValue: false,
},
backend: {
loadPath: `${window.location.origin}/i18n/{{lng}}.json`,
},
});
export const LanguageProvider = (props: PropsWithChildren): JSX.Element => {
const [currentLanguage, setCurrentLangauge] = useLocalStorage<Language>(
'language',
'fa',
);
const { t } = useTranslation();
useLayoutEffect(() => {
changeLanguage(currentLanguage);
document.documentElement.dir = currentLanguage === 'fa' ? 'rtl' : 'ltr';
document.documentElement.lang = currentLanguage;
}, [currentLanguage]);
return (
<LangaugeContext.Provider
value={{
language: currentLanguage,
changeLanguage: setCurrentLangauge,
t: t,
}}
>
{props.children}
</LangaugeContext.Provider>
);
};

1
src/types/language.ts Normal file
View File

@@ -0,0 +1 @@
export type Language = 'en' | 'fa';