Zulip 翻译指南:从 Weblate 工作流到国际化工具链的完整实践
Zulip 翻译指南从 Weblate 工作流到国际化工具链的完整实践【免费下载链接】zulipZulip server and web application. Open-source team chat that helps teams stay productive and focused.项目地址: https://gitcode.com/GitHub_Trending/zu/zulipZulip 是一款开源团队聊天服务器与 Web 应用对 Unicode 拥有完整支持并对 RTL 从右到左语言提供部分支持UI 已被翻译为包括西班牙语、德语、印地语、法语、中文、俄语、日语在内的十余种主要语言。本文以官方翻译指南为主线系统讲解贡献者如何通过 Weblate 参与翻译、如何在本机测试译文并结合仓库源码剖析 Zulip 前端的$t/{{t}}、服务端_()/{% trans %}等国际化标记语法、复数plural处理、makemessages/compilemessages命令以及大小写规范 lint 的底层实现帮助读者既能在贡献流程中即学即用又能深入理解 Zulip 完整的国际化i18n工具链。一、Zulip 的国际化概览一套面向多语言的完整设计Zulip 的国际化不是事后补丁而是从 UI 渲染到资源文件管理的系统性设计。官方文档明确了两个层面的支持目标使用层面由于完整支持 Unicode用户可以在 Zulip 的任何地方使用自己的偏好语言RTL 语言如阿拉伯语、希伯来语获得部分支持。翻译层面Zulip 官方 UI 已被翻译为十余种主要语言西班牙语、德语、印地语、法语、中文、俄语、日语等且持续欢迎新的语言贡献。从仓库结构可以直观看到这套体系的落地形态每种语言在 locale/ 目录下拥有独立的资源目录例如locale/de/德语、locale/zh_Hans/简体中文、locale/ja/日语等每个目录下同时包含两份核心资源文件LC_MESSAGES/django.po—— 服务端字符串Portico 登录页、API 错误消息等的 gettext 资源translations.json—— Web 应用前端字符串的 FormatJS/ICU 资源。实测locale/de/translations.json中形如1 day: 1 Tag、1 hour: 1 Stunde的键值对正是英文原文 → 目标语言译文的标准映射结构。对于想了解字符串如何被打上翻译标记、翻译如何同步回仓库这一整套技术流程的开发者官方建议阅读姊妹篇 Internationalization for Developers而本文接下来的主体则聚焦翻译贡献者的实际工作流。二、翻译者的工作流Translators Workflow2.1 七个标准步骤官方为翻译贡献者定义了清晰的七步流程加入翻译频道在 Zulip development community server 加入#translation频道即 translation-channel打个招呼。该频道同时是提问、汇报进度、报告问题字符串的官方渠道。注册 Weblate 账号前往 hosted.weblate.org 注册。重要提示除非你计划贡献国家/地区特有的翻译否则注册时不要在选择语言列表里勾选国家特定语言。例如若你打算翻译英式英语才选择English (United Kingdom) (en_GB)对于通用西班牙语应选Spanish (es)而非Spanish (Colombia) (es_CO)。选择国家特定语言会显著缩小你能贡献的范围且这些变体通常无需维护。进入 Zulip 的 Weblate 项目页打开 Zulip project on Weblate。选择目标语言你的偏好语言会排在列表顶部直接点选即可。可选按组件细分翻译点击顶部的 Components 标签只翻译项目的一部分。Zulip 按使用位置将项目拆分为多个组件Flutter移动端应用Zulip Mobile使用的字符串。DesktopZulip 桌面端应用中不与 Web 应用共享的部分字符串数量较少。Django与Frontend对应 Zulip 服务器与 Web 应用的下一个大版本字符串即 chat.zulip.org 和 Zulip Cloud 上正在运行的版本。名称带版本号后缀的Django与Frontend变体如(10.x)对应 Zulip 当前稳定发布系列的字符串。Weblate 足够智能即使同一字符串出现在多个资源中也只会要求你翻译一次。带(10.x)后缀的版本变体则让译者可以把某种语言在当前发布版本中做到 100% 翻译完成。点击 Translate 按钮开始翻译具体操作方式参考 Weblate 官方翻译文档。尽可能实测你的翻译测试细节见下文第四节然后在 Zulip 里请维护者把 Weblate 的字符串合并进代码库并部署到 chat.zulip.org以便你在真实环境中验证。2.2 翻译过程中的实用技巧官方给出了一批经社区验证的实战建议随身携带语言风格指南始终跟随你的语言的 翻译风格指南翻译时在标签页中保持打开。如果某语言还没有指南边译边写一份是最好的时机——它最容易边翻译边记录且能极大帮助未来的译者。提交方式参见 文档贡献说明。使用并更新 Weblate 术语表GlossaryZulip glossary 会为全应用反复出现的术语如 channel提供内联的、一致的翻译参考。不要翻译变量与代码通常以%开头、位于 HTML 标签...内、或被花括号{variable}包裹的内容一律原样保留verbatim。善用 Source string location当上下文不清晰时点击 Weblate 界面右侧栏的该链接可跳转到源码出处查看语境。存疑就问不确定的字符串到#translation频道向社区求证。留意大小写与标点细节决定质量Weblate 会捕获并警告部分标点不匹配的情况。使用 Weblate 快捷键参考 Weblate 文档中的 keyboard shortcuts 提高效率。优先级排序应优先翻译Frontend与Flutter组件因为最显眼的用户可见字符串在那里但Django组件中的API 错误消息同样会呈现给用户所以完整的翻译必须包含它们。三、机器翻译Machine TranslationWeblate 内置了机器翻译能力。如果你的语言启用了该功能可以在翻译框下方的Automatic suggestions标签页里一键生成机器翻译建议。需要特别强调的是Zulip 期望的是人类质量human-quality的翻译。机器翻译只能作为辅助工具所有机器生成的字符串都必须逐条人工复核后才能提交。四、在本机测试翻译Testing Translations本节假设你已搭好 Zulip 开发环境。如果搭建环境有困难也可以在 chat.zulip.org 上求助——社区通常可以直接把最新翻译部署上去供你验证。4.1 拉取 Weblate 的翻译提交把 Weblate 添加为 Git 远端git remote add weblate https://hosted.weblate.org/git/zulip/django/把 Weblate 的提交合入本地仓库git cherry-pick weblate/main ^upstream/main4.2 在 UI 中查看翻译的四种途径URL 前缀法把语言代码作为 URL 前缀插入即可。例如用http://localhost:9991/de/login/查看德语登录页。这适用于 Zulip UI 的任何部分包括 Portico未登录页面。应用内切换对于登录后的 Zulip 实际 Web 应用界面可以在 Zulip UI 的偏好设置里选择语言。系统语言自动检测如果你的操作系统/浏览器配置了语言Zulip 的 Portico未登录页面会自动使用该语言。注意只有用户实际需要使用的页面如/login/、/register/等才被标记为可翻译/features/这类营销页面不在此列。HTTP 头手动指定用requests、cURL 或urllib等 HTTP 客户端库传递Accept-Language头。官方给出的 Python 示例import requests headers {Accept-Language: de} response requests.get(http://localhost:9991/login/, headersheaders) print(response.content)这在调试时偶尔会很有用。4.3 浏览器语言选择优先级需要厘清上述途径的交互关系时官方明确给出了 Zulip 确定用户请求语言的优先级基本沿袭 Django 文档URL 前缀中的语言代码例如/de/login/优先其次查找名为django_language的 Cookie可通过LANGUAGE_COOKIE_NAME设置修改名称最后回退到 HTTP 请求中的Accept-Language头浏览器借此把 OS/浏览器语言告诉 Zulip。五、翻译风格指南Translation Style GuidesZulip 为各语言维护了官方翻译风格指南给出特定语言的翻译决策例如 channel 该译成什么词及其推理过程使后来的译者能理解并延续这些决策。当前仓库中已有的指南包括ChineseFinnishFrenchGermanHindiJapanesePolishRussianSpanish官方鼓励把指南中的信息同时沉淀到 Weblate glossary翻译时会以内联建议的形式呈现。尚未编写指南的语言官方强烈建议译者边翻译边写因为边译边记最容易产出高质量、可持续维护的风格文档。以 中文翻译指南 为例可以看到典型的风格决策样本术语表Message →消息Stream Message译作频道消息Direct Message译作直信Starred Message借鉴 QQ 邮箱译为星标消息Stream →频道灵感来自游戏 Ingress 的聊天 Channel比群组/主题/版块/栏目都更贴切Topic →话题Invite-Only/Public Stream →私有/公开频道Bot →机器人Integration →应用整合Notification →通知Alert Word →提示词。习惯用语Subscribe/Unsubscribe →订阅/退订Narrow to →筛选搜索语境下译搜索Mute/Unmute →开启/关闭免打扰借自微信消息免打扰比静音更贴合 ZulipDeactivate/Reactivate 分语境译为禁用/启用帐户、关闭/激活社区Invalid →不正确如 Invalid API key → API 码不正确I want →开启避免直译我想的口语化。其它细节You/Your 用敬语您/您的We 常省略不译或转换表达如 Still no email? We can resend it → 仍然没有收到邮件点击重新发送感叹号与句号一般省略——中文感叹号语气比英文更重句号。则影响页面排版句末留空即可。而 德语翻译指南 则展示了另一种语言策略总体采用非正式语气用 Du 而非 Sie、使用性别冒号Gender-Doppelpunkt如 Nutzer:innen、优先使用不定式命令式、尽量避免德语长复合词词长控制在 20 字符内例如不译 Alert words 为 Benachrichtigungsstichwörter 而是 Stichwörter, die mich benachrichtigen、对 Bot 等无准确德语等价物的外来词直接沿用并提醒警惕false friends如 actually/eigentlich、eventually/schließlich。这类风格指南的价值在于统一性是翻译质量的基石跨翻译者的用词一致性直接决定了用户界面的专业度。六、英文翻译字符串的大小写规范CapitalizationZulip 要求所有英文可翻译字符串的大小写必须与 Zulip 整体的大小写风格一致句子或短语的首字母大写但遵循 sentence case整句大小写风格而非 Title Case正确Channel settings错误Channel Settings所有专有名词大写正确This is Zulip错误This is zulip所有通用词如 URL、HTTP 等使用标准写法正确URL错误Url该规范由 Zulip 测试套件强制实施见 测试文档通过./tools/check-capitalization检查所有标记为可翻译的字符串。其中 tools/lib/capitalization.py 维护了豁免列表IGNORED_PHRASES如 AI、API、HTTP、JSON、Zulip、URL、UUID、GitHub 等专有名词与缩写以及 Im、Topics I start 等含 I 的表达并采用最长短语优先匹配的策略防止短词先匹配而破坏长词。tools/check-capitalization会先自动执行./manage.py makemessages --locale en生成最新资源再逐一校验英文消息的大小写。七、开发者视角Zulip 的国际化技术全景翻译贡献者了解这套技术栈有助于理解字符串的来龙去脉与上下文。以下内容提炼自 Internationalization for Developers 并对照源码验证。7.1 设计原则UI 必须能翻译、能排版文本宽度不是常量同一含义在不同语言中宽度差异巨大不能按英文宽度硬编码按钮/组件。测试技巧把字符串人为拉长 50%、缩短 50% 检查布局对已有字符串俄语是比英文长的典型测试用例日语则通常更短。所有用户可见字符串都应标记翻译包括错误字符串、日期、邮件内容。例外仅有三类落地页Landing pages、帮助中心页面、Zulip 更新公告。这些页面只需保证能被 Google Translate 等工具可用即可。注意用户可见四个字社区译者的时间是宝贵的只标记真正会显示给用户的内容。7.2 标记字符串时的三个陷阱标点不同语言标点习惯不同例如日语句末不用.因此.、?等句末符号必须包含在待翻译字符串内。语序拼接可翻译字符串必然产生烂翻译如主语-动词顺序差异。若句子含变量绝不能把变量前后的部分拆开分别标记。数字字符串如 5 bananas不同语言处理方式差异很大参见下方复数小节。7.3 Web 应用翻译语法FormatJS / ICU MessageFormatWeb 应用使用 FormatJS基于标准 ICU MessageFormat覆盖 Handlebars 模板与 JavaScript。JavaScript 中的$tintl.js中把intl.formatMessage化名为$t实际定义于 web/src/i18n.ts。待翻译字符串必须是常量字面量变量用花括号插值并传上下文对象$t({defaultMessage: English text with a {variable}}, {variable: Variable value})$t不转义变量若翻译结果最终作为 HTML 使用请用$t_htmlhtml_content $t_html({defaultMessage: HTML with a {variable}}, {variable: Variable value}); $(#foo).html(html_content);翻译字符串内只允许default_html_elements定义于 web/src/i18n.ts枚举的无属性简单标签b、code、em、i、kbd、p、strong以尽量避免把 HTML 细节暴露给译者。需要更复杂标记如链接时可定义局部自定义 HTML 标签或改用 Handlebars 模板$t_html( {defaultMessage: bHTML/b linking to the z-linklogin page/z-link}, {z-link: (content_html) a href/login/${content_html.join()}/a}, )Handlebars 模板Zulip 注册了两个 FormatJS 辅助函数。简单字符串用{{t ... }}传给 partial 时用variable_name(t English text)HTML 字符串用块级{{#tr}}...{{/tr}}{{t English text }} {{t Block of English text with a {variable}. }} {{#tr}} pBlock of English text with a {variable}./p {{/tr}}与 JavaScript 不同Handlebars 里的变量会被辅助函数自动转义。注意{{#tr}}...{{/tr}}内禁止Handlebars 表达式/块如{{variable}}、{{#if}}因为它们会在字符串经 FormatJS 处理前被求值导致待翻译字符串非常量——项目有专门的 linter 强制这一规则。复杂标记同样通过局部自定义标签实现{{#tr}} bHTML/b linking to the z-linklogin page/z-link {{#*inline z-link}}a href/login/{{ partial-block}}/a{{/inline}} {{/tr}}7.4 复数与列表Plurals and Lists英语只有单/复数两种形式1 banana vs 2 bananas而俄语等语言的名词变格甚至取决于数量的末位数字。Zulip 用 ICU MessageFormat 表达复数{N, plural, one {Done! {N} message marked as read.} other {Done! {N} messages marked as read.}}译者可用同一语法写出不同格范畴的译文如俄语按 one/few/many 区分。开发者只需写好英文的单/复数两个分支其余交给译者即便如此设计 UI 时也应尽量避免非必要的复数字符串例如图标 数字的呈现方式往往更优。列表构造foo, bar, and baz在语言间差异巨大有些语言甚至不用逗号Web 应用提供了util.format_array_as_list基于Intl模块来正确完成可用git grep查找使用示例。7.5 服务端翻译语法服务端字符串主要有两类API 返回的错误字符串等值以及不使用 JS/Handlebars 渲染的 Portico 页面如登录流程字符串。Jinja2 模板使用_()函数或{% trans %}块标记HTML 模板文档有更多语法说明{{ _(English text) }} {% trans %}This string will have {{ value }} inside.{% endtrans %}切勿把一句话拆成三段拼接会导致无法正确翻译# 不要这样做 {{ _(This string will have) }} {{ value }} {{ _(inside) }}Python导入django.utils.translation.gettext为_。JsonableError的错误消息必须始终是_()包裹的字面量from django.utils.translation import gettext as _ JsonableError(_(English text))在模块顶层或类中声明用户可见字符串时必须改用gettext_lazy确保翻译发生在请求处理期此时 Django 才知道目标语言AVATAR_CHANGES_DISABLED_ERROR gettext_lazy(Avatar changes are disabled in this organization.)tools/lint会对JsonableError等 JSON 错误消息的国际化用法做校验。7.6 端到端翻译流程与资源文件完整工具链如下开发者标记字符串见上文各小节运行./manage.py makemessages为每种语言生成资源文件Web 应用为translations.json服务端为django.po资源文件提交后由 Weblate 自动扫描译者在 Weblate UI 中翻译Weblate 生成 Git commit由维护者合入代码库。资源文件位置locale/lang_code/LC_MESSAGES/django.po服务端与locale/lang_code/translations.jsonWeb 应用。makemessages是幂等的具体行为可在 zerver/management/commands/makemessages.py 源码中验证仅当单数键在代码中不再使用时才删除仅当对应的单数键消失时才删除复数键不会覆盖已包含译文文本的单数键值。源码还揭示了一个关键细节Zulip 的makemessages命令继承自 Django但通过**猴子补丁monkey-patch**扩展了 Django 的正则表达式使 Jinja2 的{% trans %}、{% pluralize %}语法也能被提取Django 原生只认识 DTL 语法同时它额外注册了--frontend-source默认web/templates、--frontend-output默认locale、--frontend-namespace默认translations.json参数用前端正则表达式从.hbs模板中提取{{#tr}}、{{t ... }}等字符串并调用formatjs extract从web/src/**/*.{js,ts}中提取$t/$t_html的defaultMessage。get_new_strings方法证实了幂等语义英文 locale 的译文等于原文本身其他语言的新键默认值为空字符串旧译文原样保留。配套的 zerver/management/commands/compilemessages.py 则负责在构建期把.po编译为.mo并通过统计django.po未翻译条目与translations.json空值为每种语言计算出percent_translated翻译完成百分比汇总写入locale/language_options.json。7.7 语言列表与翻译百分比的呈现前端 web/src/i18n.ts 的initialize函数只把percent_translated 5%的语言纳入可选列表语言选择器会以display_name (percent%)形式展示完成度见get_language_list_columnsintl实例通过page_params.request_language与page_params.translation_data装载用户语言与翻译数据对新增未翻译字符串MISSING_TRANSLATION则静默忽略。这套逻辑让翻译完成度直接成为用户体验的一部分——这也从侧面说明每位译者的贡献都会即时反映在语言列表的百分比上。八、结语参与 Zulip 翻译是一条门槛低、回报高的贡献路径无需精通服务器与前端架构只需注册 Weblate、跟随语言风格指南、认真复核机器翻译建议就能让成千上万用户用上高质量的母语界面。而当你希望更进一步本文第七节梳理的$t/$t_html、{{t}}/{{#tr}}、_()/{% trans %}、ICU 复数、makemessages/compilemessages与大小写 lint则构成了理解 Zulip 国际化全貌的完整地图——两者结合正对应官方文档翻译贡献者与国际化开发者两条互补的学习路线。【免费下载链接】zulipZulip server and web application. Open-source team chat that helps teams stay productive and focused.项目地址: https://gitcode.com/GitHub_Trending/zu/zulip创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考