☰
HTML Tidy 本地化实战指南:基于 poconvert.rb 的 PO/POT 翻译与多语言集成全流程
2026/10/16 9:09:26 网站建设 项目流程

【免费下载链接】foo_openlyrics

An open-source lyric display panel for foobar2000

项目地址:https://gitcode.com/gh_mirrors/fo/foo_openlyrics
点击查看免费下载

HTML Tidy 与libtidy在全球被广泛使用,但对非英语用户并不友好。为此 Tidy 项目为所有输出字符串建立了以language_en.h为唯一源头、以 gettext 风格 PO/POT 文件为可选翻译工作流的本地化体系。本文以 Tidy 官方本地化文档为主体,结合当前仓库中3rdparty/tidy-html5-5.8.0的完整源码,系统讲解 Tidy 本地化的设计原理、poconvert.rb全部命令用法、如何把一门新语言编译进 Tidy,以及如何通过-lang与 locale 环境变量进行验证——读完你即可独立完成"翻译 → 生成语言头文件 → 注册语言 → 构建测试"的完整链路。

一、本地化设计理念:为什么 Tidy 不直接用 gettext

HTML Tidy 的本地化围绕一个核心文件展开:3rdparty/tidy-html5-5.8.0/src/language_en.h。官方文档明确说明:没有这个文件 Tidy 根本无法工作,因此"所有语言本地化工作都从这一个文件起源"。

这里有一个看似矛盾的设计:Tidy 使用 gettext 兼容的 PO/POT 文件与工具,但Tidy 本身并不使用 gettext。原因在于 Tidy 的构建哲学是"在任何地方、用任何工具链构建"(build it anywhere and build it with anything),而gettext并非在所有平台上都可用,因此 Tidy 不能依赖它。取而代之的是:

  • 所有翻译在构建期直接编译进库(以及单体内构建的命令行可执行文件);
  • 运行时不需要 MO 文件(PO 经msgfmt生成的二进制)参与,任何通用操作系统上都能直接运行;
  • 若对体积敏感,可用-DSUPPORT_LOCALIZATIONS=NO构建开关轻松去掉额外语言支持(language.c与tidylib.c中所有本地化代码均以#if SUPPORT_LOCALIZATIONS包裹,见 language.c 与 tidylib.c)。

1.1 H 文件:真正的受控源码

语言本地化使用与language_en.h结构完全一致的 C 头文件,只是字符串不同。每个语言头文件包含:

  • 语言专属的复数形式函数whichPluralForm_<ll>(uint n);
  • 一个静态languageDefinition结构,内含以TIDY_LANGUAGE开头的全部字符串条目,并以TIDY_MESSAGE_TYPE_LAST结尾(参见 language.h 的枚举定义)。

1.2 PO/POT 文件:仅供翻译者使用的便捷层

为了照顾不习惯直接编辑 C 文件的翻译者,Tidy 源码附带 Ruby 脚本poconvert.rb,提供可选的 gettext PO/POT 工作流。请务必注意:PO/POT 文件只是为翻译者提供的便利,Tidy 的语言头文件才是受控的真源码;翻译好的 PO 必须转换回 H 文件格式才能被 Tidy 使用。

二、仓库中的本地化文件体系

当前仓库3rdparty/tidy-html5-5.8.0中本地化相关的文件布局如下:

类别路径说明
官方本地化指南README/LOCALIZE.md总入口,指向 localize 目录
完整操作手册localize/README.md本地化哲学、全部命令与最佳实践
转换工具localize/poconvert.rbRuby 编写的 POT/PO/H 互转工具(1276 行)
依赖清单localize/Gemfilethor、i18n、git三个 gem
头文件模板localize/language_ll_cc.h.erb生成语言头文件的 ERB 模板
翻译样例localize/translations/tidy.pot模板 + 7 个语言的 PO 文件
语言主文件src/language_*.hlanguage_en.h及 7 个已安装语言头文件

当前已随仓库提供的语言包括:en(内置基准)、en_gb、es、es_mx、pt_br、zh_cn、fr、de,对应的 PO 文件位于localize/translations/,H 文件位于src/。

2.1 对翻译者的三点关键差异

熟悉 gettext 的译者需要注意 Tidy PO 文件的三个特殊约定:

  1. msgctxt字段存放字符串标识符:Tidy 不使用msgctxt指向源码行号,而是存放字符串的标识符(如TidyAccessibilityCheckLevel)。因为 PO 格式不允许任意元数据,这是生成 H 文件所必需的。需要深入源码时,用msgctxt值做符号或字符串搜索即可定位上下文(见 translations/tidy.pot)。
  2. msgid字段是可丢弃的:Tidy 的语言工具不使用msgid值,它仅为翻译者便利而存在。正因如此,Tidy 的工具完全支持从非英语语言出发进行翻译。
  3. 不要用 gettext 官方工具处理 Tidy 的 PO/POT 文件(除非为其他项目复用字符串),所有工作流都应使用poconvert.rb完成。

三、翻译工作流:查找或创建翻译文件

3.1 目标语言已存在

若想翻译的语言已存在于localize/translations/(例如language_zh_cn.po),直接用你喜欢的 PO 编辑器打开开工即可。虽然普通文本编辑器也能编辑,但官方强烈建议使用专用 PO 编辑器,以免文件格式被破坏导致转换工具无法解析。

3.2 目标语言不存在

如果目标语言尚未翻译,有两条路径:

路径 A:手工创建

  1. 复制模板tidy.pot为language_ll.po(基础语言)或language_ll_cc.po(区域变体),其中ll为两位语言代码、cc为两位区域代码;
  2. 相应修改 PO 文件头部的Language:、Plural-Forms:等信息。

路径 B:用 poconvert.rb 生成

./poconvert.rb msginit --locale ll

其中ll是目标语言代码。该工具识别的区域与 gettext 的msginit相同;若指定语言不被识别,请改用上面手工创建的方法。更多选项可运行./poconvert.rb help查看。

命名规范:PO 文件一律命名为language_ll.po或language_ll_cc.po;Tidy不使用gettext 从 PO 生成的 MO 文件。

3.3 提交 Pull Request

翻译完成后即可向 Tidy 提交 PR(针对master分支):提交 PO 文件时,官方开发者会将其转换为 H 文件格式;若你直接修改了 H 文件则会直接合并。也欢迎自行完成全部转换、将新语言完整加入 Tidy 后整体提交 PR。推荐的 Git 操作顺序:

  1. Fork 仓库到个人账户;
  2. 建议创建主题分支(名称简短且能说明目的,如localize Portuguese);
  3. 按逻辑节点分次提交修改;
  4. 推送至个人账户;
  5. 创建 Pull Request;
  6. 关注评论与合并结果。

仓库约定:提交 PO 文件时只能提交msgid为英文的 PO——gettext 约定msgid仅使用英文,否则其他译者可能看不懂原字符串。poconvert.rb支持生成以其他语言为msgid的 PO(便于从非英语翻译,或从基础语言翻译区域变体,如从es翻译es_mx),这类文件仅供本地工作,提交前需转换回英文msgid的 PO(具体命令见下文"提交 PR 前的准备")。

四、把新语言编译进 Tidy:添加语言到源码

官方并不要求贡献者必须走完这一流程,但自行完成可以立即测试翻译效果,也为开发者团队节省步骤。完整流程如下:

4.1 生成并放置头文件

# 将 PO 转换为 Tidy 语言头文件(Windows 上需加 ruby 前缀) ./poconvert.rb msgfmt <path_to_your_file.po>

工具会在当前工作目录生成language_ll_cc.h,将该文件复制到src/目录。

4.2 修改 language.c 注册语言

编辑 src/language.c:

  1. 在#include区(约第 13-21 行,位于#if SUPPORT_LOCALIZATIONS内)加入新头文件;
  2. 找到约第 40 行开始的static tidyLanguagesType tidyLanguages结构,定位注释These additional languages are installed.,按相同格式把新语言追加到数组中(数组末尾必须是NULL,且该结构预设了 9 个语言槽位,见 language.c)。

4.3 构建并测试

按常规方式构建 Tidy,然后用-lang选项验证输出。从源码可看到该结构体初始时.currentLanguage与.fallbackLanguage均指向&language_en,而语言查找通过tidyTestLanguage()逐个比对每个语言头文件首条TIDY_LANGUAGE记录的代码字符串(见 language.c)。

五、最佳实践:继承机制与翻译约束

5.1 语言继承(Language Inheritance)

Tidy 会按"指定语言 → 基础语言 → 默认英语"逐级回退。例如把libtidy设为es_mx:若不存在则自动改用es;若es也不存在则继续使用当前语言。该逻辑在tidySetLanguage()中实现:同时探测ll_cc与ll两个代码,前者作为 currentLanguage、后者作为 fallbackLanguage(见 language.c)。注意方向性:es_mx缺失可回退es,但请求es时不会反向去选es_XX变体。

5.2 字符串继承(String Inheritance)

除语言级回退外,单个字符串也会回退。tidyLocalizedStringN()的查找顺序为:当前语言 → fallback 语言 → 内建en→ 内建en单数形式(见 language.c)。例如libtidy设为es_mx,某字符串在es_mx找不到就查es,再找不到就用英文原文。

5.3 基础语言优先,区域变体只写差异

正是因为有继承机制,官方希望先完成基础语言——这样区域变体只需翻译与基础语言不同的字符串,从而保持 Tidy 与libtidy体积精简。如果你在翻译区域变体(如us_CA),请只翻译与基础语言确实不同的字符串。poconvert.rb的--baselang参数正是为此设计(见下节)。

5.4 位置参数(Positional Parameters)

Tidy当前不支持位置参数(如%1$s),且鉴于 Tidy 大部分输出的特性,预期未来也不需要。翻译时请保证替换值的出现顺序与原字符串一致。

六、测试本地化输出

6.1 命令行选项

使用-lang(或-language)选项并指定 POSIX 或 Windows 语言名称。该选项必须放在命令行的最前面,因为控制台程序按"先入先出"(first-in, first-out)顺序解析并执行选项。从源码可见其处理逻辑:-lang help会打印语言帮助列表;tidySetLanguage()返回失败时输出"语言未找到"提示并回显当前语言(见 console/tidy.c)。

示例(与英文帮助文档中一致):

tidy -lang es -lang help

6.2 修改 shell locale

在 Unix/Mac 与 Linux 上可临时修改 shell 的 locale 来测试(把en_GB换成目标语言即可):

export LANG=en_GB export LC_ALL=en_GB

对应地,libtidy初始化时会依次尝试LC_MESSAGES与LANG环境变量来设置语言(见 tidylib.c)。

6.3 东亚语言

东亚语言(如中文、日文、韩文)在 Linux、macOS、Windows 上均已完整支持并测试通过。但 Windows 上需要把操作系统(而非控制台)区域设置为东亚语言环境,才能在 Windows Console 与 PowerShell 中正常显示;PowerShell ISE 则始终支持东亚语言,无需修改系统区域。

七、poconvert.rb 命令完全参考

poconvert.rb是 Tidy 源码自带的 Ruby 批处理脚本(1276 行),用于生成 POT、PO、H 文件并在三者之间互转,设计风格模仿 gettext 工具族,并额外支持以不同源语言工作。使用前先安装 Ruby 与依赖:

# 依赖为 Gemfile 中列出的包(thor、i18n、git) [sudo] gem install xxx # xxx 逐个替换 # 或安装了 Bundler 时直接: bundle install

两个重要特性必须牢记:

  1. 依赖当前路径:脚本依赖其所在位置;若移动脚本,必须同步修改脚本内的@@default_en(指向src/language_en.h)与@@header_template(指向language_ll_cc.h.erb)两个变量(见 poconvert.rb);
  2. 所有输出写入当前工作目录:防止意外覆盖重要文件。

各子命令详解如下(Windows 下请在命令前加ruby)。

7.1 创建新 POT:xgettext

./poconvert.rb xgettext

在当前目录生成一份全新的tidy.pot(源码虽已自带tidy.pot,但可自行再生成)。

7.2 以非英语 msgid 创建 POT

./poconvert.rb xgettext <language_cc_ll.h>

其中<language_cc_ll.h>是已存在的 Tidy 语言头文件路径。生成的tidy.pot以该语言的译文作为msgid,缺失处用英文兜底。这对区域变体翻译(如es→es_mx)很有价值。

7.3 从 H 转换出 PO:msgunfmt

./poconvert.rb msgunfmt <language_cc_ll.h>

从 Tidy H 文件生成全新 PO 文件。

7.4 以不同 msgid 语言转换 H 为 PO

./poconvert.rb msgunfmt <language_cc_ll.h> --baselang=<other-language_cc_ll.h>

生成 PO 时,未翻译字符串取自另一个语言(而不强制用英文)。

7.5 为特定区域创建空白 PO:msginit

./poconvert.rb msginit ./poconvert.rb msginit --locale=LOCALE

第一条命令尝试猜测当前区域,第二条使用指定区域(ll或ll_CC格式)。Tidy 只认识 gettext 所知的区域;若--locale不被识别,需要手工创建 PO 并自行修改区域设置。从源码可见其内部维护了一份完整的 locale 表(含语言名与复数公式,如中文ja/vi/ko为nplurals=1; plural=0;,见 poconvert.rb)。

若要使用其他 Tidy 语言的msgid字符串创建空白 PO:

./poconvert.rb msginit <language_cc_ll.h> [--locale=LOCALE]

7.6 生成 Tidy 语言头文件:msgfmt

./poconvert.rb msgfmt <language_cc_ll.po>

生成language_cc_ll.h(语言代码取自 PO 头部的Language:字段)。注意msgfmt输出的 H 文件以 UTF-8 BOM 开头,以满足 MSVC 编译要求(见 poconvert.rb)。

区域变体瘦身:生成区域变体时强烈建议排除父语言已有的字符串以减小库与可执行文件体积。例如es已有字符串 "archivo",es_mx就没必要再包含它:

./poconvert.rb msgfmt <language_cc_ll.po> --baselang=<other-language_cc_ll.h>

7.7 提交 PR 前的准备(非英文 PO 转回英文)

仓库只接受msgid为英文的 PO。两条命令即可完成转换:

./poconvert.rb msgfmt <language_cc_ll.po> ./poconvert.rb msgunfmt <language_cc_ll.h>

第一条把非标准 PO 转成 Tidy 语言头文件,第二条从刚生成的头文件再产出msgid为英文的全新 PO。

7.8 PO 与新版 POT 同步

当 Tidy 的 POT 变化(新增字符串、新注释等)时,最简单的更新方式是把 PO 先转为头文件(会按最新 Tidy 标准规范化),再转回新 PO:

./poconvert.rb msgfmt <language_cc_ll.po> ./poconvert.rb msgunfmt <language_cc_ll.h>

此外poconvert.rb还提供rebase [--sha=HASH]命令:在language_en.h变更后一次性重新生成全新 POT、为src/下每种语言重新生成 PO,再为每个新 PO 重新生成语言头文件,并借助--sha指定的提交 SHA-1 比对旧版language_en.h,将发生变化的条目在 PO 中标记为 fuzzy(见 poconvert.rb)。所有生成文件均输出到工作目录,不会覆盖源文件。

八、本项目中的应用背景

本仓库 foo_openlyrics(foobar2000 的开源歌词显示面板)将 HTML Tidy 作为第三方库引入,用于清洗抓取到的网页 HTML:在 src/sources/lyric_source.cpp 中创建TidyDoc、设置TidyXhtmlOut与TidyForceOutput,经tidyParseString→tidyCleanAndRepair→tidyRunDiagnostics→tidySaveBuffer完成解析与修复。Tidy 的本地化机制与该集成方式正交:多语言字符串随库整体编译,libtidy按环境 locale 或显式语言设置输出对应语言的诊断信息;本项目对应的静态库构建工程位于 openlyrics_build/tidy-static.vcxproj,其中包含对src/language.c与src/language_en.h的编译引用。

九、小结

HTML Tidy 的本地化体系可以概括为一句话:以language_en.h为唯一事实源,H 文件是编译期真正生效的语言载体,PO/POT 只是翻译者友好的中间格式。掌握poconvert.rb的xgettext/msginit/msgunfmt/msgfmt四类子命令,配合语言继承与字符串继承的规则,你既可以作为翻译者提交 PO 贡献,也可以作为集成者把新语言完整注册进language.c并构建验证——这正是 Tidy "build it anywhere" 哲学在多语言输出上的落地方式。

【免费下载链接】foo_openlyrics

An open-source lyric display panel for foobar2000

项目地址:https://gitcode.com/gh_mirrors/fo/foo_openlyrics
点击查看免费下载
上一篇:如何快速搭建Sunshine游戏串流:从零到客厅大屏的完整上手指南
下一篇:网盘直链解析怎么做?免费脚本帮你彻底告别下载限速

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询