☰
CKEditor 4 Language 插件语言元素识别机制:从 manual 测试看 Set Language 按钮状态联动
2026/10/16 6:09:51 网站建设 项目流程
  • 前端
  • UI组件

【免费下载链接】ckeditor4

The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.

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

导读

本文围绕 tests/plugins/language/manual/recognizelangelements.md 这份手工测试用例,深入剖析 CKEditor 4language插件如何识别光标所在的外语标记元素(<span lang="...">),并联动工具栏Set Language按钮在 ON/OFF 之间切换的底层机制。读完本文,你将掌握该插件getCurrentLangElement的核心算法、命令状态刷新链路、RTL 方向处理策略,以及如何通过config.language_list定制语言清单,并了解配套的自动化测试与回归修复(#5085、#779)。

1. 关联文档解读:一次"按钮状态"手工验收

recognizelangelements.md是 CKEditor 4 仓库中一份典型的 Bender manual 测试脚本,用于在真实浏览器环境下验收language插件的一个关键交互行为:

@bender-tags: 4.19.1, bug, 5085 @bender-ui: collapsed @bender-ckeditor-plugins: wysiwygarea, toolbar, sourcearea, language, format 1. Click on the word inside the editor. **Expected:** Toolbar button `Set Language` is ON. **Unexpected:** Toolbar button `Set Language` is OFF. 2. Repeat above step for the editor with RTL language.

测试要点非常明确:当光标点击到已标记语言的词(如<span lang="fr">Salut</span>)内部时,工具栏的Set Language按钮必须处于 ON(激活)状态;反之则为异常。并且该行为需要在 LTR 与 RTL 两种编辑器/语言环境下都成立。

对应的测试页面 recognizelangelements.html 提供了两个编辑器实例:一个用 LTR 的<span lang="fr">Salut</span>,另一个用 RTL 的<span lang="ar">أهلاً</span>,二者共用allowedContent: true与language: 'en'的配置:

<h2>LTR</h2> <textarea name="editor1"> <p><span lang="fr">Salut</span>.</p> </textarea> <h2>RTL</h2> <textarea name="editor2"> <p><span lang="ar">أهلاً</span>.</p> </textarea> <script> var config = { allowedContent: true, language: 'en' }; CKEDITOR.replace( 'editor1', config ); CKEDITOR.replace( 'editor2', config ); </script>

可以看到,测试数据中语言标记元素只携带lang属性而没有dir属性(这是 #5085 的典型复现场景,下文会详细说明)。文档标记为4.19.1, bug, 5085,说明该用例正是为 4.19.1 中修复的缺陷 #5085 而新增的回归验收。

2. 按钮状态如何被"点亮":getCurrentLangElement源码剖析

Set Language按钮(实际注册名为Language,类型为CKEDITOR.UI_MENUBUTTON)的 ON/OFF 状态,由language命令的refresh回调驱动。在 plugins/language/plugin.js 中可以看到:

editor.addCommand( 'language', { allowedContent: allowedContent, requiredContent: requiredContent, contextSensitive: true, exec: function( editor, languageId ) { var item = items[ 'language_' + languageId ]; if ( item ) editor item.style.checkActive( editor.elementPath(), editor ) ? 'removeStyle' : 'applyStyle' ; }, refresh: function( editor ) { this.setState( plugin.getCurrentLangElement( editor ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF ); } } );

refresh在每次选区变化(selection change)时被触发,其逻辑非常简洁:当前选区路径中只要存在语言元素,命令状态即为CKEDITOR.TRISTATE_ON,否则为CKEDITOR.TRISTATE_OFF。而判断"是否存在语言元素"正是插件核心方法getCurrentLangElement的职责:

getCurrentLangElement: function( editor ) { var elementPath = editor.elementPath(), activePath = elementPath && elementPath.elements, pathMember, ret; // IE8: upon initialization if there is no path elementPath() returns null. if ( elementPath ) { for ( var i = 0; i < activePath.length; i++ ) { pathMember = activePath[ i ]; if ( !ret && pathMember.getName() == 'span' && pathMember.hasAttribute( 'lang' ) ) ret = pathMember; } } return ret; }

该方法的判定规则可以概括为两点:

  1. 遍历元素路径:从editor.elementPath()拿到选区所处的祖先元素链(elements数组),从内到外逐个检查;
  2. 精确匹配条件:元素必须是span且带有lang属性,命中即返回该元素并停止遍历。

这段实现有两点值得注意:

  • 只认span标签,与插件的allowedContent: 'span[!lang,dir]'声明完全一致——语言标记在 CKEditor 4 中统一以span承载;
  • 只要lang属性存在即判定为语言元素,不要求dir属性。这保证了即使遇到 #5085 中"只有lang没有dir"的历史数据,按钮也能正确点亮(这是 4.19.1 修复后刻意放宽的行为)。

3. 从命令状态到按钮 UI:菜单与按钮的联动细节

命令状态确定后,还需要两处 UI 联动才能让用户"看到"当前语言:

3.1 按钮本体

按钮通过editor.ui.add( 'Language', CKEDITOR.UI_MENUBUTTON, ... )注册,其command: 'language'属性把按钮状态绑定到命令状态上。因此当命令刷新为TRISTATE_ON时,Set Language按钮即处于按下/激活外观。

3.2 菜单项高亮

onMenu回调负责在展开菜单时高亮当前语言项,并控制"移除语言"项的可点击性:

onMenu: function() { var activeItems = {}, currentLanguagedElement = plugin.getCurrentLangElement( editor ); for ( var prop in items ) activeItems[ prop ] = CKEDITOR.TRISTATE_OFF; activeItems.language_remove = currentLanguagedElement ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED; if ( currentLanguagedElement ) activeItems[ 'language_' + currentLanguagedElement.getAttribute( 'lang' ) ] = CKEDITOR.TRISTATE_ON; return activeItems; }

这里体现了"识别"的第二次应用:不仅按钮要 ON,菜单中对应语言项也要打勾(TRISTATE_ON),且Remove language项仅在存在语言元素时可点击。这与此前 CHANGES.md 中记录的能力一致——#10856 为该插件"添加了活动语言高亮,并添加了移除语言的选项"。

4. LTR 与 RTL:方向信息从哪来

manual 测试要求 LTR(fr)与 RTL(ar)两种场景下按钮都能点亮,而这背后涉及插件对文本方向的两层处理。

4.1language_list配置中的方向标注

插件的语言清单来自config.language_list,默认值为[ 'ar:Arabic:rtl', 'fr:French', 'es:Spanish' ]。每个条目遵循languageCode:languageLabel[:textDirection]三段式格式,第三段可选,默认ltr:

config.language_list = [ 'he:Hebrew:rtl', 'pt:Portuguese', 'de:German' ];

插件在init中解析该配置,并为每种语言预生成一个CKEDITOR.style:

items[ languageButtonId ].style = new CKEDITOR.style( { element: 'span', attributes: { lang: curLanguageId, dir: items[ languageButtonId ].ltr ? 'ltr' : 'rtl' } } );

也就是说,通过菜单应用语言时,span会同时写入lang与dir两个属性,dir的值由配置第三段决定。exec中style.checkActive( editor.elementPath(), editor )判断该样式是否已激活,从而决定是applyStyle还是removeStyle——这正是"点击同一语言项两次即移除标记"的切换语义。

4.2 核心 RTL 语言表

core/lang.js中维护了编辑器核心支持的 RTL 语言集合:

rtl: { ar: 1, fa: 1, he: 1, ku: 1, ug: 1 },

该表在编辑器加载语言包时用于自动推导dir(见CKEDITOR.lang.load中that[ languageCode ].dir = that.rtl[ languageCode ] ? 'rtl' : 'ltr')。manual 测试选用的ar正是核心 RTL 表内成员,而fr则是典型 LTR 语言。

5. #5085 回归修复:缺失dir时自动补全

之所以recognizelangelements.md被标记为4.19.1, bug, 5085,是因为 CKEditor 4.19.1 修复了这样一个缺陷(见 CHANGES.md):

Fixed: The Language plugin removes the element marking the text in foreign language if said element does not have an information about the text direction.

即:当<span>只有lang属性、缺少dir属性时,旧版本会在数据处理阶段把整个语言标记元素删除,导致Set Language按钮无法识别、语言信息丢失。修复方式是在Language按钮(feature)上注册内容变换规则(contentTransformations):

contentTransformations: [ [ // Add missing proper `dir` attribute when <span> element has only `lang` attribute (#5085). { element: 'span', left: function( element ) { return element.attributes.lang && ( !element.attributes.dir || element.attributes.dir === '' ); }, right: function( element ) { var rtlLanguages = CKEDITOR.tools.object.keys( CKEDITOR.lang.rtl ), isRtlLanguage = CKEDITOR.tools.array.indexOf( rtlLanguages, element.attributes.lang ) !== -1, dirAttribute = isRtlLanguage ? 'rtl' : 'ltr'; element.attributes.dir = dirAttribute; } } ] ]

这条变换规则的含义是:遇到"有lang但dir缺失或为空"的span时,查询CKEDITOR.lang.rtl判断该语言是否为 RTL,然后自动补上正确的dir="rtl"或dir="ltr"。这样一来,<span lang="fr">会被规范化为<span lang="fr" dir="ltr">,<span lang="ar">会被规范化为<span lang="ar" dir="rtl">,元素不再被过滤器误删,按钮识别也就恢复了。配套的 manual 用例正是 addmissingdirattribute.md:期望粘贴含<span lang="fr">Salut</span>的内容后,源码中保留dir="ltr"属性而不是被移除。

6. 自动化测试印证:按钮状态与方向补全

除 manual 测试外,language插件还有完整的 Bender 单元测试 tests/plugins/language/language.js,其中直接对应本文主题的两个用例:

'test selection on lang element should toggle ON language button in LTR language': function() { bender.editorBot.create( { name: 'editor_lang_ltr', config: { plugins: 'language,toolbar', allowedContent: true } }, function( bot ) { var editor = bot.editor, inputHtml = '<p><span lang="fr">Sa^lut</span></p>', expectedButtonState = CKEDITOR.TRISTATE_ON; bot.setHtmlWithSelection( inputHtml ); var languageButtonState = editor.ui.get( 'Language' ).getState(); assert.areSame( expectedButtonState, languageButtonState ); } ); }, 'test selection on lang element should toggle ON language button in RTL language': function() { // ... 使用 '<p><span lang="ar">أ^هلاً</span></p>',断言同上 }

这两个用例与recognizelangelements.md的验收目标完全一致:光标置于带lang属性的span内部时,editor.ui.get( 'Language' ).getState()必须返回CKEDITOR.TRISTATE_ON。测试文件还覆盖了 apply/remove 语言、多段落选区逐段包裹、无效语言码不生效、默认language_list可用性等场景。

此外,测试文件末尾用CKEDITOR.lang.languages与CKEDITOR.lang.rtl生成了对全部支持语言的"补全dir"参数化用例:对每种语言输入<span lang="xx">bar</span>(无dir),期望输出<span dir="ltr|rtl" lang="xx">bar</span>,与第 5 节的变换规则相互印证。

7. 与 Remove Format 的协作:#779 保护机制

语言标记元素还可能被"清除格式"命令误伤。manual 用例 removeformatlanguage.md(标记4.7.3, bug, 779)要求:给文本应用语言后执行Remove Format,带lang/dir的span必须保留。插件在init中通过移除格式过滤器实现保护:

if ( editor.addRemoveFormatFilter ) { editor.addRemoveFormatFilter( function( element ) { return !( element.is( 'span' ) && element.getAttribute( 'dir' ) && element.getAttribute( 'lang' ) ); } ); }

该过滤器挂载到 plugins/removeformat/plugin.js 提供的addRemoveFormatFilter机制上(内部存于editor._.removeFormatFilters,removeFormat命令执行时逐条调用):凡是同时带有dir和lang的span,返回false即被豁免于格式清除。这解释了为什么 #5085 修复中要补全而非删除dir属性——保留dir是 #779 保护逻辑成立的前提,两条回归修复在数据规范上形成了闭环。

8. 配置与实战要点小结

基于以上分析,围绕"语言元素识别与按钮联动"这一主题,可沉淀出以下可直接落地的配置与排查要点:

关注点说明依据
语言清单config.language_list = [ 'code:Label[:rtl|ltr]' ],第三段缺省为ltrplugins/language/plugin.js 配置注释
标记结构语言标记固定为span,规范形态同时含lang与dir;allowedContent: 'span[!lang,dir]'同上
识别规则光标所在元素链中首个带lang的span即当前语言元素getCurrentLangElement
按钮状态命令refresh依据getCurrentLangElement返回结果置TRISTATE_ON/OFF命令定义
缺失dir4.19.1 起由contentTransformations依据CKEDITOR.lang.rtl自动补全#5085 修复
Remove Format同时含lang+dir的span受过滤器保护不被清除#779 修复
验证手段manual 用例见tests/plugins/language/manual/,自动化用例见tests/plugins/language/language.js仓库测试目录

8.1 定制语言清单示例

在实际项目中,可按如下方式定制语言列表(例如加入希伯来语并声明 RTL 方向):

config.language_list = [ 'ar:Arabic:rtl', 'fr:French', 'de:German', 'he:Hebrew:rtl', 'pt:Portuguese', 'es:Spanish' ];

解析逻辑为按:分割:第一段为写入lang属性的 ISO 639 语言码(推荐 2 位 ISO-639-1),第二段为菜单显示标签,第三段(可选)为rtl或ltr。即使配置中漏写方向,language_list缺省条目ar:Arabic:rtl之外的 LTR 语言也能正常以ltr处理,且 4.19.1+ 的自动补全规则会在数据进入编辑器时兜底规范化。

8.2 排查清单

若遇到"点击语言词但按钮不亮"的问题,按顺序检查:

  1. 确认<span>带有lang属性(识别硬性条件);
  2. 确认插件已加载且工具栏配置中包含Language按钮(toolbar: 'bidi,30'是默认挂载位);
  3. 确认allowedContent未因配置收紧而把span[lang,dir]过滤掉(allowedContent: true为放行);
  4. 若数据源来自外部粘贴且缺少dir,确认使用的是 4.19.1+ 版本(自动补全dir);
  5. 若问题出现在执行Remove Format之后,确认 4.7.3+ 且语言span同时保留了dir属性。

结语

一份只有几行的 manual 测试文档,背后串联的是language插件"识别语言元素—刷新命令状态—联动按钮/菜单"的完整链路,以及 #5085、#779 两条回归修复在数据规范化上的设计闭环。理解getCurrentLangElement的判定规则与contentTransformations的补全策略,是诊断和定制 CKEditor 4 多语言标记行为的关键,读者可结合 plugins/language/plugin.js、core/lang.js 与 tests/plugins/language/language.js 继续深入验证。

  • 前端
  • UI组件

【免费下载链接】ckeditor4

The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.

项目地址:https://gitcode.com/gh_mirrors/ck/ckeditor4
点击查看免费下载
上一篇:pnpm 修复 ssh:// Git 依赖解析:带括号 IPv6 主机与无用户信息 URL 的处理方案
下一篇:Sketchfab模型免费下载终极指南:Firefox浏览器一键获取3D资源

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

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

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

立即咨询