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"]
}