@ckeditor/ckeditor5-language 35.4.0 → 36.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md CHANGED
@@ -2,7 +2,7 @@ Software License Agreement
2
2
  ==========================
3
3
 
4
4
  **CKEditor 5 text part language feature** – https://github.com/ckeditor/ckeditor5-language <br>
5
- Copyright (c) 2003-2022, [CKSource Holding sp. z o.o.](https://cksource.com) All rights reserved.
5
+ Copyright (c) 2003-2023, [CKSource Holding sp. z o.o.](https://cksource.com) All rights reserved.
6
6
 
7
7
  Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
8
8
 
package/build/language.js CHANGED
@@ -1,5 +1,5 @@
1
1
  !function(e){const t=e.en=e.en||{};t.dictionary=Object.assign(t.dictionary||{},{"Choose language":"Choose language",Language:"Language","Remove language":"Remove language"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={})),
2
2
  /*!
3
- * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
4
4
  * For licensing, see LICENSE.md.
5
- */(()=>{var e={176:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});var a=n(609),r=n.n(a)()((function(e){return e[1]}));r.push([e.id,".ck-content span[lang]{font-style:italic}",""]);const o=r},609:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=e(t);return t[2]?"@media ".concat(t[2]," {").concat(n,"}"):n})).join("")},t.i=function(e,n,a){"string"==typeof e&&(e=[[null,e,""]]);var r={};if(a)for(var o=0;o<this.length;o++){var i=this[o][0];null!=i&&(r[i]=!0)}for(var s=0;s<e.length;s++){var u=[].concat(e[s]);a&&r[u[0]]||(n&&(u[2]?u[2]="".concat(n," and ").concat(u[2]):u[2]=n),t.push(u))}},t}},62:(e,t,n)=>{"use strict";var a,r=function(){return void 0===a&&(a=Boolean(window&&document&&document.all&&!window.atob)),a},o=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),i=[];function s(e){for(var t=-1,n=0;n<i.length;n++)if(i[n].identifier===e){t=n;break}return t}function u(e,t){for(var n={},a=[],r=0;r<e.length;r++){var o=e[r],u=t.base?o[0]+t.base:o[0],l=n[u]||0,c="".concat(u," ").concat(l);n[u]=l+1;var d=s(c),g={css:o[1],media:o[2],sourceMap:o[3]};-1!==d?(i[d].references++,i[d].updater(g)):i.push({identifier:c,updater:h(g,t),references:1}),a.push(c)}return a}function l(e){var t=document.createElement("style"),a=e.attributes||{};if(void 0===a.nonce){var r=n.nc;r&&(a.nonce=r)}if(Object.keys(a).forEach((function(e){t.setAttribute(e,a[e])})),"function"==typeof e.insert)e.insert(t);else{var i=o(e.insert||"head");if(!i)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");i.appendChild(t)}return t}var c,d=(c=[],function(e,t){return c[e]=t,c.filter(Boolean).join("\n")});function g(e,t,n,a){var r=n?"":a.media?"@media ".concat(a.media," {").concat(a.css,"}"):a.css;if(e.styleSheet)e.styleSheet.cssText=d(t,r);else{var o=document.createTextNode(r),i=e.childNodes;i[t]&&e.removeChild(i[t]),i.length?e.insertBefore(o,i[t]):e.appendChild(o)}}function f(e,t,n){var a=n.css,r=n.media,o=n.sourceMap;if(r?e.setAttribute("media",r):e.removeAttribute("media"),o&&"undefined"!=typeof btoa&&(a+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(o))))," */")),e.styleSheet)e.styleSheet.cssText=a;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(a))}}var m=null,p=0;function h(e,t){var n,a,r;if(t.singleton){var o=p++;n=m||(m=l(t)),a=g.bind(null,n,o,!1),r=g.bind(null,n,o,!0)}else n=l(t),a=f.bind(null,n,t),r=function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(n)};return a(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;a(e=t)}else r()}}e.exports=function(e,t){(t=t||{}).singleton||"boolean"==typeof t.singleton||(t.singleton=r());var n=u(e=e||[],t);return function(e){if(e=e||[],"[object Array]"===Object.prototype.toString.call(e)){for(var a=0;a<n.length;a++){var r=s(n[a]);i[r].references--}for(var o=u(e,t),l=0;l<n.length;l++){var c=s(n[l]);0===i[c].references&&(i[c].updater(),i.splice(c,1))}n=o}}}},704:(e,t,n)=>{e.exports=n(79)("./src/core.js")},273:(e,t,n)=>{e.exports=n(79)("./src/ui.js")},209:(e,t,n)=>{e.exports=n(79)("./src/utils.js")},79:e=>{"use strict";e.exports=CKEditor5.dll}},t={};function n(a){var r=t[a];if(void 0!==r)return r.exports;var o=t[a]={id:a,exports:{}};return e[a](o,o.exports,n),o.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nc=void 0;var a={};(()=>{"use strict";n.r(a),n.d(a,{TextPartLanguage:()=>f,TextPartLanguageEditing:()=>i,TextPartLanguageUI:()=>g});var e=n(704),t=n(209);function r(e,n){return`${e}:${n=n||(0,t.getLanguageDirection)(e)}`}class o extends e.Command{refresh(){const e=this.editor.model,t=e.document;this.value=this._getValueFromFirstAllowedNode(),this.isEnabled=e.schema.checkAttributeInSelection(t.selection,"language")}execute({languageCode:e,textDirection:t}={}){const n=this.editor.model,a=n.document.selection,o=!!e&&r(e,t);n.change((e=>{if(a.isCollapsed)o?e.setSelectionAttribute("language",o):e.removeSelectionAttribute("language");else{const t=n.schema.getValidRanges(a.getRanges(),"language");for(const n of t)o?e.setAttribute("language",o,n):e.removeAttribute("language",n)}}))}_getValueFromFirstAllowedNode(){const e=this.editor.model,t=e.schema,n=e.document.selection;if(n.isCollapsed)return n.getAttribute("language")||!1;for(const e of n.getRanges())for(const n of e.getItems())if(t.checkAttribute(n,"language"))return n.getAttribute("language")||!1;return!1}}class i extends e.Plugin{static get pluginName(){return"TextPartLanguageEditing"}constructor(e){super(e),e.config.define("language",{textPartLanguage:[{title:"Arabic",languageCode:"ar"},{title:"French",languageCode:"fr"},{title:"Spanish",languageCode:"es"}]})}init(){const e=this.editor;e.model.schema.extend("$text",{allowAttributes:"language"}),e.model.schema.setAttributeProperties("language",{copyOnEnter:!0}),this._defineConverters(),e.commands.add("textPartLanguage",new o(e))}_defineConverters(){const e=this.editor.conversion;e.for("upcast").elementToAttribute({model:{key:"language",value:e=>r(e.getAttribute("lang"),e.getAttribute("dir"))},view:{name:"span",attributes:{lang:/[\s\S]+/}}}),e.for("downcast").attributeToElement({model:"language",view:(e,{writer:t},n)=>{if(!e)return;if(!n.item.is("$textProxy")&&!n.item.is("documentSelection"))return;const{languageCode:a,textDirection:r}=function(e){const[t,n]=e.split(":");return{languageCode:t,textDirection:n}}(e);return t.createAttributeElement("span",{lang:a,dir:r})}})}}var s=n(273),u=n(62),l=n.n(u),c=n(176),d={injectType:"singletonStyleTag",attributes:{"data-cke":!0},insert:"head",singleton:!0};l()(c.Z,d);c.Z.locals;class g extends e.Plugin{static get pluginName(){return"TextPartLanguageUI"}init(){const e=this.editor,n=e.t,a=e.config.get("language.textPartLanguage"),o=n("Choose language"),i=n("Remove language"),u=n("Language");e.ui.componentFactory.add("textPartLanguage",(n=>{const l=new t.Collection,c={},d=e.commands.get("textPartLanguage");l.add({type:"button",model:new s.Model({label:i,languageCode:!1,withText:!0})}),l.add({type:"separator"});for(const e of a){const t={type:"button",model:new s.Model({label:e.title,languageCode:e.languageCode,textDirection:e.textDirection,withText:!0})},n=r(e.languageCode,e.textDirection);t.model.bind("isOn").to(d,"value",(e=>e===n)),l.add(t),c[n]=e.title}const g=(0,s.createDropdown)(n);return(0,s.addListToDropdown)(g,l),g.buttonView.set({isOn:!1,withText:!0,tooltip:u}),g.extendTemplate({attributes:{class:["ck-text-fragment-language-dropdown"]}}),g.bind("isEnabled").to(d,"isEnabled"),g.buttonView.bind("label").to(d,"value",(e=>c[e]||o)),this.listenTo(g,"execute",(t=>{d.execute({languageCode:t.source.languageCode,textDirection:t.source.textDirection}),e.editing.view.focus()})),g}))}}class f extends e.Plugin{static get requires(){return[i,g]}static get pluginName(){return"TextPartLanguage"}}})(),(window.CKEditor5=window.CKEditor5||{}).language=a})();
5
+ */(()=>{var e={176:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});var a=n(609),r=n.n(a)()((function(e){return e[1]}));r.push([e.id,".ck-content span[lang]{font-style:italic}",""]);const o=r},609:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=e(t);return t[2]?"@media ".concat(t[2]," {").concat(n,"}"):n})).join("")},t.i=function(e,n,a){"string"==typeof e&&(e=[[null,e,""]]);var r={};if(a)for(var o=0;o<this.length;o++){var i=this[o][0];null!=i&&(r[i]=!0)}for(var s=0;s<e.length;s++){var u=[].concat(e[s]);a&&r[u[0]]||(n&&(u[2]?u[2]="".concat(n," and ").concat(u[2]):u[2]=n),t.push(u))}},t}},62:(e,t,n)=>{"use strict";var a,r=function(){return void 0===a&&(a=Boolean(window&&document&&document.all&&!window.atob)),a},o=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),i=[];function s(e){for(var t=-1,n=0;n<i.length;n++)if(i[n].identifier===e){t=n;break}return t}function u(e,t){for(var n={},a=[],r=0;r<e.length;r++){var o=e[r],u=t.base?o[0]+t.base:o[0],l=n[u]||0,c="".concat(u," ").concat(l);n[u]=l+1;var d=s(c),g={css:o[1],media:o[2],sourceMap:o[3]};-1!==d?(i[d].references++,i[d].updater(g)):i.push({identifier:c,updater:h(g,t),references:1}),a.push(c)}return a}function l(e){var t=document.createElement("style"),a=e.attributes||{};if(void 0===a.nonce){var r=n.nc;r&&(a.nonce=r)}if(Object.keys(a).forEach((function(e){t.setAttribute(e,a[e])})),"function"==typeof e.insert)e.insert(t);else{var i=o(e.insert||"head");if(!i)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");i.appendChild(t)}return t}var c,d=(c=[],function(e,t){return c[e]=t,c.filter(Boolean).join("\n")});function g(e,t,n,a){var r=n?"":a.media?"@media ".concat(a.media," {").concat(a.css,"}"):a.css;if(e.styleSheet)e.styleSheet.cssText=d(t,r);else{var o=document.createTextNode(r),i=e.childNodes;i[t]&&e.removeChild(i[t]),i.length?e.insertBefore(o,i[t]):e.appendChild(o)}}function f(e,t,n){var a=n.css,r=n.media,o=n.sourceMap;if(r?e.setAttribute("media",r):e.removeAttribute("media"),o&&"undefined"!=typeof btoa&&(a+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(o))))," */")),e.styleSheet)e.styleSheet.cssText=a;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(a))}}var m=null,p=0;function h(e,t){var n,a,r;if(t.singleton){var o=p++;n=m||(m=l(t)),a=g.bind(null,n,o,!1),r=g.bind(null,n,o,!0)}else n=l(t),a=f.bind(null,n,t),r=function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(n)};return a(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;a(e=t)}else r()}}e.exports=function(e,t){(t=t||{}).singleton||"boolean"==typeof t.singleton||(t.singleton=r());var n=u(e=e||[],t);return function(e){if(e=e||[],"[object Array]"===Object.prototype.toString.call(e)){for(var a=0;a<n.length;a++){var r=s(n[a]);i[r].references--}for(var o=u(e,t),l=0;l<n.length;l++){var c=s(n[l]);0===i[c].references&&(i[c].updater(),i.splice(c,1))}n=o}}}},704:(e,t,n)=>{e.exports=n(79)("./src/core.js")},273:(e,t,n)=>{e.exports=n(79)("./src/ui.js")},209:(e,t,n)=>{e.exports=n(79)("./src/utils.js")},79:e=>{"use strict";e.exports=CKEditor5.dll}},t={};function n(a){var r=t[a];if(void 0!==r)return r.exports;var o=t[a]={id:a,exports:{}};return e[a](o,o.exports,n),o.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nc=void 0;var a={};(()=>{"use strict";n.r(a),n.d(a,{TextPartLanguage:()=>f,TextPartLanguageEditing:()=>i,TextPartLanguageUI:()=>g});var e=n(704),t=n(209);function r(e,n){return`${e}:${n=n||(0,t.getLanguageDirection)(e)}`}class o extends e.Command{refresh(){const e=this.editor.model,t=e.document;this.value=this._getValueFromFirstAllowedNode(),this.isEnabled=e.schema.checkAttributeInSelection(t.selection,"language")}execute({languageCode:e,textDirection:t}={}){const n=this.editor.model,a=n.document.selection,o=!!e&&r(e,t);n.change((e=>{if(a.isCollapsed)o?e.setSelectionAttribute("language",o):e.removeSelectionAttribute("language");else{const t=n.schema.getValidRanges(a.getRanges(),"language");for(const n of t)o?e.setAttribute("language",o,n):e.removeAttribute("language",n)}}))}_getValueFromFirstAllowedNode(){const e=this.editor.model,t=e.schema,n=e.document.selection;if(n.isCollapsed)return n.getAttribute("language")||!1;for(const e of n.getRanges())for(const n of e.getItems())if(t.checkAttribute(n,"language"))return n.getAttribute("language")||!1;return!1}}class i extends e.Plugin{static get pluginName(){return"TextPartLanguageEditing"}constructor(e){super(e),e.config.define("language",{textPartLanguage:[{title:"Arabic",languageCode:"ar"},{title:"French",languageCode:"fr"},{title:"Spanish",languageCode:"es"}]})}init(){const e=this.editor;e.model.schema.extend("$text",{allowAttributes:"language"}),e.model.schema.setAttributeProperties("language",{copyOnEnter:!0}),this._defineConverters(),e.commands.add("textPartLanguage",new o(e))}_defineConverters(){const e=this.editor.conversion;e.for("upcast").elementToAttribute({model:{key:"language",value:e=>r(e.getAttribute("lang"),e.getAttribute("dir"))},view:{name:"span",attributes:{lang:/[\s\S]+/}}}),e.for("downcast").attributeToElement({model:"language",view:(e,{writer:t},n)=>{if(!e)return;if(!n.item.is("$textProxy")&&!n.item.is("documentSelection"))return;const{languageCode:a,textDirection:r}=function(e){const[t,n]=e.split(":");return{languageCode:t,textDirection:n}}(e);return t.createAttributeElement("span",{lang:a,dir:r})}})}}var s=n(273),u=n(62),l=n.n(u),c=n(176),d={injectType:"singletonStyleTag",attributes:{"data-cke":!0},insert:"head",singleton:!0};l()(c.Z,d);c.Z.locals;class g extends e.Plugin{static get pluginName(){return"TextPartLanguageUI"}init(){const e=this.editor,n=e.t,a=e.config.get("language.textPartLanguage"),o=n("Choose language"),i=n("Remove language"),u=n("Language");e.ui.componentFactory.add("textPartLanguage",(n=>{const l=new t.Collection,c={},d=e.commands.get("textPartLanguage");l.add({type:"button",model:new s.Model({label:i,languageCode:!1,withText:!0})}),l.add({type:"separator"});for(const e of a){const t={type:"button",model:new s.Model({label:e.title,languageCode:e.languageCode,textDirection:e.textDirection,withText:!0})},n=r(e.languageCode,e.textDirection);t.model.bind("isOn").to(d,"value",(e=>e===n)),l.add(t),c[n]=e.title}const g=(0,s.createDropdown)(n);return(0,s.addListToDropdown)(g,l),g.buttonView.set({isOn:!1,withText:!0,tooltip:u}),g.extendTemplate({attributes:{class:["ck-text-fragment-language-dropdown"]}}),g.bind("isEnabled").to(d,"isEnabled"),g.buttonView.bind("label").to(d,"value",(e=>e&&c[e]||o)),this.listenTo(g,"execute",(t=>{d.execute({languageCode:t.source.languageCode,textDirection:t.source.textDirection}),e.editing.view.focus()})),g}))}}class f extends e.Plugin{static get requires(){return[i,g]}static get pluginName(){return"TextPartLanguage"}}})(),(window.CKEditor5=window.CKEditor5||{}).language=a})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-language",
3
- "version": "35.4.0",
3
+ "version": "36.0.0",
4
4
  "description": "Text part language feature for CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
@@ -12,16 +12,17 @@
12
12
  ],
13
13
  "main": "src/index.js",
14
14
  "dependencies": {
15
- "ckeditor5": "^35.4.0"
15
+ "ckeditor5": "^36.0.0"
16
16
  },
17
17
  "devDependencies": {
18
- "@ckeditor/ckeditor5-core": "^35.4.0",
19
- "@ckeditor/ckeditor5-dev-utils": "^31.0.0",
20
- "@ckeditor/ckeditor5-editor-classic": "^35.4.0",
21
- "@ckeditor/ckeditor5-engine": "^35.4.0",
22
- "@ckeditor/ckeditor5-paragraph": "^35.4.0",
23
- "@ckeditor/ckeditor5-theme-lark": "^35.4.0",
24
- "@ckeditor/ckeditor5-ui": "^35.4.0",
18
+ "@ckeditor/ckeditor5-core": "^36.0.0",
19
+ "@ckeditor/ckeditor5-dev-utils": "^32.0.0",
20
+ "@ckeditor/ckeditor5-editor-classic": "^36.0.0",
21
+ "@ckeditor/ckeditor5-engine": "^36.0.0",
22
+ "@ckeditor/ckeditor5-paragraph": "^36.0.0",
23
+ "@ckeditor/ckeditor5-theme-lark": "^36.0.0",
24
+ "@ckeditor/ckeditor5-ui": "^36.0.0",
25
+ "typescript": "^4.8.4",
25
26
  "webpack": "^5.58.1",
26
27
  "webpack-cli": "^4.9.0"
27
28
  },
@@ -40,13 +41,16 @@
40
41
  },
41
42
  "files": [
42
43
  "lang",
43
- "src",
44
+ "src/**/*.js",
45
+ "src/**/*.d.ts",
44
46
  "theme",
45
47
  "build",
46
48
  "ckeditor5-metadata.json",
47
49
  "CHANGELOG.md"
48
50
  ],
49
51
  "scripts": {
50
- "dll:build": "webpack"
52
+ "dll:build": "webpack",
53
+ "build": "tsc -p ./tsconfig.release.json",
54
+ "postversion": "npm run build"
51
55
  }
52
56
  }
package/src/index.js CHANGED
@@ -1,12 +1,10 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
5
  /**
7
6
  * @module language
8
7
  */
9
-
10
8
  export { default as TextPartLanguage } from './textpartlanguage';
11
9
  export { default as TextPartLanguageEditing } from './textpartlanguageediting';
12
10
  export { default as TextPartLanguageUI } from './textpartlanguageui';
@@ -1,17 +1,10 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
- /**
7
- * @module language/textpartlanguage
8
- */
9
-
10
5
  import { Plugin } from 'ckeditor5/src/core';
11
-
12
6
  import TextPartLanguageEditing from './textpartlanguageediting';
13
7
  import TextPartLanguageUI from './textpartlanguageui';
14
-
15
8
  /**
16
9
  * The text part language feature.
17
10
  *
@@ -26,61 +19,18 @@ import TextPartLanguageUI from './textpartlanguageui';
26
19
  * This is a "glue" plugin which loads the
27
20
  * {@link module:language/textpartlanguageediting~TextPartLanguageEditing text part language editing feature}
28
21
  * and the {@link module:language/textpartlanguageui~TextPartLanguageUI text part language UI feature}.
29
- *
30
- * @extends module:core/plugin~Plugin
31
22
  */
32
23
  export default class TextPartLanguage extends Plugin {
33
- /**
34
- * @inheritDoc
35
- */
36
- static get requires() {
37
- return [ TextPartLanguageEditing, TextPartLanguageUI ];
38
- }
39
-
40
- /**
41
- * @inheritDoc
42
- */
43
- static get pluginName() {
44
- return 'TextPartLanguage';
45
- }
24
+ /**
25
+ * @inheritDoc
26
+ */
27
+ static get requires() {
28
+ return [TextPartLanguageEditing, TextPartLanguageUI];
29
+ }
30
+ /**
31
+ * @inheritDoc
32
+ */
33
+ static get pluginName() {
34
+ return 'TextPartLanguage';
35
+ }
46
36
  }
47
-
48
- /**
49
- * The available {@link module:language/textpartlanguage~TextPartLanguage}
50
- * options that allow setting the language of parts of the content.
51
- *
52
- * This configuration option is available only with the {@glink api/language text part language feature} enabled.
53
- *
54
- * Refer to [WCAG 3.1.2 Language of Parts](https://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-other-lang-id.html) specification
55
- * to learn more.
56
- *
57
- * To change the editor's UI language, refer to the {@glink features/ui-language Setting the UI language} guide.
58
- *
59
- * The default value is:
60
- *
61
- * const config = [
62
- * { title: 'Arabic', languageCode: 'ar' },
63
- * { title: 'French', languageCode: 'fr' },
64
- * { title: 'Spanish', languageCode: 'es' }
65
- * ];
66
- *
67
- * The `title` property will be used by the text part language dropdown to render available options.
68
- *
69
- * The `languageCode` property is used for the `lang` attribute in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
70
- *
71
- * You can also specify the optional `textDirection` property indicating the reading direction of the language.
72
- * Correct values are `ltr` and `rtl`. When the `textDirection` property is missing, the text part language feature will
73
- * specify the text direction by itself.
74
- *
75
- * @member {Array.<module:language/textpartlanguage~TextPartLanguageOption>}
76
- * module:core/editor/editorconfig~LanguageConfig#textPartLanguage
77
- */
78
-
79
- /**
80
- * The text part language feature option descriptor.
81
- *
82
- * @typedef {Object} module:language/textpartlanguage~TextPartLanguageOption
83
- * @property {String} title The user-readable title of the option.
84
- * @property {String} languageCode The language code in the ISO 639 format.
85
- * @property {'ltr'|'rtl'} [textDirection] The language text direction. Automatically detected if omitted.
86
- */
@@ -1,124 +1,89 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
- /**
7
- * @module language/textpartlanguagecommand
8
- */
9
-
10
5
  import { Command } from 'ckeditor5/src/core';
11
6
  import { stringifyLanguageAttribute } from './utils';
12
-
13
7
  /**
14
8
  * The text part language command plugin.
15
- *
16
- * @extends module:core/command~Command
17
9
  */
18
10
  export default class TextPartLanguageCommand extends Command {
19
- /**
20
- * If the selection starts in a language attribute, the value is set to
21
- * the value of that language in a format:
22
- *
23
- * <languageCode>:<textDirection>
24
- *
25
- * * `languageCode` - The language code used for the `lang` attribute in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1)
26
- * format.
27
- * * `textDirection` - One of the following values: `rtl` or `ltr`, indicating the reading direction of the language.
28
- *
29
- * See the {@link module:core/editor/editorconfig~LanguageConfig#textPartLanguage text part language configuration}
30
- * for more information about language properties.
31
- *
32
- * It is set to `false` otherwise.
33
- *
34
- * @observable
35
- * @readonly
36
- * @member {Boolean|String} #value
37
- */
38
-
39
- /**
40
- * @inheritDoc
41
- */
42
- refresh() {
43
- const model = this.editor.model;
44
- const doc = model.document;
45
-
46
- this.value = this._getValueFromFirstAllowedNode();
47
- this.isEnabled = model.schema.checkAttributeInSelection( doc.selection, 'language' );
48
- }
49
-
50
- /**
51
- * Executes the command. Applies the attribute to the selection or removes it from the selection.
52
- *
53
- * If `languageCode` is set to `false` or a `null` value, it will remove attributes. Otherwise, it will set
54
- * the attribute in the `{@link #value value}` format.
55
- *
56
- * The execution result differs, depending on the {@link module:engine/model/document~Document#selection}:
57
- *
58
- * * If the selection is on a range, the command applies the attribute to all nodes in that range
59
- * (if they are allowed to have this attribute by the {@link module:engine/model/schema~Schema schema}).
60
- * * If the selection is collapsed in a non-empty node, the command applies the attribute to the
61
- * {@link module:engine/model/document~Document#selection} itself (note that typed characters copy attributes from the selection).
62
- * * If the selection is collapsed in an empty node, the command applies the attribute to the parent node of the selection (note
63
- * that the selection inherits all attributes from a node if it is in an empty node).
64
- *
65
- * @fires execute
66
- * @param {Object} [options] Command options.
67
- * @param {String|Boolean} [options.languageCode] The language code to be applied to the model.
68
- * @param {String} [options.textDirection] The language text direction.
69
- */
70
- execute( { languageCode, textDirection } = {} ) {
71
- const model = this.editor.model;
72
- const doc = model.document;
73
- const selection = doc.selection;
74
-
75
- const value = languageCode ? stringifyLanguageAttribute( languageCode, textDirection ) : false;
76
-
77
- model.change( writer => {
78
- if ( selection.isCollapsed ) {
79
- if ( value ) {
80
- writer.setSelectionAttribute( 'language', value );
81
- } else {
82
- writer.removeSelectionAttribute( 'language' );
83
- }
84
- } else {
85
- const ranges = model.schema.getValidRanges( selection.getRanges(), 'language' );
86
-
87
- for ( const range of ranges ) {
88
- if ( value ) {
89
- writer.setAttribute( 'language', value, range );
90
- } else {
91
- writer.removeAttribute( 'language', range );
92
- }
93
- }
94
- }
95
- } );
96
- }
97
-
98
- /**
99
- * Returns the attribute value of the first node in the selection that allows the attribute.
100
- * For a collapsed selection it returns the selection attribute.
101
- *
102
- * @private
103
- * @returns {Boolean|String} The attribute value.
104
- */
105
- _getValueFromFirstAllowedNode() {
106
- const model = this.editor.model;
107
- const schema = model.schema;
108
- const selection = model.document.selection;
109
-
110
- if ( selection.isCollapsed ) {
111
- return selection.getAttribute( 'language' ) || false;
112
- }
113
-
114
- for ( const range of selection.getRanges() ) {
115
- for ( const item of range.getItems() ) {
116
- if ( schema.checkAttribute( item, 'language' ) ) {
117
- return item.getAttribute( 'language' ) || false;
118
- }
119
- }
120
- }
121
-
122
- return false;
123
- }
11
+ /**
12
+ * @inheritDoc
13
+ */
14
+ refresh() {
15
+ const model = this.editor.model;
16
+ const doc = model.document;
17
+ this.value = this._getValueFromFirstAllowedNode();
18
+ this.isEnabled = model.schema.checkAttributeInSelection(doc.selection, 'language');
19
+ }
20
+ /**
21
+ * Executes the command. Applies the attribute to the selection or removes it from the selection.
22
+ *
23
+ * If `languageCode` is set to `false` or a `null` value, it will remove attributes. Otherwise, it will set
24
+ * the attribute in the `{@link #value value}` format.
25
+ *
26
+ * The execution result differs, depending on the {@link module:engine/model/document~Document#selection}:
27
+ *
28
+ * * If the selection is on a range, the command applies the attribute to all nodes in that range
29
+ * (if they are allowed to have this attribute by the {@link module:engine/model/schema~Schema schema}).
30
+ * * If the selection is collapsed in a non-empty node, the command applies the attribute to the
31
+ * {@link module:engine/model/document~Document#selection} itself (note that typed characters copy attributes from the selection).
32
+ * * If the selection is collapsed in an empty node, the command applies the attribute to the parent node of the selection (note
33
+ * that the selection inherits all attributes from a node if it is in an empty node).
34
+ *
35
+ * @fires execute
36
+ * @param options Command options.
37
+ * @param options.languageCode The language code to be applied to the model.
38
+ * @param options.textDirection The language text direction.
39
+ */
40
+ execute({ languageCode, textDirection } = {}) {
41
+ const model = this.editor.model;
42
+ const doc = model.document;
43
+ const selection = doc.selection;
44
+ const value = languageCode ? stringifyLanguageAttribute(languageCode, textDirection) : false;
45
+ model.change(writer => {
46
+ if (selection.isCollapsed) {
47
+ if (value) {
48
+ writer.setSelectionAttribute('language', value);
49
+ }
50
+ else {
51
+ writer.removeSelectionAttribute('language');
52
+ }
53
+ }
54
+ else {
55
+ const ranges = model.schema.getValidRanges(selection.getRanges(), 'language');
56
+ for (const range of ranges) {
57
+ if (value) {
58
+ writer.setAttribute('language', value, range);
59
+ }
60
+ else {
61
+ writer.removeAttribute('language', range);
62
+ }
63
+ }
64
+ }
65
+ });
66
+ }
67
+ /**
68
+ * Returns the attribute value of the first node in the selection that allows the attribute.
69
+ * For a collapsed selection it returns the selection attribute.
70
+ *
71
+ * @returns The attribute value.
72
+ */
73
+ _getValueFromFirstAllowedNode() {
74
+ const model = this.editor.model;
75
+ const schema = model.schema;
76
+ const selection = model.document.selection;
77
+ if (selection.isCollapsed) {
78
+ return selection.getAttribute('language') || false;
79
+ }
80
+ for (const range of selection.getRanges()) {
81
+ for (const item of range.getItems()) {
82
+ if (schema.checkAttribute(item, 'language')) {
83
+ return item.getAttribute('language') || false;
84
+ }
85
+ }
86
+ }
87
+ return false;
88
+ }
124
89
  }
@@ -1,105 +1,84 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
- /**
7
- * @module language/textpartlanguageediting
8
- */
9
-
10
5
  import { Plugin } from 'ckeditor5/src/core';
11
6
  import TextPartLanguageCommand from './textpartlanguagecommand';
12
7
  import { stringifyLanguageAttribute, parseLanguageAttribute } from './utils';
13
-
14
8
  /**
15
9
  * The text part language editing.
16
10
  *
17
11
  * Introduces the `'textPartLanguage'` command and the `'language'` model element attribute.
18
- *
19
- * @extends module:core/plugin~Plugin
20
12
  */
21
13
  export default class TextPartLanguageEditing extends Plugin {
22
- /**
23
- * @inheritDoc
24
- */
25
- static get pluginName() {
26
- return 'TextPartLanguageEditing';
27
- }
28
-
29
- /**
30
- * @inheritDoc
31
- */
32
- constructor( editor ) {
33
- super( editor );
34
-
35
- // Text part language options are only used to ensure that the feature works by default.
36
- // In the real usage it should be reconfigured by a developer. We are not providing
37
- // translations for `title` properties on purpose, as it's only an example configuration.
38
- editor.config.define( 'language', {
39
- textPartLanguage: [
40
- { title: 'Arabic', languageCode: 'ar' },
41
- { title: 'French', languageCode: 'fr' },
42
- { title: 'Spanish', languageCode: 'es' }
43
- ]
44
- } );
45
- }
46
-
47
- /**
48
- * @inheritDoc
49
- */
50
- init() {
51
- const editor = this.editor;
52
-
53
- editor.model.schema.extend( '$text', { allowAttributes: 'language' } );
54
- editor.model.schema.setAttributeProperties( 'language', {
55
- copyOnEnter: true
56
- } );
57
-
58
- this._defineConverters();
59
-
60
- editor.commands.add( 'textPartLanguage', new TextPartLanguageCommand( editor ) );
61
- }
62
-
63
- /**
64
- * @private
65
- */
66
- _defineConverters() {
67
- const conversion = this.editor.conversion;
68
-
69
- conversion.for( 'upcast' ).elementToAttribute( {
70
- model: {
71
- key: 'language',
72
- value: viewElement => {
73
- const languageCode = viewElement.getAttribute( 'lang' );
74
- const textDirection = viewElement.getAttribute( 'dir' );
75
-
76
- return stringifyLanguageAttribute( languageCode, textDirection );
77
- }
78
- },
79
- view: {
80
- name: 'span',
81
- attributes: { lang: /[\s\S]+/ }
82
- }
83
- } );
84
-
85
- conversion.for( 'downcast' ).attributeToElement( {
86
- model: 'language',
87
- view: ( attributeValue, { writer }, data ) => {
88
- if ( !attributeValue ) {
89
- return;
90
- }
91
-
92
- if ( !data.item.is( '$textProxy' ) && !data.item.is( 'documentSelection' ) ) {
93
- return;
94
- }
95
-
96
- const { languageCode, textDirection } = parseLanguageAttribute( attributeValue );
97
-
98
- return writer.createAttributeElement( 'span', {
99
- lang: languageCode,
100
- dir: textDirection
101
- } );
102
- }
103
- } );
104
- }
14
+ /**
15
+ * @inheritDoc
16
+ */
17
+ static get pluginName() {
18
+ return 'TextPartLanguageEditing';
19
+ }
20
+ /**
21
+ * @inheritDoc
22
+ */
23
+ constructor(editor) {
24
+ super(editor);
25
+ // Text part language options are only used to ensure that the feature works by default.
26
+ // In the real usage it should be reconfigured by a developer. We are not providing
27
+ // translations for `title` properties on purpose, as it's only an example configuration.
28
+ editor.config.define('language', {
29
+ textPartLanguage: [
30
+ { title: 'Arabic', languageCode: 'ar' },
31
+ { title: 'French', languageCode: 'fr' },
32
+ { title: 'Spanish', languageCode: 'es' }
33
+ ]
34
+ });
35
+ }
36
+ /**
37
+ * @inheritDoc
38
+ */
39
+ init() {
40
+ const editor = this.editor;
41
+ editor.model.schema.extend('$text', { allowAttributes: 'language' });
42
+ editor.model.schema.setAttributeProperties('language', {
43
+ copyOnEnter: true
44
+ });
45
+ this._defineConverters();
46
+ editor.commands.add('textPartLanguage', new TextPartLanguageCommand(editor));
47
+ }
48
+ /**
49
+ * @private
50
+ */
51
+ _defineConverters() {
52
+ const conversion = this.editor.conversion;
53
+ conversion.for('upcast').elementToAttribute({
54
+ model: {
55
+ key: 'language',
56
+ value: (viewElement) => {
57
+ const languageCode = viewElement.getAttribute('lang');
58
+ const textDirection = viewElement.getAttribute('dir');
59
+ return stringifyLanguageAttribute(languageCode, textDirection);
60
+ }
61
+ },
62
+ view: {
63
+ name: 'span',
64
+ attributes: { lang: /[\s\S]+/ }
65
+ }
66
+ });
67
+ conversion.for('downcast').attributeToElement({
68
+ model: 'language',
69
+ view: (attributeValue, { writer }, data) => {
70
+ if (!attributeValue) {
71
+ return;
72
+ }
73
+ if (!data.item.is('$textProxy') && !data.item.is('documentSelection')) {
74
+ return;
75
+ }
76
+ const { languageCode, textDirection } = parseLanguageAttribute(attributeValue);
77
+ return writer.createAttributeElement('span', {
78
+ lang: languageCode,
79
+ dir: textDirection
80
+ });
81
+ }
82
+ });
83
+ }
105
84
  }
@@ -1,119 +1,96 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
5
  /**
7
6
  * @module language/textpartlanguageui
8
7
  */
9
-
10
8
  import { Plugin } from 'ckeditor5/src/core';
11
9
  import { Model, createDropdown, addListToDropdown } from 'ckeditor5/src/ui';
12
10
  import { Collection } from 'ckeditor5/src/utils';
13
11
  import { stringifyLanguageAttribute } from './utils';
14
-
15
12
  import '../theme/language.css';
16
-
17
13
  /**
18
14
  * The text part language UI plugin.
19
15
  *
20
16
  * It introduces the `'language'` dropdown.
21
- *
22
- * @extends module:core/plugin~Plugin
23
17
  */
24
18
  export default class TextPartLanguageUI extends Plugin {
25
- /**
26
- * @inheritDoc
27
- */
28
- static get pluginName() {
29
- return 'TextPartLanguageUI';
30
- }
31
-
32
- /**
33
- * @inheritDoc
34
- */
35
- init() {
36
- const editor = this.editor;
37
- const t = editor.t;
38
- const options = editor.config.get( 'language.textPartLanguage' );
39
- const defaultTitle = t( 'Choose language' );
40
- const removeTitle = t( 'Remove language' );
41
- const dropdownTooltip = t( 'Language' );
42
-
43
- // Register UI component.
44
- editor.ui.componentFactory.add( 'textPartLanguage', locale => {
45
- const itemDefinitions = new Collection();
46
- const titles = {};
47
-
48
- const languageCommand = editor.commands.get( 'textPartLanguage' );
49
-
50
- // Item definition with false `languageCode` will behave as remove lang button.
51
- itemDefinitions.add( {
52
- type: 'button',
53
- model: new Model( {
54
- label: removeTitle,
55
- languageCode: false,
56
- withText: true
57
- } )
58
- } );
59
-
60
- itemDefinitions.add( {
61
- type: 'separator'
62
- } );
63
-
64
- for ( const option of options ) {
65
- const def = {
66
- type: 'button',
67
- model: new Model( {
68
- label: option.title,
69
- languageCode: option.languageCode,
70
- textDirection: option.textDirection,
71
- withText: true
72
- } )
73
- };
74
-
75
- const language = stringifyLanguageAttribute( option.languageCode, option.textDirection );
76
-
77
- def.model.bind( 'isOn' ).to( languageCommand, 'value', value => value === language );
78
-
79
- itemDefinitions.add( def );
80
-
81
- titles[ language ] = option.title;
82
- }
83
-
84
- const dropdownView = createDropdown( locale );
85
- addListToDropdown( dropdownView, itemDefinitions );
86
-
87
- dropdownView.buttonView.set( {
88
- isOn: false,
89
- withText: true,
90
- tooltip: dropdownTooltip
91
- } );
92
-
93
- dropdownView.extendTemplate( {
94
- attributes: {
95
- class: [
96
- 'ck-text-fragment-language-dropdown'
97
- ]
98
- }
99
- } );
100
-
101
- dropdownView.bind( 'isEnabled' ).to( languageCommand, 'isEnabled' );
102
- dropdownView.buttonView.bind( 'label' ).to( languageCommand, 'value', value => {
103
- return titles[ value ] || defaultTitle;
104
- } );
105
-
106
- // Execute command when an item from the dropdown is selected.
107
- this.listenTo( dropdownView, 'execute', evt => {
108
- languageCommand.execute( {
109
- languageCode: evt.source.languageCode,
110
- textDirection: evt.source.textDirection
111
- } );
112
-
113
- editor.editing.view.focus();
114
- } );
115
-
116
- return dropdownView;
117
- } );
118
- }
19
+ /**
20
+ * @inheritDoc
21
+ */
22
+ static get pluginName() {
23
+ return 'TextPartLanguageUI';
24
+ }
25
+ /**
26
+ * @inheritDoc
27
+ */
28
+ init() {
29
+ const editor = this.editor;
30
+ const t = editor.t;
31
+ const options = editor.config.get('language.textPartLanguage');
32
+ const defaultTitle = t('Choose language');
33
+ const removeTitle = t('Remove language');
34
+ const dropdownTooltip = t('Language');
35
+ // Register UI component.
36
+ editor.ui.componentFactory.add('textPartLanguage', locale => {
37
+ const itemDefinitions = new Collection();
38
+ const titles = {};
39
+ const languageCommand = editor.commands.get('textPartLanguage');
40
+ // Item definition with false `languageCode` will behave as remove lang button.
41
+ itemDefinitions.add({
42
+ type: 'button',
43
+ model: new Model({
44
+ label: removeTitle,
45
+ languageCode: false,
46
+ withText: true
47
+ })
48
+ });
49
+ itemDefinitions.add({
50
+ type: 'separator'
51
+ });
52
+ for (const option of options) {
53
+ const def = {
54
+ type: 'button',
55
+ model: new Model({
56
+ label: option.title,
57
+ languageCode: option.languageCode,
58
+ textDirection: option.textDirection,
59
+ withText: true
60
+ })
61
+ };
62
+ const language = stringifyLanguageAttribute(option.languageCode, option.textDirection);
63
+ def.model.bind('isOn').to(languageCommand, 'value', value => value === language);
64
+ itemDefinitions.add(def);
65
+ titles[language] = option.title;
66
+ }
67
+ const dropdownView = createDropdown(locale);
68
+ addListToDropdown(dropdownView, itemDefinitions);
69
+ dropdownView.buttonView.set({
70
+ isOn: false,
71
+ withText: true,
72
+ tooltip: dropdownTooltip
73
+ });
74
+ dropdownView.extendTemplate({
75
+ attributes: {
76
+ class: [
77
+ 'ck-text-fragment-language-dropdown'
78
+ ]
79
+ }
80
+ });
81
+ dropdownView.bind('isEnabled').to(languageCommand, 'isEnabled');
82
+ dropdownView.buttonView.bind('label').to(languageCommand, 'value', value => {
83
+ return (value && titles[value]) || defaultTitle;
84
+ });
85
+ // Execute command when an item from the dropdown is selected.
86
+ this.listenTo(dropdownView, 'execute', evt => {
87
+ languageCommand.execute({
88
+ languageCode: evt.source.languageCode,
89
+ textDirection: evt.source.textDirection
90
+ });
91
+ editor.editing.view.focus();
92
+ });
93
+ return dropdownView;
94
+ });
95
+ }
119
96
  }
package/src/utils.js CHANGED
@@ -1,18 +1,17 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
5
  /**
7
6
  * @module language/utils
8
7
  */
9
-
10
8
  import { getLanguageDirection } from 'ckeditor5/src/utils';
11
-
12
9
  /**
13
10
  * Returns the language attribute value in a human-readable text format:
14
11
  *
15
- * <languageCode>:<textDirection>
12
+ * ```
13
+ * <languageCode>:<textDirection>
14
+ * ```
16
15
  *
17
16
  * * `languageCode` - The language code used for the `lang` attribute in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
18
17
  * * `textDirection` - One of the following values: `rtl` or `ltr`, indicating the reading direction of the language.
@@ -22,26 +21,23 @@ import { getLanguageDirection } from 'ckeditor5/src/utils';
22
21
  *
23
22
  * If the `textDirection` argument is omitted, it will be automatically detected based on `languageCode`.
24
23
  *
25
- * @param {String} languageCode The language code in the ISO 639-1 format.
26
- * @param {'ltr'|'rtl'} [textDirection] The language text direction. Automatically detected if omitted.
27
- * @returns {String}
24
+ * @param languageCode The language code in the ISO 639-1 format.
25
+ * @param textDirection The language text direction. Automatically detected if omitted.
28
26
  */
29
- export function stringifyLanguageAttribute( languageCode, textDirection ) {
30
- textDirection = textDirection || getLanguageDirection( languageCode );
31
- return `${ languageCode }:${ textDirection }`;
27
+ export function stringifyLanguageAttribute(languageCode, textDirection) {
28
+ textDirection = textDirection || getLanguageDirection(languageCode);
29
+ return `${languageCode}:${textDirection}`;
32
30
  }
33
-
34
31
  /**
35
32
  * Retrieves language properties converted to attribute value by the
36
33
  * {@link module:language/utils~stringifyLanguageAttribute stringifyLanguageAttribute} function.
37
34
  *
38
- * @param {String} str The attribute value.
39
- * @returns {Object} result
40
- * @returns {String} result.languageCode The language code in the ISO 639 format.
41
- * @returns {String} result.textDirection The language text direction.
35
+ * @param str The attribute value.
36
+ * @returns The object with properties:
37
+ * * languageCode - The language code in the ISO 639 format.
38
+ * * textDirection - The language text direction.
42
39
  */
43
- export function parseLanguageAttribute( str ) {
44
- const [ languageCode, textDirection ] = str.split( ':' );
45
-
46
- return { languageCode, textDirection };
40
+ export function parseLanguageAttribute(str) {
41
+ const [languageCode, textDirection] = str.split(':');
42
+ return { languageCode, textDirection };
47
43
  }
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
2
+ * Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
5