diff --git a/admin/inertia/app/app.tsx b/admin/inertia/app/app.tsx index b71ab64..3359703 100644 --- a/admin/inertia/app/app.tsx +++ b/admin/inertia/app/app.tsx @@ -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( - + - {showDevtools && } + {showDevtools && ( + + )} diff --git a/admin/inertia/components/LanguageSwitcher.tsx b/admin/inertia/components/LanguageSwitcher.tsx new file mode 100644 index 0000000..a80d880 --- /dev/null +++ b/admin/inertia/components/LanguageSwitcher.tsx @@ -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 ( +
+ Language: +
+ {languages.map((lang) => ( + + ))} +
+
+ ) +} diff --git a/admin/inertia/i18n/index.ts b/admin/inertia/i18n/index.ts new file mode 100644 index 0000000..9fa4f6b --- /dev/null +++ b/admin/inertia/i18n/index.ts @@ -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 diff --git a/admin/inertia/i18n/locales/en.json b/admin/inertia/i18n/locales/en.json new file mode 100644 index 0000000..ed78f67 --- /dev/null +++ b/admin/inertia/i18n/locales/en.json @@ -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" + } +} \ No newline at end of file diff --git a/admin/inertia/i18n/locales/zh.json b/admin/inertia/i18n/locales/zh.json new file mode 100644 index 0000000..540e199 --- /dev/null +++ b/admin/inertia/i18n/locales/zh.json @@ -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": "重试" + } +} \ No newline at end of file diff --git a/admin/package-lock.json b/admin/package-lock.json index d34cc88..37d1d69 100644 --- a/admin/package-lock.json +++ b/admin/package-lock.json @@ -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", diff --git a/admin/package.json b/admin/package.json index ab229f4..e6931f1 100644 --- a/admin/package.json +++ b/admin/package.json @@ -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",