后端运维【免费下载链接】cockpitCockpit is a web-based graphical interface for servers.项目地址https://gitcode.com/gh_mirrors/co/cockpit点击查看免费下载Cockpit 是一个基于 Web 的服务器图形管理界面其所有面向用户的页面文案都要求可翻译。本文以doc/i18n.md为核心结合仓库中po/Makefile.am、pkg/lib/html2po、pkg/lib/manifest2po、pkg/lib/cockpit-po-plugin.js与pkg/lib/cockpit.js的源码实现完整讲解 Cockpit 的国际化流程如何在 HTML/JavaScript/C 中标记可翻译字符串、如何用xgettext与配套工具提取生成 PO 模板、如何通过 Weblate 协作翻译以及翻译在浏览器端如何被加载、查找并应用到页面。读完本文你将掌握为 Cockpit 页面或类似 Web 项目接入 GNU gettext 体系的完整实战方案。Cockpit 国际化的整体架构Cockpit 的国际化以 GNU gettext 及其配套工具为基础设施流程贯穿开发、翻译与运行时三个阶段标记Marking开发者在 HTML 中使用translateyes属性、在 JavaScript/JSX 中调用cockpit.gettext()/cockpit.ngettext()、在 C 代码中使用标准 gettext 宏标记所有面向用户的可见字符串。提取Extraction构建系统通过多次调用xgettext以及仓库自带的html2po、manifest2po脚本把上述字符串提取成 PO 模板po/cockpit.pot对应源码见 po/Makefile.am。翻译Translation模板上传到 Weblate 翻译平台由社区协作翻译也可通过 pull request 直接更新 po/ 下的XX.po文件。运行时Runtimecockpit-po-plugin把 PO 文件编译为 JavaScript页面加载po.XX.js后调用cockpit.locale()注入翻译数据cockpit.gettext()与cockpit.translate()负责实际查表与应用。这一设计与普通桌面应用读取.mo文件的经典 gettext 用法不同HTML 和 JavaScript 无法直接读取 po/mo 文件因此 Cockpit 在构建期就把翻译数据转换成浏览器可执行的 JavaScript 结构这是理解整个链路的关键。标记可翻译字符串Cockpit 页面上所有对人可见、需要翻译的字符串必须通过以下两种方式之一显式标记否则不会被提取。HTML 中的translateyes属性字符串若出现在 HTML 源码中必须用带translateyes属性的标签包裹span translateyesImage/spanCockpit 的translate属性比标准 HTML 的translate语义更丰富从 pkg/lib/cockpit.js 中cockpit.translate()的实现可以看到其完整用法属性值默认为yes即只写translate也等价于translateyestranslateno表示跳过该元素属性值可以用空格分隔多个任务例如translateyes placeholder表示除了翻译文本内容还要把placeholder属性也作为可翻译字符串处理tasks[t]作为属性名调用el.setAttribute(tasks[t], cockpit.gettext(...))可以通过translate-contextxxx或contextxxx为字符串指定翻译上下文msgctxt用于区分同名但含义不同的词条。与 HTML 提取对应的工具是仓库自带的 Python 脚本 pkg/lib/html2po它基于HTMLParser扫描所有pkg/下的*.html文件识别translate值为yes或缺失与context/translate-context属性记录每个可翻译字符串出现的位置文件:行号写入 PO 的#:引用注释有上下文的字符串会输出msgctxt条目提取结果写入po/cockpit.html.pot见 po/Makefile.am。JavaScript / JSX 中的cockpit.gettext()字符串若出现在 JavaScript 源码中必须使用cockpit.gettext()或其相关函数通常通过别名_()调用。必须使用双引号字符串这样xgettext才能正确识别const _ cockpit.gettext; var translated _(Time); // 在 JSX 块中 tr th{ _(Image) }/th /trcockpit.gettext的签名支持可选上下文参数。在 pkg/lib/cockpit.js 的实现中调用cockpit.gettext(context, string)时会把context \u0004 string作为查找键\u0004是 gettext 标准的 msgctxt 与 msgid 分隔符只传一个参数时自动把它当作字符串本身。因此// 带上下文的调用用于区分不同场景下含义不同的同一个词 var translated cockpit.gettext(key, Control);同样地上下文写法也支持别名形式const C_ cockpit.gettext;这在 pkg/base1/test-locale.js 的测试中有明确验证。xgettext在提取 JS 字符串时使用的关键字配置见 po/Makefile.am它把 JS/TS/JSX 当作 C 语言处理注册了_、C_、N_、NC_、gettext、ngettext等关键字并显式排除test-*测试文件。涉及数量的复数形式cockpit.ngettext()涉及数字时需要使用cockpit.ngettext()处理复数var translated cockpit.ngettext(This thing, The things, numberOfThings);ngettext的底层实现见 pkg/lib/cockpit.js体现了 gettext 复数机制的完整逻辑先从 PO 头部读取plural-forms函数由构建期从Plural-Forms:头转换而来对传入的数量num求值得到复数索引i从翻译数组中取第i1项数组第 0 项恒为null占位若翻译数据缺失则回退到英文规则num 1返回单数形式否则返回复数形式。仓库 pkg/base1/test-locale.js 用猪拉丁语pig latin伪造翻译数据验证了 0、1、5 三种数量的分支选择并用俄语数据验证了三态复数俄语有 1/2-4/5 三套形式而 po/zh_CN.po 中中文的Plural-Forms: nplurals1; plural0;则说明中文只有一种复数形式。参数化字符串cockpit.format()当字符串中带参数且不同语言中参数的出现顺序可能不同时应使用cockpit.format()。Cockpit 约定用$0、$1这类索引占位符而不是硬编码拼接位置。实际例子可见 pkg/lib/cockpit-components-plot.jsxreturn cockpit.format(cockpit.ngettext($0 year, $0 years, n), n); return cockpit.format(cockpit.ngettext($0 minute, $0 minutes, n), n);这里ngettext先根据数量n选出正确的单/复数模板format再把n填入$0占位符。翻译者可以在译文里自由调整$0的位置例如还有 $0 分钟或$0 分钟后。翻译提取流程生成po/cockpit.potpo/cockpit.pot是 Cockpit 的单一标准 PO 模板但它并非一次性提取生成而是由五类来源合并而成详见 po/Makefile.amPOT 文件提取工具输入来源po/cockpit.html.potpkg/lib/html2popkg/下所有*.html提取translate属性支持 Glade 风格与 angular-gettext 风格po/cockpit.js.potxgettextpkg/下所有*.js/*.jsx/*.ts/*.tsx排除test-*按 C 语言关键字规则提取po/cockpit.manifest.potpkg/lib/manifest2popkg/下所有manifest.json导航菜单menu、tools、bridges中的label、keywords.matches、docs.label等字段po/cockpit.appstream.potxgettextpkg/、src/下的*.appdata.xml.in、*.metainfo.xml.inpo/cockpit.polkit.potxgettextsrc/下的*.policy.in配合 po/its/polkit.its 与 po/its/polkit.loc 的 ITS 规则最后一步用msgcat --sort-output把上述五个 POT 合并为一个make po/cockpit.pot运行make po/cockpit.pot即可在构建树中生成标准 PO 模板文件。值得说明的是manifest.json的翻译Cockpit 每个页面模块都有manifest.json例如 pkg/networkmanager/manifest.json其中定义了导航菜单项、搜索关键字和文档入口等用户可见文本。pkg/lib/manifest2po 会递归收集menu、tools下的label与keywords.matches、bridges下的label等字符串并为每个条目写入文件:0形式的引用。通过 Weblate 平台协作翻译模板生成后翻译工作主要在 Weblate 平台上协作完成上传模板运行make upload-pot把po/cockpit.pot上传到 Cockpit 的 Weblate 项目任何贡献者都可以在平台上为各语言提交翻译。拉取翻译运行make download-po把 Weblate 上的最新翻译下载回仓库的po/XX.po文件如 po/zh_CN.po、po/de.po。该步骤由文档所述的po-refresh脚本执行并由po-trigger定期触发保证翻译与源码保持同步。手动提交翻译也可以完全脱离 Weblate用任何 PO 编辑工具完成然后通过 pull request 更新po/XX.po提交到仓库。此时建议同时把更新后的文件上传回 Weblate可在 Weblate 界面手动操作或推送到配套的 cockpit-weblate 仓库避免平台端与仓库端翻译分叉。从真实文件 po/zh_CN.po 可以看到同步回来的 PO 头部信息Language: zh_CN、Plural-Forms: nplurals1; plural0;、X-Generator: Weblate以及带#: pkg/users/accounts-list.js:236引用注释的翻译条目——这些引用注释正是后续cockpit-po-plugin按页面拆分翻译数据的依据。运行时如何使用翻译这是 Cockpit 国际化与经典 gettext 差异最大的环节浏览器端无法直接读 po/mo 文件必须在构建期转换。cockpit-po-plugin把 PO 编译为 JavaScriptpo/Makefile.am 负责提取而把po/XX.po转换成可在浏览器执行的 JavaScript 的是 esbuild 插件 pkg/lib/cockpit-po-plugin.js。它的工作流程如下get_po_files()扫描po/目录下所有.po文件parsePo()读取每个 PO 文件过滤掉#~开头的禁用条目以避免校验失败并识别语言方向ar阿拉伯语、fa波斯语、he希伯来语、ur乌尔都语被归为rtl从右到左其余为ltrbuildFile()生成一个 JavaScript 对象其键存放从 PO 头转换来的plural-forms函数、language与language-direction其余键为msgctxt\u0004msgid有上下文时或msgid值为[null, msgstr, ...]数组第 0 项固定为null供cockpit.ngettext按复数索引取值按引用注释过滤只有引用出现在当前页面子目录pkg/subdir或pkg/lib的词条才会被打包进该页面的翻译文件并跳过标记为fuzzy的未定稿翻译生成两个文件po.lang.js普通页面字符串和po.manifest.lang.js仅含manifest.json中引用的词条供导航菜单等场景使用输出到dist/page/目录文件名形如dist/shell/po.zh_CN.js文件内容以cockpit.locale(PO_DATA);包装见DEFAULT_WRAPPER页面引入该文件即完成翻译数据注入。cockpit.locale()、cockpit.gettext()与cockpit.translate()翻译数据的注入与查找由 pkg/lib/cockpit.js 中的三个核心 API 完成cockpit.locale(po)合并 PO 数据到全局po_data并依据头更新cockpit.language、cockpit.language_direction以及po_plural复数规则函数。传入null会清空当前翻译数据。cockpit.gettext(context, string)以context ? context \u0004 string : string为键查表命中则返回译文未命中返回英文原文。cockpit.translate()用于 HTML 页面。无参数时遍历整个document也可传入元素或元素数组对每个[translate]元素按前面介绍的任务拆分规则翻译文本内容或指定属性翻译完成后移除translate属性避免重复处理。因此使用translateyes标记的 HTML 页面需要显式调用cockpit.translate()才会实际应用翻译而使用cockpit.gettext()/JSX 的页面则是在渲染时逐个取译。调试技巧test_l10n本地化测试开关在cockpit.gettext()的实现中有一个方便的调试机制当浏览器localStorage中的test_l10n值为true时所有字符串都会被包装成»string«形式返回见 pkg/lib/cockpit.js。这让你无需真正切换语言即可直观地发现页面中哪些文本还没有走翻译通道。测试验证test-locale.jspkg/base1/test-locale.js 用 QUnit 完整覆盖了上述 API 的行为用伪造的猪拉丁语翻译数据验证locale()、gettext()、带上下文取译、ngettext()的 0/1/5 分支用俄语数据验证多态复数三套形式的索引计算用中文数据验证language-direction为ltr、plural-forms恒返回 0 的简化模型还有underscore用例验证_()与C_()别名用法与gettext完全等价。这个测试文件本身就是理解 Cockpit 翻译数据结构头 数组值最好的活教材。为新分支初始化翻译Cockpit 偶尔会为新的上游分支例如某个新的稳定发行版初始化翻译。文档给出的完整步骤如下在 cockpit-weblate 仓库创建同名分支先用新上游分支生成po/cockpit.potmake po/cockpit.pot上传到该分支若已有.po文件也一并放入。在 Weblate 上创建新的翻译组件打开 Cockpit 的 Weblate 项目页点击 Add new translation component组件名称与分支同名并指向 cockpit-weblate 仓库上一步创建的分支。修改WEBLATE_REPO_BRANCH在新 cockpit 分支的 po/Makefile.am 中把WEBLATE_REPO_BRANCH从main改为新分支名使make upload-pot/make download-po与新的 Weblate 组件对齐。这样即可让新分支的翻译独立于main演进避免开发中的新字符串干扰稳定分支的翻译进度。面向开发者的最佳实践清单结合文档与源码为 Cockpit 页面贡献可翻译代码时应注意HTML 中所有用户可见文本都包上translateyes需要区分同词歧义时加translate-context需翻译属性如placeholder时使用空格分隔的任务列表。JS/JSX 中一律使用_(...)双引号字符串以便xgettext识别切勿用模板字符串或单引号拼接用户可见文本。涉及数量时用cockpit.ngettext单/复数对而不是在代码里自己判断数量拼字符串数量与占位符结合时配合cockpit.format的$0索引占位给译者调整语序的空间。不要硬编码 HTML 实体或拼接 HTML 片段中的可见文本——html2po只提取标签包裹的文本节点。新词条添加后运行make po/cockpit.pot刷新模板再通过 Weblate 或 PR 补齐po/XX.po译文运行时翻译由cockpit-po-plugin按页面拆分自动生成页面只需引入po.XX.js并对 HTML 页面调用cockpit.translate()。检查遗漏时开启localStorage.test_l10n true凡是没有变成»...«的字符串就是未走翻译通道的漏网之鱼。整套体系以标准 gettext 为骨架、以构建期 PO→JS 编译为特色既让 C 语言后端与 Web 前端共享同一套翻译基础设施又让浏览器端获得了轻量、按页面拆分的翻译加载能力——这正是 Cockpit 支撑数十种语言po/ 下现有 ar、cs、de、es、fr、ja、ko、ru、zh_CN 等 20 余个语言文件高效协作的基石。赞分享后端运维【免费下载链接】cockpitCockpit is a web-based graphical interface for servers.项目地址https://gitcode.com/gh_mirrors/co/cockpit点击查看免费下载相关推荐vnpy 国际化i18n开发全流程指南从字符串标记、pot/po/mo 生成到打包运行vnpy 国际化i18n开发全流程指南从字符串标记、pot/po/mo 生成到打包运行 vnpyVeighNa是基于 Python 的开源量化交易平台金融科技机器学习VeighNa 国际化i18n实战指南从源码标记、翻译提取到多语言打包与运行VeighNa 国际化i18n实战指南从源码标记、翻译提取到多语言打包与运行 本文以 Python 量化交易平台 VeighNavnpy为核心完整讲金融科技后端机器学习Zcash 翻译字符串策略全解析从 _() 标记到字符串冻结的国际化工程实践Zcash 翻译字符串策略全解析从 _ 标记到字符串冻结的国际化工程实践 导读 本篇文章围绕 Zcash 仓库中 translation_strings_po区块链金融科技密码学后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考