
Cataclysm-DDA 模组本地化实战指南从 JSON 模组到 .pot/.po/.mo 的完整翻译流程【免费下载链接】Cataclysm-DDACataclysm - Dark Days Ahead. A turn-based survival game set in a post-apocalyptic world.项目地址: https://gitcode.com/GitHub_Trending/ca/Cataclysm-DDA本篇指南以 Cataclysm-DDA大灾变黑暗之日以下简称 CDDA官方文档 doc/TRANSLATING_MOD.md 为骨架以仓库内真实源码与工具脚本为佐证讲解如何为自制模组MOD接入游戏的国际化i18n体系。读完你将掌握提取可翻译字符串 → 生成翻译模板 .pot → 制作各语言 .po 翻译文件 → 编译 .mo 并随模组发布 → 游戏自动加载的完整链路并能理解游戏端按语言目录扫描加载模组翻译的底层机制。一、为什么模组需要翻译CDDA 的 i18n 机制概览CDDA 本体使用 gettext 体系管理多语言文本开发者在源码与 JSON 数据中标记可翻译字符串工具链将其汇总为.pot翻译模板、.po各语言译文、.mo编译后的二进制译文三种文件。玩家切换语言后游戏运行时会扫描语言目录并加载对应译文。模组MOD作为独立内容包同样可以携带自己的翻译。游戏在启动时会扫描用户模组目录与内置模组目录下所有LC_MESSAGES目录并加载其中的.mo文件——这一点可以在 src/translation_manager_impl.cpp 的ScanTranslationDocuments()中直接看到if( dir_exist( PATH_INFO::user_moddir() ) ) { // 扫描用户模组目录如 ~/.cataclysm-dda/mods/ for( const std::string dir : get_files_from_path( LC_MESSAGES, PATH_INFO::user_moddir(), true ) ) { mo_dirs.emplace_back( dir, .mo ); } } if( dir_exist( locale_dir() ) ) { // 扫描核心翻译目录 for( const std::string dir : get_files_from_path( LC_MESSAGES, locale_dir(), true ) ) { mo_dirs.emplace_back( dir, cataclysm-dda.mo ); } }其中PATH_INFO::moddir()指向游戏数据目录下的mods文件夹PATH_INFO::user_moddir()指向用户目录下的mods文件夹见 src/path_info.cpp。模组翻译文件只需按约定路径放置玩家切换语言后即可自动生效无需玩家或模组作者做任何额外配置。二、准备一个示例模组文档以在./mods/demo目录放置一个添加图书物品的模组为例展开。模组包含两个 JSON 文件modinfo.json模组信息与items.json物品数据内容如下// modinfo.json [ { type: MOD_INFO, id: demo, name: Demo MOD, authors: [ ... ], description: This mod adds a book., category: content, dependencies: [ cdda ] }, { type: BOOK, id: demo_item, name: { str: Guide to Translate a MOD, str_pl: copies of Guide to Translate a MOD }, description: A thin book teaching how to translate a mod. } ]注意物品名称使用了{ str: ..., str_pl: ... }的字典形式来显式指定单复数形式。CDDA 的 JSON 字符串提取器对这类写法有专门处理详见后文第四节。三、第一步提取可翻译字符串生成翻译模板.pot翻译工作从生成翻译模板开始。在CDDA 仓库根目录执行# 在 Cataclysm DDA 根目录下 mkdir -p ./mods/demo/lang/po python3 ./lang/extract_json_strings.py -i ./mods/demo -o ./mods/demo/lang/po/demo.pot该命令扫描./mods/demo目录下所有.json文件提取全部可翻译文本汇总输出到./mods/demo/lang/po/demo.pot。.pot格式文件即翻译模板translation template它包含所有待翻译的原始语言文本此处为英文。3.1 提取器的真实参数源码级从源码看lang/extract_json_strings.py 实际注册的参数比文档示例更丰富参数完整形式作用-i--include_dir指定要扫描的 JSON 目录可多次追加-n--namePOT 包名写入Project-Id-Version头-r--reference参考/输出 POT 文件字符串会被追加写入该文件-X--exclude排除单个文件-x--exclude_dir排除整个目录-D--obsolete_paths标记废弃目录或文件对应字符串会被标注为[DEPRECATED]-v--verbose输出详细日志两个细节值得注意对应 lang/extract_json_strings.py 的入口校验脚本要求 Python3.7 或更高否则直接报错退出脚本内部通过-r/--reference指定的文件读取已有 POT 并追加新字符串write_to_pot以modea打开文件且要求该参考文件必须已存在lang/string_extractor/pot_export.py 中的sanitize()会对不存在的文件抛异常。因此若你当前仓库的脚本版本较新文档示例中的-o写法可能不再适用。更稳妥的可运行写法是先创建一个空模板再追加例如mkdir -p ./mods/demo/lang/po touch ./mods/demo/lang/po/demo.pot # 先建立空文件作为 reference python3 ./lang/extract_json_strings.py -i ./mods/demo -r ./mods/demo/lang/po/demo.pot -n demo如果你遇到报错Have to specify reference file path.即表明当前脚本需要上述-r形式。核心游戏的完整提取流程lang/update_pot.sh也是同样思路先用xgettext从 C 源码生成base.pot再把它作为 reference 传给提取脚本逐目录追加 JSON 字符串。3.2 生成的 .pot 文件结构示例模组生成的demo.pot大致如下msgid msgstr Project-Id-Version: None\n POT-Creation-Date: 1970-01-01 00:000000\n PO-Revision-Date: 1970-01-01 00:000000\n Last-Translator: None\n Language-Team: None\n Language: en\n MIME-Version: 1.0\n Content-Type: text/plain; charsetUTF-8\n Content-Transfer-Encoding: 8bit\n Plural-Forms: nplurals2; plural(n 1);\n #. ~ MOD name #: mods/demo/modinfo.json msgid Demo MOD msgstr #. ~ Description of MOD Demo MOD #: mods/demo/modinfo.json msgid This mod adds a book. msgstr #. ~ Item name #: mods/demo/modinfo.json msgid Guide to Translate a MOD msgid_plural copies of Guide to Translate a MOD msgstr[0] msgstr[1] #. ~ Description of Guide to Translate a MOD #: mods/demo/modinfo.json msgid A thin book teaching how to translate a mod. msgstr 逐字段理解与 lang/string_extractor/pot_export.py 的write_to_pot()输出逻辑一一对应头部元数据Project-Id-Version、POT-Creation-Date、Language、Plural-Forms复数规则等由sanitize()统一写入pot_export.py。#. ~注释行面向翻译者的提示例如MOD name、Description of MOD Demo MOD、Item name。这些注释来自提取器为不同 JSON 类型附加的说明如 lang/string_extractor/parsers/mod_info.py 中为MOD_INFO的 name 和 description 分别加上MOD name与Description of MOD name注释。#:引用行标注该字符串来自哪个 JSON 文件此处为mods/demo/modinfo.json便于翻译者定位上下文。msgid/msgid_plural原始英文文本及其复数形式。因为 JSON 中显式给出了str_pl提取器会生成复数条目若未显式给出提取器会默认追加s构成复数见 lang/string_extractor/write_text.py。msgstr/msgstr[0]、msgstr[1]译文占位模板中为空。四、第二步为每种目标语言创建 .po 翻译文件有了模板之后你可以把它上传到在线翻译平台供翻译者协作也可以在本机用msginit为某个语言初始化.po翻译文件。.po与.pot同为文本格式区别在于.pot是模板译文为空.po是针对某一语言的译文文件。以下示例为俄语ru创建翻译文件msginit -o mods/demo/lang/po/ru.po -i mods/demo/lang/po/translation.pot -l ru提示-i指定输入模板-l指定语言代码如ru、ja、es_ES、zh_CN。若你的模板文件名是demo.pot请将命令中的translation.pot替换为实际文件名。生成的ru.po形如msgid msgstr Project-Id-Version: None\n POT-Creation-Date: 1970-01-01 00:000000\n PO-Revision-Date: 1970-01-01 00:0000000\n Last-Translator: None\n Language-Team: Russian gnud07.ru\n Language: ru\n MIME-Version: 1.0\n Content-Type: text/plain; charsetUTF-8\n Content-Transfer-Encoding: 8bit\n Plural-Forms: nplurals3; plural(n%101 n%100!11 ? 0 : n%102 n %104 (n%10010 || n%10020) ? 1 : 2);\n #. ~ MOD name #: mods/demo/modinfo.json msgid Demo MOD msgstr fill in translations here #. ~ Description of MOD Demo MOD #: mods/demo/modinfo.json msgid This mod adds a book. msgstr fill in translations here #. ~ Item name #: mods/demo/modinfo.json msgid Guide to Translate a MOD msgid_plural copies of Guide to Translate a MOD msgstr[0] fill in translations here for the first plural form msgstr[1] fill in translations here for the second plural form msgstr[2] fill in translations here for the third plural form #. ~ Description of Guide to Translate a MOD #: mods/demo/modinfo.json msgid A thin book teaching how to translate a mod. msgstr fill in translations here4.1 复数规则Plural-Forms为何随语言变化注意俄语条目的Plural-Forms变成了nplurals3对应的msgstr也有 3 个槽位msgstr[0]、msgstr[1]、msgstr[2]而英文模板只有 2 个。这是因为 gettext 的复数机制由目标语言决定俄语有单数 / 少量2-4 / 多数三种形态因此游戏运行时调用TranslatePlural()时会根据实际数量n和当前语言的复数规则选择正确的译文见 src/translation_manager_impl.cpp。4.2 JSON 侧的字符串写法约定提取器对 JSON 字符串的处理逻辑集中在 lang/string_extractor/write_text.py 的write_text()模组作者在写 JSON 时应遵循这些约定翻译效果才会正确纯字符串name: Foo直接作为单数文本提取字典形式推荐用于物品/复数场景str单数形式str_pl显式复数形式不指定时提取器自动在str后加sstr_sp单复数同形时使用提取器会将其同时作为单复数explicit_plural标记见 write_text.pyctxt翻译上下文context用于区分同名但语义不同的字符串对应 GNU gettext 的 msgctxt//~给翻译者的注释会出现在.pot的#.行中跳过规则空文本、包含NO_I18N注释的文本、以及形如...的标签is_tag()判断如color_yellow这类颜色标签不会被提取见 write_text.py。此外如果文本中包含%占位符如%d、%s提取器会自动为其打上c-format标记write_text.py提示翻译者保留占位符结构。4.3 模组目录此时的结构完成多个语言的.po文件后模组目录结构如下文档示例demo ├── items.json ├── lang │ └── po │ ├── es_AR.po - Spanish (Argentina) translation │ ├── es_ES.po - Spanish (Spain) translation │ ├── ja.po - Japanese translation │ ├── ru.po - Russian translation │ └── translation.pot - translation template in English ├── modinfo.json └── your_mod_content.json五、第三步编译 .mo 并随模组发布翻译完成后需要把人类可读的.po编译成游戏可用的二进制.mo格式。文档以俄语为例mkdir -p mods/demo/lang/mo/ru/LC_MESSAGES/ msgfmt -o mods/demo/lang/mo/ru/LC_MESSAGES/demo.mo mods/demo/lang/po/ru.po.mo是 gettext 的二进制格式体积小、查找快。关键约定是目标目录必须形如lang/mo/语言代码/LC_MESSAGES/任意名.mo——游戏端正是靠LC_MESSAGES这个目录名来发现模组翻译并从路径中反推出语言代码的src/translation_manager_impl.cpp 的LanguageCodeOfPath()取/LC_MESSAGES之前、最后一个/之后的那段作为语言代码。5.1 发布时只需包含 .mo.pot和.po是作者与翻译者之间协作使用的中间产物随模组发布时只需打包.mo数据最终发布结构如下demo ├── lang │ └── mo │ ├── es_AR │ │ └── LC_MESSAGES │ │ └── demo.mo │ ├── es_ES │ │ └── LC_MESSAGES │ │ └── demo.mo │ ├── ja │ │ └── LC_MESSAGES │ │ └── demo.mo │ └── ru │ └── LC_MESSAGES │ └── demo.mo ├── modinfo.json └── your_mod_content.json当玩家以俄语运行 CDDA 时模组内的lang/mo/ru/LC_MESSAGES/demo.mo会被自动加载游戏中模组文本将以俄语显示。六、游戏端加载模组翻译的原理与验证6.1 加载与查找链路结合 src/translation_manager_impl.cpp完整链路如下扫描ScanTranslationDocuments()遍历用户模组目录与内置模组目录下所有LC_MESSAGES目录收集全部.mo文件按语言代码归类到mo_files[lang]加载SetLanguage()切换语言时将对应语言的所有.mo文件载入documents并为其内每条字符串建立哈希 → (文档, 索引)的查找表strings查询Translate()/TranslatePlural()/TranslateWithContext()通过LookupString()按哈希 strcmp双重校验命中后返回译文未命中则原样返回原文。其中Hash()采用经典的 djb2 变体hash hash * 33 c初值 5381见 translation_manager_impl.cpp整张哈希表以max_load_factor(1.0)配置translation_manager_impl.cpp兼顾了运行时查询性能。6.2 一个值得注意的细节TEST_DATA 模组被跳过LoadDocuments()中有如下逻辑translation_manager_impl.cpp非测试模式下路径中包含TEST_DATA的.mo文件会被跳过。这是因为仓库自带的 data/mods/TEST_DATA/lang 模组翻译含ru/LC_MESSAGES/TEST_DATA.mo及一个故意损坏的INVALID_RAND.mo主要用于自动化测试不应污染正常游戏会话。6.3 测试用例佐证仓库的 tests/translation_system_test.cpp 直接验证了这套机制TranslationDocument_loads_valid_MO正确加载./data/mods/TEST_DATA/lang/mo/ru/LC_MESSAGES/TEST_DATA.moTranslationDocument_rejects_invalid_MO加载损坏的INVALID_RAND.mo时抛出InvalidTranslationDocumentExceptionTranslationManager_translates_message加载该 MO 后Translate(battery)返回俄语译文而非原文。这组测试同时也说明一个格式损坏的.mo文件不会导致游戏崩溃只会被当作无效文档跳过模组作者在发布前可以用msgfmt -c严格校验模式自查译文文件。七、进阶复用核心游戏的翻译流水线除了文档给出的手工三步仓库还提供了一批可直接复用的脚本位于 lang/ 目录模组作者可借鉴其流程或直接在自己的模组目录里套用相同目录约定lang/update_pot.sh核心游戏的完整模板更新流水线。先用xgettext从src/*.cpp、src/*.h提取 C 侧字符串关键字覆盖_、_fmt、pgettext、n_gettext等再用 lang/extract_json_strings.py 以-i data -i data/json -i data/mods ...的方式追加 JSON 侧字符串最后用msgfmt -c做编译校验、用lang/unicode_check.py检查损坏的 Unicode 字符lang/compile_mo.sh批量编译脚本。支持./lang/compile_mo.sh all或指定语言参数为lang/po/*.po逐个生成lang/mo/lang/LC_MESSAGES/cataclysm-dda.mo模组作者可以按同样模式为自己的每个语言执行msgfmtlang/merge_po.sh将lang/incoming/中翻译平台回传的译文合并进lang/po/并用msgmerge --no-fuzzy-matching同步最新模板lang/strip_line_numbers.py剥离 POT/PO 注释中的行号减少翻译者看到的噪音lang/unicode_check.py检测模板中的异常 Unicode 符号。核心游戏使用lang/mo/语言/LC_MESSAGES/cataclysm-dda.mo的命名约定见 lang/compile_mo.sh 与ScanTranslationDocuments()中cataclysm-dda.mo的模式匹配而模组侧则不限.mo文件名——只要放在LC_MESSAGES目录下即可被扫描到这正是模组.mo可以命名为demo.mo的原因。八、常见问题与最佳实践命令报错Have to specify reference file path.当前仓库的 lang/extract_json_strings.py 要求以-r指定已存在的参考 POT 文件见第三节说明请先touch一个空.pot再执行或直接按 lang/update_pot.sh 的做法把已有 POT 作为 reference 追加。不要发布.po/.pot这些是协作产物玩家端只需要lang/mo/lang/LC_MESSAGES/*.mo多余的源文件会白白增加模组体积。语言代码必须与游戏一致目录语言代码如ru、es_ES、ja需要与游戏支持的代码匹配游戏通过路径解析语言代码translation_manager_impl.cpp拼写不一致会导致译文不加载。复数形式务必按目标语言填写不同语言复数槽位数量不同英文 2 个、俄语 3 个漏填或填错会导致复数场景回退到原文。发布前校验用msgfmt -c -o /dev/null your.po校验语法避免向玩家分发损坏的.mo损坏文件虽不会导致崩溃但该语言的模组翻译会整体失效参见第六节测试用例。善用 JSON 侧注释与上下文在 JSON 中通过//~给翻译者提供注释、用ctxt区分同名不同义文本、用str_sp处理单复数同形词可显著提升译文质量见 lang/string_extractor/write_text.py。至此从写一个带英文文本的 JSON 模组到俄语玩家看到俄语模组文本的完整闭环已经打通extract_json_strings.py生成模板 → 翻译者产出.po→msgfmt编译为.mo→ 随模组按lang/mo/lang/LC_MESSAGES/布局发布 → 游戏启动时ScanTranslationDocuments()自动发现并加载。把这套流程固化到你的模组开发模板中即可让模组受众覆盖 CDDA 的全部语言社区。【免费下载链接】Cataclysm-DDACataclysm - Dark Days Ahead. A turn-based survival game set in a post-apocalyptic world.项目地址: https://gitcode.com/GitHub_Trending/ca/Cataclysm-DDA创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考