结论:这套 UI 翻译构建链(update_translations → extract_strings → POT/PO → Multilang)目前只扫描四组 Python 目录,而 mici 不在其中:扫描范围为 70/125 个 Python 文件,脚本现行范围与 app.pot 都含 199 个唯一 msgid,全 UI 范围是 217 个,新增的 18 个唯一 msgid 全部来自未扫描的 mici,12 份现有语言文件对这 18 个均为 0/18。缺 msgid 不会崩溃,但非英语模式会静默显示英文。本文为固定源码快照的构建时静态分析,未运行会改写归档的更新脚本,未做实屏语言测试。
扫描范围与 AST 提取
update_translations() 遍历 SYSTEM_UI_DIR 以及 UI_DIR/widgets、layouts、onroad,收集 .py 相对路径,提取字符串后改写 app.pot,再逐语言 merge 或 init。mici、body 和 UI_DIR 根目录不在扫描集合内。extract_strings() 用 ast.walk 找名称或属性名为 tr、trn、tr_noop 的调用,只读取前一或两个位置参数为字符串常量的调用,按 msgid 去重并记录 file:line,解析失败的文件直接跳过。
| 函数/组件 | 输入、输出与关键行为 |
|---|---|
update_translations() |
遍历四组目录,改写 app.pot,逐语言 merge 或 init;mici/body 不在扫描集合内 |
extract_strings() |
ast.walk 找 tr/trn/tr_noop 调用,仅取位置参数字符串常量,按 msgid 去重 |
generate_pot() |
为提取结果写最小 POT 头部;本身不检查源文件覆盖率或占位符 |
parse_po() |
解析头部、单复数条目、续行、注释、引用与 flags;跳过 #~ 过时条目 |
write_po() |
引用从 file:line 折叠到文件名;不输出 flags,故 python-format 不会保留 |
merge_po() |
按 msgid 匹配旧译文,更新引用与注释,删除 POT 不再存在的条目,按 msgid 排序写回 |
Multilang.trn() |
运行时另行解析 PO;单数缺译回退原文,复数缺槽或空译文回退英文单复数 |
generate_pot() 只写最小 POT 头部,本身不检查覆盖率或占位符;parse_po() 也不是完整的 gettext 元数据解析器,它会跳过 #~ 过时条目。构建器与运行时共享文件格式,却不是同一个解析器:load_translations() 与 Multilang.trn() 在运行时另行解析 PO。
已核对的五条构建边界
第一,目录漏扫会导致静默英文回退。mici 的离车告警布局使用了 tr("no alerts"),但 update_translations() 未扫描 mici,当前 app.pot 及中文语言文件均无该 msgid;某些其他 mici 字符串因大屏目录使用同一原文而碰巧出现在 POT 中,不能据此认为 mici 已被完整覆盖。
第二,单复数同键冲突依赖遍历顺序。extract_strings() 只以单数 msgid 为键:若 tr("same") 先于 trn("same", "many", n),生成项仍是单数,反过来则为复数;属性调用仅按属性名匹配,并不确认调用目标就是翻译函数。第三,合并不做形态迁移:原 PO 的单数 msgstr 若遇到新 POT 同 msgid 的复数项,merge_po() 会更新 msgid_plural 却不生成复数槽,运行时 trn() 因缺译回退英文。
第四,新条目与已有语言的复数槽不按语言初始化:extract_strings() 给 trn 固定生成索引 0/1 两槽,init_po() 只在新建语言文件时按 nplurals 调整,已存在 PO 经 merge_po() 接收新复数条目时直接接收两槽,乌克兰语第三槽不会自动补齐。第五,msgid 是无上下文的全局 key,没有 msgctxt、插件命名空间或相同英文不同语义的分离机制,write_po() 又不保留 flags。
| 边界 | 触发条件 | 可见后果 |
|---|---|---|
| 目录漏扫 | 字符串位于未扫描目录(如 mici) |
非英语模式静默显示英文 |
| 同键形态冲突 | 单数与复数使用同一 msgid |
结果取决于遍历顺序 |
| 合并不做形态迁移 | 旧 PO 单数遇到新 POT 复数项 | 复数槽为空,运行时回退英文 |
| 复数槽不按语言初始化 | 已有 PO 接收新复数条目 | 多复数语言第三槽缺失 |
| 无上下文 key | 同一英文在不同语义下复用 | 无法分离语义,flags 不保留 |
覆盖率实测
用仓库的 extract_strings() 对 update_translations() 指定的四组目录,以及 selfdrive/ui 加 system/ui 的全部 Python 文件分别提取,并与当前 app.pot 的 parse_po() 结果按 msgid 比较:扫描范围是 70/125 个 Python 文件,脚本现行范围与 POT 都有 199 个唯一 msgid,全 UI 范围为 217 个。这证明的是当前提取器能识别的常量覆盖差值,不是所有动态文案、上下文语义或屏幕翻译率。
| 页面组件 | 缺失 key 数 | 可核查的例子 |
|---|---|---|
| 离车告警页 | 1 | 空状态 no alerts |
| 设备配对页 | 2 | Wi-Fi 连接与设备注册提示 |
| Firehose 页 | 12 | FAQ 标题与问答、ACTIVE/INACTIVE 状态和贡献片段复数文案 |
| eSIM 页 | 1 | 网络错误提示,同一 msgid 在两处使用 |
| 软件更新页 | 2 | Wi-Fi 要求与分支列表获取失败提示 |
TestTranslations 会检查语言文件存在、非空译文占位符、引用不含行号和疑似实体拼写,但不检查 AST 覆盖率、POT/PO 同步或单复数形态迁移,因此它无法捕捉漏提取的字符串或错误的调用归属。
插件资源边界与迁移顺序
显示硬件固件不应依赖插件侧任意 PO 直接覆盖宿主告警。更合适的边界是宿主登记 plugin_id + key + source + plural_forms + placeholders,构建期从已登记插件源码目录提取并做覆盖率/重复键/占位符/复数槽检查,再由宿主决定语言回退和安全告警优先级;第三方资源只扩展其命名空间,不能修改接管、失联和制动类文案。迁移顺序上,先补扫描清单与漏提取测试,再定义翻译资源清单和命名空间;之后让运行时按插件 ID 查找,最后逐步迁移现有 tr() 调用。旧 msgid 资源可作为过渡映射,但要报告歧义,不能静默覆盖。
常见问题
新增一个 tr() 字符串为什么没进翻译文件?
先确认所在目录是否被扫描。update_translations() 只遍历 SYSTEM_UI_DIR 与 UI_DIR 下的 widgets、layouts、onroad,mici、body 和 UI_DIR 根目录不在集合内;此外 extract_strings() 只接受位置参数为字符串常量的调用,属性调用也仅按属性名匹配。
为什么非英语界面会显示英文?
Multilang.tr() 与 trn() 在缺项时使用英文源文案,因此缺 msgid 或译文为空时页面不会崩溃,只是回退英文。实测 12 份现有语言文件对 mici 新增的 18 个唯一 msgid 均为 0/18,这类页面正是静默回退的典型。
同一个英文在单数和复数里会怎样?
extract_strings() 只以单数 msgid 为键,若 tr("same") 先于 trn("same", "many", n) 出现,生成项仍是单数,反之则为复数,结果依赖遍历顺序。并且 merge_po() 遇到旧单数变新复数时只更新 msgid_plural 而不生成复数槽,运行时仍回退英文。
插件能直接用自己的 PO 覆盖界面文案吗?
不应如此。更合适的边界是宿主登记 plugin_id、key、来源、复数形态与占位符,构建期从已登记插件源码目录提取并做覆盖率/重复键/占位符/复数槽检查,由宿主决定语言回退与安全告警优先级,第三方资源只扩展自身命名空间。
延伸阅读
- UI 运行时与界面状态边界 —— 承接本文构建期翻译资源在运行时的消费位置。
- 一套令牌覆盖 5 种分辨率:车载 HUD 从 456×280 到 1280×720 的适配工程 —— 说明多语言文案在分辨率适配下的排版约束。
- 52 张预览自动校验:五屏 HUD 怎么把「文字出界」变成一条断言 —— 把翻译文本出界变成可自动校验的断言。
- 座舱预览与驾驶员监测音画生命周期 —— 对照座舱 UI 资源在生命周期里的加载与回退。
参考资料
- 项目 02 ——
openpilot-research/UI_TRANSLATION_BUILD_PIPELINE_DEEP_DIVE.md(取用了扫描范围、AST 提取、POT/PO 生成合并、五条构建边界与覆盖率实测)。 - 项目 02 ——
openpilot-research/UI_LANGUAGE_TEXT_LAYOUT_DEEP_DIVE.md(仅用于确认运行时语言切换与字体排版的分工位置)。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11
发表回复