节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
19.53 KiB
Stardust
# 多语言本地化 > 基线:NewLife.Core 12 编译期词表|运行时 31 种语言|英文为源文与兜底 ## 1. 语言分层 本库全部运行时组件统一维护 NewLife.Core 的 31 种语言词表。缺失译文在运行时自动回退英文,新增语言不需要改代码。 词表文件(31 个,另有英文源文兜底): - zh-Hans - zh-Hant - es - de - ja - fr - pt - ru - ko - ar - bn - cs - da - el - fa - fi - he - hi - hu - id - it - ms - nb - nl - pl - ro - sv - th - tr - uk - vi ## 2. 词表文件 - `Stardust/Locales` - `Stardust.Extensions/Locales` - `Stardust.Server/Locales` - `Stardust.Web/Locales` - `Stardust.WebApi/Locales` - `Stardust.Data/Locales` - `StarAgent/Locales` - `StarAgent.Mobile/Locales` - `StarGateway/Locales` - `Plugins/AgentExpansion/Locales` - `Plugins/NetworkDetect/Locales` - `DeployAgent/Locales` - 文件名即 BCP 47 语言代码;英文是源文与兜底,**不需要** `en.yml`。 - 键为**英文原文**,必须与代码字符串逐字一致(含空格与标点);占位符 `{0}` 与格式说明符 `{1:n0}` 必须与英文一致。 - 词表由 NewLife.Core 包内的源生成器在编译期读取并生成压缩语言表,NativeAOT 下同样可用。 - 全部运行时组件(SDK、服务端、管理端、代理、网关、插件、发布工具)的异常、日志、控制台、事件、命令返回结果、历史备注、埋点标签与配置项描述已英文化,并补齐 31 种语言译文(缺失或同形条目回退英文源文)。告警通知(`AlarmService`、`RobotHelper` 与下线告警文案)与 Cube 界面静态文案仍为中文源文,见第 7 节。 日志语言与界面语言相互独立:`Localization.Culture` 控制异常与界面文案,`Localization.LogCulture` 控制日志与配置注释。日志直接写英文模板,由日志器按日志语言翻译;需要手动生成日志文本时用 `L.TLog`,异常文案用 `L.T`。带动态前缀的日志使用 `InfoPrefixed`/`ErrorPrefixed`,前缀在翻译之后拼接,避免前缀导致整串查表失败而回退英文。 日志与异常一律使用**格式字符串 + 参数**(`Log?.Info("Deleted {0}", path)`),不要用字符串插值(`$"Deleted {path}"`):插值后模板已展开,无法再按原文查表。模板按整句匹配,参数本身不翻译;不要把英文状态词塞进 `{n}` 参数,应按语义拆成完整句子。 ## 3. 新增一条消息 1. 代码中写英文源文:`throw new XException("Session {0} is closed.", sessionId);`,日志直接写英文模板(`Logger.Format` 自动翻译)。 2. 在全部语言文件的同一位置补齐该键;暂未翻译的语言先写英文原文兜底,遗漏只会回退英文,不会编译失败。 3. 运行校验脚本确认键集合、占位符与格式说明符一致。 ## 4. 校验 ```powershell # 运行时消息不得含中文(异常、日志、控制台、界面提示、埋点标签) pwsh -File check-english-exceptions.ps1 -Root . -Dir Stardust pwsh -File check-english-exceptions.ps1 -Root . -Dir Stardust.Extensions pwsh -File check-english-exceptions.ps1 -Root . -Dir Stardust.Server pwsh -File check-english-exceptions.ps1 -Root . -Dir Stardust.Web pwsh -File check-english-exceptions.ps1 -Root . -Dir Stardust.WebApi pwsh -File check-english-exceptions.ps1 -Root . -Dir Stardust.Data pwsh -File check-english-exceptions.ps1 -Root . -Dir StarAgent pwsh -File check-english-exceptions.ps1 -Root . -Dir StarGateway pwsh -File check-english-exceptions.ps1 -Root . -Dir Plugins/AgentExpansion pwsh -File check-english-exceptions.ps1 -Root . -Dir Plugins/NetworkDetect pwsh -File check-english-exceptions.ps1 -Root . -Dir DeployAgent pwsh -File check-english-exceptions.ps1 -Root . -Dir StarAgent.Mobile # 词表一致性。Web/WebApi 通过 Compile Include 复用 Server 源码,孤儿键扫描需同时覆盖 pwsh -File check-locales.ps1 -LocaleDir Stardust/Locales -SourceDir Stardust pwsh -File check-locales.ps1 -LocaleDir Stardust.Extensions/Locales -SourceDir Stardust.Extensions pwsh -File check-locales.ps1 -LocaleDir Stardust.Server/Locales -SourceDir Stardust.Server pwsh -File check-locales.ps1 -LocaleDir Stardust.Web/Locales -SourceDir "Stardust.Web,Stardust.Server" pwsh -File check-locales.ps1 -LocaleDir Stardust.WebApi/Locales -SourceDir "Stardust.WebApi,Stardust.Server" pwsh -File check-locales.ps1 -LocaleDir Stardust.Data/Locales -SourceDir Stardust.Data pwsh -File check-locales.ps1 -LocaleDir StarAgent/Locales -SourceDir StarAgent pwsh -File check-locales.ps1 -LocaleDir StarGateway/Locales -SourceDir StarGateway pwsh -File check-locales.ps1 -LocaleDir Plugins/AgentExpansion/Locales -SourceDir Plugins/AgentExpansion pwsh -File check-locales.ps1 -LocaleDir Plugins/NetworkDetect/Locales -SourceDir Plugins/NetworkDetect pwsh -File check-locales.ps1 -LocaleDir DeployAgent/Locales -SourceDir DeployAgent pwsh -File check-locales.ps1 -LocaleDir StarAgent.Mobile/Locales -SourceDir StarAgent.Mobile ``` CI 的 localization job 会执行同一组脚本。检查脚本覆盖抛异常、控制台与日志器(含 `log?.Warn` 这类可空调用)、`XTrace`、事件上报(`WriteInfoEvent`/`WriteEvent`)、自定义日志包装(`WriteError`/`WriteInfo`)以及 `return` 命令结果;只对字符串字面量判中文,中文标识符(如 `CommandStatus.错误`)不误报。孤儿键扫描同时读取 `.cs` 与 `.csproj`,程序集标题与描述作为词表 key 时不会误报。 覆盖率报告由 `report-locales.ps1` 生成到 `Doc/多语言覆盖.md`,按语言与组件统计已翻译条目与唯一源文总数: ```powershell pwsh -File report-locales.ps1 ``` `Stardust.Tests` 中的 `LocaleConsistencyTests` 在 `dotnet test` 阶段执行同一套键集、占位符与格式说明符校验,并断言简体与繁体词表全部提供译文。`StarPanelLocaleTests` 校验代理面板前端词表:31 份 `i18n/*.json` 键集一致,且 `index.html` 里的中文源文经词表替换后不再残留中文,新增面板文案忘记补词表会直接测试失败。`AgentPanelLocalizationTests` 验证代理面板的服务端配置文案、运行时长模板与鉴权消息在运行期确实能取到译文,并校验服务端本地化不改写内联脚本(避免译文撇号破坏脚本)。 ## 5. 运行环境与语言选择 目标语言来自 `CultureInfo.CurrentUICulture`(`Localization.Culture` 为空时),即操作系统的界面语言:Windows 取系统显示语言,Linux 取 `LANG`/`LC_MESSAGES`。`zh-CN` 命中 `zh-Hans`,`zh-TW` 命中 `zh-Hant`;没有对应词表的语言回退英文。 ### 5.1 配置项固定语言 `Star.Language`(客户端 SDK,Star.json)与 `StarServer.Language`(服务端,数据库配置)可固定进程的界面与日志语言,取值如 `zh-Hans`、`zh-Hant`、`en`、`ja`。留空时跟随系统语言。星尘代理与代理面板统一使用 Core 全局配置 `NewLife.Setting.Language`(`Config/Core.config`),不再读取 `StarAgent.Language`;面板右上角的选择器通过 `/api/SetLanguage` 写入该配置,留空恢复自动识别。Core 的语言码是 `zh-CN`、`en-US` 这类地区码,面板词表按 `zh-Hans`、`en` 命名,`i18n.js` 负责归一化后加载对应词表。 `StarLocalization.NormalizeCulture` 负责把 `zh-CN`、`de-AT`、`no`、`iw` 等写法归一化到词表语言;`StarLocalization.UseLanguage` 供客户端 SDK 与服务端写入 `Localization.Culture` 与 `Localization.LogCulture`,代理与面板则走 Core 的 `Localization.SetLanguage`(由 `Setting.Save()` 自动应用)。 ### 5.2 Web 宿主按请求识别语言 `StarServer.Language` 留空时,StarServer、StarWeb 与 StarWebApi 通过 `UseStardustLocalization` 注册请求语言中间件,优先级为查询串 `?lang=`、Cookie `.AspNet.Culture`、请求头 `Accept-Language`。中间件只修改 `CurrentUICulture`,不影响协议中的数字与日期格式;请求结束后还原。 识别期间日志与配置文件注释固定为宿主启动语言,避免同一日志文件随请求语言混杂。 .NET 6+ 在 Linux 上依赖 ICU(Debian/Ubuntu 包名通常为 `libicu`,Alpine 为 `icu-libs`)。缺少 ICU 时进程会在启动阶段失败,提示设置 `System.Globalization.Invariant`。无 ICU 的老旧/嵌入式系统可用环境变量降级: ```bash # 直接运行 export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 # systemd 服务 sudo systemctl edit staragent # [Service] # Environment=DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 sudo systemctl daemon-reload && sudo systemctl restart staragent ``` 降级后进程照常运行,但只有英文,且不得显式设置 `Localization.Culture`(invariant 模式下创建具体区域性会抛 `CultureNotFoundException`)。环境变量优先级高于 `runtimeconfig.json`,升级部署时无需重新发布即可切换。 ## 6. 批量翻译工作流 译文以英文源文为键批量写入,`apply-locales.js` 负责落到各组件词表: ```powershell # entries.json: { "English key": { "ja": "译文", "de": "译文" }, ... } node apply-locales.js entries.json Stardust/Locales Stardust.Server/Locales StarAgent/Locales ... # 统计某语言仍为英文占位的键 node apply-locales.js --missing es ``` 脚本先按各组件的 `zh-Hans.yml` 键集剔除多余键,再就地更新译文;词条未覆盖的语言保持英文占位。写入后依次执行 `check-locales.ps1` 与 `report-locales.ps1` 校验并刷新覆盖率。 界面元数据(菜单名、实体字段描述,键为中文源文)走同一脚本的元数据模式: ```powershell # 1) 收集键清单(模型 XML + 控制器/视图中的 DisplayName/Description) pwsh -File collect-metadata-keys.ps1 -Model Stardust.Data/Nodes/Model.xml -Output Stardust.Data/Locales/metadata-keys.json pwsh -File collect-metadata-keys.ps1 -SourceDir Stardust.Web -Output Stardust.Web/Locales/metadata-keys.json # 2) 写入译文(元数据键免于 zh-Hans 键集裁剪,单独成节) node apply-locales.js --metadata Stardust.Data/Locales/metadata-keys.json --section "# 界面元数据词条" entries.json Stardust.Data/Locales # 3) 校验(`-MetadataKeys` 允许元数据键只出现在部分语言) pwsh -File check-locales.ps1 -LocaleDir Stardust.Data/Locales -SourceDir Stardust.Data -MetadataKeys Stardust.Data/Locales/metadata-keys.json ``` ### 6.1 与英文同形的词条 少数条目在目标语言中本就与英文同形,属正确译文而非漏翻,`report-locales.ps1` 会将其计为未翻译:Hash、Commit、Status、Server、Site、Repository、Branch、OOM score 等技术通用词,以及 NAT proxy、Process ID 这类缩写加借词的组合。当前只有德语 8 条、意大利语/葡萄牙语/罗马尼亚语各 4 条、西班牙语/土耳其语各 2 条、捷克语/希腊语/匈牙利语/印尼语/马来语/荷兰语各 1 条属于此列。此外,第 8 节的程序集标题与描述已补齐各语言译文,因此各语言汇总覆盖率约 99.5%,德语因同形词条为 98.8%;简体中文为 87.9%,是界面元数据键以中文为键、值与键相同导致按口径不计入已翻译,属统计口径而非漏翻。 ## 7. 管理端界面文案 界面文案分两类,处理方式不同: - **服务端返回的消息**(异常、API `message`、事件、命令返回结果、历史备注):已是英文源文并走 `L.T` 或日志翻译,随请求语言输出,已覆盖。代理面板的配置项分组、字段名、说明、运行时长(`FormatUptime`)与鉴权/服务管理器错误同样由 `StarApi` 用 `L.T` 输出,语言由 Core 全局配置决定。 - **界面静态文案与 Cube 操作结果**(控制器 `df.DisplayName`、实体 `[DisplayName]`、区域菜单、`JsonRefresh`/`Alert` 提示):仍是中文源文,与第二类一并处理。 第二类暂不改英文源文:当前引用的 NewLife.Cube 版本(6.15.2026.1018-beta1727)不调用 `Localization`,`[DisplayName]`、`[Description]` 与 `ListField.DisplayName` 原样渲染。此时把中文标签改成英文源文,会让中文界面直接显示英文,属于功能回退;Cube 主线已在 `ConfigController` 与 `CubeLocalization` 中实现 `L.T` 翻译,待发布后再统一切换并同步词表。 2026-10-21 实测确认该结论:临时站点分别以 `zh-CN`、`en-US`、`ja-JP` 请求 `/Nodes/Node`、`/Configs/AppConfig`、`/Platform/StarServer`,页面标题、表头与按钮全部相同且为中文;二进制检查 `NewLife.Cube.dll`/`NewLife.Cube.Core.dll` 中 `Localization` 引用数为 0。Cube 仓库的 i18n 提交(`4a5d0f44` MVC 视图外壳按请求语言渲染、`0fa0ce42` 服务端消息与实体元数据接入编译期词表)均晚于该 beta 包,NuGet 尚无更新版本。 为缩短发版后的切换时间,Stardust 侧词表已提前备好(升级 Cube 后无需再改代码即可生效): - `Stardust.Data/Locales/metadata-keys.json`:76 个实体元数据键(表/列描述),对应 30 种语言共 2280 条译文。 - `Stardust.Web/Locales/metadata-keys.json`:67 个菜单与列表列名键(58 个中文源文需翻译,其余为 `{Name}` 这类占位符模板),对应 30 种语言共 1740 条译文。程序集标题与描述(csproj 的 `AssemblyTitle`/`Description`)不进入该清单,仍按第 8 节的「英文源文 + Locales 词条」约定处理。 - 元数据键以**中文为 key**(与 Cube `DataField.Localize`、`MenuHelper.GetDisplayName` 的约定一致),写在非中文语言的 `*.yml` 中;简体中文直接返回源文,因此不进入 `zh-Hans.yml`。 - 工具链:`collect-metadata-keys.ps1` 收集键(支持 `metadata-extra.json` 增量补 `L.T` 标签)、`check-locales.ps1 -MetadataKeys` 校验(元数据键可部分缺失、免孤儿键检查)、`apply-locales.js --metadata --section "# 界面元数据词条"` 写入、`report-locales.ps1` 第 3 节统计元数据覆盖率。 - 生效条件:升级到含 `UseCubeLocalization()` 的 NewLife.Cube 版本;届时用 E2E 增加 `Accept-Language` 断言回归。 浏览器标题后缀仍为英文(`Stardust management portal`):Cube 的 `_Layout.cshtml` 直接输出 `SysConfig.Current.DisplayName`,未经过 `L.T`,属上游待办;E2E 断言已兼容中英文两种站点名。 告警通知(`AlarmService`、`RobotHelper` 与节点/应用下线告警)面向中文运维群,保持中文源文,不参与运行时消息检查。 ### 7.1 代理面板前端词表 `StarAgent/WebPanel/wwwroot/index.html` 保持中文源文,`StarPanel` 重写 `WriteIndex`,在输出 HTML 前用 `UiLocalization.GetDictionary` 的词表按当前语言对源文做最长匹配替换,简体中文直接返回源文;因此登录页等静态文案在浏览器收到 HTML 时已是目标语言。替换只作用于脚本与样式之外的标记——基类 `UiLocalization` 会连内联脚本一起替换,法语、乌克兰语译文中的撇号会破坏单引号字符串导致脚本语法错误。脚本内的中文由同目录的 `i18n.js` 负责:它把 DOM 文本节点、`placeholder`、`title` 与 `aria-label` 按词表替换,`MutationObserver` 覆盖动态渲染。词表是 `wwwroot/i18n/<语言>.json`(31 份,键为中文源文),随 wwwroot 作为嵌入资源打进程序集,由内嵌静态文件服务按 `/i18n/<语言>.json` 提供,服务端与浏览器端共用同一份词表。 `i18n.js` 启动时读取 `/api/Languages` 拿到语言清单与语言状态:有固定语言(`current`)时界面跟随它,未固定时跟随服务端实际生效语言(`effective`),接口不可用时才退回 `navigator.language`,并复用同一套归一化规则(`zh-CN` 映射到 `zh-Hans`)。下拉项显示服务端返回名称括号内的本地名(`德语 (Deutsch)` → `Deutsch`),避免中文前缀混入其他语言的界面。面板右上角的语言选择器调用 `/api/SetLanguage` 把选择写入 Core 全局配置 `NewLife.Setting.Language`(留空恢复自动识别),写入后重载页面;阿拉伯语、波斯语、希伯来语同时把根元素方向设为 `rtl`。`/api/SetLanguage` 需要登录,因此登录页固定跟随服务端语言,登录后才能切换。 匹配规则:文本节点整串命中词表优先;否则按最长短语多轮替换,直到没有可命中的词条,因此 `确定要重启服务吗?重启后服务将自动恢复。` 这类拼句会被整体译出。单字词条只在两侧都不是汉字时替换,避免误伤。`placeholder`、`title` 与 `aria-label` 同样走词表翻译。需要拼接动态值或 HTML 的句子用 `StarI18N.t()` 显式翻译后再插入,占位符写成 `{0}`,例如删除确认弹窗的 `确定要删除子服务 {0} 吗?`。 接口请求不再携带语言参数:固定语言写入 `NewLife.Setting.Language` 后由 `Localization` 进程级生效,服务端消息、接口错误与日志保持一致。面板不按请求头识别语言,未固定时所有请求都使用进程的界面语言,因此 `i18n.js` 以 `/api/Languages` 的 `effective` 为准,而不是浏览器语言,避免界面与服务端消息不一致。`StarApi` 不再自行解析语言。 新增面板文案时先改 `index.html` 的中文源文,再把该源文补进 31 份词表 JSON;`Stardust.Tests` 的 `StarPanelLocaleTests` 会校验遗漏。面板词表与 `StarAgent/Locales` 的后端词表互相独立:后者只用于服务端生成的消息(配置项文案、运行时长等),不参与 `index.html` 中文源文的替换。 不要在 csproj 里写死 `System.Globalization.Invariant`:它会让该项目所有平台、所有目标框架的构建永久失去多语言能力(StarAgent 2020 年为 linux-arm 无 ICU 环境加的开关即属此类,2026 年已移除)。 ## 8. 程序集与包元数据 跟随 NewLife.Core v12 规范,程序集标题与描述采用「英文源文 + 词表 + 展示层翻译」: - csproj 的 `AssemblyTitle` 与 `Description` 使用**英文源文**,同一字符串作为 key 写入本程序集 `Locales/*.yml`:中文给译文,其余语言同样写出译文,缺失时回退英文源文。 - 展示点用 `L.T(value)` 翻译:NewLife.Agent 的 `DisplayTitle`/`DisplayDescription` 已内置(控制台标题、`-status` 输出、面板、Windows SCM/systemd 注册),`DeployAgent` 控制台描述自行调用。服务名不翻译,用户配置的 `DisplayName` 同样走 `L.T`。 - `check-locales.ps1` 的孤儿键扫描已覆盖 `.csproj`,元数据 key 不会被误判。 - NuGet 包另有 `<PackageDescription>` 承接中英双语描述;`Description` 保持纯英文供反射使用,`PackageTags` 可保留中英关键词。 `StarAgent/MyService` 与 `DeployAgent/DeployService` 的 `DisplayName`/`Description` 与各自 csproj 保持同一英文串,安装注册和面板显示随语言切换。 插件与测试的 `[DisplayName]`/`[Description]` 属表结构或插件元数据,保持项目母语,不进入运行时消息检查;确需翻译时按 Core 规范的元数据 key 机制单独登记。 配置项 `[Description]` 已改为英文源文,同一字符串作为 key 写入各组件 `Locales/*.yml` 并补齐 31 种语言译文;Core 在读取与保存配置时按 `Localization.LogCulture` 查表输出,缺失回退英文源文。实体与控制器元数据(菜单名、字段描述)仍保持中文源文,走第 7 节的界面元数据词条:`Stardust.Data/Locales/metadata-keys.json` 与 `Stardust.Web/Locales/metadata-keys.json` 登记中文源文键,30 种语言的译文写在同一 `Locales` 目录的 `*.yml` 中。 `Installer`、`StarAgentTool`、`StarAgent.Android`、`StarAgent.iOS`、`MySqlAgent` 未纳入解决方案的词表体系,暂保留中文 `AssemblyTitle`/`Description`;它们不参与服务注册与面板展示。