@ckeditor/ckeditor5-language 48.2.0 → 48.3.0-alpha.1

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/dist/index.js CHANGED
@@ -2,412 +2,413 @@
2
2
  * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
4
  */
5
- import { Command, Plugin } from '@ckeditor/ckeditor5-core/dist/index.js';
6
- import { ModelDocumentSelection } from '@ckeditor/ckeditor5-engine/dist/index.js';
7
- import { getLanguageDirection, Collection } from '@ckeditor/ckeditor5-utils/dist/index.js';
8
- import { createDropdown, addListToDropdown, MenuBarMenuView, MenuBarMenuListView, ListSeparatorView, MenuBarMenuListItemView, MenuBarMenuListItemButtonView, UIModel } from '@ckeditor/ckeditor5-ui/dist/index.js';
5
+ import { Command, Plugin } from "@ckeditor/ckeditor5-core";
6
+ import { ModelDocumentSelection } from "@ckeditor/ckeditor5-engine";
7
+ import { Collection, getLanguageDirection } from "@ckeditor/ckeditor5-utils";
8
+ import { ListSeparatorView, MenuBarMenuListItemButtonView, MenuBarMenuListItemView, MenuBarMenuListView, MenuBarMenuView, UIModel, addListToDropdown, createDropdown } from "@ckeditor/ckeditor5-ui";
9
9
 
10
10
  /**
11
- * Returns the language attribute value in a human-readable text format:
12
- *
13
- * ```
14
- * <languageCode>:<textDirection>
15
- * ```
16
- *
17
- * * `languageCode` - The language code used for the `lang` attribute in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
18
- * * `textDirection` - One of the following values: `rtl` or `ltr`, indicating the reading direction of the language.
19
- *
20
- * See the {@link module:core/editor/editorconfig~LanguageConfig#textPartLanguage text part language configuration}
21
- * for more information about language properties.
22
- *
23
- * If the `textDirection` argument is omitted, it will be automatically detected based on `languageCode`.
24
- *
25
- * @param languageCode The language code in the ISO 639-1 format.
26
- * @param textDirection The language text direction. Automatically detected if omitted.
27
- * @internal
28
- */ function stringifyLanguageAttribute(languageCode, textDirection) {
29
- textDirection = textDirection || getLanguageDirection(languageCode);
30
- return `${languageCode}:${textDirection}`;
11
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
12
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
13
+ */
14
+ /**
15
+ * @module language/utils
16
+ */
17
+ /**
18
+ * Returns the language attribute value in a human-readable text format:
19
+ *
20
+ * ```
21
+ * <languageCode>:<textDirection>
22
+ * ```
23
+ *
24
+ * * `languageCode` - The language code used for the `lang` attribute in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
25
+ * * `textDirection` - One of the following values: `rtl` or `ltr`, indicating the reading direction of the language.
26
+ *
27
+ * See the {@link module:core/editor/editorconfig~LanguageConfig#textPartLanguage text part language configuration}
28
+ * for more information about language properties.
29
+ *
30
+ * If the `textDirection` argument is omitted, it will be automatically detected based on `languageCode`.
31
+ *
32
+ * @param languageCode The language code in the ISO 639-1 format.
33
+ * @param textDirection The language text direction. Automatically detected if omitted.
34
+ * @internal
35
+ */
36
+ function stringifyLanguageAttribute(languageCode, textDirection) {
37
+ textDirection = textDirection || getLanguageDirection(languageCode);
38
+ return `${languageCode}:${textDirection}`;
31
39
  }
32
40
  /**
33
- * Retrieves language properties converted to attribute value by the
34
- * {@link module:language/utils~stringifyLanguageAttribute stringifyLanguageAttribute} function.
35
- *
36
- * @internal
37
- * @param str The attribute value.
38
- * @returns The object with properties:
39
- * * languageCode - The language code in the ISO 639 format.
40
- * * textDirection - The language text direction.
41
- */ function parseLanguageAttribute(str) {
42
- const [languageCode, textDirection] = str.split(':');
43
- return {
44
- languageCode,
45
- textDirection
46
- };
41
+ * Retrieves language properties converted to attribute value by the
42
+ * {@link module:language/utils~stringifyLanguageAttribute stringifyLanguageAttribute} function.
43
+ *
44
+ * @internal
45
+ * @param str The attribute value.
46
+ * @returns The object with properties:
47
+ * * languageCode - The language code in the ISO 639 format.
48
+ * * textDirection - The language text direction.
49
+ */
50
+ function parseLanguageAttribute(str) {
51
+ const [languageCode, textDirection] = str.split(":");
52
+ return {
53
+ languageCode,
54
+ textDirection
55
+ };
47
56
  }
48
57
 
49
58
  /**
50
- * The text part language command plugin.
51
- */ class TextPartLanguageCommand extends Command {
52
- /**
53
- * @inheritDoc
54
- */ refresh() {
55
- const model = this.editor.model;
56
- const doc = model.document;
57
- this.value = this._getValueFromFirstAllowedNode();
58
- this.isEnabled = model.schema.checkAttributeInSelection(doc.selection, 'language');
59
- }
60
- /**
61
- * Executes the command. Applies the attribute to the selection or removes it from the selection.
62
- *
63
- * If `languageCode` is set to `false` or a `null` value, it will remove attributes. Otherwise, it will set
64
- * the attribute in the `{@link #value value}` format.
65
- *
66
- * The execution result differs, depending on the {@link module:engine/model/document~ModelDocument#selection}:
67
- *
68
- * * If the selection is on a range, the command applies the attribute to all nodes in that range
69
- * (if they are allowed to have this attribute by the {@link module:engine/model/schema~ModelSchema schema}).
70
- * * If the selection is collapsed in a non-empty node, the command applies the attribute to the
71
- * {@link module:engine/model/document~ModelDocument#selection} itself (note that typed characters copy attributes from the selection).
72
- * * If the selection is collapsed in an empty node, the command applies the attribute to the parent node of the selection (note
73
- * that the selection inherits all attributes from a node if it is in an empty node).
74
- *
75
- * @fires execute
76
- * @param options Command options.
77
- * @param options.languageCode The language code to be applied to the model.
78
- * @param options.textDirection The language text direction.
79
- */ execute({ languageCode, textDirection } = {}) {
80
- const model = this.editor.model;
81
- const doc = model.document;
82
- const selection = doc.selection;
83
- const value = languageCode ? stringifyLanguageAttribute(languageCode, textDirection) : false;
84
- model.change((writer)=>{
85
- if (selection.isCollapsed) {
86
- if (value) {
87
- writer.setSelectionAttribute('language', value);
88
- } else {
89
- writer.removeSelectionAttribute('language');
90
- }
91
- } else {
92
- const ranges = model.schema.getValidRanges(selection.getRanges(), 'language', {
93
- includeEmptyRanges: true
94
- });
95
- for (const range of ranges){
96
- let itemOrRange = range;
97
- let attributeKey = 'language';
98
- if (range.isCollapsed) {
99
- itemOrRange = range.start.parent;
100
- attributeKey = ModelDocumentSelection._getStoreAttributeKey('language');
101
- }
102
- if (value) {
103
- writer.setAttribute(attributeKey, value, itemOrRange);
104
- } else {
105
- writer.removeAttribute(attributeKey, itemOrRange);
106
- }
107
- }
108
- }
109
- });
110
- }
111
- /**
112
- * Returns the attribute value of the first node in the selection that allows the attribute.
113
- * For a collapsed selection it returns the selection attribute.
114
- *
115
- * @returns The attribute value.
116
- */ _getValueFromFirstAllowedNode() {
117
- const model = this.editor.model;
118
- const schema = model.schema;
119
- const selection = model.document.selection;
120
- if (selection.isCollapsed) {
121
- return selection.getAttribute('language') || false;
122
- }
123
- for (const range of selection.getRanges()){
124
- for (const item of range.getItems()){
125
- if (schema.checkAttribute(item, 'language')) {
126
- return item.getAttribute('language') || false;
127
- }
128
- }
129
- }
130
- return false;
131
- }
132
- }
59
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
60
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
61
+ */
62
+ /**
63
+ * The text part language command plugin.
64
+ */
65
+ var TextPartLanguageCommand = class extends Command {
66
+ /**
67
+ * @inheritDoc
68
+ */
69
+ refresh() {
70
+ const model = this.editor.model;
71
+ const doc = model.document;
72
+ this.value = this._getValueFromFirstAllowedNode();
73
+ this.isEnabled = model.schema.checkAttributeInSelection(doc.selection, "language");
74
+ }
75
+ /**
76
+ * Executes the command. Applies the attribute to the selection or removes it from the selection.
77
+ *
78
+ * If `languageCode` is set to `false` or a `null` value, it will remove attributes. Otherwise, it will set
79
+ * the attribute in the `{@link #value value}` format.
80
+ *
81
+ * The execution result differs, depending on the {@link module:engine/model/document~ModelDocument#selection}:
82
+ *
83
+ * * If the selection is on a range, the command applies the attribute to all nodes in that range
84
+ * (if they are allowed to have this attribute by the {@link module:engine/model/schema~ModelSchema schema}).
85
+ * * If the selection is collapsed in a non-empty node, the command applies the attribute to the
86
+ * {@link module:engine/model/document~ModelDocument#selection} itself (note that typed characters copy attributes from the selection).
87
+ * * If the selection is collapsed in an empty node, the command applies the attribute to the parent node of the selection (note
88
+ * that the selection inherits all attributes from a node if it is in an empty node).
89
+ *
90
+ * @fires execute
91
+ * @param options Command options.
92
+ * @param options.languageCode The language code to be applied to the model.
93
+ * @param options.textDirection The language text direction.
94
+ */
95
+ execute({ languageCode, textDirection } = {}) {
96
+ const model = this.editor.model;
97
+ const selection = model.document.selection;
98
+ const value = languageCode ? stringifyLanguageAttribute(languageCode, textDirection) : false;
99
+ model.change((writer) => {
100
+ if (selection.isCollapsed) if (value) writer.setSelectionAttribute("language", value);
101
+ else writer.removeSelectionAttribute("language");
102
+ else {
103
+ const ranges = model.schema.getValidRanges(selection.getRanges(), "language", { includeEmptyRanges: true });
104
+ for (const range of ranges) {
105
+ let itemOrRange = range;
106
+ let attributeKey = "language";
107
+ if (range.isCollapsed) {
108
+ itemOrRange = range.start.parent;
109
+ attributeKey = ModelDocumentSelection._getStoreAttributeKey("language");
110
+ }
111
+ if (value) writer.setAttribute(attributeKey, value, itemOrRange);
112
+ else writer.removeAttribute(attributeKey, itemOrRange);
113
+ }
114
+ }
115
+ });
116
+ }
117
+ /**
118
+ * Returns the attribute value of the first node in the selection that allows the attribute.
119
+ * For a collapsed selection it returns the selection attribute.
120
+ *
121
+ * @returns The attribute value.
122
+ */
123
+ _getValueFromFirstAllowedNode() {
124
+ const model = this.editor.model;
125
+ const schema = model.schema;
126
+ const selection = model.document.selection;
127
+ if (selection.isCollapsed) return selection.getAttribute("language") || false;
128
+ for (const range of selection.getRanges()) for (const item of range.getItems()) if (schema.checkAttribute(item, "language")) return item.getAttribute("language") || false;
129
+ return false;
130
+ }
131
+ };
133
132
 
134
133
  /**
135
- * The text part language editing.
136
- *
137
- * Introduces the `'textPartLanguage'` command and the `'language'` model element attribute.
138
- */ class TextPartLanguageEditing extends Plugin {
139
- /**
140
- * @inheritDoc
141
- */ static get pluginName() {
142
- return 'TextPartLanguageEditing';
143
- }
144
- /**
145
- * @inheritDoc
146
- */ static get isOfficialPlugin() {
147
- return true;
148
- }
149
- /**
150
- * @inheritDoc
151
- */ constructor(editor){
152
- super(editor);
153
- // Text part language options are only used to ensure that the feature works by default.
154
- // In the real usage it should be reconfigured by a developer. We are not providing
155
- // translations for `title` properties on purpose, as it's only an example configuration.
156
- editor.config.define('language', {
157
- textPartLanguage: [
158
- {
159
- title: 'Arabic',
160
- languageCode: 'ar'
161
- },
162
- {
163
- title: 'French',
164
- languageCode: 'fr'
165
- },
166
- {
167
- title: 'Spanish',
168
- languageCode: 'es'
169
- }
170
- ]
171
- });
172
- }
173
- /**
174
- * @inheritDoc
175
- */ init() {
176
- const editor = this.editor;
177
- editor.model.schema.extend('$text', {
178
- allowAttributes: 'language'
179
- });
180
- editor.model.schema.setAttributeProperties('language', {
181
- copyOnEnter: true
182
- });
183
- this._defineConverters();
184
- editor.commands.add('textPartLanguage', new TextPartLanguageCommand(editor));
185
- }
186
- /**
187
- * @private
188
- */ _defineConverters() {
189
- const conversion = this.editor.conversion;
190
- conversion.for('upcast').elementToAttribute({
191
- model: {
192
- key: 'language',
193
- value: (viewElement)=>{
194
- const languageCode = viewElement.getAttribute('lang');
195
- const textDirection = viewElement.getAttribute('dir');
196
- return stringifyLanguageAttribute(languageCode, textDirection);
197
- }
198
- },
199
- view: {
200
- name: 'span',
201
- attributes: {
202
- lang: /[\s\S]+/
203
- }
204
- }
205
- });
206
- conversion.for('downcast').attributeToElement({
207
- model: 'language',
208
- view: (attributeValue, { writer }, data)=>{
209
- if (!attributeValue) {
210
- return;
211
- }
212
- if (!data.item.is('$textProxy') && !data.item.is('documentSelection')) {
213
- return;
214
- }
215
- const { languageCode, textDirection } = parseLanguageAttribute(attributeValue);
216
- return writer.createAttributeElement('span', {
217
- lang: languageCode,
218
- dir: textDirection
219
- });
220
- }
221
- });
222
- }
223
- }
134
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
135
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
136
+ */
137
+ /**
138
+ * The text part language editing.
139
+ *
140
+ * Introduces the `'textPartLanguage'` command and the `'language'` model element attribute.
141
+ */
142
+ var TextPartLanguageEditing = class extends Plugin {
143
+ /**
144
+ * @inheritDoc
145
+ */
146
+ static get pluginName() {
147
+ return "TextPartLanguageEditing";
148
+ }
149
+ /**
150
+ * @inheritDoc
151
+ */
152
+ static get isOfficialPlugin() {
153
+ return true;
154
+ }
155
+ /**
156
+ * @inheritDoc
157
+ */
158
+ constructor(editor) {
159
+ super(editor);
160
+ editor.config.define("language", { textPartLanguage: [
161
+ {
162
+ title: "Arabic",
163
+ languageCode: "ar"
164
+ },
165
+ {
166
+ title: "French",
167
+ languageCode: "fr"
168
+ },
169
+ {
170
+ title: "Spanish",
171
+ languageCode: "es"
172
+ }
173
+ ] });
174
+ }
175
+ /**
176
+ * @inheritDoc
177
+ */
178
+ init() {
179
+ const editor = this.editor;
180
+ editor.model.schema.extend("$text", { allowAttributes: "language" });
181
+ editor.model.schema.setAttributeProperties("language", { copyOnEnter: true });
182
+ this._defineConverters();
183
+ editor.commands.add("textPartLanguage", new TextPartLanguageCommand(editor));
184
+ }
185
+ /**
186
+ * @private
187
+ */
188
+ _defineConverters() {
189
+ const conversion = this.editor.conversion;
190
+ conversion.for("upcast").elementToAttribute({
191
+ model: {
192
+ key: "language",
193
+ value: (viewElement) => {
194
+ return stringifyLanguageAttribute(viewElement.getAttribute("lang"), viewElement.getAttribute("dir"));
195
+ }
196
+ },
197
+ view: {
198
+ name: "span",
199
+ attributes: { lang: /[\s\S]+/ }
200
+ }
201
+ });
202
+ conversion.for("downcast").attributeToElement({
203
+ model: "language",
204
+ view: (attributeValue, { writer }, data) => {
205
+ if (!attributeValue) return;
206
+ if (!data.item.is("$textProxy") && !data.item.is("documentSelection")) return;
207
+ const { languageCode, textDirection } = parseLanguageAttribute(attributeValue);
208
+ return writer.createAttributeElement("span", {
209
+ lang: languageCode,
210
+ dir: textDirection
211
+ });
212
+ }
213
+ });
214
+ }
215
+ };
224
216
 
225
217
  /**
226
- * The text part language UI plugin.
227
- *
228
- * It introduces the `'language'` dropdown.
229
- */ class TextPartLanguageUI extends Plugin {
230
- /**
231
- * @inheritDoc
232
- */ static get pluginName() {
233
- return 'TextPartLanguageUI';
234
- }
235
- /**
236
- * @inheritDoc
237
- */ static get isOfficialPlugin() {
238
- return true;
239
- }
240
- /**
241
- * @inheritDoc
242
- */ init() {
243
- const editor = this.editor;
244
- const t = editor.t;
245
- const defaultTitle = t('Choose language');
246
- const accessibleLabel = t('Language');
247
- // Register UI component.
248
- editor.ui.componentFactory.add('textPartLanguage', (locale)=>{
249
- const { definitions, titles } = this._getItemMetadata();
250
- const languageCommand = editor.commands.get('textPartLanguage');
251
- const dropdownView = createDropdown(locale);
252
- addListToDropdown(dropdownView, definitions, {
253
- ariaLabel: accessibleLabel,
254
- role: 'menu'
255
- });
256
- dropdownView.buttonView.set({
257
- ariaLabel: accessibleLabel,
258
- ariaLabelledBy: undefined,
259
- isOn: false,
260
- withText: true,
261
- tooltip: accessibleLabel
262
- });
263
- dropdownView.extendTemplate({
264
- attributes: {
265
- class: [
266
- 'ck-text-fragment-language-dropdown'
267
- ]
268
- }
269
- });
270
- dropdownView.bind('isEnabled').to(languageCommand, 'isEnabled');
271
- dropdownView.buttonView.bind('label').to(languageCommand, 'value', (value)=>{
272
- return value && titles[value] || defaultTitle;
273
- });
274
- dropdownView.buttonView.bind('ariaLabel').to(languageCommand, 'value', (value)=>{
275
- const selectedLanguageTitle = value && titles[value];
276
- if (!selectedLanguageTitle) {
277
- return accessibleLabel;
278
- }
279
- return `${selectedLanguageTitle}, ${accessibleLabel}`;
280
- });
281
- // Execute command when an item from the dropdown is selected.
282
- this.listenTo(dropdownView, 'execute', (evt)=>{
283
- languageCommand.execute({
284
- languageCode: evt.source.languageCode,
285
- textDirection: evt.source.textDirection
286
- });
287
- editor.editing.view.focus();
288
- });
289
- return dropdownView;
290
- });
291
- // Register menu bar UI component.
292
- editor.ui.componentFactory.add('menuBar:textPartLanguage', (locale)=>{
293
- const { definitions } = this._getItemMetadata();
294
- const languageCommand = editor.commands.get('textPartLanguage');
295
- const menuView = new MenuBarMenuView(locale);
296
- menuView.buttonView.set({
297
- label: accessibleLabel
298
- });
299
- const listView = new MenuBarMenuListView(locale);
300
- listView.set({
301
- ariaLabel: t('Language'),
302
- role: 'menu'
303
- });
304
- for (const definition of definitions){
305
- if (definition.type != 'button') {
306
- listView.items.add(new ListSeparatorView(locale));
307
- continue;
308
- }
309
- const listItemView = new MenuBarMenuListItemView(locale, menuView);
310
- const buttonView = new MenuBarMenuListItemButtonView(locale);
311
- buttonView.set({
312
- role: 'menuitemradio',
313
- isToggleable: true
314
- });
315
- buttonView.bind(...Object.keys(definition.model)).to(definition.model);
316
- buttonView.delegate('execute').to(menuView);
317
- listItemView.children.add(buttonView);
318
- listView.items.add(listItemView);
319
- }
320
- menuView.bind('isEnabled').to(languageCommand, 'isEnabled');
321
- menuView.panelView.children.add(listView);
322
- menuView.on('execute', (evt)=>{
323
- languageCommand.execute({
324
- languageCode: evt.source.languageCode,
325
- textDirection: evt.source.textDirection
326
- });
327
- editor.editing.view.focus();
328
- });
329
- return menuView;
330
- });
331
- }
332
- /**
333
- * Returns metadata for dropdown and menu items.
334
- */ _getItemMetadata() {
335
- const editor = this.editor;
336
- const itemDefinitions = new Collection();
337
- const titles = {};
338
- const languageCommand = editor.commands.get('textPartLanguage');
339
- const options = editor.config.get('language.textPartLanguage');
340
- const t = editor.locale.t;
341
- const removeTitle = t('Remove language');
342
- // Item definition with false `languageCode` will behave as remove lang button.
343
- itemDefinitions.add({
344
- type: 'button',
345
- model: new UIModel({
346
- label: removeTitle,
347
- languageCode: false,
348
- withText: true
349
- })
350
- });
351
- itemDefinitions.add({
352
- type: 'separator'
353
- });
354
- for (const option of options){
355
- const def = {
356
- type: 'button',
357
- model: new UIModel({
358
- label: option.title,
359
- languageCode: option.languageCode,
360
- role: 'menuitemradio',
361
- textDirection: option.textDirection,
362
- withText: true
363
- })
364
- };
365
- const language = stringifyLanguageAttribute(option.languageCode, option.textDirection);
366
- def.model.bind('isOn').to(languageCommand, 'value', (value)=>value === language);
367
- itemDefinitions.add(def);
368
- titles[language] = option.title;
369
- }
370
- return {
371
- definitions: itemDefinitions,
372
- titles
373
- };
374
- }
375
- }
218
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
219
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
220
+ */
221
+ /**
222
+ * @module language/textpartlanguageui
223
+ */
224
+ /**
225
+ * The text part language UI plugin.
226
+ *
227
+ * It introduces the `'language'` dropdown.
228
+ */
229
+ var TextPartLanguageUI = class extends Plugin {
230
+ /**
231
+ * @inheritDoc
232
+ */
233
+ static get pluginName() {
234
+ return "TextPartLanguageUI";
235
+ }
236
+ /**
237
+ * @inheritDoc
238
+ */
239
+ static get isOfficialPlugin() {
240
+ return true;
241
+ }
242
+ /**
243
+ * @inheritDoc
244
+ */
245
+ init() {
246
+ const editor = this.editor;
247
+ const t = editor.t;
248
+ const defaultTitle = t("Choose language");
249
+ const accessibleLabel = t("Language");
250
+ editor.ui.componentFactory.add("textPartLanguage", (locale) => {
251
+ const { definitions, titles } = this._getItemMetadata();
252
+ const languageCommand = editor.commands.get("textPartLanguage");
253
+ const dropdownView = createDropdown(locale);
254
+ addListToDropdown(dropdownView, definitions, {
255
+ ariaLabel: accessibleLabel,
256
+ role: "menu"
257
+ });
258
+ dropdownView.buttonView.set({
259
+ ariaLabel: accessibleLabel,
260
+ ariaLabelledBy: void 0,
261
+ isOn: false,
262
+ withText: true,
263
+ tooltip: accessibleLabel
264
+ });
265
+ dropdownView.extendTemplate({ attributes: { class: ["ck-text-fragment-language-dropdown"] } });
266
+ dropdownView.bind("isEnabled").to(languageCommand, "isEnabled");
267
+ dropdownView.buttonView.bind("label").to(languageCommand, "value", (value) => {
268
+ return value && titles[value] || defaultTitle;
269
+ });
270
+ dropdownView.buttonView.bind("ariaLabel").to(languageCommand, "value", (value) => {
271
+ const selectedLanguageTitle = value && titles[value];
272
+ if (!selectedLanguageTitle) return accessibleLabel;
273
+ return `${selectedLanguageTitle}, ${accessibleLabel}`;
274
+ });
275
+ this.listenTo(dropdownView, "execute", (evt) => {
276
+ languageCommand.execute({
277
+ languageCode: evt.source.languageCode,
278
+ textDirection: evt.source.textDirection
279
+ });
280
+ editor.editing.view.focus();
281
+ });
282
+ return dropdownView;
283
+ });
284
+ editor.ui.componentFactory.add("menuBar:textPartLanguage", (locale) => {
285
+ const { definitions } = this._getItemMetadata();
286
+ const languageCommand = editor.commands.get("textPartLanguage");
287
+ const menuView = new MenuBarMenuView(locale);
288
+ menuView.buttonView.set({ label: accessibleLabel });
289
+ const listView = new MenuBarMenuListView(locale);
290
+ listView.set({
291
+ ariaLabel: t("Language"),
292
+ role: "menu"
293
+ });
294
+ for (const definition of definitions) {
295
+ if (definition.type != "button") {
296
+ listView.items.add(new ListSeparatorView(locale));
297
+ continue;
298
+ }
299
+ const listItemView = new MenuBarMenuListItemView(locale, menuView);
300
+ const buttonView = new MenuBarMenuListItemButtonView(locale);
301
+ buttonView.set({
302
+ role: "menuitemradio",
303
+ isToggleable: true
304
+ });
305
+ buttonView.bind(...Object.keys(definition.model)).to(definition.model);
306
+ buttonView.delegate("execute").to(menuView);
307
+ listItemView.children.add(buttonView);
308
+ listView.items.add(listItemView);
309
+ }
310
+ menuView.bind("isEnabled").to(languageCommand, "isEnabled");
311
+ menuView.panelView.children.add(listView);
312
+ menuView.on("execute", (evt) => {
313
+ languageCommand.execute({
314
+ languageCode: evt.source.languageCode,
315
+ textDirection: evt.source.textDirection
316
+ });
317
+ editor.editing.view.focus();
318
+ });
319
+ return menuView;
320
+ });
321
+ }
322
+ /**
323
+ * Returns metadata for dropdown and menu items.
324
+ */
325
+ _getItemMetadata() {
326
+ const editor = this.editor;
327
+ const itemDefinitions = new Collection();
328
+ const titles = {};
329
+ const languageCommand = editor.commands.get("textPartLanguage");
330
+ const options = editor.config.get("language.textPartLanguage");
331
+ const t = editor.locale.t;
332
+ const removeTitle = t("Remove language");
333
+ itemDefinitions.add({
334
+ type: "button",
335
+ model: new UIModel({
336
+ label: removeTitle,
337
+ languageCode: false,
338
+ withText: true
339
+ })
340
+ });
341
+ itemDefinitions.add({ type: "separator" });
342
+ for (const option of options) {
343
+ const def = {
344
+ type: "button",
345
+ model: new UIModel({
346
+ label: option.title,
347
+ languageCode: option.languageCode,
348
+ role: "menuitemradio",
349
+ textDirection: option.textDirection,
350
+ withText: true
351
+ })
352
+ };
353
+ const language = stringifyLanguageAttribute(option.languageCode, option.textDirection);
354
+ def.model.bind("isOn").to(languageCommand, "value", (value) => value === language);
355
+ itemDefinitions.add(def);
356
+ titles[language] = option.title;
357
+ }
358
+ return {
359
+ definitions: itemDefinitions,
360
+ titles
361
+ };
362
+ }
363
+ };
376
364
 
377
365
  /**
378
- * The text part language feature.
379
- *
380
- * This feature allows setting a language of the document's text part to support
381
- * [WCAG 3.1.2 Language of Parts](https://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-other-lang-id.html) specification.
382
- *
383
- * To change the editor's UI language, refer to the {@glink getting-started/setup/ui-language Setting the UI language} guide.
384
- *
385
- * For more information about this feature, check the {@glink api/language package page} as well as the {@glink features/language
386
- * Text part language} feature guide.
387
- *
388
- * This is a "glue" plugin which loads the
389
- * {@link module:language/textpartlanguageediting~TextPartLanguageEditing text part language editing feature}
390
- * and the {@link module:language/textpartlanguageui~TextPartLanguageUI text part language UI feature}.
391
- */ class TextPartLanguage extends Plugin {
392
- /**
393
- * @inheritDoc
394
- */ static get requires() {
395
- return [
396
- TextPartLanguageEditing,
397
- TextPartLanguageUI
398
- ];
399
- }
400
- /**
401
- * @inheritDoc
402
- */ static get pluginName() {
403
- return 'TextPartLanguage';
404
- }
405
- /**
406
- * @inheritDoc
407
- */ static get isOfficialPlugin() {
408
- return true;
409
- }
410
- }
366
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
367
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
368
+ */
369
+ /**
370
+ * @module language/textpartlanguage
371
+ */
372
+ /**
373
+ * The text part language feature.
374
+ *
375
+ * This feature allows setting a language of the document's text part to support
376
+ * [WCAG 3.1.2 Language of Parts](https://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-other-lang-id.html) specification.
377
+ *
378
+ * To change the editor's UI language, refer to the {@glink getting-started/setup/ui-language Setting the UI language} guide.
379
+ *
380
+ * For more information about this feature, check the {@glink api/language package page} as well as the {@glink features/language
381
+ * Text part language} feature guide.
382
+ *
383
+ * This is a "glue" plugin which loads the
384
+ * {@link module:language/textpartlanguageediting~TextPartLanguageEditing text part language editing feature}
385
+ * and the {@link module:language/textpartlanguageui~TextPartLanguageUI text part language UI feature}.
386
+ */
387
+ var TextPartLanguage = class extends Plugin {
388
+ /**
389
+ * @inheritDoc
390
+ */
391
+ static get requires() {
392
+ return [TextPartLanguageEditing, TextPartLanguageUI];
393
+ }
394
+ /**
395
+ * @inheritDoc
396
+ */
397
+ static get pluginName() {
398
+ return "TextPartLanguage";
399
+ }
400
+ /**
401
+ * @inheritDoc
402
+ */
403
+ static get isOfficialPlugin() {
404
+ return true;
405
+ }
406
+ };
407
+
408
+ /**
409
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
410
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
411
+ */
411
412
 
412
413
  export { TextPartLanguage, TextPartLanguageCommand, TextPartLanguageEditing, TextPartLanguageUI, parseLanguageAttribute as _parseLanguageAttribute, stringifyLanguageAttribute as _stringifyLanguageAttribute };
413
- //# sourceMappingURL=index.js.map
414
+ //# sourceMappingURL=index.js.map