chore(i18n): use default i18n provider, define LanguageManager component to handle direction and lng
This commit is contained in:
10
package-lock.json
generated
10
package-lock.json
generated
@@ -12,6 +12,7 @@
|
|||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@mui/material": "^7.2.0",
|
"@mui/material": "^7.2.0",
|
||||||
"i18next": "^25.3.0",
|
"i18next": "^25.3.0",
|
||||||
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
@@ -3039,6 +3040,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/i18next-browser-languagedetector": {
|
||||||
|
"version": "8.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz",
|
||||||
|
"integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/i18next-http-backend": {
|
"node_modules/i18next-http-backend": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@mui/material": "^7.2.0",
|
"@mui/material": "^7.2.0",
|
||||||
"i18next": "^25.3.0",
|
"i18next": "^25.3.0",
|
||||||
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"title": "Loyalty Club"
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"title": "باشگاه مشتریان"
|
|
||||||
}
|
|
||||||
3
public/locales/en/common.json
Normal file
3
public/locales/en/common.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"helloWorld": "hello world"
|
||||||
|
}
|
||||||
3
public/locales/fa/common.json
Normal file
3
public/locales/fa/common.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"helloWorld": "سلام دنیا"
|
||||||
|
}
|
||||||
16
src/App.tsx
16
src/App.tsx
@@ -1,10 +1,20 @@
|
|||||||
|
import { CssBaseline } from '@mui/material';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { LanguageManager } from './components/LanguageManager';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<h1>Hello World</h1>
|
<CssBaseline />
|
||||||
</div>
|
<LanguageManager />
|
||||||
|
<div style={{ padding: '16px' }}>
|
||||||
|
<h1>{t('helloWorld')}</h1>
|
||||||
|
<p>The main content and router will go here.</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
30
src/components/LanguageManager.tsx
Normal file
30
src/components/LanguageManager.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component listens to i18next language changes and applies
|
||||||
|
* side effects to the document. It renders no visible UI.
|
||||||
|
*/
|
||||||
|
export const LanguageManager = () => {
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleLanguageChange = (lng: string) => {
|
||||||
|
document.documentElement.dir = i18n.dir(lng);
|
||||||
|
document.documentElement.lang = lng;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set initial values on component mount
|
||||||
|
handleLanguageChange(i18n.language);
|
||||||
|
|
||||||
|
// Listen for future language changes
|
||||||
|
i18n.on('languageChanged', handleLanguageChange);
|
||||||
|
|
||||||
|
// Cleanup the event listener on unmount
|
||||||
|
return () => {
|
||||||
|
i18n.off('languageChanged', handleLanguageChange);
|
||||||
|
};
|
||||||
|
}, [i18n]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
22
src/config/i18n.ts
Normal file
22
src/config/i18n.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import i18n from 'i18next';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
import HttpApi from 'i18next-http-backend';
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(HttpApi) // Loads translations from your /public/locales folder
|
||||||
|
.use(LanguageDetector) // Detects user language
|
||||||
|
.use(initReactI18next) // Passes i18n down to react-i18next
|
||||||
|
.init({
|
||||||
|
supportedLngs: ['en', 'fa'], // Supported languages
|
||||||
|
fallbackLng: 'fa',
|
||||||
|
detection: {
|
||||||
|
order: ['localStorage', 'cookie', 'navigator'],
|
||||||
|
caches: ['localStorage', 'cookie'],
|
||||||
|
lookupLocalStorage: 'language', // The key to use in localStorage
|
||||||
|
},
|
||||||
|
ns: ['common'], // Add new namespaces here
|
||||||
|
defaultNS: 'common',
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import type { AppTheme } from '@/types/theme';
|
|
||||||
import { createContext } from 'react';
|
|
||||||
|
|
||||||
export interface AppThemeContextModel {
|
|
||||||
mode: AppTheme;
|
|
||||||
changeTheme: (theme: AppTheme) => void;
|
|
||||||
}
|
|
||||||
// The context is used by the LangaugeProvider
|
|
||||||
export const AppThemeContext = createContext<AppThemeContextModel>({
|
|
||||||
mode: 'default',
|
|
||||||
changeTheme: () => {},
|
|
||||||
});
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { type Language } from '@/types/language';
|
|
||||||
import { createContext } from 'react';
|
|
||||||
|
|
||||||
export interface LangaugeContextModel {
|
|
||||||
language: Language;
|
|
||||||
changeLanguage: (langauge: Language) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The context is used by the LangaugeProvider
|
|
||||||
export const LangaugeContext = createContext<LangaugeContextModel>({
|
|
||||||
language: 'fa',
|
|
||||||
changeLanguage: () => {},
|
|
||||||
});
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import {
|
|
||||||
AppThemeContext,
|
|
||||||
type AppThemeContextModel,
|
|
||||||
} from '@/contexts/AppThemeContext';
|
|
||||||
import { useTheme, type Theme } from '@mui/material';
|
|
||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
export interface AppThemeHookModel extends AppThemeContextModel {
|
|
||||||
theme: Theme;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAppTheme = (): AppThemeHookModel => {
|
|
||||||
const appThemeContext = useContext(AppThemeContext);
|
|
||||||
const muiTheme = useTheme();
|
|
||||||
|
|
||||||
return {
|
|
||||||
mode: appThemeContext.mode,
|
|
||||||
changeTheme: appThemeContext.changeTheme,
|
|
||||||
theme: muiTheme,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import {
|
|
||||||
LangaugeContext,
|
|
||||||
type LangaugeContextModel,
|
|
||||||
} from '@/contexts/LangaugeContext';
|
|
||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
export const useLangauge = (): LangaugeContextModel => {
|
|
||||||
return useContext(LangaugeContext);
|
|
||||||
};
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import i18next from 'i18next';
|
|
||||||
import I18NextHttpBackend from 'i18next-http-backend';
|
|
||||||
import { initReactI18next } from 'react-i18next';
|
|
||||||
|
|
||||||
i18next
|
|
||||||
.use(I18NextHttpBackend)
|
|
||||||
.use(initReactI18next)
|
|
||||||
.init({
|
|
||||||
lng: 'en',
|
|
||||||
interpolation: {
|
|
||||||
escapeValue: false,
|
|
||||||
},
|
|
||||||
backend: {
|
|
||||||
loadPath: `${window.location.origin}/i18n/{{lng}}.json`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
13
src/main.tsx
13
src/main.tsx
@@ -1,18 +1,13 @@
|
|||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import './lib/i18n';
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import { LanguageProvider } from './providers/LanguageProvider';
|
import { AppProviders } from './providers/AppProvider';
|
||||||
import { createTheme, ThemeProvider } from '@mui/material';
|
|
||||||
import { AppThemeProvider } from './providers/ThemeProvider';
|
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<LanguageProvider>
|
<AppProviders>
|
||||||
<AppThemeProvider>
|
<App />
|
||||||
<App />
|
</AppProviders>
|
||||||
</AppThemeProvider>
|
|
||||||
</LanguageProvider>
|
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
|
|||||||
9
src/providers/AppProvider.tsx
Normal file
9
src/providers/AppProvider.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { I18nextProvider } from 'react-i18next';
|
||||||
|
import i18n from '@/config/i18n';
|
||||||
|
|
||||||
|
export const AppProviders: React.FC<{ children: React.ReactNode }> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
|
||||||
|
};
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import type { Language } from '@/types/language';
|
|
||||||
import { changeLanguage } from 'i18next';
|
|
||||||
import { useLayoutEffect, type JSX, type PropsWithChildren } from 'react';
|
|
||||||
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
|
||||||
import { LangaugeContext } from '@/contexts/LangaugeContext';
|
|
||||||
|
|
||||||
export const LanguageProvider = (props: PropsWithChildren): JSX.Element => {
|
|
||||||
const [currentLanguage, setCurrentLangauge] = useLocalStorage<Language>(
|
|
||||||
'language',
|
|
||||||
'fa',
|
|
||||||
);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
changeLanguage(currentLanguage);
|
|
||||||
document.documentElement.dir = currentLanguage === 'fa' ? 'rtl' : 'ltr';
|
|
||||||
document.documentElement.lang = currentLanguage;
|
|
||||||
}, [currentLanguage]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LangaugeContext.Provider
|
|
||||||
value={{
|
|
||||||
language: currentLanguage,
|
|
||||||
changeLanguage: setCurrentLangauge,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</LangaugeContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { useLayoutEffect, type JSX, type PropsWithChildren } from 'react';
|
|
||||||
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
|
||||||
import type { AppTheme } from '@/types/theme';
|
|
||||||
import { AppThemeContext } from '@/contexts/AppThemeContext';
|
|
||||||
import { createTheme, ThemeProvider } from '@mui/material';
|
|
||||||
import { useLangauge } from '@/hooks/useLanguage';
|
|
||||||
|
|
||||||
export const AppThemeProvider = (props: PropsWithChildren): JSX.Element => {
|
|
||||||
const { language } = useLangauge();
|
|
||||||
const [currentThemeMode, setCurrentTheme] = useLocalStorage<AppTheme>(
|
|
||||||
'theme',
|
|
||||||
'default',
|
|
||||||
);
|
|
||||||
|
|
||||||
const muiTheme = createTheme({
|
|
||||||
direction: language === 'fa' ? 'rtl' : 'ltr',
|
|
||||||
});
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
document.body.setAttribute('theme', currentThemeMode);
|
|
||||||
}, [currentThemeMode]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppThemeContext.Provider
|
|
||||||
value={{
|
|
||||||
mode: currentThemeMode,
|
|
||||||
changeTheme: setCurrentTheme,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ThemeProvider theme={muiTheme}>{props.children}</ThemeProvider>
|
|
||||||
</AppThemeContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export type Language = 'en' | 'fa';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export type AppTheme = 'default' | 'dark';
|
|
||||||
Reference in New Issue
Block a user