OpenProject 配置目录国际化规范:i18n-tasks 工作流与 Crowdin 翻译管理实战
【免费下载链接】openprojectOpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue tracking, roadmaps, Gantt charts, time tracking, collaboration features, and more. Available on premises or in the cloud. ⭐ Star us on GitHub项目地址: https://gitcode.com/GitHub_Trending/op/openproject
导读
OpenProject 作为覆盖项目管理、敏捷看板与组合管理的开源软件,其界面文本支持数十种语言,而这背后是一套严格的“配置目录国际化(i18n)规范”。本文以仓库config/目录下的 AGENTS.md 约定为骨架,系统讲解 OpenProject 的翻译键约束、en.yml源文件编辑方式、Crowdin 社区翻译协作流程,以及i18n-tasks四个核心 CLI 命令的用法与底层原理,并结合仓库源码说明前端 JS 翻译、复数化兜底等关键实现,帮助开发者在参与 OpenProject 开发或自建多语言功能时快速上手。
一、总体约定:UI 字符串必须走翻译键
config/AGENTS.md首先确立了一条铁律:
UI strings must use translation keys (never hard-coded)
即界面字符串一律使用翻译键(translation key),严禁硬编码。这条约束在仓库中随处可见:视图层通过t('...')/I18n.t(...)取键,例如 app/components/admin/backups/reset_token_dialog_component.html.erb 中的按钮文案,组件层则在 Ruby 中调用I18n.t。如此设计的目标是让“文本”与“逻辑”彻底解耦:任何新增的 UI 文案都必须在英文源文件中登记一个键,再由各语言文件提供对应翻译,从而保证语言切换、复数规则、无障碍标签等能力的一致性。
在组件开发层面,OpenProject 对 ViewComponent 的惰性查找(lazy lookup)做了专门适配。app/components/translations_override.rb 注释解释了原因:由于项目未使用 ViewComponent 自带的 sidecar 翻译文件,VC 4.0 的#translate会转发到I18n.translate,而项目依赖 ActionView 的覆盖实现来支持组件内的惰性键解析,因此该模块显式include ActionView::Helpers::TranslationHelper。这意味着组件内的t('.some_key')相对键能够按“组件路径 + 方法名”正确解析(参见 config/i18n-tasks.yml 中关于relative_roots与relative_exclude_method_name_paths的配置)。
二、英文源文件:可以直接修改的en.yml
文档规定,源翻译存放在**/config/locales/en.yml中,可以直接修改。这里的**通配符覆盖两层含义:
- 核心仓库:config/locales/en.yml 是主英文翻译文件,包含约 6000 余条键,覆盖 accessibility、activerecord、work_package、project 等全部命名空间;
- 各功能模块:OpenProject 以模块化方式组织代码,29 个模块各自维护独立的
config/locales/en.yml,例如 modules/storages/config/locales/en.yml(文件存储模块)、modules/meeting/config/locales/en.yml 等。这也是 config/i18n-tasks.yml 中data.read读取来源的一部分。
因此新增或修改英文文案时,先定位该功能所属的模块目录,再编辑对应en.yml;根目录的config/locales/en.yml则承载核心应用文案。
三、其他语言:统一交给 Crowdin 管理
与英文源文件不同,其他语言的翻译统一由 Crowdin 管理,开发者不应手工编辑config/locales/crowdin/下的 165 个语言文件(涵盖 af、ar、az、de、fr、ja、zh 等)。仓库 crowdin.yml 定义了与 Crowdin 平台的同步配置,这一工作流带来的收益是:翻译由社区译者分布式完成,且版本回退、词条一致性检查都在平台侧完成。
仓库中与 Crowdin 配套的脚本位于 script/i18n/:
generate_seeders_i18n_source_file:把种子数据(seeds)中的默认内容(如颜色名、默认状态名)抽成可翻译的源文件,文件头明确标注“This file has been generated … Please do not edit directly”,见 config/locales/crowdin/de.seeders.yml;rewrite_crowdin_yml_files/fix_crowdin_pt_language_root_key:处理 Crowdin 下载产物中的键根节点等格式问题;generate_languages_translations:基于 CLDR 数据(代码中CLDR_VERSION = 44)生成本地化语言元数据;test_seed_all_locales:批量校验所有语言环境下种子数据的可加载性。
此外,config/locales/generated/存放生成产物(en.yml、de.yml等按语言聚合的文件),config/locales/js-en.yml 则是前端 JS 用的英文翻译源。整体目录分工可归纳为:
| 目录/文件 | 角色 | 编辑方式 |
|---|---|---|
**/config/locales/en.yml | 英文源翻译(含各模块) | 开发者直接修改 |
config/locales/crowdin/*.yml | 各语言社区翻译 | 仅通过 Crowdin 平台 |
config/locales/generated/*.yml | 生成/聚合产物 | 脚本生成,勿手改 |
config/locales/js-*.yml | 前端 JS 翻译源 | 按需更新英文源 |
四、i18n-tasks 四件套:检查、清理与归一化
文档给出了开发者在提交翻译改动前必须运行的四个命令,全部基于i18n-tasks这个 Ruby gem(依赖声明见 Gemfile,版本约束为~> 1.1.0,require: false按需加载):
bundle exec i18n-tasks missing # Show missing translation keys bundle exec i18n-tasks unused # Show unused translation keys bundle exec i18n-tasks normalize # Fix/normalize translation files bundle exec i18n-tasks check-consistent-interpolations # Check interpolation consistency1.missing:找出缺失的翻译键
扫描代码中所有翻译调用,对照各语言文件找出缺失的键。工具的搜索范围与规则在 config/i18n-tasks.yml 中定义:
search.paths:默认搜索app/,当前配置额外加入了modules/storages/app/(模块内代码同样纳入检查);exclude:排除app/assets/images、app/assets/fonts、app/assets/videos、app/assets/builds等非代码资源目录;strict模式(默认 true)下,t("categories.#{category}.title")这类动态拼接会被识别为“猜测用法”,从而避免误报。
2.unused:找出未使用的翻译键
用于发现已被代码移除、但仍残留在语言文件中的键。为避免误报,config/i18n-tasks.yml 配置了ignore_unused白名单,例如:
ignore_unused: - 'activerecord.{models,attributes,errors}.*' - 'permission_*' - '{devise,kaminari,will_paginate}.*' - '*.permission_header_explanation' - 'storages.upsell.*' - 'services.*' - 'storages.health.checks.*'这些键要么由 Rails 框架按约定动态查找(如activerecord.attributes.*对应User.human_attribute_name(:email)),要么由权限/服务等反射式机制使用,静态扫描无法定位,因此需要显式豁免。
3.normalize:归一化翻译文件
对 YAML 文件做排序、键结构调整,保证多语言文件结构一致,避免合入 Crowdin 后产生大量无意义 diff。归一化遵循 config/i18n-tasks.yml 的data.write路由规则,例如 storages 模块的键会被强制写入modules/storages/config/locales/%{locale}.yml,其余键走config/locales/%{locale}.yml的 catch-all 规则;YAML 写入时设置line_width: -1(不自动换行,见第 43-46 行)。normalize -p变体可强制按规则搬移键。
4.check-consistent-interpolations:校验插值一致性
检查同一个键在不同语言中的插值占位符(如%{name}、%{descendants})是否一致。若英文写作%{name}: %{description}(见 config/locales/en.yml 中accessibility.macro.aria_label_with_name),而某语言漏掉%{description},运行时就会出现KeyError或渲染异常,该命令在 CI/提交前拦截这类问题。如确有例外可配置ignore_inconsistent_interpolations。
提示:i18n-tasks 本身也在 config/i18n-tasks.yml 中通过
PatternMapper注册了自定义扫描器,用来识别项目特有的link_translate("some.key", ...)辅助方法调用——这类调用因未走标准t()/I18n.t()而无法被默认扫描器捕获。这意味着项目内所有翻译入口都纳入了同一套静态检查体系。
五、运行时兜底:复数化与回退机制
翻译键的正确性不仅依赖静态检查,还依赖运行时的健壮性设计。config/initializers/i18n.rb 展示了两个关键增强:
- 复数化兜底:加载
lib/open_project/translations/pluralization_backend.rb定义的PluralizationBackend,并include进I18n::Backend::Simple。该模块(pluralization_backend.rb)覆写pluralize方法:当俄语、匈牙利语、波兰语等东欧语言缺少:many复数键触发I18n::InvalidPluralizationData异常时,安全回退到:other键;若无:other则返回 nil,表现为“缺失翻译”而不是直接抛异常——避免单个语言文件缺陷拖垮整个请求; - 默认语言回退:
include I18n::Backend::Fallbacks为未翻译的字符串回退到默认语言(base_locale: en)。注释特别指出,管理员配置的邮件头/邮件尾(Setting#localized_emails_header/Setting#localized_emails_footer)可能未提供所有语言版本,必须依赖该回退机制。
六、前端 JS 翻译:从 YAML 到 JSON 的自动化管线
OpenProject 的 Angular 前端同样使用翻译键,其英文源为 config/locales/js-en.yml,而输出映射规则定义在 config/i18n.yml(注意:该文件只控制前端 i18n-js 的翻译):
embed_fallback_translations: enabled: true translations: - file: "frontend/src/locales/:locale.json" patterns: - '*.js.*' - '*.number.*' - '*.time.*' - '*.date.*'即只有js.*、number.*、time.*、date.*四类命名空间的键会被打包进前端产物 frontend/src/locales/,并以<locale>.json形式输出,且启用回退翻译嵌入。config/initializers/i18n-js.rb 进一步说明:开发模式下应用启动后会调用I18nJS.listen,持续监听根目录config/locales及所有模块的**/config/locales目录,翻译文件一有改动即自动重建 JS 翻译,实现热更新式的前端多语言开发体验(frontend/src/locales/README.md 也说明该目录存放开发/生产环境生成的翻译,并指向项目翻译贡献指南)。
七、提交前检查清单
结合上述约定,一个完整的“新增 UI 文案”开发流程应为:
- 在对应模块或根目录的
config/locales/en.yml中添加英文键(如some_feature.title: "..."); - 运行
bundle exec i18n-tasks missing确认代码中引用的键已全部登记; - 运行
bundle exec i18n-tasks normalize归一化文件结构; - 运行
bundle exec i18n-tasks check-consistent-interpolations校验插值一致性; - 运行
bundle exec i18n-tasks unused清理废弃键; - 将新英文键通过 crowdin.yml 同步到 Crowdin 平台,等待社区译者补充各语言翻译;
- 前端若需要显示该文案,确认键位于
js.*等白名单命名空间,开发模式下由监听进程自动产出frontend/src/locales/:locale.json。
结语
OpenProject 的国际化并非零散地“在代码里写字符串”,而是一套从“禁止硬编码”的编码规范、en.yml源文件与 Crowdin 协作分工、i18n-tasks静态检查,到复数化与回退兜底、前端 JSON 自动生成的完整工程体系。理解 config/AGENTS.md 这份简短约定背后的实现细节,能帮助贡献者以最低成本、最高质量地向这一多语言开源项目提交翻译相关改动。
【免费下载链接】openprojectOpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue tracking, roadmaps, Gantt charts, time tracking, collaboration features, and more. Available on premises or in the cloud. ⭐ Star us on GitHub项目地址: https://gitcode.com/GitHub_Trending/op/openproject
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考