CKEditor 5 本地化(Localization)完全指南:翻译 UI、多语言配置与插件国际化
【免费下载链接】ckeditor5Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.项目地址: https://gitcode.com/GitHub_Trending/ck/ckeditor5
本文以 docs/framework/deep-dive/localization.md 为骨架,结合 ckeditor5 仓库中
Locale、translation-service等核心实现,系统讲解 CKEditor 5 的消息本地化机制:如何在插件中编写可翻译的 UI、如何通过三种方式为编辑器补充或覆盖翻译、如何处理复数形式,以及如何复用其他包的既有翻译。读完本文,你将能够为自己的自定义插件接入完整的国际化能力,并为编辑器装配任意语言的界面。
引言:CKEditor 5 的本地化体系
CKEditor 5 的所有 WYSIWYG 编辑器功能都支持消息本地化(message localization),即任何功能(feature)的用户界面都可以根据用户偏好翻译成各种语言与地区变体。这套翻译系统对第三方插件完全开放:
- 支持第三方插件的本地化;
- 允许传入自定义翻译以修复缺失或错误的本地化;
- 能够生成确定性(deterministic)构建产物;
- 提供易用的 API 用于提供翻译与编写可本地化内容;
- 在本地化流程的每一步都支持复数形式(plural forms)。
注意:请务必使用较新的 CKEditor 5 开发工具包版本。低于 v60.0.0 的旧版本工具不支持本文档描述的功能。
在仓库中,本地化的核心代码位于packages/ckeditor5-utils包:
- packages/ckeditor5-utils/src/locale.ts —
Locale类与Translations类型定义; - packages/ckeditor5-utils/src/translation-service.ts —
add()翻译注册函数与_translate()底层翻译逻辑、Message接口; - packages/ckeditor5-utils/src/language.ts — 语言方向(LTR/RTL)判定。
术语表
在开始之前,先明确翻译流程中的关键术语:
- Message(消息):需要被翻译的字符串或对象。字符串形式是
{ id: message, string: message }对象形式的快捷写法。 - Message ID(消息 ID):用于区分消息的属性。对于可能发生冲突的短消息(如
%0 images)尤为有用。 - Message string(消息字符串):消息的默认(英文)形式。当消息支持复数时,它是默认的单数形式。
- Message plural(消息复数形式):消息可选的复数(英文)形式。该属性的存在表示消息应当同时支持单数和复数形式。
- Translation source(
.ts,翻译源):为某一种语言生成、包含词典(dictionary)的 TypeScript 模块。所有可本地化的 CKEditor 5 包都在lang/translations/目录中包含此类文件。 - Translation asset(翻译资源):包含某一种语言生成翻译的 JavaScript 文件,或文件的一部分。
仓库中的真实例子:packages/ckeditor5-alignment/lang/translations/pl.ts 是ckeditor5-alignment包的波兰语翻译源,其导出结构完全符合Translations类型;而 packages/ckeditor5-alignment/lang/contexts.json 则记录了每条消息的语义上下文,供翻译人员理解消息的用途。
编写可本地化的 UI:t()函数
所有需要本地化的message都应传给 CKEditor 5 专门的t()函数(Locale#t,见 packages/ckeditor5-utils/src/locale.ts)。
在 JavaScript 文件中,把它作为独立函数取出使用,例如从编辑器的Locale实例获取(const { t } = editor.locale;),或从任意视图方法中获取(const t = this.t;)。
在 TypeScript 文件中,翻译工具还能基于类型信息识别直接的Locale#t()调用,因此editor.t()、editor.locale.t()、locale.t()、this.t()等写法均被支持。
t()函数接受两个参数:
- 第一个参数:字符串字面量或包含
id、string与可选plural属性的对象字面量。字符串字面量会同时充当message ID与message string; - 第二个参数:单个值或值数组,用于填充更复杂翻译场景中的占位符。如果指定了
plural属性,数组中的第一个值将作为决定复数形式的关键量(quantity)。
重要限制:由于翻译流程依赖静态代码分析器(static code analyzer),支持的调用模式取决于源文件类型。在 JavaScript 文件中,分析器只查找名为
t()的函数,因此它不能挂在Locale实例上调用,也不能改名;在 TypeScript 文件中,分析器还会根据类型信息识别直接的Locale#t()调用。同理,第一个参数只能是字符串字面量或对象字面量,不能传入变量。
简单场景:字符串形式
const emojiName = 'cat'; // 假设选择了英语: t( 'insert emoji' ); // "insert emoji" t( 'insert %0 emoji', emojiName ); // "insert cat emoji" t( 'insert %0 emoji', [ emojiName ] ); // "insert cat emoji"从源码可以看到,Locale#_t()内部会把字符串消息归一化为{ string: message }对象,然后交给_translate()处理,最后通过interpolateString()将%0、%1等占位符替换为传入值(packages/ckeditor5-utils/src/locale.ts)。占位符使用%<index>形式,单个值只填充%0,数组则按下标一一对应。
高级场景:对象形式与复数
const quantity = 3; // 假设选择了英语: t( { string: '%0 emoji', id: 'ACTION_EMOJI' }, 'insert' ); // "insert emoji" t( { string: '%0 emoji', plural: '%0 emojis', id: 'N_EMOJIS' }, quantity ); // "3 emojis" t( { string: '%1 %0 emoji', plural: '%1 %0 emojis', id: 'ACTION_N_EMOJIS' }, [ quantity, 'Insert' ] ); // "Insert 3 emojis"id属性用于区分字符串相同但翻译应不同的消息(例如英文同为editor的 "in the editor" 与 "my editor")。Message接口的完整定义见 packages/ckeditor5-utils/src/translation-service.ts。
示例:本地化插件 UI
下面的例子展示如何为插件创建可本地化的用户界面——一个插入笑脸表情的按钮,其悬浮提示(tooltip)可被翻译:
// 自定义插件配置,包括必要的导入。 // 以下代码应放入继承自 Plugin 类的自定义插件类中。 // ... editor.ui.componentFactory.add( 'smilingFaceEmoji', locale => { const buttonView = new ButtonView( locale ); // 本地化的标签。 const label = editor.locale.t( 'Insert smiling face emoji' ); buttonView.set( { label, icon: emojiIcon, tooltip: true } ); buttonView.on( 'execute', () => { editor.execute( 'insertSmilingFaceEmoji' ); editor.editing.view.focus(); } ); } ); // 其余自定义插件配置。 // ...关于如何完整创建一个 CKEditor 5 插件,可参考文档 docs/tutorials/crash-course/ 下的入门教程。
示例:本地化 pending actions
Pending actions(待处理操作)用于告知用户某个操作正在进行中,此时退出编辑器会丢失数据。其实现位于 packages/ckeditor5-core/src/pendingactions.ts。下面展示如何本地化这些提示消息:
class FileRepository { // 更多方法。 // ... updatePendingAction() { const pendingActions = this.editor.plugins.get( PendingActions ); const t = this.editor.t; const getMessage = value => t( 'Upload in progress (%0%).', value ); // Upload in progress (12%). this._pendingAction = pendingActions.add( getMessage( this.uploadedPercent ) ); this._pendingAction.bind( 'message' ).to( this, 'uploadedPercent', getMessage ); } }这里使用了bind( 'message' ).to( ... )将待处理操作的消息与上传进度动态绑定,每次进度变化都会用getMessage重新生成带百分比的本地化消息。
为编辑器添加翻译:三种方式
首先,如果你在某个 CKEditor 5 功能中发现缺失或错误的翻译,可以参见 docs/framework/contributing/ 中的翻译贡献指南——CKEditor 5 是开源项目,来自世界各地用户的翻译贡献都会被其他使用者感激。
向编辑器添加翻译有三种方式,可满足不同场景的需求:
- 通过
translation-service的add()函数添加翻译——需要在创建编辑器实例之前完成,且要求导入 CKEditor 5 的 utility 函数; - 通过扩展全局
window.CKEDITOR_TRANSLATIONS对象——同样需要在创建编辑器实例之前完成; - 像其他 CKEditor 5 包那样,在发布包的
lang/translations/目录中创建 TypeScript 翻译源——适合第三方插件作者,可在构建阶段只打包所需语言的翻译。
方式一:使用add()函数
translation-service的add()辅助函数通过扩展全局window.CKEDITOR_TRANSLATIONS对象来添加翻译(packages/ckeditor5-utils/src/translation-service.ts)。由于需要导入,它只能在构建编辑器之前使用。
自 CKEditor 5 v19.0.0 起,add()方法接受一个可选的第三个参数getPluralForm()函数。该函数仅在没有为某种语言加载语言文件时,才需要用来定义复数形式。如果某个message需要支持单复数,则其翻译应传一个翻译数组。
add( 'pl', { 'Add space': [ 'Dodaj spację', 'Dodaj %0 spacje', 'Dodaj %0 spacji' ] } ); // 假设选择了波兰语: t( { string: 'Add space', plural: 'Add %0 spaces' }, 1 ) // "Dodaj spację" t( { string: 'Add space', plural: 'Add %0 spaces' }, 2 ) // "Dodaj 2 spacje" t( { string: 'Add space', plural: 'Add %0 spaces' }, 5 ) // "Dodaj 5 spacji"在add()的源码实现中,每条翻译都会通过Object.assign()合并进对应语言的dictionary,同时仅在首次注册该语言时写入getPluralForm(已存在的语言不会被覆盖)。从源码注释还可以看到,getPluralForm()既支持返回布尔值(如英语n => n !== 1),也支持返回数值下标(如波兰语的复合规则)。
方式二:扩展window.CKEDITOR_TRANSLATIONS对象
第二种方式是通过全局window.CKEDITOR_TRANSLATIONS对象添加翻译。对于每种要支持的语言,需要扩展该对象的dictionary属性,并在缺失时提供getPluralForm()函数。
dictionary属性:一个message ID ⇒ translations映射。translations可以是字符串;如果消息需要支持复数,则是该语言下包含单数与各复数形式的翻译数组。getPluralForm()属性:一个根据给定数量返回复数形式下标的函数。注意,使用 CKEditor 5 翻译时,该属性会由CKEditor 5 翻译资源(translation assets)自动定义。
下面是一个window.CKEDITOR_TRANSLATIONS对象的部分示例,包含波兰语的Cancel与Add space两个消息 ID:
{ // 每个键应是有效的语言代码。 pl: { // 'pl' 语言的翻译映射。 dictionary: { 'Cancel': 'Anuluj', 'Add space': [ 'Dodaj spację', 'Dodaj %0 spacje', 'Dodaj %0 spacji' ] }, // 返回给定语言复数形式下标的函数。 // 注意:只有为一种新语言添加翻译时才需要传入该函数。 getPluralForm: n => n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 } // 其他语言。 // ... }必须扩展window.CKEDITOR_TRANSLATIONS对象中已存在的属性,以免丢失其他翻译。这可以借助Object.assign()与||运算符轻松实现:
// 确保全局对象已定义;若未定义则创建。 window.CKEDITOR_TRANSLATIONS = window.CKEDITOR_TRANSLATIONS || {}; // 确保波兰语词典存在。 window.CKEDITOR_TRANSLATIONS[ 'pl' ] = window.CKEDITOR_TRANSLATIONS[ 'pl' ] || {}; window.CKEDITOR_TRANSLATIONS[ 'pl' ].dictionary = window.CKEDITOR_TRANSLATIONS[ 'pl' ].dictionary || {}; // 用你的翻译扩展波兰语词典: Object.assign( window.CKEDITOR_TRANSLATIONS[ 'pl' ].dictionary, { 'Save': 'Zapisz' } );如果你添加了一种全新语言,请记得设置getPluralForm()函数——它应返回一个数字(对于英语这类复数规则简单的语言也可以返回布尔值),用于指示给定值应使用哪种形式。
方式三:创建翻译源文件(推荐给插件作者)
第三种方式主要面向包含大量可本地化消息的插件:在lang/translations/目录中,为每种语言代码创建一个 TypeScript 文件。默认导出必须符合Translations类型(定义见 packages/ckeditor5-utils/src/locale.ts):
// lang/translations/es.ts import type { Translations } from '@ckeditor/ckeditor5-utils'; const translations: Translations = { es: { dictionary: { // 文本对齐工具栏按钮的标签。 'Align left': 'Alinear a la izquierda' } } }; export default translations;如果你在 CKEditor 5 生态之外开发自己的插件,可以使用 package generator(构建工具会处理
lang/目录,包括翻译同步)来创建翻译源与翻译资源。
翻译源只包含词典。需要从ckeditor5包加载匹配语言的翻译(含getPluralForm),编辑器才能获得该语言的复数形式函数——因为包的翻译源中只有dictionary,不携带复数规则。
要构建并配置一个本地化的编辑器,请遵循 docs/getting-started/setup/ui-language.md 中的步骤。该文档还给出了完整的实践示例:通过 npm 导入ckeditor5/translations/pl.js并放入编辑器配置的translations数组,或通过 CDN 加载translations/es.umd.js脚本(CDN 方式无需手动传入配置),并支持通过config.language.ui/config.language.content分别设置 UI 语言与内容语言(例如英文 UI + 阿拉伯文内容)。仓库中各包实际生成的语言文件,可参考 packages/ckeditor5-alignment/lang/translations/ 下的 74 个语言目录。
复用其他包中的翻译
如果你想复用另一个包中已存在的message,应当通过不同名称的别名调用翻译函数,而不是直接使用t()。这样可防止静态代码分析器把它当作一条新的源消息来处理。
协作功能(collaboration features)与斜杠命令功能(slash commands)已经在使用这种方案。下面取自斜杠命令默认命令列表的例子展示了t()与translateVariableKey()的区别:translateVariableKey( 'Block quote' )会复用其他包中的翻译,而t( 'Create a block quote' )会被静态代码分析器处理为一条新消息。这样既保证了title的翻译来自块引用(block quote)功能中已有的 "Block quote" 消息,又为description创建了一条新翻译。
public getDefaultCommands() { const t = this.editor.t; const translateVariableKey = this.editor.locale.t; return [ { id: 'blockQuote', commandName: 'blockQuote', icon: IconQuote, title: translateVariableKey( 'Block quote' ), description: t( 'Create a block quote' ) }, // 更多命令定义 // ... ] }注意这里两种调用方式都来自同一个Locale实例:this.editor.t与this.editor.locale.t指向同一底层方法,区别只在于名字是否会让静态分析器误判为新消息。
底层原理:Locale与_translate()的协作
从源码层面看,一次完整的翻译流程是这样的:
- 编辑器初始化时创建
Locale实例,通过构造参数接收uiLanguage、contentLanguage与translations(默认uiLanguage = 'en'),并计算出uiLanguageDirection/contentLanguageDirection(packages/ckeditor5-utils/src/locale.ts); Locale#t是一个静态绑定到实例上下文的函数,因此应始终以函数形式调用(const t = locale.t; t( 'Label' )),这也是文档强调从editor.locale取出后直接调用的原因(packages/ckeditor5-utils/src/locale.ts);_t()把values统一转为数组,字符串消息规范化为对象,若有plural则以第一个值为数量,调用_translate()并最终完成占位符插值(packages/ckeditor5-utils/src/locale.ts);_translate()的查找优先级为:配置传入的translations优先于全局window.CKEDITOR_TRANSLATIONS;若全局对象中只有一种语言,还会自动将该语言作为目标语言;当某语言缺少翻译或词典不存在时,会回退到消息的默认string(数量为 1)或默认plural(数量不为 1)(packages/ckeditor5-utils/src/translation-service.ts)。
RTL 支持方面,packages/ckeditor5-utils/src/language.ts 内置了一份 RTL 语言代码清单(阿拉伯语、波斯语、希伯来语、库尔德语、维吾尔语、乌尔都语等),Locale依据它自动确定 UI 与内容方向,因此无需手动处理镜像布局。
此外,translation-service模块在加载时就会确保window.CKEDITOR_TRANSLATIONS全局对象存在(packages/ckeditor5-utils/src/translation-service.ts),翻译资产的 UMD 构建正是依赖这一点在运行期注册各语言翻译。仓库配套的单元测试 packages/ckeditor5-utils/tests/translation-service.js 覆盖了add()、_translate()、_clear()等函数的典型行为,可作为理解与验证翻译行为的参考。
已知限制
- 目前无法在不销毁编辑器的情况下于运行时更改已选定的编辑器语言;如需切换语言,必须重新创建编辑器实例(这通常意味着重新
create()并传入新的translations配置)。
小结
CKEditor 5 的本地化体系贯穿从消息编写(t()函数)、翻译收集(静态分析器扫描字面量)、翻译注册(add()/window.CKEDITOR_TRANSLATIONS/lang/translations/*.ts)到运行时查找(_translate()字典 + 复数形式函数)的完整链路。对插件作者而言,推荐的实践是:编写 UI 时坚持使用字面量形式的t()调用、需要复用时改用别名、为多语言消息提供plural属性,并通过lang/translations/目录发布各语言翻译源——这样既能生成确定性构建,也能让用户按需加载语言资源。
【免费下载链接】ckeditor5Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.项目地址: https://gitcode.com/GitHub_Trending/ck/ckeditor5
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考