From 765db115d6373163c9db952b08b610362064ef0b Mon Sep 17 00:00:00 2001 From: l-lejman Date: Fri, 9 May 2025 12:21:59 +0200 Subject: [PATCH 1/6] Fix manual tests language.ui value. --- packages/ckeditor5-core/src/context.ts | 36 ++++++++++++++--- packages/ckeditor5-core/tests/context.js | 28 +++++++++++-- .../ckeditor5-core/tests/editor/editor.js | 40 ++++++++++++++++--- 3 files changed, 90 insertions(+), 14 deletions(-) diff --git a/packages/ckeditor5-core/src/context.ts b/packages/ckeditor5-core/src/context.ts index a62feeab1fa..80f324bb5de 100644 --- a/packages/ckeditor5-core/src/context.ts +++ b/packages/ckeditor5-core/src/context.ts @@ -12,6 +12,7 @@ import { Collection, CKEditorError, Locale, + global, type LocaleTranslate } from '@ckeditor/ckeditor5-utils'; @@ -160,11 +161,36 @@ export default class Context { const languageConfig = this.config.get( 'language' ) || {}; - this.locale = new Locale( { - uiLanguage: typeof languageConfig === 'string' ? languageConfig : languageConfig.ui, - contentLanguage: this.config.get( 'language.content' ), - translations - } ); + if ( !translations && global.window.CKEDITOR_TRANSLATIONS ) { + /** + * Function _translate from translation-service.ts gets translations from + * global.window.CKEDITOR_TRANSLATIONS when translations injected into Locale are empty. + * Value of global.window.CKEDITOR_TRANSLATIONS is provided by CKEditorTranslationsPlugin from + * ckeditor5-dev-translations package (for example in manual tests) when --language flag is used. + * Function _translate is called often and has no access to the editing editor config, so here is a better place to + * check if translations will be taken from dev-translations, and if yes – update config.language.ui value. + */ + const devTranslations = global.window.CKEDITOR_TRANSLATIONS; + const uiLanguageFromConfig = typeof languageConfig === 'string' ? languageConfig : languageConfig.ui; + const hasMatchingTranslations = devTranslations[ uiLanguageFromConfig! ]; + const defaultDevTranslationsLanguage = Object.keys( devTranslations )[ 0 ]; + + this.locale = new Locale( { + uiLanguage: hasMatchingTranslations ? uiLanguageFromConfig : defaultDevTranslationsLanguage, + contentLanguage: this.config.get( 'language.content' ), + translations: global.window.CKEDITOR_TRANSLATIONS + } ); + + if ( !hasMatchingTranslations && this.config.get( 'language.ui' ) !== defaultDevTranslationsLanguage ) { + this.config.define( 'language.ui', defaultDevTranslationsLanguage ); + } + } else { + this.locale = new Locale( { + uiLanguage: typeof languageConfig === 'string' ? languageConfig : languageConfig.ui, + contentLanguage: this.config.get( 'language.content' ), + translations + } ); + } this.t = this.locale.t; diff --git a/packages/ckeditor5-core/tests/context.js b/packages/ckeditor5-core/tests/context.js index 83935c91864..f618cf5d948 100644 --- a/packages/ckeditor5-core/tests/context.js +++ b/packages/ckeditor5-core/tests/context.js @@ -12,7 +12,7 @@ import Locale from '@ckeditor/ckeditor5-utils/src/locale.js'; import VirtualTestEditor from './_utils/virtualtesteditor.js'; import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror.js'; -/* globals document */ +/* globals document, window */ describe( 'Context', () => { describe( 'config', () => { @@ -93,26 +93,46 @@ describe( 'Context', () => { } ); it( 'is configured with the config.language (UI and the content)', () => { - const context = new Context( { language: 'pl' } ); + const context = new Context( { language: 'pl', translations: { pl: {} } } ); expect( context.locale.uiLanguage ).to.equal( 'pl' ); expect( context.locale.contentLanguage ).to.equal( 'pl' ); } ); it( 'is configured with the config.language (different for UI and the content)', () => { - const context = new Context( { language: { ui: 'pl', content: 'ar' } } ); + const context = new Context( { language: { ui: 'pl', content: 'ar' }, translations: { pl: {} } } ); expect( context.locale.uiLanguage ).to.equal( 'pl' ); expect( context.locale.contentLanguage ).to.equal( 'ar' ); } ); it( 'is configured with the config.language (just the content)', () => { - const context = new Context( { language: { content: 'ar' } } ); + const context = new Context( { language: { content: 'ar' }, translations: { pl: {} } } ); expect( context.locale.uiLanguage ).to.equal( 'en' ); expect( context.locale.contentLanguage ).to.equal( 'ar' ); } ); + it( 'is configured with the default config.language (when no translations provided)', () => { + const context = new Context( { language: 'pl' } ); + + expect( context.locale.uiLanguage ).to.equal( 'en' ); + expect( context.locale.contentLanguage ).to.equal( 'en' ); + } ); + + it( 'is configured with config.language (when no translations provided and dev-translations are matching)', () => { + window.CKEDITOR_TRANSLATIONS = { + en: { dictionary: { + key: '' + }, + getPluralForm: () => '' } + }; + const context = new Context( { language: { ui: 'en', content: 'en' } } ); + + expect( context.locale.uiLanguage ).to.equal( 'en' ); + expect( context.locale.contentLanguage ).to.equal( 'en' ); + } ); + it( 'is configured with the config.translations', () => { const context = new Context( { translations: { diff --git a/packages/ckeditor5-core/tests/editor/editor.js b/packages/ckeditor5-core/tests/editor/editor.js index 10674e4d58a..ebc23a7e9e7 100644 --- a/packages/ckeditor5-core/tests/editor/editor.js +++ b/packages/ckeditor5-core/tests/editor/editor.js @@ -353,7 +353,13 @@ describe( 'Editor', () => { it( 'should use locale instance with a proper configuration passed as the argument to the constructor', () => { const editor = new TestEditor( { - language: 'pl' + language: 'pl', + translations: [ { + pl: { + dictionary: [], + getPluralForm: sinon.spy() + } + } ] } ); expect( editor.locale ).to.have.property( 'uiLanguage', 'pl' ); @@ -362,7 +368,13 @@ describe( 'Editor', () => { it( 'should use locale instance with a proper configuration set as the defaultConfig option on the constructor', () => { TestEditor.defaultConfig = { - language: 'pl' + language: 'pl', + translations: [ { + pl: { + dictionary: [], + getPluralForm: sinon.spy() + } + } ] }; const editor = new TestEditor(); @@ -373,7 +385,13 @@ describe( 'Editor', () => { it( 'should prefer the language passed as the argument to the constructor instead of the defaultConfig if both are set', () => { TestEditor.defaultConfig = { - language: 'de' + language: 'de', + translations: [ { + pl: { + dictionary: [], + getPluralForm: sinon.spy() + } + } ] }; const editor = new TestEditor( { @@ -386,10 +404,22 @@ describe( 'Editor', () => { it( 'should prefer the language from the context instead of the constructor config or defaultConfig if all are set', async () => { TestEditor.defaultConfig = { - language: 'de' + language: 'de', + translations: [ { + pl: { + dictionary: [], + getPluralForm: sinon.spy() + } + } ] }; - const context = await Context.create( { language: 'pl' } ); + const context = await Context.create( { + language: 'pl', + translations: [ { pl: { + dictionary: [], + getPluralForm: sinon.spy() + } } ] + } ); const editor = new TestEditor( { context, language: 'ru' } ); expect( editor.locale ).to.have.property( 'uiLanguage', 'pl' ); From 5bba8dc54e98c1405db39b157b8671f76094b241 Mon Sep 17 00:00:00 2001 From: l-lejman Date: Fri, 9 May 2025 15:53:34 +0200 Subject: [PATCH 2/6] Adjust tests to manuals language change. --- .../ckeditor5-alignment/tests/alignmentui.js | 8 ++++++++ packages/ckeditor5-ckbox/tests/ckboxutils.js | 4 ++++ .../ckeditor5-ckfinder/tests/ckfindercommand.js | 6 +++++- packages/ckeditor5-core/src/context.ts | 5 +++-- packages/ckeditor5-font/tests/ui/colorui.js | 7 ++++++- packages/ckeditor5-indent/tests/indentui.js | 6 +++++- .../legacytodolist/legacytodolistediting.js | 4 ++++ .../tablecellpropertiesediting.js | 6 +++++- .../tablecolumnresizeediting.js | 6 +++++- packages/ckeditor5-table/tests/tablekeyboard.js | 6 +++++- packages/ckeditor5-ui/tests/badge/badge.js | 6 +++++- .../tests/editorui/evaluationbadge.js | 6 +++++- .../ckeditor5-ui/tests/editorui/poweredby.js | 6 +++++- packages/ckeditor5-undo/tests/undoui.js | 16 +++++++++++++--- 14 files changed, 78 insertions(+), 14 deletions(-) diff --git a/packages/ckeditor5-alignment/tests/alignmentui.js b/packages/ckeditor5-alignment/tests/alignmentui.js index 3c9b758d0d2..793431152a3 100644 --- a/packages/ckeditor5-alignment/tests/alignmentui.js +++ b/packages/ckeditor5-alignment/tests/alignmentui.js @@ -305,6 +305,10 @@ describe( 'Alignment UI', () => { content: 'ar', ui: 'ar' }, + translations: [ { ar: { + dictionary: [], + getPluralForm: sinon.spy() + } } ], plugins: [ AlignmentEditing, AlignmentUI ] } ); @@ -439,6 +443,10 @@ describe( 'Alignment UI', () => { language: { content: 'ar' }, + translations: [ { ar: { + dictionary: [], + getPluralForm: sinon.spy() + } } ], plugins: [ AlignmentEditing, AlignmentUI ], alignment: { options: [ 'center', 'justify' ] } } ) diff --git a/packages/ckeditor5-ckbox/tests/ckboxutils.js b/packages/ckeditor5-ckbox/tests/ckboxutils.js index 9aa0be03b52..b21446af40e 100644 --- a/packages/ckeditor5-ckbox/tests/ckboxutils.js +++ b/packages/ckeditor5-ckbox/tests/ckboxutils.js @@ -314,6 +314,10 @@ describe( 'CKBoxUtils', () => { it( 'should set default values', async () => { const editor = await createTestEditor( { language: 'pl', + translations: [ { pl: { + dictionary: [], + getPluralForm: sinon.spy() + } } ], cloudServices: { tokenUrl: 'http://cs.example.com' } diff --git a/packages/ckeditor5-ckfinder/tests/ckfindercommand.js b/packages/ckeditor5-ckfinder/tests/ckfindercommand.js index b3ad9362047..9466d163959 100644 --- a/packages/ckeditor5-ckfinder/tests/ckfindercommand.js +++ b/packages/ckeditor5-ckfinder/tests/ckfindercommand.js @@ -262,7 +262,11 @@ describe( 'CKFinderCommand', () => { return VirtualTestEditor .create( { plugins: [ Paragraph, ImageBlockEditing, ImageUploadEditing, LinkEditing, Notification, ClipboardPipeline ], - language: 'pl' + language: 'pl', + translations: [ { pl: { + dictionary: [], + getPluralForm: sinon.spy() + } } ] } ) .then( newEditor => { editor = newEditor; diff --git a/packages/ckeditor5-core/src/context.ts b/packages/ckeditor5-core/src/context.ts index 80f324bb5de..e5c046bafa3 100644 --- a/packages/ckeditor5-core/src/context.ts +++ b/packages/ckeditor5-core/src/context.ts @@ -20,6 +20,7 @@ import PluginCollection from './plugincollection.js'; import type Editor from './editor/editor.js'; import type { LoadedPlugins, PluginConstructor } from './plugin.js'; import type { EditorConfig } from './editor/editorconfig.js'; +import { cloneDeep } from 'es-toolkit/compat'; /** * Provides a common, higher-level environment for solutions that use multiple {@link module:core/editor/editor~Editor editors} @@ -170,7 +171,7 @@ export default class Context { * Function _translate is called often and has no access to the editing editor config, so here is a better place to * check if translations will be taken from dev-translations, and if yes – update config.language.ui value. */ - const devTranslations = global.window.CKEDITOR_TRANSLATIONS; + const devTranslations = cloneDeep( global.window.CKEDITOR_TRANSLATIONS ); const uiLanguageFromConfig = typeof languageConfig === 'string' ? languageConfig : languageConfig.ui; const hasMatchingTranslations = devTranslations[ uiLanguageFromConfig! ]; const defaultDevTranslationsLanguage = Object.keys( devTranslations )[ 0 ]; @@ -178,7 +179,7 @@ export default class Context { this.locale = new Locale( { uiLanguage: hasMatchingTranslations ? uiLanguageFromConfig : defaultDevTranslationsLanguage, contentLanguage: this.config.get( 'language.content' ), - translations: global.window.CKEDITOR_TRANSLATIONS + translations: devTranslations } ); if ( !hasMatchingTranslations && this.config.get( 'language.ui' ) !== defaultDevTranslationsLanguage ) { diff --git a/packages/ckeditor5-font/tests/ui/colorui.js b/packages/ckeditor5-font/tests/ui/colorui.js index 36c7441eab2..f4fd5b1b213 100644 --- a/packages/ckeditor5-font/tests/ui/colorui.js +++ b/packages/ckeditor5-font/tests/ui/colorui.js @@ -74,7 +74,12 @@ describe( 'ColorUI', () => { return ClassicTestEditor .create( element, { plugins: [ Paragraph, TestColorPlugin, Undo ], - testColor: testColorConfig + testColor: testColorConfig, + language: 'en', + translations: [ { en: { + dictionary: [], + getPluralForm: sinon.spy() + } } ] } ) .then( newEditor => { editor = newEditor; diff --git a/packages/ckeditor5-indent/tests/indentui.js b/packages/ckeditor5-indent/tests/indentui.js index 2b4c67f6d72..5eb230c37ef 100644 --- a/packages/ckeditor5-indent/tests/indentui.js +++ b/packages/ckeditor5-indent/tests/indentui.js @@ -28,7 +28,11 @@ describe( 'IndentUI', () => { rtlEditor = await ClassicTestEditor .create( element, { plugins: [ IndentUI, IndentEditing ], - language: 'ar' + language: 'ar', + translations: [ { ar: { + dictionary: [], + getPluralForm: sinon.spy() + } } ] } ); } ); diff --git a/packages/ckeditor5-list/tests/legacytodolist/legacytodolistediting.js b/packages/ckeditor5-list/tests/legacytodolist/legacytodolistediting.js index 5d7aba94497..3589efd8936 100644 --- a/packages/ckeditor5-list/tests/legacytodolist/legacytodolistediting.js +++ b/packages/ckeditor5-list/tests/legacytodolist/legacytodolistediting.js @@ -1166,6 +1166,10 @@ describe( 'LegacyTodoListEditing', () => { return VirtualTestEditor .create( { language: 'ar', + translations: [ { ar: { + dictionary: [], + getPluralForm: sinon.spy() + } } ], plugins: [ Paragraph, LegacyTodoListEditing, Typing, BoldEditing, BlockQuoteEditing ] } ) .then( newEditor => { diff --git a/packages/ckeditor5-table/tests/tablecellproperties/tablecellpropertiesediting.js b/packages/ckeditor5-table/tests/tablecellproperties/tablecellpropertiesediting.js index 19511695417..260bbc6323e 100644 --- a/packages/ckeditor5-table/tests/tablecellproperties/tablecellpropertiesediting.js +++ b/packages/ckeditor5-table/tests/tablecellproperties/tablecellpropertiesediting.js @@ -756,7 +756,11 @@ describe( 'table cell properties', () => { beforeEach( async () => { editor = await VirtualTestEditor.create( { plugins: [ TableCellPropertiesEditing, Paragraph, TableEditing ], - language: 'ar' + language: 'ar', + translations: [ { ar: { + dictionary: [], + getPluralForm: sinon.spy() + } } ] } ); model = editor.model; diff --git a/packages/ckeditor5-table/tests/tablecolumnresize/tablecolumnresizeediting.js b/packages/ckeditor5-table/tests/tablecolumnresize/tablecolumnresizeediting.js index 7a9dfcd6a18..67692c23827 100644 --- a/packages/ckeditor5-table/tests/tablecolumnresize/tablecolumnresizeediting.js +++ b/packages/ckeditor5-table/tests/tablecolumnresize/tablecolumnresizeediting.js @@ -2064,7 +2064,11 @@ describe( 'TableColumnResizeEditing', () => { } editor = await createEditor( { - language: 'ar' + language: 'ar', + translations: [ { ar: { + dictionary: [], + getPluralForm: sinon.spy() + } } ] } ); model = editor.model; diff --git a/packages/ckeditor5-table/tests/tablekeyboard.js b/packages/ckeditor5-table/tests/tablekeyboard.js index 57e33f81016..7a649f491da 100644 --- a/packages/ckeditor5-table/tests/tablekeyboard.js +++ b/packages/ckeditor5-table/tests/tablekeyboard.js @@ -3602,7 +3602,11 @@ describe( 'TableKeyboard', () => { return VirtualTestEditor .create( { plugins: [ TableEditing, TableKeyboard, TableSelection, Paragraph, ImageBlockEditing, MediaEmbedEditing ], - language: 'ar' + language: 'ar', + translations: [ { ar: { + dictionary: [], + getPluralForm: sinon.spy() + } } ] } ) .then( newEditor => { editor = newEditor; diff --git a/packages/ckeditor5-ui/tests/badge/badge.js b/packages/ckeditor5-ui/tests/badge/badge.js index b84ec8afcd8..5bbbd185835 100644 --- a/packages/ckeditor5-ui/tests/badge/badge.js +++ b/packages/ckeditor5-ui/tests/badge/badge.js @@ -380,7 +380,11 @@ describe( 'Badge', () => { it( 'should position to the left side if the UI language is RTL and no side was configured', async () => { const editor = await createEditor( element, { - language: 'ar' + language: 'ar', + translations: [ { ar: { + dictionary: [], + getPluralForm: sinon.spy() + } } ] } ); badge = new BadgeExtended( editor ); diff --git a/packages/ckeditor5-ui/tests/editorui/evaluationbadge.js b/packages/ckeditor5-ui/tests/editorui/evaluationbadge.js index 421fdbbe50c..bcea49a1909 100644 --- a/packages/ckeditor5-ui/tests/editorui/evaluationbadge.js +++ b/packages/ckeditor5-ui/tests/editorui/evaluationbadge.js @@ -522,7 +522,11 @@ describe( 'EvaluationBadge', () => { it( 'should position the badge to the left right if the UI language is RTL (and powered-by is on the left)', async () => { const editor = await createEditor( element, { language: 'ar', - licenseKey: developmentLicenseKey + licenseKey: developmentLicenseKey, + translations: [ { ar: { + dictionary: [], + getPluralForm: sinon.spy() + } } ] } ); testUtils.sinon.stub( editor.ui.getEditableElement( 'main' ), 'getBoundingClientRect' ).returns( { diff --git a/packages/ckeditor5-ui/tests/editorui/poweredby.js b/packages/ckeditor5-ui/tests/editorui/poweredby.js index c9b855cc64e..58d76a81fc3 100644 --- a/packages/ckeditor5-ui/tests/editorui/poweredby.js +++ b/packages/ckeditor5-ui/tests/editorui/poweredby.js @@ -567,7 +567,11 @@ describe( 'PoweredBy', () => { it( 'should position the to the left side if the UI language is RTL and no side was configured', async () => { const editor = await createEditor( element, { - language: 'ar' + language: 'ar', + translations: [ { ar: { + dictionary: [], + getPluralForm: sinon.spy() + } } ] } ); testUtils.sinon.stub( editor.ui.getEditableElement( 'main' ), 'getBoundingClientRect' ).returns( { diff --git a/packages/ckeditor5-undo/tests/undoui.js b/packages/ckeditor5-undo/tests/undoui.js index 7c040921938..6477ecbf105 100644 --- a/packages/ckeditor5-undo/tests/undoui.js +++ b/packages/ckeditor5-undo/tests/undoui.js @@ -23,7 +23,9 @@ describe( 'UndoUI', () => { editorElement = document.createElement( 'div' ); document.body.appendChild( editorElement ); - return ClassicTestEditor.create( editorElement, { plugins: [ UndoEditing, UndoUI ] } ) + return ClassicTestEditor.create( editorElement, { plugins: [ UndoEditing, UndoUI ], + language: { ui: 'en' } + } ) .then( newEditor => { editor = newEditor; } ); @@ -110,7 +112,11 @@ describe( 'UndoUI', () => { return ClassicTestEditor .create( element, { plugins: [ UndoEditing, UndoUI ], - language: 'ar' + language: 'ar', + translations: [ { ar: { + dictionary: [], + getPluralForm: sinon.spy() + } } ] } ) .then( newEditor => { const undoButton = newEditor.ui.componentFactory.create( 'undo' ); @@ -131,7 +137,11 @@ describe( 'UndoUI', () => { return ClassicTestEditor .create( element, { plugins: [ UndoEditing, UndoUI ], - language: 'ar' + language: 'ar', + translations: [ { ar: { + dictionary: [], + getPluralForm: sinon.spy() + } } ] } ) .then( newEditor => { const redoButton = newEditor.ui.componentFactory.create( 'redo' ); From 868cc19ab78b4f571faa65cc4cc7a21e0a474547 Mon Sep 17 00:00:00 2001 From: l-lejman Date: Mon, 12 May 2025 15:07:30 +0200 Subject: [PATCH 3/6] Fix mathtype manual test and null handling in creating config objects. --- packages/ckeditor5-utils/src/config.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ckeditor5-utils/src/config.ts b/packages/ckeditor5-utils/src/config.ts index 85b2ceafe87..64864235d94 100644 --- a/packages/ckeditor5-utils/src/config.ts +++ b/packages/ckeditor5-utils/src/config.ts @@ -193,9 +193,9 @@ export default class Config { // Iterate over parts to check if currently stored configuration has proper structure. for ( const part of parts ) { - // If there is no object for specified part then create one. - if ( !isPlainObject( target[ part ] ) ) { - target[ part ] = Object.create( null ); + // If target[part] is not an object or array, initialize as empty object. + if ( typeof target[ part ] !== 'object' || target[ part ] === null ) { + target[ part ] = {}; } // Nested object becomes a target. @@ -206,7 +206,7 @@ export default class Config { if ( isPlainObject( value ) ) { // We take care of proper config structure. if ( !isPlainObject( target[ name ] ) ) { - target[ name ] = Object.create( null ); + target[ name ] = {}; } target = target[ name ]; @@ -218,7 +218,7 @@ export default class Config { } // Do nothing if we are defining configuration for non empty name. - if ( isDefine && typeof target[ name ] != 'undefined' ) { + if ( isDefine && typeof target[ name ] !== 'undefined' ) { return; } From 521beafa60ac4965bd21d99949ead5e08b1e75e3 Mon Sep 17 00:00:00 2001 From: l-lejman Date: Wed, 25 Jun 2025 18:31:16 +0200 Subject: [PATCH 4/6] Remove prototype change from config and adjust comments. --- packages/ckeditor5-core/src/context.ts | 17 ++++++++++++----- packages/ckeditor5-utils/src/config.ts | 10 +++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/ckeditor5-core/src/context.ts b/packages/ckeditor5-core/src/context.ts index 8b116700db9..be43823a21f 100644 --- a/packages/ckeditor5-core/src/context.ts +++ b/packages/ckeditor5-core/src/context.ts @@ -164,12 +164,19 @@ export class Context { if ( !translations && global.window.CKEDITOR_TRANSLATIONS ) { /** - * Function _translate from translation-service.ts gets translations from + * When translations are not provided directly but available via global.window.CKEDITOR_TRANSLATIONS + * (which can be set by CKEditorTranslationsPlugin during manual tests with --language flag, + * or loaded from CDN translation files), we need to ensure the config.language.ui value is properly set. + * + * When translations are loaded via the global variable, the config.language.ui might be missing + * or incorrect, which can cause issues with utilities that depend on the language configuration + * (e.g., date formatting utilities). To fix this, we check if the configured language has matching + * translations, and if not, fall back to the first available language from the global translations object. + * + * Note: The _translate function from translation-service.ts gets translations from * global.window.CKEDITOR_TRANSLATIONS when translations injected into Locale are empty. - * Value of global.window.CKEDITOR_TRANSLATIONS is provided by CKEditorTranslationsPlugin from - * ckeditor5-dev-translations package (for example in manual tests) when --language flag is used. - * Function _translate is called often and has no access to the editing editor config, so here is a better place to - * check if translations will be taken from dev-translations, and if yes – update config.language.ui value. + * Since _translate is called often and has no access to the editor config, this is the better place + * to check if translations will be taken from the global variable and update config.language.ui accordingly. */ const devTranslations = cloneDeep( global.window.CKEDITOR_TRANSLATIONS ); const uiLanguageFromConfig = typeof languageConfig === 'string' ? languageConfig : languageConfig.ui; diff --git a/packages/ckeditor5-utils/src/config.ts b/packages/ckeditor5-utils/src/config.ts index fea3037786f..279b14f265b 100644 --- a/packages/ckeditor5-utils/src/config.ts +++ b/packages/ckeditor5-utils/src/config.ts @@ -193,9 +193,9 @@ export class Config { // Iterate over parts to check if currently stored configuration has proper structure. for ( const part of parts ) { - // If target[part] is not an object or array, initialize as empty object. - if ( typeof target[ part ] !== 'object' || target[ part ] === null ) { - target[ part ] = {}; + // If there is no object for specified part then create one. + if ( !isPlainObject( target[ part ] ) ) { + target[ part ] = Object.create( null ); } // Nested object becomes a target. @@ -206,7 +206,7 @@ export class Config { if ( isPlainObject( value ) ) { // We take care of proper config structure. if ( !isPlainObject( target[ name ] ) ) { - target[ name ] = {}; + target[ name ] = Object.create( null ); } target = target[ name ]; @@ -218,7 +218,7 @@ export class Config { } // Do nothing if we are defining configuration for non empty name. - if ( isDefine && typeof target[ name ] !== 'undefined' ) { + if ( isDefine && typeof target[ name ] != 'undefined' ) { return; } From 03fd7e66eba8d43b6783160901cadda4b5519607 Mon Sep 17 00:00:00 2001 From: l-lejman Date: Wed, 25 Jun 2025 19:14:29 +0200 Subject: [PATCH 5/6] Remove clone deep from translations in config --- packages/ckeditor5-core/src/context.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/ckeditor5-core/src/context.ts b/packages/ckeditor5-core/src/context.ts index be43823a21f..2d2a959008e 100644 --- a/packages/ckeditor5-core/src/context.ts +++ b/packages/ckeditor5-core/src/context.ts @@ -20,7 +20,6 @@ import { PluginCollection } from './plugincollection.js'; import { type Editor } from './editor/editor.js'; import type { LoadedPlugins, PluginConstructor } from './plugin.js'; import type { EditorConfig } from './editor/editorconfig.js'; -import { cloneDeep } from 'es-toolkit/compat'; /** * Provides a common, higher-level environment for solutions that use multiple {@link module:core/editor/editor~Editor editors} @@ -178,19 +177,19 @@ export class Context { * Since _translate is called often and has no access to the editor config, this is the better place * to check if translations will be taken from the global variable and update config.language.ui accordingly. */ - const devTranslations = cloneDeep( global.window.CKEDITOR_TRANSLATIONS ); + const globalTranslations = global.window.CKEDITOR_TRANSLATIONS; const uiLanguageFromConfig = typeof languageConfig === 'string' ? languageConfig : languageConfig.ui; - const hasMatchingTranslations = devTranslations[ uiLanguageFromConfig! ]; - const defaultDevTranslationsLanguage = Object.keys( devTranslations )[ 0 ]; + const hasMatchingTranslations = globalTranslations[ uiLanguageFromConfig! ]; + const defaultGlobalTranslationsLanguage = Object.keys( globalTranslations )[ 0 ]; this.locale = new Locale( { - uiLanguage: hasMatchingTranslations ? uiLanguageFromConfig : defaultDevTranslationsLanguage, + uiLanguage: hasMatchingTranslations ? uiLanguageFromConfig : defaultGlobalTranslationsLanguage, contentLanguage: this.config.get( 'language.content' ), - translations: devTranslations + translations: globalTranslations } ); - if ( !hasMatchingTranslations && this.config.get( 'language.ui' ) !== defaultDevTranslationsLanguage ) { - this.config.define( 'language.ui', defaultDevTranslationsLanguage ); + if ( !hasMatchingTranslations && this.config.get( 'language.ui' ) !== defaultGlobalTranslationsLanguage ) { + this.config.define( 'language.ui', defaultGlobalTranslationsLanguage ); } } else { this.locale = new Locale( { From 1c4a0842d62e589acc3ac104a3ccc68d48481dd8 Mon Sep 17 00:00:00 2001 From: l-lejman Date: Wed, 25 Jun 2025 20:29:52 +0200 Subject: [PATCH 6/6] Restore clone deep to not fail unit tests. --- packages/ckeditor5-core/src/context.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-core/src/context.ts b/packages/ckeditor5-core/src/context.ts index 2d2a959008e..8ba6f37cc09 100644 --- a/packages/ckeditor5-core/src/context.ts +++ b/packages/ckeditor5-core/src/context.ts @@ -21,6 +21,8 @@ import { type Editor } from './editor/editor.js'; import type { LoadedPlugins, PluginConstructor } from './plugin.js'; import type { EditorConfig } from './editor/editorconfig.js'; +import { cloneDeep } from 'es-toolkit/compat'; + /** * Provides a common, higher-level environment for solutions that use multiple {@link module:core/editor/editor~Editor editors} * or plugins that work outside the editor. Use it instead of {@link module:core/editor/editor~Editor.create `Editor.create()`} @@ -177,7 +179,7 @@ export class Context { * Since _translate is called often and has no access to the editor config, this is the better place * to check if translations will be taken from the global variable and update config.language.ui accordingly. */ - const globalTranslations = global.window.CKEDITOR_TRANSLATIONS; + const globalTranslations = cloneDeep( global.window.CKEDITOR_TRANSLATIONS ); const uiLanguageFromConfig = typeof languageConfig === 'string' ? languageConfig : languageConfig.ui; const hasMatchingTranslations = globalTranslations[ uiLanguageFromConfig! ]; const defaultGlobalTranslationsLanguage = Object.keys( globalTranslations )[ 0 ];