UNPKG

@ckeditor/ckeditor5-language

Version:

Text part language feature for CKEditor 5.

168 lines (167 loc) 6.54 kB
/** * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ /** * @module language/textpartlanguageui */ import { Plugin } from 'ckeditor5/src/core.js'; import { addListToDropdown, createDropdown, ListSeparatorView, MenuBarMenuView, MenuBarMenuListView, MenuBarMenuListItemView, MenuBarMenuListItemButtonView, ViewModel } from 'ckeditor5/src/ui.js'; import { Collection } from 'ckeditor5/src/utils.js'; import { stringifyLanguageAttribute } from './utils.js'; /** * The text part language UI plugin. * * It introduces the `'language'` dropdown. */ export default class TextPartLanguageUI extends Plugin { /** * @inheritDoc */ static get pluginName() { return 'TextPartLanguageUI'; } /** * @inheritDoc */ static get isOfficialPlugin() { return true; } /** * @inheritDoc */ init() { const editor = this.editor; const t = editor.t; const defaultTitle = t('Choose language'); const accessibleLabel = t('Language'); // Register UI component. editor.ui.componentFactory.add('textPartLanguage', locale => { const { definitions, titles } = this._getItemMetadata(); const languageCommand = editor.commands.get('textPartLanguage'); const dropdownView = createDropdown(locale); addListToDropdown(dropdownView, definitions, { ariaLabel: accessibleLabel, role: 'menu' }); dropdownView.buttonView.set({ ariaLabel: accessibleLabel, ariaLabelledBy: undefined, isOn: false, withText: true, tooltip: accessibleLabel }); dropdownView.extendTemplate({ attributes: { class: [ 'ck-text-fragment-language-dropdown' ] } }); dropdownView.bind('isEnabled').to(languageCommand, 'isEnabled'); dropdownView.buttonView.bind('label').to(languageCommand, 'value', value => { return (value && titles[value]) || defaultTitle; }); dropdownView.buttonView.bind('ariaLabel').to(languageCommand, 'value', value => { const selectedLanguageTitle = value && titles[value]; if (!selectedLanguageTitle) { return accessibleLabel; } return `${selectedLanguageTitle}, ${accessibleLabel}`; }); // Execute command when an item from the dropdown is selected. this.listenTo(dropdownView, 'execute', evt => { languageCommand.execute({ languageCode: evt.source.languageCode, textDirection: evt.source.textDirection }); editor.editing.view.focus(); }); return dropdownView; }); // Register menu bar UI component. editor.ui.componentFactory.add('menuBar:textPartLanguage', locale => { const { definitions } = this._getItemMetadata(); const languageCommand = editor.commands.get('textPartLanguage'); const menuView = new MenuBarMenuView(locale); menuView.buttonView.set({ label: accessibleLabel }); const listView = new MenuBarMenuListView(locale); listView.set({ ariaLabel: t('Language'), role: 'menu' }); for (const definition of definitions) { if (definition.type != 'button') { listView.items.add(new ListSeparatorView(locale)); continue; } const listItemView = new MenuBarMenuListItemView(locale, menuView); const buttonView = new MenuBarMenuListItemButtonView(locale); buttonView.set({ role: 'menuitemradio', isToggleable: true }); buttonView.bind(...Object.keys(definition.model)).to(definition.model); buttonView.delegate('execute').to(menuView); listItemView.children.add(buttonView); listView.items.add(listItemView); } menuView.bind('isEnabled').to(languageCommand, 'isEnabled'); menuView.panelView.children.add(listView); menuView.on('execute', evt => { languageCommand.execute({ languageCode: evt.source.languageCode, textDirection: evt.source.textDirection }); editor.editing.view.focus(); }); return menuView; }); } /** * Returns metadata for dropdown and menu items. */ _getItemMetadata() { const editor = this.editor; const itemDefinitions = new Collection(); const titles = {}; const languageCommand = editor.commands.get('textPartLanguage'); const options = editor.config.get('language.textPartLanguage'); const t = editor.locale.t; const removeTitle = t('Remove language'); // Item definition with false `languageCode` will behave as remove lang button. itemDefinitions.add({ type: 'button', model: new ViewModel({ label: removeTitle, languageCode: false, withText: true }) }); itemDefinitions.add({ type: 'separator' }); for (const option of options) { const def = { type: 'button', model: new ViewModel({ label: option.title, languageCode: option.languageCode, role: 'menuitemradio', textDirection: option.textDirection, withText: true }) }; const language = stringifyLanguageAttribute(option.languageCode, option.textDirection); def.model.bind('isOn').to(languageCommand, 'value', value => value === language); itemDefinitions.add(def); titles[language] = option.title; } return { definitions: itemDefinitions, titles }; } }