diff --git a/package.json b/package.json index 1926775..a14b0f2 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,13 @@ "@tailwindcss/vite": "^4.1.18", "axios": "^1.13.2", "dayjs": "^1.11.19", + "i18next": "^25.7.4", + "i18next-browser-languagedetector": "^8.2.0", "jsonpath": "^1.1.1", "lodash": "^4.17.21", "react": "^19.2.3", "react-dom": "^19.2.3", + "react-i18next": "^16.5.3", "react-redux": "^9.2.0", "react-router": "^7.12.0", "react-router-dom": "^7.12.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99c835e..26a2d54 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,12 @@ importers: dayjs: specifier: ^1.11.19 version: 1.11.19 + i18next: + specifier: ^25.7.4 + version: 25.7.4(typescript@5.9.3) + i18next-browser-languagedetector: + specifier: ^8.2.0 + version: 8.2.0 jsonpath: specifier: ^1.1.1 version: 1.1.1 @@ -32,6 +38,9 @@ importers: react-dom: specifier: ^19.2.3 version: 19.2.3(react@19.2.3) + react-i18next: + specifier: ^16.5.3 + version: 16.5.3(i18next@25.7.4(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) react-redux: specifier: ^9.2.0 version: 9.2.0(@types/react@19.2.8)(react@19.2.3)(redux@5.0.1) @@ -152,6 +161,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'} @@ -798,6 +811,20 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + + i18next-browser-languagedetector@8.2.0: + resolution: {integrity: sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==} + + i18next@25.7.4: + resolution: {integrity: sha512-hRkpEblXXcXSNbw8mBNq9042OEetgyB/ahc/X17uV/khPwzV+uB8RHceHh3qavyrkPJvmXFKXME2Sy1E0KjAfw==} + peerDependencies: + typescript: ^5 + peerDependenciesMeta: + typescript: + optional: true + immer@11.1.3: resolution: {integrity: sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==} @@ -959,6 +986,22 @@ packages: peerDependencies: react: ^19.2.3 + react-i18next@16.5.3: + resolution: {integrity: sha512-fo+/NNch37zqxOzlBYrWMx0uy/yInPkRfjSuy4lqKdaecR17nvCHnEUt3QyzA8XjQ2B/0iW/5BhaHR3ZmukpGw==} + 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-redux@9.2.0: resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} peerDependencies: @@ -1119,6 +1162,10 @@ packages: yaml: optional: true + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -1217,6 +1264,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 @@ -1733,6 +1782,20 @@ snapshots: dependencies: function-bind: 1.1.2 + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + + i18next-browser-languagedetector@8.2.0: + dependencies: + '@babel/runtime': 7.28.6 + + i18next@25.7.4(typescript@5.9.3): + dependencies: + '@babel/runtime': 7.28.6 + optionalDependencies: + typescript: 5.9.3 + immer@11.1.3: {} jiti@2.6.1: {} @@ -1857,6 +1920,17 @@ snapshots: react: 19.2.3 scheduler: 0.27.0 + react-i18next@16.5.3(i18next@25.7.4(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3): + dependencies: + '@babel/runtime': 7.28.6 + html-parse-stringify: 3.0.1 + i18next: 25.7.4(typescript@5.9.3) + react: 19.2.3 + use-sync-external-store: 1.6.0(react@19.2.3) + optionalDependencies: + react-dom: 19.2.3(react@19.2.3) + typescript: 5.9.3 + react-redux@9.2.0(@types/react@19.2.8)(react@19.2.3)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 @@ -1987,6 +2061,8 @@ snapshots: jiti: 2.6.1 lightningcss: 1.30.2 + void-elements@3.1.0: {} + word-wrap@1.2.5: {} yallist@3.1.1: {} diff --git a/src/init/i18n/index.ts b/src/init/i18n/index.ts new file mode 100644 index 0000000..3d3df12 --- /dev/null +++ b/src/init/i18n/index.ts @@ -0,0 +1,36 @@ +import i18n from "i18next" +import { initReactI18next } from "react-i18next" +import LanguageDetector from "i18next-browser-languagedetector" + +// Import translation files +import BritishEnglishTranslations from "./locales/BritishEnglish.json" +import SimplifiedChineseTranslations from "./locales/SimplifiedChinese.json" + +const resources = { + "en-GB": { + translation: BritishEnglishTranslations as Record, + }, + "zh-CN": { + translation: SimplifiedChineseTranslations as Record, + }, +} as const + +void i18n + .use(LanguageDetector) + .use(initReactI18next) + .init({ + resources, + fallbackLng: "en-GB", + debug: process.env.NODE_ENV === "development", + + interpolation: { + escapeValue: false, // React already does escaping + }, + + detection: { + order: ["localStorage", "navigator", "htmlTag"], + caches: ["localStorage"], + }, + }) + +export default i18n diff --git a/src/init/i18n/locales/BritishEnglish.json b/src/init/i18n/locales/BritishEnglish.json new file mode 100644 index 0000000..32e95d0 --- /dev/null +++ b/src/init/i18n/locales/BritishEnglish.json @@ -0,0 +1,47 @@ +{ + "app": { + "title": "DevHub", + "pageTitle": "DevHub", + "copyright": "© {{year}} OnixByte. Built with React & TypeScript." + }, + "navigation": { + "home": "Home" + }, + "language": { + "switch": "Switch Language", + "english": "English (Great Britain)", + "chinese": "简体中文" + }, + "bmi": { + "title": "BMI Calculator", + "description": "Calculate your Body Mass Index (BMI) to assess your weight status and health.", + "weight": { + "label": "Weight", + "placeholder": "Enter your weight" + }, + "height": { + "label": "Height", + "placeholder": "Enter your height" + }, + "calculate": "Calculate BMI", + "reset": "Reset", + "result": { + "title": "Your BMI Result" + }, + "category": { + "underweight": "Underweight", + "normal": "Normal Weight", + "overweight": "Overweight", + "obese": "Obese" + }, + "advice": { + "underweight": "You may need to gain weight. Consider consulting with a healthcare professional for personalised advice.", + "normal": "You have a healthy weight for your height. Maintain your current lifestyle with regular exercise and balanced nutrition.", + "overweight": "You may benefit from losing some weight. Consider increasing physical activity and improving your diet.", + "obese": "You may be at increased health risk. It's recommended to consult with a healthcare professional for guidance." + }, + "scale": { + "title": "BMI Categories" + } + } +} diff --git a/src/init/i18n/locales/SimplifiedChinese.json b/src/init/i18n/locales/SimplifiedChinese.json new file mode 100644 index 0000000..5e14132 --- /dev/null +++ b/src/init/i18n/locales/SimplifiedChinese.json @@ -0,0 +1,47 @@ +{ + "app": { + "title": "DevHub", + "pageTitle": "DevHub", + "copyright": "© {{year}} OnixByte。 使用 React 和 TypeScript 构建。" + }, + "navigation": { + "home": "首页" + }, + "language": { + "switch": "切换语言", + "english": "English (Great Britain)", + "chinese": "简体中文" + }, + "bmi": { + "title": "BMI 计算器", + "description": "计算您的身体质量指数(BMI)以评估您的体重状态和健康状况。", + "weight": { + "label": "体重", + "placeholder": "请输入您的体重" + }, + "height": { + "label": "身高", + "placeholder": "请输入您的身高" + }, + "calculate": "计算 BMI", + "reset": "重置", + "result": { + "title": "您的 BMI 结果" + }, + "category": { + "underweight": "体重过轻", + "normal": "正常体重", + "overweight": "超重", + "obese": "肥胖" + }, + "advice": { + "underweight": "您可能需要增加体重。建议咨询医疗专业人士获取个性化建议。", + "normal": "您的体重对于您的身高来说是健康的。保持目前的生活方式,规律运动和均衡营养。", + "overweight": "您可能需要减轻一些体重。建议增加体育活动并改善饮食。", + "obese": "您可能面临健康风险增加。建议咨询医疗专业人士获取指导。" + }, + "scale": { + "title": "BMI 分类" + } + } +} diff --git a/src/init/index.ts b/src/init/index.ts index fc6e552..c421129 100644 --- a/src/init/index.ts +++ b/src/init/index.ts @@ -1 +1,2 @@ -import "./dayjs" \ No newline at end of file +import "./dayjs" +import "./i18n"