news 2026/10/9 2:19:03

i18n-calypso-cli 实战指南:从 JavaScript 源码自动提取 WordPress/GlotPress 翻译文件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
i18n-calypso-cli 实战指南:从 JavaScript 源码自动提取 WordPress/GlotPress 翻译文件
  • 前端
  • CMS

【免费下载链接】wp-calypso

The JavaScript and API powered WordPress.com

项目地址:https://gitcode.com/gh_mirrors/wp/wp-calypso
点击查看免费下载

i18n-calypso-cli是 wp-calypso 仓库(packages/i18n-calypso-cli)中负责国际化字符串提取的命令行工具:它扫描 JavaScript/JSX/TypeScript 源码,将代码中的translate()调用统一提取为可供 GlotPress 插件消费的 POT 文件或 WordPress 风格的 PHP 翻译文件。本文将从安装、CLI 用法、编程 API 三个层面展开,并结合源码解析其解析器配置、三种输出格式器、行号过滤与内置 extras 机制的实现细节,帮助你把它接入自己的前端项目,实现“前端源码 → 翻译模板 → 服务器端翻译”的完整链路。

工具定位:它解决什么问题

WordPress.com 的 Calypso 前端代码使用i18n.translate()这类调用标记所有需要翻译的字符串,但 GlotPress 服务器端只能识别.pot(Gettext PO 模板)或 WordPress 风格的 PHP 翻译调用(__()、_n()、_x()、_nx())。i18n-calypso-cli正是两者之间的桥梁——如 README.md 所描述:

Scans your JavaScript sources/build files and generates a POT file or a PHP translation file which can be understood by the GlotPress plugin.

它做的事情本质上就是“静态扫描 + 格式转换”:不执行你的源码,而是用 xgettext-js 将源码解析成 AST,找出所有翻译方法调用,再按目标格式输出。包以 GPL-2.0-or-later 协议发布(见 LICENSE.md),通过bin字段暴露i18n-calypso命令(package.json)。

安装

作为独立 npm 包安装到当前项目:

yarn add i18n-calypso-cli

包依赖commander、debug、globby与xgettext-js(见 package.json),globby用于把传入的 glob 输入模式展开成真实文件列表,xgettext-js负责源码解析。

使用 CLI 提取翻译

README 给出两种典型用法。第一种是全局安装后直接调用命令:

yarn global add i18n-calypso-cli i18n-calypso -i <input_file> -o <output_file> -f <format:POT|PHP>

例如:

i18n-calypso -o ./outputFile.pot -i ./inputFile.js -i ./inputFile2.js

完整命令行参数

基于 cli.js 的 commander 配置,i18n-calypso支持以下选项:

参数说明默认值
-i, --input-file <filename>待扫描的源文件,可重复传入多次(内部用collect累积为数组);也可在命令末尾直接以位置参数传入必填,无默认
-o, --output-file <file>输出文件路径不传时结果打印到 stdout
-f, --format <format>输出格式:php或potpot
-k, --keywords <keyword,keyword>需要识别的翻译函数名(逗号分隔)translate
-p, --project-name <name>项目名,用于自动生成的头部元信息空
-e, --extra <name>额外注入的字符串类型,目前仅支持date无
-l, --lines-filter <file>JSON 文件,按文件+行号过滤,只保留指定行的匹配无
-a, --array-name <name>PHP 输出中承载方法调用数组的变量名projectName + '_i18n_strings'

命令行细节值得注意:

  • 未提供任何输入文件时会抛出Error: You must enter the input file. Run i18n-calypso -h for examples.(cli.js);
  • 输入路径会先交给globby.sync()展开 glob(例如client/**/*.js),展开后的路径若不存在会在 stderr 打印Error: inputFile, ... does not exist,但不会中断(cli.js);
  • -l指定的 JSON 文件键是源码相对路径、值是行号数组,键还会被转换为相对于cli.js所在目录的路径(cli.js);
  • 传入-o时成功会打印Done.,未传则把生成内容打印到控制台(cli.js)。

在代码中以 API 方式调用

除 CLI 外,也可以把它作为模块在构建脚本或 Node 工具链中使用,README 给出了最小示例:

const i18nCalypso = require( 'i18n-calypso-cli' ); i18nCalypso( { inputPaths, // <paths to your js files to scan>, output, // <path to your destination> format, // <format of the output: POT, PHP or JSON> projectName, // <Meta information about the project which can be used for autogenerated headers> } );

入口实现见 index.js,其完整配置项比 README 示例更丰富:

  • keywords:翻译函数名数组,默认[ 'translate' ],可自定义为项目自己的函数名;
  • data或inputPaths:二者必填其一,否则抛出Must provide input data or inputPaths。data模式下把字符串直接交给解析器,匹配位置标记为<unknown>(index.js);
  • extras:额外字符串数组,见下文“内置 extras”小节;
  • lines:行号过滤对象(对应 CLI 的-l);
  • phpArrayName:PHP 输出数组名;
  • textdomain:PHP 输出中附加的文本域参数;
  • format:pot(默认)或php;formatters表定义在 formatters/index.js,README 中提到的JSON格式目前并未在格式器表中注册,实际可用的是 POT 与 PHP;
  • copyrightNotice、potHeader、projectBugsUrl:用于定制 POT 头部;
  • 返回值是生成的字符串;传入output时同步写入文件(index.js)。

解析器配置:现代 JavaScript 语法全覆盖

index.js 中,xgettext-js的解析器启用了大量 Babel 风格插件:asyncFunctions、classProperties、dynamicImport、exportDefaultFrom、exportExtensions、exportNamespaceFrom、jsx、objectRestSpread、trailingFunctionCommas、typescript、nullishCoalescingOperator与optionalChaining,并开启allowImportExportEverywhere。

这意味着该工具可以解析现代前端工程中的常见语法:JSX 组件内嵌的翻译、export default模块、动态import()、TypeScript 文件、可选链a?.b与空值合并a ?? b等——后两项正是 CHANGELOG 中「unreleased」条目明确新增的能力(CHANGELOG.md)。

匹配预处理:字符串拼接、模板字符串与复数

每个匹配的translate()调用会先经过 preprocess-xgettextjs-match.js 归一化,该模块负责:

  • 多段字符串拼接:"A long string " + "broken up over multiple lines"这类+连接的 BinaryExpression 会被递归拼接为单个字符串(concatenateBinaryExpression);
  • 引号与转义归一化:makeDoubleQuoted()把单引号、双引号与反引号模板字符串统一转换为 PHP 可消费的双引号形式,并对\和"做转义;
  • 模板字符串:TemplateLiteral 直接提取第一个quasi的原始值;
  • 复数默认 count:只要存在plural字段就强制finalProps.count = 1——源码注释说明服务器端只关心字符串是否注册到 GlotPress,真实 count 由客户端决定展示哪个复数形态(preprocess-xgettextjs-match.js);
  • d3 冲突防御:若single字段为空(如 d3 库自己的translate()方法)则返回false跳过该匹配(preprocess-xgettextjs-match.js)。

两种输出格式深度解析

POT 格式器

formatters/pot.js 生成标准 Gettext PO 模板,结构如下:

# THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY. msgid "" msgstr "" "Project-Id-Version: _s <projectName>\n" "Report-Msgid-Bugs-To: <projectBugsUrl>\n" "POT-Creation-Date: <ISO时间>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2014-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" #: test/examples/i18n-test-examples.jsx:9 msgid "My hat has three corners too." msgstr "" msgctxt "verb" msgid "post" msgstr ""

格式器要点(结合 formatters/pot.js):

  • 每条匹配输出#: 文件:行号位置注释、可选的#. 译者注释、可选的msgctxt上下文、msgid与(复数时)msgid_plural/msgstr[0]/msgstr[1];
  • 去重与聚合:以msgid + 上下文作为唯一标识,同一字符串的多个出现位置会合并到一条#:注释中;单独出现的单数形式会被合并进已有的复数条目(#: ... #: ... #. Second ocurrence\nmsgid "My hat has three corners."正是这一行为的测试用例,见 test/i18n.js);
  • 头部可用potHeader整体覆盖,copyrightNotice会以#前缀逐行写入文件头部。

PHP 格式器

formatters/php.js 生成可直接被 WordPress/GlotPress 加载的 PHP 文件,把 JS 侧的translate()调用映射为 WP 翻译函数,映射规则在getGlotPressFunction()中定义(formatters/php.js):

JS 调用形态生成的 PHP 函数
仅单数字符串__( "..." ),
单数 + 复数_n( "single", "plural", 1 ),
单数 + 上下文_x( "single", "context" ),
单数 + 复数 + 上下文_nx( "single", "plural", 1, "context" ),

输出文件骨架为:

<?php /* THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY. */ $<arrayName> = array( __( "My hat has three corners." ), // test/examples/i18n-test-examples.jsx:6 /* translators: draft saved date format, see http://php.net/date */ __( "g:i:s a" ), _n( "single test", "plural test", 1 ), _x( "post", "verb" ), ); /* THIS IS THE END OF THE GENERATED FILE */

细节包括:

  • 数组名默认取projectName + '_i18n_strings',可用phpArrayName/-a覆盖(formatters/php.js);
  • textdomain存在时追加为函数第二/第四参数,并转义其中的双引号('", "' + textdomain.replace( /"/g, '\\"' ),见 formatters/php.js);
  • 带注释的翻译前会输出/* translators: ... */,且会把注释里的*/转义为*\/,防止译者注释意外截断 PHP 代码(formatters/php.js);
  • 每条调用尾部追加// 文件:行号便于回溯。

79 列换行算法

POT 格式要求单行不超过 80 字符,formatters/multiline.js 负责把长字符串按MAX_COLUMNS = 79拆行:优先在行内向左侧寻找空格/,;等分隔符断行,找不到则向右找;若整行没有任何分隔符(单个超长词)则保持不拆;换行符统一转为\n字面量。长字符串会生成形如"第一段 "\n"第二段"的多行拼接形式。

行号过滤:只提取你关心的代码行

-l, --lines-filter提供精确控制能力。过滤文件是一个 JSON 对象,键为源码相对路径,值为行号数组:

{ "client/my-sites/example.js": [ 12, 47, 103 ] }

机制如下(index.js):每个匹配携带文件:行号定位信息,过滤时按:拆开,仅当文件名存在于过滤对象且行号命中数组时才保留该匹配。此特性适合只对改动文件、或只对特定代码片段做增量提取,避免每次全量扫描造成重复条目。

内置 extras:date 时间与数字格式字符串

-e date(或配置extras: [ 'date' ])会把 extras/date.js 中的预置翻译字符串合并进输出,内容分两类:

  • 日期/时间相对表述:in %s(上下文future time)、a few seconds、a minute、%d minutes、%d hours、%d days、a month、%d months、a year、%d years;
  • 数字格式:number_format_thousands_sep与number_format_decimal_point(均带/* translators: */注释,指向http://php.net/number_format的$thousands_sep与$dec_point参数)。

这些字符串服务于前端相对时间与数字本地化(如 Moment.js 时间差文案)。实现上,extras 文件会走与普通源码相同的解析管道(index.js),因此同样支持多文件聚合与去重。测试中对这一行为有明确断言(test/i18n.js)。

源码支持的翻译调用形态一览

测试示例文件 test/examples/i18n-test-examples.jsx 汇总了工具支持的全部调用形态,可作为接入时的语法速查表:

形态示例
最简字符串i18n.translate( 'My hat has three corners.' )
original对象键i18n.translate( { original: '...' } )
单数/复数对象i18n.translate( { original: { single: '...', plural: '...', count } } )
模板字符串i18n.translate( \My hat has six corners.` )`
上下文i18n.translate( { original: 'post', context: 'verb' } )
位置参数 + 上下文i18n.translate( 'post2', { context: 'verb2' } )
译者注释i18n.translate( { original: 'g:i:s a', comment: 'draft saved date format' } )
sprintf 命名占位i18n.translate( { original: 'Your city is %(city)s...', args: { city, zip } } )
新复数语法i18n.translate( 'single test', 'plural test', { count: 1 } )
+拼接多行字符串"A long string " + 'and mixed quotes'
字面量字符串键{ 'context with a literal string key': ... }风格选项
Unicode 转义'This is how the test performed\u2026'

验证与测试

仓库自带完整测试套件(test/i18n.js,README 中引用的test/index.js在仓库中实际对应此文件),覆盖 POT 与 PHP 两条链路的断言:

  • POT:默认头部字段齐全、单数与复数条目生成、单复数合并、上下文、译者注释、行号、多文件聚合、+拼接、模板字符串、Unicode 转义、数字格式 extras;
  • PHP:以<?php开头、数组名自定义、四种 WP 函数映射、注释与行号、textdomain(含双引号转义场景)。

运行方式(包内 jest 配置见 jest.config.js):

yarn workspace @automattic/i18n-calypso-cli test

典型接入流程

把上述能力串起来,一个完整的“前端源码 → 翻译文件”接入流程如下:

  1. 前端统一使用i18n.translate()(或通过-k自定义函数名)标记字符串,支持 JSX、TS、模板字符串等现代语法;

  2. 构建/发布前执行:

    i18n-calypso -i "src/**/*.{js,jsx,ts,tsx}" -o ./i18n/languages/calypso.pot -f POT -p my-project -e date

    生成 POT 模板供 GlotPress 导入词条;

  3. 若需把翻译随服务端渲染下发,改用 PHP 格式:

    i18n-calypso -i "src/**/*.{js,jsx}" -o ./i18n/languages/calypso-strings.php -f PHP -p my-project -a i18n_strings --textdomain my-domain
  4. 只做增量更新时,配合-l lines.json按文件行号过滤,避免重复条目。

小结

i18n-calypso-cli是一个聚焦单一职责、设计精巧的翻译提取工具:CLI 与编程 API 双入口、现代语法全覆盖的 xgettext-js 解析配置、POT/PHP 双格式输出、按行号精确过滤以及可扩展的 extras 机制,共同支撑 wp-calypso 这样大型前端项目的持续国际化。通过 cli.js、index.js 与 test/i18n.js 三份关键文件,你可以快速定位其行为细节,并参考测试用例把同样的提取管线复用到自己的工程中。

  • 前端
  • CMS

【免费下载链接】wp-calypso

The JavaScript and API powered WordPress.com

项目地址:https://gitcode.com/gh_mirrors/wp/wp-calypso
点击查看免费下载
上一篇:Discuz 7.x/6.x 全局变量防御绕过(request_order=GP)导致代码执行漏洞:原理分析与实战复现
下一篇:思源黑体TTF:专业级免费商用字体构建方案深度解析

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 2:18:54

基于BERT的文本纠错模型实战:从数据构造到推理调优

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/9 2:16:29

RS485老电表不换表上云:DTU、采集器、边缘网关三条路径对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/9 2:15:01

开源工具NotionLinkTuner:解决Notion网络问题

做这个开源项目之前&#xff0c;我大概被 Notion 的访问问题折磨了两周。页面转圈、桌面端白屏、同步一直失败&#xff0c;最崩溃的是每次报错还不一样&#xff0c;搜教程要么让清缓存&#xff0c;要么让重装&#xff0c;试了一圈没有任何改善。后来我耐下性子把整个访问链路拆…

作者头像 李华