diff --git a/i18nConfig.js b/i18nConfig.js new file mode 100644 index 0000000..770f6ba --- /dev/null +++ b/i18nConfig.js @@ -0,0 +1,6 @@ +const i18nConfig = { + locales: ['en', 'fa'], + defaultLocale: 'fa' +}; + +module.exports = i18nConfig; diff --git a/package-lock.json b/package-lock.json index af5320a..3a339f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,13 +24,17 @@ "cmdk": "^1.1.1", "dmenu-plus-frontned": "file:", "framer-motion": "^12.23.3", + "i18next": "^25.3.2", + "i18next-resources-to-backend": "^1.2.1", "iconsax-react": "^0.0.8", "lucide-react": "^0.525.0", "next": "15.3.4", + "next-i18n-router": "^5.5.3", "next-usequerystate": "^1.20.0", "react": "^19.0.0", "react-day-picker": "^9.8.0", "react-dom": "^19.0.0", + "react-i18next": "^15.6.1", "react-virtualized": "^9.22.6", "tailwind-merge": "^3.3.1", "zustand": "^5.0.6" @@ -316,6 +320,15 @@ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz", + "integrity": "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==", + "license": "MIT", + "dependencies": { + "tslib": "2" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -4691,6 +4704,55 @@ "node": ">= 0.4" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "25.3.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.3.2.tgz", + "integrity": "sha512-JSnbZDxRVbphc5jiptxr3o2zocy5dEqpVm9qCGdJwRNO+9saUJS0/u4LnM/13C23fUEWxAylPqKU/NpMV/IjqA==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-resources-to-backend": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/i18next-resources-to-backend/-/i18next-resources-to-backend-1.2.1.tgz", + "integrity": "sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/iconsax-react": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/iconsax-react/-/iconsax-react-0.0.8.tgz", @@ -5796,6 +5858,15 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next": { "version": "15.3.4", "resolved": "https://registry.npmjs.org/next/-/next-15.3.4.tgz", @@ -5850,6 +5921,16 @@ } } }, + "node_modules/next-i18n-router": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/next-i18n-router/-/next-i18n-router-5.5.3.tgz", + "integrity": "sha512-btKVR/Mup05scxJp3EdPNALlufnZ/BO/zkIZc8a5ESdu+CNnmLW1AOkziTfRawXiZcznACCQtyfKNobC/WBTEA==", + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "^0.5.2", + "negotiator": "^0.6.3" + } + }, "node_modules/next-usequerystate": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/next-usequerystate/-/next-usequerystate-1.20.0.tgz", @@ -6278,6 +6359,32 @@ "react": "^19.1.0" } }, + "node_modules/react-i18next": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.6.1.tgz", + "integrity": "sha512-uGrzSsOUUe2sDBG/+FJq2J1MM+Y4368/QW8OLEKSFvnDflHBbZhSd1u3UkW0Z06rMhZmnB/AQrhCpYfE5/5XNg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -7367,6 +7474,15 @@ } } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 9cdd7c4..c1b06dd 100644 --- a/package.json +++ b/package.json @@ -25,13 +25,17 @@ "cmdk": "^1.1.1", "dmenu-plus-frontned": "file:", "framer-motion": "^12.23.3", + "i18next": "^25.3.2", + "i18next-resources-to-backend": "^1.2.1", "iconsax-react": "^0.0.8", "lucide-react": "^0.525.0", "next": "15.3.4", + "next-i18n-router": "^5.5.3", "next-usequerystate": "^1.20.0", "react": "^19.0.0", "react-day-picker": "^9.8.0", "react-dom": "^19.0.0", + "react-i18next": "^15.6.1", "react-virtualized": "^9.22.6", "tailwind-merge": "^3.3.1", "zustand": "^5.0.6" diff --git a/src/app/[name]/[id]/page.tsx b/src/app/[locale]/[name]/[id]/page.tsx similarity index 100% rename from src/app/[name]/[id]/page.tsx rename to src/app/[locale]/[name]/[id]/page.tsx diff --git a/src/app/[name]/about/AboutPage.tsx b/src/app/[locale]/[name]/about/AboutPage.tsx similarity index 100% rename from src/app/[name]/about/AboutPage.tsx rename to src/app/[locale]/[name]/about/AboutPage.tsx diff --git a/src/app/[name]/about/layout.tsx b/src/app/[locale]/[name]/about/layout.tsx similarity index 100% rename from src/app/[name]/about/layout.tsx rename to src/app/[locale]/[name]/about/layout.tsx diff --git a/src/app/[name]/about/page.tsx b/src/app/[locale]/[name]/about/page.tsx similarity index 100% rename from src/app/[name]/about/page.tsx rename to src/app/[locale]/[name]/about/page.tsx diff --git a/src/app/[name]/chat/[id]/page.tsx b/src/app/[locale]/[name]/chat/[id]/page.tsx similarity index 100% rename from src/app/[name]/chat/[id]/page.tsx rename to src/app/[locale]/[name]/chat/[id]/page.tsx diff --git a/src/app/[name]/chat/page.tsx b/src/app/[locale]/[name]/chat/page.tsx similarity index 100% rename from src/app/[name]/chat/page.tsx rename to src/app/[locale]/[name]/chat/page.tsx diff --git a/src/app/[name]/layout.tsx b/src/app/[locale]/[name]/layout.tsx similarity index 100% rename from src/app/[name]/layout.tsx rename to src/app/[locale]/[name]/layout.tsx diff --git a/src/app/[name]/loading.tsx b/src/app/[locale]/[name]/loading.tsx similarity index 100% rename from src/app/[name]/loading.tsx rename to src/app/[locale]/[name]/loading.tsx diff --git a/src/app/[name]/orders/layout.tsx b/src/app/[locale]/[name]/orders/layout.tsx similarity index 100% rename from src/app/[name]/orders/layout.tsx rename to src/app/[locale]/[name]/orders/layout.tsx diff --git a/src/app/[name]/orders/page.tsx b/src/app/[locale]/[name]/orders/page.tsx similarity index 100% rename from src/app/[name]/orders/page.tsx rename to src/app/[locale]/[name]/orders/page.tsx diff --git a/src/app/[name]/page.tsx b/src/app/[locale]/[name]/page.tsx similarity index 100% rename from src/app/[name]/page.tsx rename to src/app/[locale]/[name]/page.tsx diff --git a/src/app/[name]/rating/[orderId]/page.tsx b/src/app/[locale]/[name]/rating/[orderId]/page.tsx similarity index 100% rename from src/app/[name]/rating/[orderId]/page.tsx rename to src/app/[locale]/[name]/rating/[orderId]/page.tsx diff --git a/src/app/[name]/transactions/page.tsx b/src/app/[locale]/[name]/transactions/page.tsx similarity index 100% rename from src/app/[name]/transactions/page.tsx rename to src/app/[locale]/[name]/transactions/page.tsx diff --git a/src/app/[name]/transactions/review/page.tsx b/src/app/[locale]/[name]/transactions/review/page.tsx similarity index 100% rename from src/app/[name]/transactions/review/page.tsx rename to src/app/[locale]/[name]/transactions/review/page.tsx diff --git a/src/app/auth/layout.tsx b/src/app/[locale]/auth/layout.tsx similarity index 100% rename from src/app/auth/layout.tsx rename to src/app/[locale]/auth/layout.tsx diff --git a/src/app/auth/page.tsx b/src/app/[locale]/auth/page.tsx similarity index 100% rename from src/app/auth/page.tsx rename to src/app/[locale]/auth/page.tsx diff --git a/src/app/[locale]/dictionaries.ts b/src/app/[locale]/dictionaries.ts new file mode 100644 index 0000000..c96bb59 --- /dev/null +++ b/src/app/[locale]/dictionaries.ts @@ -0,0 +1,9 @@ +import 'server-only' + +const dictionaries = { + en: () => import('@/dictionaries/en.json').then((module) => module.default), + fa: () => import('@/dictionaries/fa.json').then((module) => module.default), +} + +export const getDictionary = async (locale: 'en' | 'fa') => + dictionaries[locale]() \ No newline at end of file diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx new file mode 100644 index 0000000..9256933 --- /dev/null +++ b/src/app/[locale]/layout.tsx @@ -0,0 +1,49 @@ +import { Metadata } from "next"; +import "../globals.css"; +import { ReactQueryProvider } from "@/components/providers/ReactQueryProvider"; +import { notFound } from "next/navigation"; +import i18nConfig from "../../../i18nConfig"; +import initTranslations from '@/lib/i18n'; +import TranslationsProvider from "@/components/utils/TranslationsProdiver"; + +export const metadata: Metadata = { + title: 'Dashboard', + description: 'Webapp dashboard' +} + +const i18nNamespaces = ['auth']; + +export default async function RootLayout({ + children, + params, +}: Readonly<{ + children: React.ReactNode; + params: { locale: string } +}>) { + const { locale } = await params; + if (!i18nConfig.locales.includes(locale)) { + notFound(); + } + const { resources } = await initTranslations(locale, i18nNamespaces); + + return ( + + + + + +
+ {children} +
+
+
+ + + ); +} diff --git a/src/app/loading.tsx b/src/app/[locale]/loading.tsx similarity index 100% rename from src/app/loading.tsx rename to src/app/[locale]/loading.tsx diff --git a/src/app/page.tsx b/src/app/[locale]/page.tsx similarity index 100% rename from src/app/page.tsx rename to src/app/[locale]/page.tsx diff --git a/src/app/profile/edit/page.tsx b/src/app/[locale]/profile/edit/page.tsx similarity index 100% rename from src/app/profile/edit/page.tsx rename to src/app/[locale]/profile/edit/page.tsx diff --git a/src/app/profile/layout.tsx b/src/app/[locale]/profile/layout.tsx similarity index 100% rename from src/app/profile/layout.tsx rename to src/app/[locale]/profile/layout.tsx diff --git a/src/app/profile/page.tsx b/src/app/[locale]/profile/page.tsx similarity index 100% rename from src/app/profile/page.tsx rename to src/app/[locale]/profile/page.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx deleted file mode 100644 index e3d51d4..0000000 --- a/src/app/layout.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Metadata } from "next"; -import "./globals.css"; -import { ReactQueryProvider } from "@/components/providers/ReactQueryProvider"; - -export const metadata: Metadata = { - title: 'Dashboard', - description: 'Webapp dashboard' -} - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - - return ( - - - - -
- {children} -
-
- - - ); -} diff --git a/src/components/input/InputField.tsx b/src/components/input/InputField.tsx index ac7bd89..fce0d5d 100644 --- a/src/components/input/InputField.tsx +++ b/src/components/input/InputField.tsx @@ -15,7 +15,7 @@ function InputField({ onChange, htmlFor, labelText, children, valid = true, clas return (
diff --git a/src/components/utils/TranslationsProdiver.jsx b/src/components/utils/TranslationsProdiver.jsx new file mode 100644 index 0000000..a013f06 --- /dev/null +++ b/src/components/utils/TranslationsProdiver.jsx @@ -0,0 +1,18 @@ +'use client'; + +import { I18nextProvider } from 'react-i18next'; +import initTranslations from '@/lib/i18n'; +import { createInstance } from 'i18next'; + +export default function TranslationsProvider({ + children, + locale, + namespaces, + resources +}) { + const i18n = createInstance(); + + initTranslations(locale, namespaces, i18n, resources); + + return {children}; +} \ No newline at end of file diff --git a/src/features/auth/components/StepEnterNumber.tsx b/src/features/auth/components/StepEnterNumber.tsx index 5639dc1..07cd928 100644 --- a/src/features/auth/components/StepEnterNumber.tsx +++ b/src/features/auth/components/StepEnterNumber.tsx @@ -1,8 +1,11 @@ +'use client'; + import Button from '@/components/button/PrimaryButton'; import InputField from '@/components/input/InputField'; import { AUTH_PAGE_ELEMENT } from '@/enums'; import Image from 'next/image'; import React, { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next'; type Props = { onChange: React.ChangeEventHandler; @@ -12,6 +15,7 @@ type Props = { function StepEnterNumber({ onChange, pending, disabled, value }: Props) { const [error, setError] = useState(''); + const { t } = useTranslation(); const hasError = (e = error) => { return e.trim().length > 0 @@ -21,12 +25,12 @@ function StepEnterNumber({ onChange, pending, disabled, value }: Props) { let error = ''; if (value.length > 0) { if (!/^09\d{7,9}$/.test(value)) { - error = 'فرمت اشتباه است'; + error = t('Errors.PhoneNumberFormat'); } } setError(error); - }, [value]) + }, [t, value]) return ( <> @@ -41,9 +45,9 @@ function StepEnterNumber({ onChange, pending, disabled, value }: Props) { />
-
-
ورود به سیستم
-

برای ورود شماره همراه خود را وارد نمایید.

+
+
{t('Enter.Heading')}
+

{t('Enter.Description')}

- +
) diff --git a/src/features/auth/components/StepEnterOtp.tsx b/src/features/auth/components/StepEnterOtp.tsx index f570f25..6472c01 100644 --- a/src/features/auth/components/StepEnterOtp.tsx +++ b/src/features/auth/components/StepEnterOtp.tsx @@ -4,6 +4,7 @@ import { AUTH_PAGE_ELEMENT, AUTH_PAGE_ELEMENT as AUTH_PAGE_ELEMENTS } from '@/en import clsx from 'clsx'; import Image from 'next/image'; import React, { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next'; type Props = { onChange: ((e: React.ChangeEvent) => void) & React.ChangeEventHandler; @@ -17,6 +18,7 @@ type Props = { function StepEnterOtp({ onChange, onClick, pending, disabled = false, value, phoneNumber, timerRunning, secondsLeft }: Props) { const [error, setError] = useState(''); + const { t } = useTranslation(); const hasError = (e = error) => { return e.trim().length > 0 @@ -25,11 +27,11 @@ function StepEnterOtp({ onChange, onClick, pending, disabled = false, value, pho useEffect(() => { let error = ''; if (!/^\d{6}$/.test(value)) { - error = 'عبارت کامل نیست'; + error = t('Errors.OTPFormat'); } setError(error); - }, [value]) + }, [t, value]) return ( <> @@ -43,10 +45,10 @@ function StepEnterOtp({ onChange, onClick, pending, disabled = false, value, pho priority />
-
+
{/* // TODO: add persian digits font */} -
کد فعالسازی را وارد کنید
-

کد ۶ رقمی ارسال شده به شماره {phoneNumber} را وارد نمایید.

+
{t('OTP.Heading')}
+

{t('OTP.Description').replace('{phoneNumber}', phoneNumber)}

-
+
{timerRunning ? -
{secondsLeft} ثانیه دیگر تا دریافت کد
: +
{t('OTP.TimerRunning').replace('{seconds}', String(secondsLeft))}
:
- کد را دریافت نکردید؟ + {t('OTP.TimerOut')}
}
- +
) diff --git a/src/features/auth/components/StepEnterPassword.tsx b/src/features/auth/components/StepEnterPassword.tsx index 91a2aa5..b5abb9c 100644 --- a/src/features/auth/components/StepEnterPassword.tsx +++ b/src/features/auth/components/StepEnterPassword.tsx @@ -4,6 +4,7 @@ import InputField from '@/components/input/InputField'; import { AUTH_PAGE_ELEMENT } from '@/enums'; import Image from 'next/image'; import React, { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next'; type Props = { onChange: ((e: React.ChangeEvent) => void) & React.ChangeEventHandler; @@ -16,6 +17,7 @@ type Props = { function StepEnterPassword({ onChange, onClick, pending, disabled = false, value, passwordVisible, rememberMe }: Props) { const [error, setError] = useState(''); + const { t } = useTranslation(); const hasError = (e = error) => { return e.trim().length > 0 @@ -25,12 +27,12 @@ function StepEnterPassword({ onChange, onClick, pending, disabled = false, value let error = ''; if (value.length > 0) { if (!/^.{6,}$/.test(value)) { - error = 'کلمه عبور باید شامل حداقل 6 کاراکتر باشد'; + error = t('Errors.PasswordLength'); } } setError(error); - }, [value]) + }, [t, value]) return ( <> @@ -45,9 +47,9 @@ function StepEnterPassword({ onChange, onClick, pending, disabled = false, value />
-
-
ورود به سیستم
-

کلمه عبور خود را وارد نمایید.

+
+
{t('EnterPass.Heading')}
+

{t('EnterPass.Description')}

- +
{/* TODO: customize checkbox */} - +
- +
) diff --git a/src/features/auth/components/StepNewPassword.tsx b/src/features/auth/components/StepNewPassword.tsx index 15c0a41..a334d73 100644 --- a/src/features/auth/components/StepNewPassword.tsx +++ b/src/features/auth/components/StepNewPassword.tsx @@ -4,6 +4,7 @@ import InputField from '@/components/input/InputField'; import { AUTH_PAGE_ELEMENT } from '@/enums'; import Image from 'next/image'; import React, { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next'; type Props = { onChange: ((e: React.ChangeEvent) => void) & React.ChangeEventHandler; @@ -19,6 +20,7 @@ type Props = { function StepNewPassword({ isReset: reset = false, pending, disabled = false, onChange, onClick, value, repeatValue, passwordVisible, repeatPasswordVisible }: Props) { const [error, setError] = useState(''); const [error2, setError2] = useState(''); + const { t } = useTranslation(); const hasError = (e = error) => { return e.trim().length > 0 @@ -29,20 +31,20 @@ function StepNewPassword({ isReset: reset = false, pending, disabled = false, on let error2 = ''; if (value.length > 0) { if (!/^.{6,}$/.test(value)) { - error = 'کلمه عبور باید شامل حداقل 6 کاراکتر باشد'; + error = t('Errors.PasswordLength'); } } if (repeatValue.length > 0) { if (!/^.{6,}$/.test(repeatValue)) { - error2 = 'کلمه عبور باید شامل حداقل 6 کاراکتر باشد'; + error2 = t('Errors.PasswordLength'); } else if (repeatValue !== value) { - error2 = 'تکرار کلمه عبور مطابقت ندارد'; + error2 = t('Errors.PasswordMatch'); } } setError(error); setError2(error2); - }, [value, repeatValue]) + }, [value, repeatValue, t]) return ( <> @@ -57,11 +59,11 @@ function StepNewPassword({ isReset: reset = false, pending, disabled = false, on />
-
+
- {reset ? "تغییر کلمه عبور" : "انتخاب کلمه عبور"} + {reset ? t('NewPassword.HeadingResetPass') : t('NewPassword.HeadingCreatePass')}
-

کلمه عبور جدید باید ترکیبی از حروف بزرگ و کوچک و نشانه ها باشد مثل A126bdz@

+

{t('NewPassword.Description')}

- +
) diff --git a/src/lib/i18n.js b/src/lib/i18n.js new file mode 100644 index 0000000..1f9856f --- /dev/null +++ b/src/lib/i18n.js @@ -0,0 +1,41 @@ +import { createInstance } from 'i18next'; +import { initReactI18next } from 'react-i18next/initReactI18next'; +import resourcesToBackend from 'i18next-resources-to-backend'; +import i18nConfig from '../../i18nConfig'; + +export default async function initTranslations( + locale, + namespaces, + i18nInstance, + resources +) { + i18nInstance = i18nInstance || createInstance(); + + i18nInstance.use(initReactI18next); + + if (!resources) { + i18nInstance.use( + resourcesToBackend( + (language, namespace) => + import(`@/locales/${language}/${namespace}.json`) + ) + ); + } + + await i18nInstance.init({ + lng: locale, + resources, + fallbackLng: i18nConfig.defaultLocale, + supportedLngs: i18nConfig.locales, + defaultNS: namespaces[0], + fallbackNS: namespaces[0], + ns: namespaces, + preload: resources ? [] : i18nConfig.locales + }); + + return { + i18n: i18nInstance, + resources: { [locale]: i18nInstance.services.resourceStore.data[locale] }, + t: i18nInstance.t + }; +} \ No newline at end of file diff --git a/src/locales/en/auth.json b/src/locales/en/auth.json new file mode 100644 index 0000000..467ea6e --- /dev/null +++ b/src/locales/en/auth.json @@ -0,0 +1,39 @@ +{ + "Enter": { + "Heading": "Login", + "Description": "Please enter your mobile number to log in.", + "LabelPhoneNumber": "Mobile Number", + "ButtonSubmit": "Next" + }, + "OTP": { + "Heading": "Enter Activation Code", + "Description": "Please enter the 6-digit code sent to the number {phoneNumber}.", + "Label": "", + "TimerRunning": "{seconds} seconds left to receive the code", + "TimerOut": "Didn’t receive the code?", + "TimerReset": "Try again", + "ButtonSubmit": "Next" + }, + "NewPassword": { + "HeadingCreatePass": "Choose Password", + "HeadingResetPass": "Change Password", + "Description": "The new password must be a combination of uppercase and lowercase letters and symbols, like A126bdz@", + "LabelPass": "Password", + "LabelRepeatPass": "Repeat Password", + "ButtonSubmit": "Login" + }, + "EnterPass": { + "Heading": "Login", + "Description": "Please enter your password.", + "LabelPass": "Password", + "ButtonRecover": "Recover Password", + "LabelRememberance": "Remember me", + "ButtonSubmit": "Enter Menu" + }, + "Errors": { + "PasswordLength": "Password must be at least 6 characters long", + "PasswordMatch": "Repeated password does not match", + "PhoneNumberFormat": "Phone number format is incorrect", + "OTPFormat": "Activation code is incomplete" + } +} diff --git a/src/locales/fa/auth.json b/src/locales/fa/auth.json new file mode 100644 index 0000000..e0a2462 --- /dev/null +++ b/src/locales/fa/auth.json @@ -0,0 +1,39 @@ +{ + "Enter": { + "Heading": "ورود به سیستم", + "Description": "برای ورود شماره همراه خود را وارد نمایید.", + "LabelPhoneNumber": "شماره همراه", + "ButtonSubmit": "بعدی" + }, + "OTP": { + "Heading": "کد فعالسازی را وارد کنید", + "Description": "کد ۶ رقمی ارسال شده به شماره {phoneNumber} را وارد نمایید.", + "Label": "", + "TimerRunning": "{seconds} ثانیه دیگر تا دریافت کد", + "TimerOut": "کد را دریافت نکردید؟", + "TimerReset": "دوباره امتحان کنید", + "ButtonSubmit": "بعدی" + }, + "NewPassword": { + "HeadingCreatePass": "انتخاب کلمه عبور", + "HeadingResetPass": "تغییر کلمه عبور", + "Description": "کلمه عبور جدید باید ترکیبی از حروف بزرگ و کوچک و نشانه ها باشد مثل A126bdz@", + "LabelPass": "کلمه عبور", + "LabelRepeatPass": "تکرار کلمه عبور", + "ButtonSubmit": "ورود" + }, + "EnterPass": { + "Heading": "ورود به سیستم", + "Description": "کلمه عبور خود را وارد نمایید.", + "LabelPass": "کلمه عبور", + "ButtonRecover": "بازیابی کلمه عبور", + "LabelRememberance": "مرا به خاطر بسپار", + "ButtonSubmit": "ورود به منو" + }, + "Errors": { + "PasswordLength": "کلمه عبور باید شامل حداقل 6 کاراکتر باشد", + "PasswordMatch": "تکرار کلمه عبور مطابقت ندارد", + "PhoneNumberFormat": "فرمت شماره تلفن اشتباه است", + "OTPFormat": "کد فعالسازی کامل نیست" + } +} \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..0ed4a3e --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,12 @@ +import { i18nRouter } from 'next-i18n-router'; +import { NextRequest } from 'next/server'; +import i18nConfig from '../i18nConfig'; + +export function middleware(request: NextRequest) { + return i18nRouter(request, i18nConfig); +} + +// only applies this middleware to files in the app directory +export const config = { + matcher: '/((?!api|static|favicon.ico|.*\\..*|_next).*)' +}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index c133409..a800600 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,6 @@ "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/app/middleware.js", "src/components/utils/TranslationsProdiver.jsx"], "exclude": ["node_modules"] }