From 660616b08b8de41bf67a8d4b11966d317b775135 Mon Sep 17 00:00:00 2001 From: hailin Date: Sun, 22 Feb 2026 04:56:04 -0800 Subject: [PATCH] feat: add multi-language (i18n) support to web admin with Chinese and English - Add react-i18next with browser language auto-detection and localStorage persistence - Create Zustand locale store with UI language selector in Settings > General - Add 17 translation namespace files for both English and Chinese (34 JSON files) - Convert all 37 pages (auth, admin, settings) to use useTranslation hooks - Convert sidebar and topbar layout components to i18n Co-Authored-By: Claude Opus 4.6 --- it0-web-admin/package-lock.json | 92 +++++++- it0-web-admin/package.json | 58 +++-- .../app/(admin)/agent-config/hooks/page.tsx | 68 +++--- .../src/app/(admin)/agent-config/page.tsx | 77 +++---- .../src/app/(admin)/agent-config/sdk/page.tsx | 110 +++++---- .../agent-config/skills/[name]/page.tsx | 98 ++++---- .../app/(admin)/agent-config/skills/page.tsx | 76 ++++--- .../src/app/(admin)/audit/logs/page.tsx | 68 +++--- .../src/app/(admin)/audit/replay/page.tsx | 105 ++++----- .../src/app/(admin)/communication/page.tsx | 210 ++++++++++-------- .../src/app/(admin)/dashboard/page.tsx | 40 ++-- .../monitoring/alert-rules/[id]/page.tsx | 168 +++++++------- .../(admin)/monitoring/alert-rules/page.tsx | 121 +++++----- .../(admin)/monitoring/health-checks/page.tsx | 57 ++--- .../app/(admin)/monitoring/metrics/page.tsx | 68 +++--- .../src/app/(admin)/runbooks/[id]/page.tsx | 115 +++++----- .../src/app/(admin)/runbooks/page.tsx | 83 +++---- .../app/(admin)/security/credentials/page.tsx | 103 +++++---- .../app/(admin)/security/permissions/page.tsx | 48 ++-- .../app/(admin)/security/risk-rules/page.tsx | 130 ++++++----- .../src/app/(admin)/security/roles/page.tsx | 71 +++--- .../src/app/(admin)/servers/[id]/page.tsx | 117 +++++----- .../src/app/(admin)/servers/clusters/page.tsx | 74 +++--- .../src/app/(admin)/servers/page.tsx | 90 ++++---- .../src/app/(admin)/sessions/[id]/page.tsx | 84 +++---- .../src/app/(admin)/settings/page.tsx | 181 ++++++++------- .../app/(admin)/standing-orders/[id]/page.tsx | 157 ++++++------- .../src/app/(admin)/standing-orders/page.tsx | 101 +++++---- .../src/app/(admin)/tenants/[id]/page.tsx | 139 ++++++------ .../src/app/(admin)/tenants/page.tsx | 121 +++++----- .../src/app/(admin)/terminal/page.tsx | 65 +++--- .../src/app/(admin)/users/[id]/page.tsx | 141 ++++++------ it0-web-admin/src/app/(admin)/users/page.tsx | 140 +++++++----- .../src/app/(auth)/invite/[token]/page.tsx | 45 ++-- it0-web-admin/src/app/(auth)/login/page.tsx | 21 +- .../src/app/(auth)/register/page.tsx | 39 ++-- it0-web-admin/src/app/providers.tsx | 5 + it0-web-admin/src/i18n/config.ts | 107 +++++++++ .../src/i18n/locales/en/agent-config.json | 175 +++++++++++++++ it0-web-admin/src/i18n/locales/en/audit.json | 98 ++++++++ it0-web-admin/src/i18n/locales/en/auth.json | 35 +++ it0-web-admin/src/i18n/locales/en/common.json | 89 ++++++++ .../src/i18n/locales/en/communication.json | 112 ++++++++++ .../src/i18n/locales/en/dashboard.json | 27 +++ .../src/i18n/locales/en/monitoring.json | 173 +++++++++++++++ .../src/i18n/locales/en/runbooks.json | 75 +++++++ .../src/i18n/locales/en/security.json | 151 +++++++++++++ .../src/i18n/locales/en/servers.json | 97 ++++++++ .../src/i18n/locales/en/sessions.json | 46 ++++ .../src/i18n/locales/en/settings.json | 79 +++++++ .../src/i18n/locales/en/sidebar.json | 33 +++ .../src/i18n/locales/en/standing-orders.json | 111 +++++++++ .../src/i18n/locales/en/tenants.json | 107 +++++++++ .../src/i18n/locales/en/terminal.json | 44 ++++ it0-web-admin/src/i18n/locales/en/topbar.json | 10 + it0-web-admin/src/i18n/locales/en/users.json | 89 ++++++++ .../src/i18n/locales/zh/agent-config.json | 175 +++++++++++++++ it0-web-admin/src/i18n/locales/zh/audit.json | 98 ++++++++ it0-web-admin/src/i18n/locales/zh/auth.json | 35 +++ it0-web-admin/src/i18n/locales/zh/common.json | 89 ++++++++ .../src/i18n/locales/zh/communication.json | 106 +++++++++ .../src/i18n/locales/zh/dashboard.json | 27 +++ .../src/i18n/locales/zh/monitoring.json | 173 +++++++++++++++ .../src/i18n/locales/zh/runbooks.json | 75 +++++++ .../src/i18n/locales/zh/security.json | 151 +++++++++++++ .../src/i18n/locales/zh/servers.json | 97 ++++++++ .../src/i18n/locales/zh/sessions.json | 46 ++++ .../src/i18n/locales/zh/settings.json | 79 +++++++ .../src/i18n/locales/zh/sidebar.json | 33 +++ .../src/i18n/locales/zh/standing-orders.json | 111 +++++++++ .../src/i18n/locales/zh/tenants.json | 107 +++++++++ .../src/i18n/locales/zh/terminal.json | 35 +++ it0-web-admin/src/i18n/locales/zh/topbar.json | 10 + it0-web-admin/src/i18n/locales/zh/users.json | 89 ++++++++ .../components/layout/sidebar.tsx | 149 +++++++------ .../components/layout/top-bar.tsx | 18 +- .../src/stores/zustand/locale-store.ts | 39 ++++ 77 files changed, 5209 insertions(+), 1677 deletions(-) create mode 100644 it0-web-admin/src/i18n/config.ts create mode 100644 it0-web-admin/src/i18n/locales/en/agent-config.json create mode 100644 it0-web-admin/src/i18n/locales/en/audit.json create mode 100644 it0-web-admin/src/i18n/locales/en/auth.json create mode 100644 it0-web-admin/src/i18n/locales/en/common.json create mode 100644 it0-web-admin/src/i18n/locales/en/communication.json create mode 100644 it0-web-admin/src/i18n/locales/en/dashboard.json create mode 100644 it0-web-admin/src/i18n/locales/en/monitoring.json create mode 100644 it0-web-admin/src/i18n/locales/en/runbooks.json create mode 100644 it0-web-admin/src/i18n/locales/en/security.json create mode 100644 it0-web-admin/src/i18n/locales/en/servers.json create mode 100644 it0-web-admin/src/i18n/locales/en/sessions.json create mode 100644 it0-web-admin/src/i18n/locales/en/settings.json create mode 100644 it0-web-admin/src/i18n/locales/en/sidebar.json create mode 100644 it0-web-admin/src/i18n/locales/en/standing-orders.json create mode 100644 it0-web-admin/src/i18n/locales/en/tenants.json create mode 100644 it0-web-admin/src/i18n/locales/en/terminal.json create mode 100644 it0-web-admin/src/i18n/locales/en/topbar.json create mode 100644 it0-web-admin/src/i18n/locales/en/users.json create mode 100644 it0-web-admin/src/i18n/locales/zh/agent-config.json create mode 100644 it0-web-admin/src/i18n/locales/zh/audit.json create mode 100644 it0-web-admin/src/i18n/locales/zh/auth.json create mode 100644 it0-web-admin/src/i18n/locales/zh/common.json create mode 100644 it0-web-admin/src/i18n/locales/zh/communication.json create mode 100644 it0-web-admin/src/i18n/locales/zh/dashboard.json create mode 100644 it0-web-admin/src/i18n/locales/zh/monitoring.json create mode 100644 it0-web-admin/src/i18n/locales/zh/runbooks.json create mode 100644 it0-web-admin/src/i18n/locales/zh/security.json create mode 100644 it0-web-admin/src/i18n/locales/zh/servers.json create mode 100644 it0-web-admin/src/i18n/locales/zh/sessions.json create mode 100644 it0-web-admin/src/i18n/locales/zh/settings.json create mode 100644 it0-web-admin/src/i18n/locales/zh/sidebar.json create mode 100644 it0-web-admin/src/i18n/locales/zh/standing-orders.json create mode 100644 it0-web-admin/src/i18n/locales/zh/tenants.json create mode 100644 it0-web-admin/src/i18n/locales/zh/terminal.json create mode 100644 it0-web-admin/src/i18n/locales/zh/topbar.json create mode 100644 it0-web-admin/src/i18n/locales/zh/users.json create mode 100644 it0-web-admin/src/stores/zustand/locale-store.ts diff --git a/it0-web-admin/package-lock.json b/it0-web-admin/package-lock.json index 808c5fc..82426f1 100644 --- a/it0-web-admin/package-lock.json +++ b/it0-web-admin/package-lock.json @@ -25,11 +25,14 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "date-fns": "^3.6.0", + "i18next": "^25.8.13", + "i18next-browser-languagedetector": "^8.2.1", "lucide-react": "^0.378.0", "next": "^14.2.0", "react": "^18.3.0", "react-dom": "^18.3.0", "react-hook-form": "^7.51.0", + "react-i18next": "^16.5.4", "react-redux": "^9.1.0", "recharts": "^2.12.0", "sonner": "^1.4.0", @@ -4774,6 +4777,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5661,6 +5665,15 @@ "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/human-signals": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", @@ -5671,6 +5684,47 @@ "node": ">=16.17.0" } }, + "node_modules/i18next": { + "version": "25.8.13", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.13.tgz", + "integrity": "sha512-E0vzjBY1yM+nsFrtgkjLhST2NBkirkvOVoQa0MSldhsuZ3jUge7ZNpuwG0Cfc74zwo5ZwRzg3uOgT+McBn32iA==", + "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", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz", + "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -7485,6 +7539,33 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-i18next": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.5.4.tgz", + "integrity": "sha512-6yj+dcfMncEC21QPhOTsW8mOSO+pzFmT6uvU7XXdvM/Cp38zJkmTeMeKmTrmCMD5ToT79FmiE/mRWiYWcJYW4g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "i18next": ">= 25.6.2", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -8869,7 +8950,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "peer": true, "bin": { @@ -9218,6 +9299,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/it0-web-admin/package.json b/it0-web-admin/package.json index f89099b..982a208 100644 --- a/it0-web-admin/package.json +++ b/it0-web-admin/package.json @@ -11,52 +11,50 @@ "test:e2e": "playwright test" }, "dependencies": { - "next": "^14.2.0", - "react": "^18.3.0", - "react-dom": "^18.3.0", - - "@reduxjs/toolkit": "^2.2.0", - "react-redux": "^9.1.0", - "zustand": "^4.5.0", - "@tanstack/react-query": "^5.45.0", - "@tanstack/react-table": "^8.17.0", - - "tailwindcss": "^3.4.0", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", - "tailwind-merge": "^2.3.0", + "@hookform/resolvers": "^3.3.0", + "@monaco-editor/react": "^4.6.0", "@radix-ui/react-dialog": "^1.0.0", "@radix-ui/react-dropdown-menu": "^2.0.0", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-tabs": "^1.0.0", "@radix-ui/react-toast": "^1.1.0", "@radix-ui/react-tooltip": "^1.0.0", - "lucide-react": "^0.378.0", - - "react-hook-form": "^7.51.0", - "zod": "^3.23.0", - "@hookform/resolvers": "^3.3.0", - - "@monaco-editor/react": "^4.6.0", - "@xterm/xterm": "^5.5.0", + "@reduxjs/toolkit": "^2.2.0", + "@tanstack/react-query": "^5.45.0", + "@tanstack/react-table": "^8.17.0", "@xterm/addon-fit": "^0.10.0", "@xterm/addon-web-links": "^0.11.0", - - "recharts": "^2.12.0", + "@xterm/xterm": "^5.5.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", "date-fns": "^3.6.0", - "sonner": "^1.4.0" + "i18next": "^25.8.13", + "i18next-browser-languagedetector": "^8.2.1", + "lucide-react": "^0.378.0", + "next": "^14.2.0", + "react": "^18.3.0", + "react-dom": "^18.3.0", + "react-hook-form": "^7.51.0", + "react-i18next": "^16.5.4", + "react-redux": "^9.1.0", + "recharts": "^2.12.0", + "sonner": "^1.4.0", + "tailwind-merge": "^2.3.0", + "tailwindcss": "^3.4.0", + "zod": "^3.23.0", + "zustand": "^4.5.0" }, "devDependencies": { + "@testing-library/react": "^15.0.0", "@types/node": "^20.0.0", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", - "typescript": "^5.4.0", + "autoprefixer": "^10.4.0", "eslint": "^8.57.0", "eslint-config-next": "^14.2.0", + "postcss": "^8.4.0", "prettier": "^3.2.0", - "vitest": "^1.6.0", - "@testing-library/react": "^15.0.0", - "autoprefixer": "^10.4.0", - "postcss": "^8.4.0" + "typescript": "^5.4.0", + "vitest": "^1.6.0" } } diff --git a/it0-web-admin/src/app/(admin)/agent-config/hooks/page.tsx b/it0-web-admin/src/app/(admin)/agent-config/hooks/page.tsx index fae8122..93c8193 100644 --- a/it0-web-admin/src/app/(admin)/agent-config/hooks/page.tsx +++ b/it0-web-admin/src/app/(admin)/agent-config/hooks/page.tsx @@ -1,6 +1,7 @@ 'use client'; import { useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { apiClient } from '@/infrastructure/api/api-client'; import { queryKeys } from '@/infrastructure/api/query-keys'; @@ -106,6 +107,9 @@ function HookDialog({ onChange: (field: keyof HookFormData, value: string | number | boolean) => void; onSubmit: () => void; }) { + const { t } = useTranslation('agent-config'); + const { t: tc } = useTranslation('common'); + if (!open) return null; return ( @@ -121,7 +125,7 @@ function HookDialog({ {/* name */}
- +