diff --git a/next.config.ts b/next.config.ts index e9ffa30..b22af96 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,5 @@ -import type { NextConfig } from "next"; +import type { NextConfig } from 'next'; -const nextConfig: NextConfig = { - /* config options here */ -}; +const nextConfig: NextConfig = {}; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index 22a557c..80cf4ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,9 @@ "name": "pasargad_bricks", "version": "0.1.0", "dependencies": { - "i18next": "^25.2.1", - "i18next-browser-languagedetector": "^8.1.0", - "i18next-http-backend": "^3.0.2", "next": "15.3.3", "react": "^19.0.0", - "react-dom": "^19.0.0", - "react-i18next": "^15.5.2" + "react-dom": "^19.0.0" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -57,14 +53,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.4.tgz", - "integrity": "sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@emnapi/core": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", @@ -2214,14 +2202,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3430,60 +3410,6 @@ "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==", - "dependencies": { - "void-elements": "3.1.0" - } - }, - "node_modules/i18next": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.2.1.tgz", - "integrity": "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==", - "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" - } - ], - "dependencies": { - "@babel/runtime": "^7.27.1" - }, - "peerDependencies": { - "typescript": "^5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/i18next-browser-languagedetector": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.1.0.tgz", - "integrity": "sha512-mHZxNx1Lq09xt5kCauZ/4bsXOEA2pfpwSoU11/QTJB+pD94iONFwp+ohqi///PwiFvjFOxe1akYCdHyFo1ng5Q==", - "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", - "integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==", - "dependencies": { - "cross-fetch": "4.0.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4536,25 +4462,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4919,31 +4826,6 @@ "react": "^19.1.0" } }, - "node_modules/react-i18next": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.2.tgz", - "integrity": "sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==", - "dependencies": { - "@babel/runtime": "^7.25.0", - "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", @@ -5631,11 +5513,6 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -5755,7 +5632,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "devOptional": true, + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5829,28 +5706,6 @@ "punycode": "^2.1.0" } }, - "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==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.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 e3e0dec..502a45c 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,9 @@ "lint": "next lint" }, "dependencies": { - "i18next": "^25.2.1", - "i18next-browser-languagedetector": "^8.1.0", - "i18next-http-backend": "^3.0.2", "next": "15.3.3", "react": "^19.0.0", - "react-dom": "^19.0.0", - "react-i18next": "^15.5.2" + "react-dom": "^19.0.0" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/src/app/aaa/page.tsx b/src/app/aaa/page.tsx index bfcfb19..0dec3c1 100644 --- a/src/app/aaa/page.tsx +++ b/src/app/aaa/page.tsx @@ -10,7 +10,7 @@ export default async function Home() { const data = await getData(); return ( -
+
ssss
); diff --git a/src/app/globals.css b/src/app/globals.css index bc2f3c8..b22fef7 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -59,7 +59,7 @@ } :root { - --primary: #6366F1; + --primary: #FA003F; --secondary: #A78BFA; --textColorLight: #FFFFFF; --textColorGray: #222222; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b680298..f0a1bf9 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,23 +1,22 @@ import type { Metadata } from 'next'; import './globals.css'; -import { I18nextProvider } from 'react-i18next'; -import i18n from '../config/i18n'; +import { cookies } from 'next/headers'; export const metadata: Metadata = { title: 'Create Next App', description: 'Generated by create next app', }; -export default function RootLayout({ +export default async function RootLayout({ children, -}: { +}: Readonly<{ children: React.ReactNode; -}) { +}>) { + const locale = (await cookies()).get('lang')?.value || 'en'; + return ( - - - {children} - + + {children} ); } diff --git a/src/app/page.tsx b/src/app/page.tsx index bfcfb19..20d667f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,4 +1,6 @@ // ... existing imports ... +import ChangeLangButton from '@/components/layout/languageSwitcher'; +import { changeLanguage, t } from '@/locales/translates'; async function getData() { // Simulate a delay to show server-side data fetching @@ -6,12 +8,18 @@ async function getData() { return { message: 'This data was fetched on the server!' }; } +const change = () => { + changeLanguage('fa'); +}; + export default async function Home() { const data = await getData(); return (
- ssss + {t('pages_home_title') as string} + +
); } diff --git a/src/assets/constants/routeFactory/index.ts b/src/assets/constants/routeFactory/index.ts index 9c0c981..8fbba9e 100644 --- a/src/assets/constants/routeFactory/index.ts +++ b/src/assets/constants/routeFactory/index.ts @@ -1,13 +1,12 @@ -import { useTranslation } from 'react-i18next'; -const { t } = useTranslation('routes'); +import { t, translates } from '@/locales/translates'; export default { home: { - title: t('home'), + title: t('pages_home_title'), route: () => '/', }, about: { - title: t('about'), + title: t('pages_about_title'), route: () => '/about', }, }; diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 70d262b..8d2b6dd 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -1,3 +1,4 @@ +'use client'; import React, { ButtonHTMLAttributes, ReactNode } from 'react'; interface ButtonProps extends ButtonHTMLAttributes { diff --git a/src/components/layout/languageSwitcher.tsx b/src/components/layout/languageSwitcher.tsx new file mode 100644 index 0000000..b67f6f9 --- /dev/null +++ b/src/components/layout/languageSwitcher.tsx @@ -0,0 +1,17 @@ +'use client'; +import { changeLanguage, TLocales } from '@/locales/translates'; +import Button from '../Button'; + +export default function ChangeLangButton() { + const changeLang = (lang: TLocales) => { + changeLanguage(lang); + window.location.reload(); // Reload to get new SSR content + }; + + return ( + <> + + + + ); +} diff --git a/src/config/i18n.ts b/src/config/i18n.ts deleted file mode 100644 index 25d9662..0000000 --- a/src/config/i18n.ts +++ /dev/null @@ -1,19 +0,0 @@ -import i18n from 'i18next'; -import { initReactI18next } from 'react-i18next'; -import Backend from 'i18next-http-backend'; - -i18n - .use(Backend) - .use(initReactI18next) - .init({ - fallbackLng: 'en', - debug: false, - interpolation: { - escapeValue: false, - }, - backend: { - loadPath: '/locales/{{lng}}/{{ns}}.json', - } - }); - -export default i18n; \ No newline at end of file diff --git a/src/locales/en.ts b/src/locales/en.ts new file mode 100644 index 0000000..ad43bcc --- /dev/null +++ b/src/locales/en.ts @@ -0,0 +1,10 @@ +const en = { + pages_home_title: 'Home', + pages_home_description: 'Welcome to the home page', + pages_about_title: 'About Us', + pages_about_description: 'Learn more about us on this page', + pages_contact_title: 'Contact Us', + pages_contact_description: 'Get in touch with us through this page', +}; + +export default en; diff --git a/src/locales/fa.ts b/src/locales/fa.ts new file mode 100644 index 0000000..94567b7 --- /dev/null +++ b/src/locales/fa.ts @@ -0,0 +1,12 @@ +import { Translates } from './translates'; + +const fa = { + pages_home_title: 'صفحه‌ی اصلی', + pages_home_description: 'Welcome to the home page', + pages_about_title: 'About Us', + pages_about_description: 'Learn more about us on this page', + pages_contact_title: 'Contact Us', + pages_contact_description: 'Get in touch with us through this page', +} as Translates; + +export default fa; diff --git a/src/locales/translates.ts b/src/locales/translates.ts new file mode 100644 index 0000000..17adc01 --- /dev/null +++ b/src/locales/translates.ts @@ -0,0 +1,57 @@ +import en from './en'; +import fa from './fa'; + +// Universal getCookie function +export function getCookie(name: string): string | undefined { + if (typeof window !== 'undefined') { + // Client-side + const match = document.cookie.match( + new RegExp('(^| )' + name + '=([^;]+)'), + ); + return match ? decodeURIComponent(match[2]) : undefined; + } else { + // Server-side (Next.js App Router) + try { + // Dynamically import to avoid errors in client bundle + const { cookies } = require('next/headers'); + return cookies().get(name)?.value; + } catch { + return undefined; + } + } +} + +// Universal setCookie function to change language +export function changeLanguage(lang: TLocales, days = 365) { + if (typeof window !== 'undefined') { + // Client-side + const expires = new Date(Date.now() + days * 864e5).toUTCString(); + document.cookie = `lang=${encodeURIComponent(lang)}; expires=${expires}; path=/`; + } else { + // Server-side (Next.js App Router) + try { + const { cookies } = require('next/headers'); + cookies().set('lang', lang, { path: '/', maxAge: days * 24 * 60 * 60 }); + } catch { + // No-op on server if not available + } + } +} + +export const locales = { + en: en as Translates, + fa, +} as Record; + +export const translates = (key: keyof Translates): string | (() => string) => { + const lang = (getCookie('lang') || 'en') as TLocales; + return locales[lang][key] || locales.en[key] || key; +}; + +export const t = (key: keyof Translates) => translates(key); + +export type TLocales = 'en' | 'fa'; + +export type Translates = { + [K in keyof typeof en]: string | (() => string); +}; diff --git a/tailwind.config.ts b/tailwind.config.ts index ccdc167..5240217 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -9,51 +9,51 @@ const config: Config = { ], theme: { // Move your custom colors directly here to override default Tailwind colors - extend: { - colors: { - // Custom colors - primary: { - light: '#6366F1', - DEFAULT: '#6366F1', - dark: '#FA003F', + colors: { + // Custom colors + primary: { + light: '#6366F1', + DEFAULT: '#6366F1', + dark: '#FA003F', + }, + secondary: { + light: '#A78BFA', + DEFAULT: '#8B5CF6', + dark: '#7C3AED', + }, + textColorLight: { + light: '#FFFFFF', + DEFAULT: '#FFFFFF', + dark: '#FA003F', + }, + textColorGray: { + light: '#222222', + DEFAULT: '#222222', + dark: '#FA003F', + }, + bgPrimary: { + light: '#FFFFFF', + DEFAULT: '#FFFFFF', + dark: '#FA003F', + }, + gray: { + light: { + 100: '#EBEBEB', + 200: '#F5F5F5', + 300: '#81848A', + 400: '#777', + 500: '#222', }, - secondary: { - light: '#A78BFA', - DEFAULT: '#8B5CF6', - dark: '#7C3AED', - }, - textColorLight: { - light: '#FFFFFF', - DEFAULT: '#FFFFFF', - dark: '#FA003F', - }, - textColorGray: { - light: '#222222', - DEFAULT: '#222222', - dark: '#FA003F', - }, - bgPrimary: { - light: '#FFFFFF', - DEFAULT: '#FFFFFF', - dark: '#FA003F', - }, - gray: { - light: { - 100: '#EBEBEB', - 200: '#F5F5F5', - 300: '#81848A', - 400: '#777', - 500: '#222', - }, - dark: { - 100: '#EBEBEB', - 200: '#F5F5F5', - 300: '#81848A', - 400: '#777', - 500: '#222', - }, + dark: { + 100: '#EBEBEB', + 200: '#F5F5F5', + 300: '#81848A', + 400: '#777', + 500: '#222', }, }, + }, + extend: { backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic':