diff --git a/handlers/settings.go b/handlers/settings.go index aa1cb86..b4c9124 100644 --- a/handlers/settings.go +++ b/handlers/settings.go @@ -14,6 +14,15 @@ import ( ) // GetSettings 获取设置 +func GetLanguage(c *gin.Context) { + var setting models.Setting + lang := "en" + if err := database.DB.Where("key = ?", "language").First(&setting).Error; err == nil && setting.Value != "" { + lang = setting.Value + } + c.JSON(http.StatusOK, gin.H{"language": lang}) +} + func GetSettings(c *gin.Context) { var settings []models.Setting database.DB.Find(&settings) diff --git a/main.go b/main.go index 7ab464d..c2d51a9 100644 --- a/main.go +++ b/main.go @@ -36,6 +36,7 @@ func main() { ) r.GET("/", handlers.IndexHandler) + r.GET("/api/language", handlers.GetLanguage) r.GET("/admin/login", func(c *gin.Context) { c.HTML(http.StatusOK, "login.html", nil) diff --git a/static/js/i18n.js b/static/js/i18n.js new file mode 100644 index 0000000..8a5b819 --- /dev/null +++ b/static/js/i18n.js @@ -0,0 +1,21 @@ +(function(){ +const zh={ +"OVERVIEW":"概览","SERVICES":"服务","SYSTEM":"系统","Admin sign in":"管理员登录","Overview":"概览","Service directory and system status":"服务目录与系统状态","Services":"服务","Manage service entries":"管理服务条目","Categories":"分类","Organize your directory":"整理服务目录","Settings":"设置","Site, backup and MCP":"站点、备份与 MCP","Backup":"备份","Run cloud backup now":"立即执行云端备份","System status":"系统状态","Quick actions":"快捷操作","Open navigation":"打开导航","Sign out":"退出登录","Open site":"打开网站","Theme":"主题", +"Administration":"管理后台","Username":"用户名","Password":"密码","Sign in":"登录","Current password":"当前密码","New password":"新密码","Confirm new password":"确认新密码","Update password":"更新密码","For security, the initial password must be changed before continuing.":"为了安全,继续操作前必须修改初始密码。", +"Service directory":"服务目录","Search services, tags, descriptions…":"搜索服务、标签、描述…","All":"全部","Uncategorized":"未分类","No services found":"没有找到服务","Loading…":"加载中…","System settings":"系统设置","Site title":"站点标题","WebDAV URL":"WebDAV 地址","Daily backup at 03:00":"每天 03:00 自动备份","MCP Server":"MCP 服务","Enable MCP endpoint":"启用 MCP 接口","Copy":"复制","Copied":"已复制","Regenerate":"重新生成","Cloud backups":"云端备份","No cloud backups":"暂无云端备份","Restore":"恢复","Delete":"删除","Close":"关闭","Save changes":"保存更改","Save":"保存","Cancel":"取消", +"Manage entries in your service directory":"管理服务目录中的条目","Search services...":"搜索服务…","Service":"服务","Category":"分类","Status":"状态","Order":"排序","Actions":"操作","Add service":"添加服务","Edit service":"编辑服务","Name *":"名称 *","URL *":"网址 *","Icon":"图标","No category":"无分类","Description":"描述","Tags (comma separated)":"标签(逗号分隔)","Sort order":"排序值","Health check URL":"健康检查地址","Enabled":"启用","Health check enabled":"启用健康检查", +"Add category":"添加分类","Edit category":"编辑分类","Organize services into groups":"将服务整理到不同分类","No categories yet.":"暂无分类。","Name":"名称","Save failed":"保存失败","Delete failed":"删除失败","Name is required":"名称不能为空","Name and URL are required":"名称和网址不能为空","Online":"在线","Offline":"离线","Unknown":"未知", +"POST/GET /mcp · changes apply immediately":"POST/GET /mcp · 修改立即生效","New token:":"新 Token:","Regenerate token? The old token will stop working.":"重新生成 Token?旧 Token 将立即失效。","Run cloud backup now?":"立即执行云端备份?","Current database will be backed up first.":"当前数据库会先进行备份。","Unable to load backups":"无法加载备份","English":"English","Chinese (Simplified)":"简体中文","Chinese":"中文","Language":"语言","Language preference":"语言偏好","Saved":"已保存","Backup complete":"备份完成","Done":"完成","Failed":"失败","Copy failed: ":"复制失败:","Delete ":"删除 ","Restore ":"恢复 ","?":"?" +}; +function tr(s){const v=(s||"").trim();if(/^\d+ services$/.test(v))return v.replace(/^(\d+) services$/,"$1 个服务");if(/^order \d+$/.test(v))return v.replace(/^order (\d+)$/,"排序 $1");return zh[v]||null} +function translate(root=document){ + const w=document.createTreeWalker(root,NodeFilter.SHOW_TEXT),a=[];while(w.nextNode())a.push(w.currentNode); + a.forEach(n=>{const x=tr(n.nodeValue);if(x)n.nodeValue=n.nodeValue.replace(n.nodeValue.trim(),x)}); + root.querySelectorAll?.("input[placeholder],textarea[placeholder],button[title],a[title]").forEach(e=>{for(const k of ["placeholder","title"]){if(e.hasAttribute(k)){const x=tr(e.getAttribute(k));if(x)e.setAttribute(k,x)}}}); + const x=tr(document.title);if(x)document.title=x; +} +function apply(lang){document.documentElement.lang=lang==="zh-CN"?"zh-CN":"en";if(lang==="zh-CN")translate(document)} +async function init(){try{const endpoint=location.pathname.startsWith("/admin")?"/admin/api/settings":"/api/language";const r=await fetch(endpoint,{credentials:"same-origin"});const d=await r.json();window.tonavLanguage=d.language||"en";apply(window.tonavLanguage);window.tonavTranslate=()=>translate(document);document.dispatchEvent(new CustomEvent("tonav-language",{detail:window.tonavLanguage}))}catch(e){}} +window.tonavSetLanguage=apply;window.t=function(s){return window.tonavLanguage==="zh-CN"?tr(s)||s:s};init(); +new MutationObserver(ms=>{if(window.tonavLanguage==="zh-CN")ms.forEach(m=>m.addedNodes.forEach(n=>{if(n.nodeType===1)translate(n)}))}).observe(document.documentElement,{childList:true,subtree:true}); +})(); diff --git a/templates/admin/categories.html b/templates/admin/categories.html index 8df15f9..6b7f51b 100644 --- a/templates/admin/categories.html +++ b/templates/admin/categories.html @@ -4,7 +4,7 @@ async function load(){const r=await fetch('/admin/api/categories'),d=await r.json();if(!d.data?.length){list.innerHTML='