ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PPSSPP 本地化工作流:`/add-string` 命令与 langtool 工具链详解

PPSSPP 本地化工作流:`/add-string` 命令与 langtool 工具链详解 PPSSPP 本地化工作流/add-string命令与 langtool 工具链详解【免费下载链接】ppssppA PSP emulator for Android, Windows, Mac, Linux and iOS, written in C. Want to contribute? Join us on Discord at https://discord.gg/5NJB6dD or just send pull requests / issues.项目地址: https://gitcode.com/GitHub_Trending/pp/ppssppPPSSPP 是一个以 C 编写的 PSP 模拟器其 UI 界面支持约 47 种语言全部维护在 assets/lang 目录下的 .ini 文件中。本文以官方斜杠命令/add-string及其背后的工作流文档 docs/translations.md 为主体结合 Rust 编写的 Tools/langtool 工具链源码完整讲解如何为一条 UI 字符串安全地新增或补全翻译从定位 C 调用点理解语义、构造临时翻译文件到用import-single写回、用validate校验占位符直至收尾报告的每一步实操。/add-string命令参数约定与前置校验/add-string是定义在.claude/commands/add-string.md中的斜杠命令frontmatter 声明了调用格式description: Translate a UI string into all the languages in assets/lang, using Tools/langtool argument-hint: Section Key [English string]三个参数按位置切分在 Claude Code 的约定中为$1、$2、$3参数含义校验要求$1Section目标 ini 文件中的[Section]名必须是 assets/lang/en_US.ini 中真实存在的 section$2Key要翻译的键名已存在 key 下必须存在新字符串则要求它在 C 代码中找得到但在任何语言文件中都不存在$3English string英文源字符串可为空。为空表示 key 已存在于en_US.ini任务只是补全尚未翻译的语言非空则连同 key 一起新建文档特别强调了一个防御性步骤位置切分只有在严格按/add-string Section Key [English string]调用时才正确。如果调用者传入的是一句话而非规范参数$1/$2可能只是恰好拆出来的三个任意单词。因此指令要求动手之前先校验$1不是en_US.ini里的真实 section、或$2在其下不存在时应回到原始诉求本身推断真实的 section 与 key说明自己最终采用了哪组值再继续——绝不能照搬位置切分碰巧产生的垃圾值去翻译。为什么这一步必须由人或能读代码的 Agent完成docs/translations.md 是整个翻译工作流的总纲其中两条原则贯穿/add-string的全过程翻译永远放在最后。实现新 UI 时先写好英文字符串、把功能做到可用并提交之后停下来请用户确认英文措辞再开始翻译。英文字符串是所有约 47 种语言的派生源事后改动英文意味着整轮翻译返工。不要手工编辑约 47 个语言文件也不要跑 langtool 自带的 AI 命令add-new-key-ai、add-new-key-value-ai、finish-language-with-ai。理由是AI 命令使用固定 prompt 批量翻译而人/Agent 能够阅读 C 调用点工具自身的固定 prompt 做不到这一点——让 AI 做文件手术人来把握语义。因此/add-string工作流的第一步是翻译前弄清字符串的真实含义文档列出了四个具体的调查动作在 C 中 grep 该 key 的调用点它是什么控件按钮、复选框标签、tooltip 还是错误消息该调用点上%1/%d占位符最终会被替换成什么内容UI 给这个字符串的空间有多大——翻译太长会不会被截断同 section 下相邻的 key 在各语言中是怎么措辞的各语言文件已经做出的风格选择正式程度、术语、英文技术词保留还是翻译就是你的风格指南照它执行。langtool 的 AI 路径其实也部分意识到了上下文的重要性从 main.rs 中finish-language-with-ai组装的 prompt 可以看到它会附带同一 section 中已翻译的字符串作为上下文并要求翻译结果不超过约 60% 的长度增幅见 main.rs 的generate_prompt。但这些 prompt 是写死的无法回答这个 key 在 C 里被谁使用、占位符实际替换什么——这正是本工作流要求人工先调查、并把结论简要陈述后再动手的原因。第二步构造仓库外的临时翻译文件调查完成后把翻译写进一个放在仓库之外的临时文件scratch file格式固定如下[Single] en_US Test string sv_SE Teststräng lt-LT Testeilutė规则的每一条都有明确的工具侧原因见 main.rs 对[Single]section 的解析逻辑一个语言一行行名取 ini 文件名去掉扩展名sv_SE、lt-LT、he_IL_invert、zh_TW等。工具遍历assets/lang下每个语言代码.ini时用去掉.ini的文件名lang_id到Singlesection 里找对应行找不到就跳过该语言并打印No lang_id ... in single section。en_US行承载英文源字符串本身。对新建 key这一行就是让 key 出现在en_US.ini的方式。对已存在的 key这一行必须与现有英文文本逐字符一致import-single对en_US.ini的写入方式与其他语言完全相同是覆盖式的——任何不经意的措辞改动都会静默改变所有其他语言所基于的源字符串。文档要求在写入后 diff 一次en_US.ini确认它没有漂移。行尾不要写# 注释否则注释内容会被当作翻译的一部分写进去工具的解析器按#拆分注释见 section.rs 的split_comment。%1、%d等占位符必须原样保留位置可以按目标语言语法调整工具允许占位符重排但不允许丢失或变形详见下一节的 validate 规则。不够自信的语言直接省略。缺失的语言在运行时自动回退到英文字符串这是正常且被接受的行为远好于提交一个自信但没有母语者能审阅的猜测翻译。刻意保留英文的情况另当别论。某些语言本就直用英文如 Vsync 这类术语此时要把英文原文写上工具检测到某语言的值与en_US行完全相同时会自动为该值附加# same as English注释SAME_COMMENT常量见 section.rs此后 langtool 的 AI 命令在每次运行时都会跳过这类行不再重复付费翻译。marked_same的判断是宽容的# same、# same as English, checked by xxx都能识别section.rs如果你不认可某行的该标注删掉注释即可让它下次重新参与翻译。第三步import-single把翻译写回所有语言文件在Tools/langtool目录下执行cargo run -- import-single scratch-file $1 $2从 main.rs 的ImportSingle分支可以看到它的精确行为只读取临时文件的[Single]section临时文件缺少该 section 时直接报错退出不做任何写入对每个语言文件包括en_US.ini若目标 key 尚不存在通过insert_line_if_missing插入并在 section 内找一个近似字母序的位置落位section.rs 中的实现若 key 已存在则用set_value整体覆盖——所以文档警告若该 key 已有翻译尤其是人工翻译先看清你要替换掉什么注释标记规则en_US.ini本身不加注释它不是翻译是源非参考语言中值与英文相同者标# same as English其余标# AI translated。两个硬性前提值得记住目标 section 必须已经存在工具不会替你新建 section缺失时打印No section ...并跳过以及覆盖语义决定了它对已有翻译的 key并不温柔。另外工具处理语言的根路径是相对Tools/langtool硬编码的../../assets/langmain.rs所以必须在该目录下cargo run。第四步validate校验必须打印Found 0 problems.收尾一步永远执行cargo run -- validate它检查assets/lang下每一个语言文件中每一条翻译与其英文源字符串的一致性全部通过时打印Found 0 problems.否则逐条打印语言 [Section] key: 问题及英文/译文对照并以非零退出码结束因此可以直接用作 CI 或脚本里的检查项main.rs。具体检查逻辑在 validate.rs 中占位符存活用正则%[0-9sdf]提取两边的占位符——%1–%9是位置占位符%s/%d/%f是 printf 风格占位符——排序后比对。占位符重排是合法的不少语序不同的语言需要但丢失、数量不符、甚至数字被本地化如波斯文用 ۱ 替换 1都会报错。单元测试中引用的都是真实历史事故ko_KR曾丢失占位符、fa_IR曾把占位符数字本地化、km_KH曾在%与数字之间混入空格validate.rs。而100%这类非占位符的百分号不受影响。空翻译trim 后为空直接报Empty。换行翻译中不允许含\n/\r对应工具里的RemoveLinebreaks修复命令。引号包裹AI 常见的Grafik或Grafik式包裹会被Quoted拦下。Untranslated仅 AI 路径检查与英文原文完全相同的翻译只在check_ai_translation中被拒绝用于防止 AI 把英文原样回传还被打上已翻译标记普通validate则放行因为大量字符串本就该两语相同。同样的校验也会在 AI 产出写入文件之前先行执行main.rs。收尾报告、未提交与copy-missing-lines按命令文档的收尾要求报告你翻译了哪些语言、跳过了哪些、以及为什么跳过——跳过应只出现在不确信该语言的情形下改动保持未提交状态留待人工审查除非被明确要求提交。这一点与 docs/translations.md 的流程一致新 UI 的翻译应在功能提交之后单独成 commit如果希望被跳过的语言显示英文字符串作为可见占位而不是运行时静默回退运行cargo run -- copy-missing-lines对应 main.rs 的copy_missing_lines它从参考文件en_US.ini向每个语言文件补齐缺失的整个 section 或缺失的行默认还会把参考文件中已不存在的过期行注释掉加--dont-comment-missing可关闭该行为。其他机械操作也应交给 langtool/add-string工作流只覆盖翻译一条字符串而 main.rs 的Command枚举列出了 langtool 的全部子命令。除import-single/validate/copy-missing-lines外常见的机械整理操作有命令用途add-new-key Section Key/add-new-key-value Section Key Value新建 key不带 AImove-key OldSection NewSection Key/copy-key .../dupe-key ...跨 section 移动、复制 keyrename-key Section Old New重命名 keysort-section Section对 section 内行排序remove-key Section Key删除 keyget-new-keys列出参考文件有、语言文件缺的 keylist-unknown-lines/comment-unknown-lines/remove-unknown-lines处理语言文件中参考文件里不存在的行remove-linebreaks/remove-ampersands/apply-regex针对单条 key 的批量清洗finish-language-with-ai 语言用 LLM 补全整个语言文件的未翻译项需 API key且本工作流刻意不用它来翻译单条字符串其中 AI 相关命令支持两个后端设置ANTHROPIC_API_KEYClaude或OPENAI_API_KEYOpenAI双设时默认 Claude可用--provider openai切换、--model指定模型详见 Tools/langtool/README.md。适用前提与限制需已安装 Rust 工具链且必须在Tools/langtool目录下运行cargo run -- ...因为语言文件根目录是相对该目录的../../assets/langimport-single要求目标 section 已存在于所有语言文件不会自动建 section临时文件必须含[Single]section行名必须是assets/lang中真实存在的语言代码即 ini 文件名去扩展名否则该语言被静默跳过本工作流刻意绕开 langtool 的 AI 翻译命令其设计前提是执行者能阅读 C 调用点若只是批量补全某个语言的大量旧缺口finish-language-with-ai才是对应的工具。整套流程的核心可以概括为一句话语义由读代码的一方负责文件改写由工具负责占位符正确性由validate兜底——三者分离让约 47 个语言文件的每一次单条变更都可复现、可 diff、可校验。【免费下载链接】ppssppA PSP emulator for Android, Windows, Mac, Linux and iOS, written in C. Want to contribute? Join us on Discord at https://discord.gg/5NJB6dD or just send pull requests / issues.项目地址: https://gitcode.com/GitHub_Trending/pp/ppsspp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表