feat(i18n): Add internationalization support with English and Chinese

- Add i18next, react-i18next, and i18next-browser-languagedetector
- Create i18n configuration with language detection
- Add English (en) and Chinese (zh) translation files
- Create LanguageSwitcher component for runtime language switching
- Integrate i18n initialization in app.tsx

Translation keys organized by section:
- common: Common UI elements
- home: Dashboard/home page
- menu: Navigation menu items
- maps: Maps feature
- chat: AI chat feature
- settings: Settings pages
- system: System settings
- apps: App management
- models: AI models
- easySetup: Setup wizard
- docs: Documentation
- about: About page
- errors: Error pages

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude 2026-04-04 10:24:25 +08:00
parent 8dcbf7dbcf
commit a8f6fe353b
7 changed files with 428 additions and 2 deletions

View File

@ -13,6 +13,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import NotificationsProvider from '~/providers/NotificationProvider'
import { ThemeProvider } from '~/providers/ThemeProvider'
import { UsePageProps } from '../../types/system'
import '../i18n'
const appName = import.meta.env.VITE_APP_NAME || 'Project N.O.M.A.D.'
const queryClient = new QueryClient()
@ -40,11 +41,16 @@ createInertiaApp({
createRoot(el).render(
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<TransmitProvider baseUrl={window.location.origin} enableLogging={environment === 'development'}>
<TransmitProvider
baseUrl={window.location.origin}
enableLogging={environment === 'development'}
>
<NotificationsProvider>
<ModalsProvider>
<App {...props} />
{showDevtools && <ReactQueryDevtools initialIsOpen={false} buttonPosition='bottom-left' />}
{showDevtools && (
<ReactQueryDevtools initialIsOpen={false} buttonPosition="bottom-left" />
)}
</ModalsProvider>
</NotificationsProvider>
</TransmitProvider>

View File

@ -0,0 +1,31 @@
import { useTranslation } from 'react-i18next'
const languages = [
{ code: 'en', name: 'English', flag: '🇺🇸' },
{ code: 'zh', name: '中文', flag: '🇨🇳' },
]
export default function LanguageSwitcher() {
const { i18n } = useTranslation()
return (
<div className="flex items-center gap-2">
<span className="text-sm text-gray-500">Language:</span>
<div className="flex gap-1">
{languages.map((lang) => (
<button
key={lang.code}
onClick={() => i18n.changeLanguage(lang.code)}
className={`px-3 py-1 rounded text-sm transition-colors ${
i18n.language === lang.code
? 'bg-desert-green text-white'
: 'bg-gray-200 hover:bg-gray-300 text-gray-700'
}`}
>
{lang.flag} {lang.name}
</button>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,29 @@
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
import en from './locales/en.json'
import zh from './locales/zh.json'
const resources = {
en: { translation: en },
zh: { translation: zh },
}
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: 'en',
supportedLngs: ['en', 'zh'],
interpolation: {
escapeValue: false,
},
detection: {
order: ['localStorage', 'navigator'],
caches: ['localStorage'],
},
})
export default i18n

View File

@ -0,0 +1,133 @@
{
"common": {
"home": "Home",
"settings": "Settings",
"docs": "Docs",
"chat": "Chat",
"maps": "Maps",
"search": "Search",
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"edit": "Edit",
"install": "Install",
"uninstall": "Uninstall",
"start": "Start",
"stop": "Stop",
"restart": "Restart",
"loading": "Loading...",
"error": "Error",
"success": "Success",
"warning": "Warning",
"info": "Info",
"confirm": "Confirm",
"yes": "Yes",
"no": "No",
"enabled": "Enabled",
"disabled": "Disabled",
"unknown": "Unknown",
"back": "Back",
"next": "Next",
"previous": "Previous",
"close": "Close",
"submit": "Submit",
"reset": "Reset"
},
"home": {
"title": "Command Center",
"subtitle": "Your offline knowledge and education hub",
"updateAvailable": "An update is available for Project N.O.M.A.D.!",
"goToSettings": "Go to Settings",
"startHere": "Start here!",
"poweredBy": "Powered by"
},
"menu": {
"maps": "Maps",
"easySetup": "Easy Setup",
"installApps": "Install Apps",
"docs": "Docs",
"settings": "Settings"
},
"maps": {
"title": "Offline Maps",
"search": "Search locations...",
"noResults": "No results found",
"loadingMap": "Loading map...",
"offline": "Offline map - no internet required"
},
"chat": {
"title": "AI Assistant",
"placeholder": "Ask me anything...",
"thinking": "Thinking...",
"noMessages": "Start a conversation with the AI assistant",
"uploadDocument": "Upload Document",
"knowledgeBase": "Knowledge Base"
},
"settings": {
"title": "Settings",
"system": "System",
"apps": "Apps",
"models": "Models",
"maps": "Maps",
"benchmark": "Benchmark",
"update": "Update",
"legal": "Legal",
"support": "Support"
},
"system": {
"title": "System Settings",
"hostname": "Hostname",
"version": "Version",
"uptime": "Uptime",
"storage": "Storage",
"memory": "Memory",
"cpu": "CPU",
"network": "Network",
"theme": "Theme",
"language": "Language",
"darkMode": "Dark Mode",
"lightMode": "Light Mode"
},
"apps": {
"title": "Install Apps",
"installed": "Installed",
"notInstalled": "Not Installed",
"installing": "Installing...",
"noApps": "No apps available"
},
"models": {
"title": "AI Models",
"download": "Download",
"downloading": "Downloading...",
"downloaded": "Downloaded",
"delete": "Delete Model",
"noModels": "No models installed"
},
"easySetup": {
"title": "Easy Setup",
"welcome": "Welcome to Project N.O.M.A.D.!",
"getStarted": "Let's get you started with the basics.",
"next": "Next",
"skip": "Skip",
"complete": "Complete"
},
"docs": {
"title": "Documentation",
"searchDocs": "Search documentation...",
"noResults": "No documentation found"
},
"about": {
"title": "About Project N.O.M.A.D.",
"description": "Project N.O.M.A.D. (Node for Offline Media, Archives, and Data) is an offline-first knowledge and education server.",
"version": "Version",
"license": "License",
"github": "GitHub",
"website": "Website"
},
"errors": {
"notFound": "Page Not Found",
"serverError": "Server Error",
"goHome": "Go to Home",
"tryAgain": "Try Again"
}
}

View File

@ -0,0 +1,133 @@
{
"common": {
"home": "首页",
"settings": "设置",
"docs": "文档",
"chat": "聊天",
"maps": "地图",
"search": "搜索",
"save": "保存",
"cancel": "取消",
"delete": "删除",
"edit": "编辑",
"install": "安装",
"uninstall": "卸载",
"start": "启动",
"stop": "停止",
"restart": "重启",
"loading": "加载中...",
"error": "错误",
"success": "成功",
"warning": "警告",
"info": "信息",
"confirm": "确认",
"yes": "是",
"no": "否",
"enabled": "已启用",
"disabled": "已禁用",
"unknown": "未知",
"back": "返回",
"next": "下一步",
"previous": "上一步",
"close": "关闭",
"submit": "提交",
"reset": "重置"
},
"home": {
"title": "控制中心",
"subtitle": "您的离线知识与教育中心",
"updateAvailable": "Project N.O.M.A.D. 有可用更新!",
"goToSettings": "前往设置",
"startHere": "从这里开始!",
"poweredBy": "技术支持"
},
"menu": {
"maps": "地图",
"easySetup": "快速设置",
"installApps": "安装应用",
"docs": "文档",
"settings": "设置"
},
"maps": {
"title": "离线地图",
"search": "搜索位置...",
"noResults": "未找到结果",
"loadingMap": "加载地图中...",
"offline": "离线地图 - 无需互联网"
},
"chat": {
"title": "AI 助手",
"placeholder": "问我任何问题...",
"thinking": "思考中...",
"noMessages": "开始与 AI 助手对话",
"uploadDocument": "上传文档",
"knowledgeBase": "知识库"
},
"settings": {
"title": "设置",
"system": "系统",
"apps": "应用",
"models": "模型",
"maps": "地图",
"benchmark": "基准测试",
"update": "更新",
"legal": "法律",
"support": "支持"
},
"system": {
"title": "系统设置",
"hostname": "主机名",
"version": "版本",
"uptime": "运行时间",
"storage": "存储",
"memory": "内存",
"cpu": "处理器",
"network": "网络",
"theme": "主题",
"language": "语言",
"darkMode": "深色模式",
"lightMode": "浅色模式"
},
"apps": {
"title": "安装应用",
"installed": "已安装",
"notInstalled": "未安装",
"installing": "安装中...",
"noApps": "没有可用的应用"
},
"models": {
"title": "AI 模型",
"download": "下载",
"downloading": "下载中...",
"downloaded": "已下载",
"delete": "删除模型",
"noModels": "没有已安装的模型"
},
"easySetup": {
"title": "快速设置",
"welcome": "欢迎使用 Project N.O.M.A.D.",
"getStarted": "让我们从基础设置开始。",
"next": "下一步",
"skip": "跳过",
"complete": "完成"
},
"docs": {
"title": "文档",
"searchDocs": "搜索文档...",
"noResults": "未找到文档"
},
"about": {
"title": "关于 Project N.O.M.A.D.",
"description": "Project N.O.M.A.D.(离线媒体、档案和数据节点)是一个离线优先的知识与教育服务器。",
"version": "版本",
"license": "许可证",
"github": "GitHub",
"website": "网站"
},
"errors": {
"notFound": "页面未找到",
"serverError": "服务器错误",
"goHome": "返回首页",
"tryAgain": "重试"
}
}

View File

@ -47,6 +47,8 @@
"edge.js": "^6.2.1",
"fast-xml-parser": "^5.5.7",
"fuse.js": "^7.1.0",
"i18next": "^26.0.3",
"i18next-browser-languagedetector": "^8.2.1",
"jszip": "^3.10.1",
"luxon": "^3.6.1",
"maplibre-gl": "^4.7.1",
@ -61,6 +63,7 @@
"react": "^19.1.0",
"react-adonis-transmit": "^1.0.1",
"react-dom": "^19.1.0",
"react-i18next": "^17.0.2",
"react-map-gl": "^8.1.0",
"react-markdown": "^10.1.0",
"reflect-metadata": "^0.2.2",
@ -1096,6 +1099,14 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
@ -9814,6 +9825,14 @@
],
"license": "MIT"
},
"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/html-url-attributes": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
@ -9917,6 +9936,44 @@
"node": ">=18.18.0"
}
},
"node_modules/i18next": {
"version": "26.0.3",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.3.tgz",
"integrity": "sha512-1571kXINxHKY7LksWp8wP+zP0YqHSSpl/OW0Y0owFEf2H3s8gCAffWaZivcz14rMkOvn3R/psiQxVsR9t2Nafg==",
"funding": [
{
"type": "individual",
"url": "https://www.locize.com/i18next"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
},
{
"type": "individual",
"url": "https://www.locize.com"
}
],
"dependencies": {
"@babel/runtime": "^7.29.2"
},
"peerDependencies": {
"typescript": "^5 || ^6"
},
"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==",
"dependencies": {
"@babel/runtime": "^7.23.2"
}
},
"node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
@ -14049,6 +14106,32 @@
"react": "^19.2.4"
}
},
"node_modules/react-i18next": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.2.tgz",
"integrity": "sha512-shBftH2vaTWK2Bsp7FiL+cevx3xFJlvFxmsDFQSrJc+6twHkP0tv/bGa01VVWzpreUVVwU+3Hev5iFqRg65RwA==",
"dependencies": {
"@babel/runtime": "^7.29.2",
"html-parse-stringify": "^3.0.1",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"i18next": ">= 26.0.1",
"react": ">= 16.8.0",
"typescript": "^5 || ^6"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
@ -16513,6 +16596,14 @@
"vite": "^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.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/vt-pbf": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",

View File

@ -100,6 +100,8 @@
"edge.js": "^6.2.1",
"fast-xml-parser": "^5.5.7",
"fuse.js": "^7.1.0",
"i18next": "^26.0.3",
"i18next-browser-languagedetector": "^8.2.1",
"jszip": "^3.10.1",
"luxon": "^3.6.1",
"maplibre-gl": "^4.7.1",
@ -114,6 +116,7 @@
"react": "^19.1.0",
"react-adonis-transmit": "^1.0.1",
"react-dom": "^19.1.0",
"react-i18next": "^17.0.2",
"react-map-gl": "^8.1.0",
"react-markdown": "^10.1.0",
"reflect-metadata": "^0.2.2",