From 444c1234c0b45f27b5b2a955d50e5586cdc62f97 Mon Sep 17 00:00:00 2001 From: dhinesh-m24 Date: Mon, 9 Feb 2026 12:24:28 +0530 Subject: [PATCH] feat: Add base setup for Internationalization(i18n) --- apps/web/package.json | 4 + apps/web/pnpm-lock.yaml | 128 ++++++++++++++++++ apps/web/src/i18n/index.ts | 15 ++ apps/web/src/i18n/locales/en/translation.json | 5 + apps/web/src/i18n/locales/es/translation.json | 5 + apps/web/src/main.tsx | 6 +- 6 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/i18n/index.ts create mode 100644 apps/web/src/i18n/locales/en/translation.json create mode 100644 apps/web/src/i18n/locales/es/translation.json diff --git a/apps/web/package.json b/apps/web/package.json index 2a5f31fc..e952afcd 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -32,12 +32,16 @@ "date-fns": "^4.1.0", "firebase": "^12.8.0", "framer-motion": "^12.29.2", + "i18next": "^25.8.4", + "i18next-browser-languagedetector": "^8.2.0", + "i18next-http-backend": "^3.0.2", "lucide-react": "^0.563.0", "react": "^19.2.0", "react-datepicker": "^9.1.0", "react-day-picker": "^9.13.0", "react-dom": "^19.2.0", "react-hook-form": "^7.71.1", + "react-i18next": "^16.5.4", "react-redux": "^9.2.0", "react-router-dom": "^7.13.0", "recharts": "^3.7.0", diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml index 5de2cfe2..5f4687dc 100644 --- a/apps/web/pnpm-lock.yaml +++ b/apps/web/pnpm-lock.yaml @@ -77,6 +77,15 @@ importers: framer-motion: specifier: ^12.29.2 version: 12.29.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + i18next: + specifier: ^25.8.4 + version: 25.8.4(typescript@5.9.3) + i18next-browser-languagedetector: + specifier: ^8.2.0 + version: 8.2.0 + i18next-http-backend: + specifier: ^3.0.2 + version: 3.0.2 lucide-react: specifier: ^0.563.0 version: 0.563.0(react@19.2.4) @@ -95,6 +104,9 @@ importers: react-hook-form: specifier: ^7.71.1 version: 7.71.1(react@19.2.4) + react-i18next: + specifier: ^16.5.4 + version: 16.5.4(i18next@25.8.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) react-redux: specifier: ^9.2.0 version: 9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1) @@ -233,6 +245,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -1999,6 +2015,9 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} + cross-fetch@4.0.0: + resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -2323,9 +2342,26 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + http-parser-js@0.5.10: resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} + i18next-browser-languagedetector@8.2.0: + resolution: {integrity: sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==} + + i18next-http-backend@3.0.2: + resolution: {integrity: sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==} + + i18next@25.8.4: + resolution: {integrity: sha512-a9A0MnUjKvzjEN/26ZY1okpra9kA8MEwzYEz1BNm+IyxUKPRH6ihf0p7vj8YvULwZHKHl3zkJ6KOt4hewxBecQ==} + peerDependencies: + typescript: ^5 + peerDependenciesMeta: + typescript: + optional: true + idb@7.1.1: resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} @@ -2541,6 +2577,15 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -2637,6 +2682,22 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 + react-i18next@16.5.4: + resolution: {integrity: sha512-6yj+dcfMncEC21QPhOTsW8mOSO+pzFmT6uvU7XXdvM/Cp38zJkmTeMeKmTrmCMD5ToT79FmiE/mRWiYWcJYW4g==} + peerDependencies: + i18next: '>= 25.6.2' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + typescript: ^5 + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + typescript: + optional: true + react-is@19.2.4: resolution: {integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==} @@ -2810,6 +2871,9 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-api-utils@2.4.0: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} @@ -2919,9 +2983,16 @@ packages: yaml: optional: true + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + web-vitals@4.2.4: resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==} + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + websocket-driver@0.7.4: resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} engines: {node: '>=0.8.0'} @@ -2930,6 +3001,9 @@ packages: resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} engines: {node: '>=0.8.0'} + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -3062,6 +3136,8 @@ snapshots: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime@7.28.6': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.28.6 @@ -4875,6 +4951,12 @@ snapshots: cookie@1.1.1: {} + cross-fetch@4.0.0: + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -5232,8 +5314,28 @@ snapshots: dependencies: hermes-estree: 0.25.1 + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + http-parser-js@0.5.10: {} + i18next-browser-languagedetector@8.2.0: + dependencies: + '@babel/runtime': 7.28.6 + + i18next-http-backend@3.0.2: + dependencies: + cross-fetch: 4.0.0 + transitivePeerDependencies: + - encoding + + i18next@25.8.4(typescript@5.9.3): + dependencies: + '@babel/runtime': 7.28.6 + optionalDependencies: + typescript: 5.9.3 + idb@7.1.1: {} ignore@5.3.2: {} @@ -5389,6 +5491,10 @@ snapshots: natural-compare@1.4.0: {} + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-releases@2.0.27: {} optionator@0.9.4: @@ -5536,6 +5642,17 @@ snapshots: dependencies: react: 19.2.4 + react-i18next@16.5.4(i18next@25.8.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): + dependencies: + '@babel/runtime': 7.28.6 + html-parse-stringify: 3.0.1 + i18next: 25.8.4(typescript@5.9.3) + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + react-dom: 19.2.4(react@19.2.4) + typescript: 5.9.3 + react-is@19.2.4: {} react-redux@9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1): @@ -5708,6 +5825,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tr46@0.0.3: {} + ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -5795,8 +5914,12 @@ snapshots: jiti: 2.6.1 lightningcss: 1.30.2 + void-elements@3.1.0: {} + web-vitals@4.2.4: {} + webidl-conversions@3.0.1: {} + websocket-driver@0.7.4: dependencies: http-parser-js: 0.5.10 @@ -5805,6 +5928,11 @@ snapshots: websocket-extensions@0.1.4: {} + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/apps/web/src/i18n/index.ts b/apps/web/src/i18n/index.ts new file mode 100644 index 00000000..39e83c80 --- /dev/null +++ b/apps/web/src/i18n/index.ts @@ -0,0 +1,15 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import LanguageDetector from "i18next-browser-languagedetector"; +import Backend from "i18next-http-backend"; + +i18n + .use(LanguageDetector) // detects browser language + .use(initReactI18next)// passes i18n down to react-i18next + .use(Backend) // loads translations asynchoronusly from /locales/{{lng}}/{{ns}}.json + .init({ + fallbackLng: "en", // fallback language + debug: true, + }); + +export default i18n; diff --git a/apps/web/src/i18n/locales/en/translation.json b/apps/web/src/i18n/locales/en/translation.json new file mode 100644 index 00000000..2711ba8c --- /dev/null +++ b/apps/web/src/i18n/locales/en/translation.json @@ -0,0 +1,5 @@ +{ + "welcome": "Welcome", + "login": "Login", + "logout": "Logout" +} diff --git a/apps/web/src/i18n/locales/es/translation.json b/apps/web/src/i18n/locales/es/translation.json new file mode 100644 index 00000000..8ad54e07 --- /dev/null +++ b/apps/web/src/i18n/locales/es/translation.json @@ -0,0 +1,5 @@ +{ + "welcome": "Bienvenido", + "login": "Iniciar sesión", + "logout": "Cerrar sesión" +} diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index bef5202a..0e8878b6 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -2,9 +2,13 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import App from './App.tsx' +import "@/i18n" +import { Suspense } from 'react' createRoot(document.getElementById('root')!).render( - + Loading...}> + + , )