diff --git a/package-lock.json b/package-lock.json index c8d172d..081d470 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.14.1", "@mui/material": "^7.2.0", "i18next": "^25.3.0", + "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", "react": "^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": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz", diff --git a/package.json b/package.json index ed0a945..f20b020 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@emotion/styled": "^11.14.1", "@mui/material": "^7.2.0", "i18next": "^25.3.0", + "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", "react": "^19.1.0", "react-dom": "^19.1.0", diff --git a/public/i18n/en.json b/public/i18n/en.json deleted file mode 100644 index df5f62e..0000000 --- a/public/i18n/en.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Loyalty Club" -} diff --git a/public/i18n/fa.json b/public/i18n/fa.json deleted file mode 100644 index 3c6a9d4..0000000 --- a/public/i18n/fa.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "باشگاه مشتریان" -} diff --git a/public/locales/en/common.json b/public/locales/en/common.json new file mode 100644 index 0000000..f63d94d --- /dev/null +++ b/public/locales/en/common.json @@ -0,0 +1,3 @@ +{ + "helloWorld": "hello world" +} \ No newline at end of file diff --git a/public/locales/fa/common.json b/public/locales/fa/common.json new file mode 100644 index 0000000..7a3cc8e --- /dev/null +++ b/public/locales/fa/common.json @@ -0,0 +1,3 @@ +{ + "helloWorld": "سلام دنیا" +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index f053091..4f4cadd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,20 @@ +import { CssBaseline } from '@mui/material'; import './App.css'; +import { useTranslation } from 'react-i18next'; +import { LanguageManager } from './components/LanguageManager'; function App() { + const { t } = useTranslation(); + return ( -
-

Hello World

-
+ <> + + +
+

{t('helloWorld')}

+

The main content and router will go here.

+
+ ); } diff --git a/src/components/LanguageManager.tsx b/src/components/LanguageManager.tsx new file mode 100644 index 0000000..52072b1 --- /dev/null +++ b/src/components/LanguageManager.tsx @@ -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; +}; diff --git a/src/config/i18n.ts b/src/config/i18n.ts new file mode 100644 index 0000000..c05d74f --- /dev/null +++ b/src/config/i18n.ts @@ -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; diff --git a/src/contexts/AppThemeContext.ts b/src/contexts/AppThemeContext.ts deleted file mode 100644 index 75dced3..0000000 --- a/src/contexts/AppThemeContext.ts +++ /dev/null @@ -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({ - mode: 'default', - changeTheme: () => {}, -}); diff --git a/src/contexts/LangaugeContext.ts b/src/contexts/LangaugeContext.ts deleted file mode 100644 index a2e84a5..0000000 --- a/src/contexts/LangaugeContext.ts +++ /dev/null @@ -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({ - language: 'fa', - changeLanguage: () => {}, -}); diff --git a/src/hooks/useAppTheme.ts b/src/hooks/useAppTheme.ts deleted file mode 100644 index ceffc07..0000000 --- a/src/hooks/useAppTheme.ts +++ /dev/null @@ -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, - }; -}; diff --git a/src/hooks/useLanguage.ts b/src/hooks/useLanguage.ts deleted file mode 100644 index b176261..0000000 --- a/src/hooks/useLanguage.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { - LangaugeContext, - type LangaugeContextModel, -} from '@/contexts/LangaugeContext'; -import { useContext } from 'react'; - -export const useLangauge = (): LangaugeContextModel => { - return useContext(LangaugeContext); -}; diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts deleted file mode 100644 index 6466d4c..0000000 --- a/src/lib/i18n.ts +++ /dev/null @@ -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`, - }, - }); diff --git a/src/main.tsx b/src/main.tsx index 58f5869..be3d73c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,18 +1,13 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import './index.css'; -import './lib/i18n'; import App from './App'; -import { LanguageProvider } from './providers/LanguageProvider'; -import { createTheme, ThemeProvider } from '@mui/material'; -import { AppThemeProvider } from './providers/ThemeProvider'; +import { AppProviders } from './providers/AppProvider'; createRoot(document.getElementById('root')!).render( - - - - - + + + , ); diff --git a/src/providers/AppProvider.tsx b/src/providers/AppProvider.tsx new file mode 100644 index 0000000..195619e --- /dev/null +++ b/src/providers/AppProvider.tsx @@ -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 {children}; +}; diff --git a/src/providers/LanguageProvider.tsx b/src/providers/LanguageProvider.tsx deleted file mode 100644 index 046bd83..0000000 --- a/src/providers/LanguageProvider.tsx +++ /dev/null @@ -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', - 'fa', - ); - - useLayoutEffect(() => { - changeLanguage(currentLanguage); - document.documentElement.dir = currentLanguage === 'fa' ? 'rtl' : 'ltr'; - document.documentElement.lang = currentLanguage; - }, [currentLanguage]); - - return ( - - {props.children} - - ); -}; diff --git a/src/providers/ThemeProvider.tsx b/src/providers/ThemeProvider.tsx deleted file mode 100644 index 8aaa8ba..0000000 --- a/src/providers/ThemeProvider.tsx +++ /dev/null @@ -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( - 'theme', - 'default', - ); - - const muiTheme = createTheme({ - direction: language === 'fa' ? 'rtl' : 'ltr', - }); - - useLayoutEffect(() => { - document.body.setAttribute('theme', currentThemeMode); - }, [currentThemeMode]); - - return ( - - {props.children} - - ); -}; diff --git a/src/types/language.ts b/src/types/language.ts deleted file mode 100644 index ca7f950..0000000 --- a/src/types/language.ts +++ /dev/null @@ -1 +0,0 @@ -export type Language = 'en' | 'fa'; diff --git a/src/types/theme.ts b/src/types/theme.ts deleted file mode 100644 index 18e81a8..0000000 --- a/src/types/theme.ts +++ /dev/null @@ -1 +0,0 @@ -export type AppTheme = 'default' | 'dark';