ckeditor5 1.26.2 → 1.27.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e46c611bc11950481442cb335ae063943043937c9792b6eeedd731e030cdf1c
4
- data.tar.gz: d16b561470173168c325457a00f5e83d5ca8689b0644f0d96533c05b7075193f
3
+ metadata.gz: 93b0f87e014924cf2ba04dc4d8ce96afef84152c4c8fc7bdebf5a736db864b95
4
+ data.tar.gz: 02b97648d5c94907741382795721e5944ecc0752c3b2792cfe0c7455c482f81f
5
5
  SHA512:
6
- metadata.gz: 6769430ee90f270077c474bf1999e7d5fc4d2ec5c62918063e6503c39956e63a9f0bfb948de3dd376bc2bcb7212fc505a61e94cf04e46a1c2e85867d8e037e9a
7
- data.tar.gz: 93071afc776e615781cc9ff845b631bc823653f229ee9dbba22ccc2f9e9b2c5b9813c4cfaf6d672dfc9764a8db6d205631cf992b2d94a16fe8aba9df230681ad
6
+ metadata.gz: 992f3a903d5bd123e91b572f0d0154c42044f90768264fe07a5c61d343431921ed3a45515f8f74ad48a68a0870876e9f73184cdc14266f4c0e43a4f393306a93
7
+ data.tar.gz: 64a1063477787141603fd850b6de704223c15b8dd6dbedcd713ea4a5ae69fb1ebf630ba1eaef9fc245a95272f4b32b40618065e04e0c69d72b8536677ef107bd
data/README.md CHANGED
@@ -149,7 +149,9 @@ For extending CKEditor's functionality, refer to the [plugins directory](https:/
149
149
  - [`simple_upload_adapter(url)` method](#simple_upload_adapterurl-method)
150
150
  - [`special_characters(&block)` method](#special_charactersblock-method)
151
151
  - [`wproofreader(version: nil, cdn: nil, **config)` method](#wproofreaderversion-nil-cdn-nil-config-method)
152
+ - [`custom_translations(lang_code = nil, translations = {})` method](#custom_translationslang_code--nil-translations---method)
152
153
  - [Controller / View helpers 📦](#controller--view-helpers-)
154
+ - [`ckeditor5_translation_ref(key)` method](#ckeditor5_translation_refkey-method)
153
155
  - [`ckeditor5_element_ref(selector)` method](#ckeditor5_element_refselector-method)
154
156
  - [`ckeditor5_preset(name = nil, &block)` method](#ckeditor5_presetname--nil-block-method)
155
157
  - [Including CKEditor 5 assets 📦](#including-ckeditor-5-assets-)
@@ -1197,8 +1199,71 @@ For more info about the WProofreader plugin, check the [official documentation](
1197
1199
 
1198
1200
  </details>
1199
1201
 
1202
+ #### `custom_translations(lang_code = nil, translations = {})` method
1203
+
1204
+ <details>
1205
+ <summary>Define custom translations for CKEditor components and UI</summary>
1206
+
1207
+ <br />
1208
+
1209
+ Allows setting custom translations for editor components, UI elements, and headings. The translations are applied globally since they override the global translation object.
1210
+
1211
+ > [!NOTE]
1212
+ > This helper allows overriding builtin translations of the editor, but translations are overridden globally, as the CKEditor 5 uses a single translation object for all instances of the editor. It's recommended to use the `ckeditor5_translation_ref` helper to reference the translations in the configuration.
1213
+
1214
+ ```rb
1215
+ # config/initializers/ckeditor5.rb
1216
+
1217
+ CKEditor5::Rails.configure do
1218
+ # ... other configuration
1219
+
1220
+ custom_translations :en, {
1221
+ 'Heading 1': 'Your custom translation value'
1222
+ }
1223
+
1224
+ configure :heading, {
1225
+ options: [
1226
+ { model: 'heading1', title: ckeditor5_translation_ref('Heading 1') },
1227
+ # ...
1228
+ ]
1229
+ }
1230
+ end
1231
+ ```
1232
+
1233
+ </details>
1234
+
1200
1235
  ### Controller / View helpers 📦
1201
1236
 
1237
+ #### `ckeditor5_translation_ref(key)` method
1238
+
1239
+ <details>
1240
+ <summary>Defines a reference to a CKEditor 5 translation</summary>
1241
+
1242
+ <br />
1243
+
1244
+ Allows you to reference CKEditor 5 translations in the configuration. It's particularly useful when you want to use custom translations in the editor configuration.
1245
+
1246
+ ```rb
1247
+ # config/initializers/ckeditor5.rb
1248
+
1249
+ CKEditor5::Rails.configure do
1250
+ # ... other configuration
1251
+
1252
+ custom_translations :en, {
1253
+ 'Heading 1': 'Your custom translation value'
1254
+ }
1255
+
1256
+ configure :heading, {
1257
+ options: [
1258
+ { model: 'heading1', title: ckeditor5_translation_ref('Heading 1') },
1259
+ # ...
1260
+ ]
1261
+ }
1262
+ end
1263
+ ```
1264
+
1265
+ </details>
1266
+
1202
1267
  #### `ckeditor5_element_ref(selector)` method
1203
1268
 
1204
1269
  <details>
@@ -17,6 +17,20 @@ module CKEditor5::Rails::Editor::Helpers
17
17
  { '$element': selector }
18
18
  end
19
19
 
20
+ # Creates a reference to a translation key that will be used by CKEditor.
21
+ # This helper creates a special object that CKEditor recognizes as a translation reference.
22
+ #
23
+ # @param key [String] The translation key to reference
24
+ # @return [Hash] A hash with the translation reference in CKEditor's format
25
+ #
26
+ # @example Referencing a translation in plugin configuration
27
+ # configure :toolbar, {
28
+ # placeholder: ckeditor5_translation_ref("editor.placeholder")
29
+ # }
30
+ def ckeditor5_translation_ref(key)
31
+ { '$translation': key }
32
+ end
33
+
20
34
  # Creates or retrieves a preset configuration for CKEditor.
21
35
  # When called with a name, finds and returns an existing preset.
22
36
  # When called with a block, creates a new preset with the given configuration.
@@ -78,7 +78,7 @@ module CKEditor5::Rails
78
78
  :type, :menubar, :plugins, :plugin, :inline_plugin,
79
79
  :toolbar, :block_toolbar, :balloon_toolbar,
80
80
  :language, :ckbox, :configure, :automatic_upgrades, :simple_upload_adapter,
81
- :editable_height, :wproofreader, to: :default_preset
81
+ :editable_height, :wproofreader, :custom_translations, to: :default_preset
82
82
 
83
83
  def initialize(configuration)
84
84
  @configuration = configuration
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../editor/props_inline_plugin'
4
+
5
+ module CKEditor5::Rails::Plugins
6
+ class CustomTranslationsLoader < CKEditor5::Rails::Editor::PropsInlinePlugin
7
+ def initialize(translations) # rubocop:disable Metrics/MethodLength
8
+ code = <<~JS.freeze
9
+ const { Plugin } = await import('ckeditor5');
10
+
11
+ function resolveTranslationReferences(uiLanguage, config, visited = new WeakSet()) {
12
+ if (!config || typeof config !== 'object') {
13
+ return config;
14
+ }
15
+
16
+ if (visited.has(config)) {
17
+ return config;
18
+ }
19
+
20
+ visited.add(config);
21
+
22
+ if (Array.isArray(config)) {
23
+ config.forEach((item, index) => {
24
+ config[index] = resolveTranslationReferences(uiLanguage, item, visited);
25
+ });
26
+
27
+ return config;
28
+ }
29
+
30
+ for (const key of Object.getOwnPropertyNames(config)) {
31
+ const value = config[key];
32
+
33
+ if (value && typeof value === 'object') {
34
+ if (value.$translation) {
35
+ const translationKey = value.$translation;
36
+ const translations = window.CKEDITOR_TRANSLATIONS?.[uiLanguage];
37
+
38
+ if (!translations?.dictionary[translationKey]) {
39
+ console.warn(`Translation not found for key: ${translationKey}`);
40
+ }
41
+
42
+ config[key] = translations?.dictionary[translationKey] || translationKey;
43
+ } else {
44
+ resolveTranslationReferences(uiLanguage, value, visited);
45
+ }
46
+ }
47
+ }
48
+
49
+ return config;
50
+ }
51
+
52
+ return class CustomTranslationsLoader extends Plugin {
53
+ static CUSTOM_TRANSLATIONS = Object.create( #{translations.to_json} );
54
+
55
+ static get pluginName() {
56
+ return 'CustomTranslationsLoader';
57
+ }
58
+
59
+ constructor( editor ) {
60
+ super( editor );
61
+
62
+ const { locale, config } = this.editor;
63
+
64
+ this.#extendPack();
65
+ resolveTranslationReferences(locale.uiLanguage, config._config)
66
+ }
67
+
68
+ #extendPack() {
69
+ const { uiLanguage } = this.editor.locale;
70
+ const translations = this.#translations;
71
+
72
+ if (!window.CKEDITOR_TRANSLATIONS) {
73
+ window.CKEDITOR_TRANSLATIONS = {};
74
+ }
75
+
76
+ if (!window.CKEDITOR_TRANSLATIONS[uiLanguage]) {
77
+ window.CKEDITOR_TRANSLATIONS[uiLanguage] = { dictionary: {} };
78
+ }
79
+
80
+ Object.entries(translations).forEach(([key, value]) => {
81
+ window.CKEDITOR_TRANSLATIONS[uiLanguage].dictionary[key] = value;
82
+ });
83
+ }
84
+
85
+ get #translations() {
86
+ const { uiLanguage } = this.editor.locale;
87
+
88
+ return CustomTranslationsLoader.CUSTOM_TRANSLATIONS[uiLanguage];
89
+ }
90
+ }
91
+ JS
92
+
93
+ super(:CustomTranslationsLoader, code)
94
+ compress!
95
+ end
96
+ end
97
+ end
@@ -9,6 +9,7 @@ end
9
9
  require_relative 'plugins/simple_upload_adapter'
10
10
  require_relative 'plugins/wproofreader'
11
11
  require_relative 'plugins/special_characters_bootstrap'
12
+ require_relative 'plugins/custom_translations_loader'
12
13
 
13
14
  # Plugin patches and fixes
14
15
  require_relative 'plugins/patches/fix_color_picker_race_condition'
@@ -6,7 +6,7 @@ require_relative 'special_characters_builder'
6
6
 
7
7
  module CKEditor5::Rails
8
8
  module Presets
9
- class PresetBuilder
9
+ class PresetBuilder # rubocop:disable Metrics/ClassLength
10
10
  include Editor::Helpers::Config
11
11
  include Concerns::ConfigurationMethods
12
12
  include Concerns::PluginMethods
@@ -27,6 +27,7 @@ module CKEditor5::Rails
27
27
  @ckbox = nil
28
28
  @editable_height = nil
29
29
  @automatic_upgrades = false
30
+ @custom_translations = {}
30
31
  @config = {
31
32
  plugins: [],
32
33
  toolbar: []
@@ -35,6 +36,12 @@ module CKEditor5::Rails
35
36
  instance_eval(&block) if block_given?
36
37
  end
37
38
 
39
+ def deconstruct_keys(keys)
40
+ keys.index_with do |key|
41
+ public_send(key)
42
+ end
43
+ end
44
+
38
45
  # @example Copy preset and modify it
39
46
  # original = PresetBuilder.new
40
47
  # copied = original.initialize_copy(original)
@@ -43,6 +50,7 @@ module CKEditor5::Rails
43
50
 
44
51
  @translations = source.translations.dup
45
52
  @ckbox = source.ckbox.dup if source.ckbox
53
+ @custom_translations = source.custom_translations.deep_dup
46
54
  @config = {
47
55
  plugins: source.config[:plugins].map(&:dup),
48
56
  toolbar: deep_copy_toolbar(source.config[:toolbar])
@@ -63,12 +71,6 @@ module CKEditor5::Rails
63
71
  license_key == 'GPL'
64
72
  end
65
73
 
66
- def deconstruct_keys(keys)
67
- keys.index_with do |key|
68
- public_send(key)
69
- end
70
- end
71
-
72
74
  # Create a new preset by overriding current configuration
73
75
  # @example Override existing preset
74
76
  # preset.override do
@@ -199,7 +201,7 @@ module CKEditor5::Rails
199
201
  # Apply integration patches for the current version
200
202
  # @return [void]
201
203
  def apply_integration_patches
202
- patch_plugin Plugins::Patches::FixColorPickerRaceCondition.new
204
+ patch_plugin(Plugins::Patches::FixColorPickerRaceCondition.new)
203
205
  end
204
206
 
205
207
  # Enable or disable automatic version upgrades
@@ -333,6 +335,37 @@ module CKEditor5::Rails
333
335
  config[:language].present?
334
336
  end
335
337
 
338
+ # Configure custom translations for the editor
339
+ # @param lang_code [Symbol] Language code for translations (e.g. :en, :pl)
340
+ # @param translations [Hash] A hash containing translations in format { key => translation }
341
+ # @example Add multiple translations
342
+ # custom_translations :en, {
343
+ # 'my.button': 'My Button'
344
+ # }
345
+ # custom_translations :pl, {
346
+ # 'my.button': 'Mój przycisk'
347
+ # }
348
+ # @example Use translations in configuration with ckeditor5_translation_ref
349
+ # custom_translations :en, {
350
+ # 'custom.heading': 'Custom Section'
351
+ # }
352
+ #
353
+ # configure :heading, {
354
+ # options: [
355
+ # { model: 'heading1', title: ckeditor5_translation_ref('custom.heading') }
356
+ # ]
357
+ # }
358
+ # @return [void]
359
+ def custom_translations(lang_code = nil, translations = {})
360
+ return @custom_translations if lang_code.blank?
361
+
362
+ @custom_translations[lang_code.to_sym] ||= {}
363
+ @custom_translations[lang_code.to_sym].merge!(translations)
364
+
365
+ plugins.remove(:CustomTranslationsLoader)
366
+ plugins.prepend(Plugins::CustomTranslationsLoader.new(@custom_translations))
367
+ end
368
+
336
369
  # Configure editor language
337
370
  # @param ui [Symbol, nil] UI language code
338
371
  # @param content [Symbol] Content language code
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CKEditor5
4
4
  module Rails
5
- VERSION = '1.26.2'
5
+ VERSION = '1.27.0'
6
6
 
7
7
  DEFAULT_CKEDITOR_VERSION = '44.2.0'
8
8
  end
@@ -8,7 +8,7 @@ RSpec.describe 'AJAX Form Integration', type: :feature, js: true do
8
8
  setup_form_tracking(page)
9
9
  end
10
10
 
11
- shared_examples 'an ajax form with CKEditor' do |form_testid, editor_testid, submit_testid, response_id| # rubocop:disable Metrics/BlockLength
11
+ shared_examples 'an ajax form with CKEditor' do |form_testid, editor_testid, submit_testid, response_id|
12
12
  let(:form) { find("[data-testid='#{form_testid}']") }
13
13
  let(:editor) { find("[data-testid='#{editor_testid}']") }
14
14
  let(:editable) { editor.find('.ck-editor__editable') }
@@ -44,10 +44,10 @@ RSpec.describe 'CKEditor5 Types Integration', type: :feature, js: true do
44
44
  end
45
45
  end
46
46
 
47
- shared_examples 'a multiroot editor that fires change events' do |path, editables| # rubocop:disable Metrics/BlockLength
47
+ shared_examples 'a multiroot editor that fires change events' do |path, editables|
48
48
  before { visit path }
49
49
 
50
- it 'sends properly change events with proper payload for editables' do # rubocop:disable Metrics/BlockLength
50
+ it 'sends properly change events with proper payload for editables' do
51
51
  editors = editables.map do |name|
52
52
  find("[data-testid='#{name}-editable']")
53
53
  end
@@ -151,7 +151,7 @@ RSpec.describe 'CKEditor5 Types Integration', type: :feature, js: true do
151
151
  expect(page).to have_css('.ck-toolbar', count: 1)
152
152
  end
153
153
 
154
- it 'handles dynamically added editables' do # rubocop:disable Metrics/BlockLength
154
+ it 'handles dynamically added editables' do
155
155
  # Set up event listener
156
156
  page.execute_script(<<~JS)
157
157
  window._newEditableEvents = [];
@@ -8,7 +8,7 @@ RSpec.describe 'Form Integration', type: :feature, js: true do
8
8
  setup_form_tracking(page)
9
9
  end
10
10
 
11
- shared_examples 'a form with CKEditor' do |form_testid, editor_testid, submit_testid| # rubocop:disable Metrics/BlockLength
11
+ shared_examples 'a form with CKEditor' do |form_testid, editor_testid, submit_testid|
12
12
  let(:form) { find("[data-testid='#{form_testid}']") }
13
13
  let(:editor) { find("[data-testid='#{editor_testid}']") }
14
14
  let(:editable) { editor.find('.ck-editor__editable') }
@@ -494,6 +494,61 @@ RSpec.describe CKEditor5::Rails::Presets::PresetBuilder do
494
494
  end
495
495
  end
496
496
 
497
+ describe '#custom_translations' do
498
+ it 'returns empty hash when called without arguments' do
499
+ expect(builder.custom_translations).to eq({})
500
+ end
501
+
502
+ it 'stores translations for a language' do
503
+ translations = { 'my.button': 'My Button' }
504
+ builder.custom_translations(:en, translations)
505
+ expect(builder.custom_translations).to eq({ en: translations })
506
+ end
507
+
508
+ it 'merges translations for the same language' do
509
+ builder.custom_translations(:en, { 'button.one': 'One' })
510
+ builder.custom_translations(:en, { 'button.two': 'Two' })
511
+
512
+ expect(builder.custom_translations[:en]).to eq({
513
+ 'button.one': 'One',
514
+ 'button.two': 'Two'
515
+ })
516
+ end
517
+
518
+ it 'handles multiple languages' do
519
+ builder.custom_translations(:en, { 'button': 'Button' })
520
+ builder.custom_translations(:pl, { 'button': 'Przycisk' })
521
+
522
+ expect(builder.custom_translations).to eq({
523
+ en: { button: 'Button' },
524
+ pl: { button: 'Przycisk' }
525
+ })
526
+ end
527
+
528
+ it 'normalizes language codes to symbols' do
529
+ builder.custom_translations('EN', { 'button': 'Button' })
530
+ expect(builder.custom_translations.keys).to eq([:EN])
531
+ end
532
+
533
+ it 'adds and replaces CustomTranslationsLoader plugin' do
534
+ translations = { 'my.button': 'My Button' }
535
+
536
+ expect do
537
+ builder.custom_translations(:en, translations)
538
+ end.to change { builder.config[:plugins].count }.by(1)
539
+
540
+ plugin = builder.config[:plugins].first
541
+ expect(plugin).to be_a(CKEditor5::Rails::Plugins::CustomTranslationsLoader)
542
+
543
+ # Adding more translations should replace the plugin
544
+ expect do
545
+ builder.custom_translations(:pl, { 'my.button': 'Mój przycisk' })
546
+ end.not_to(change { builder.config[:plugins].count })
547
+
548
+ expect(builder.config[:plugins].first).to be_a(CKEditor5::Rails::Plugins::CustomTranslationsLoader)
549
+ end
550
+ end
551
+
497
552
  describe '#deep_copy_toolbar' do
498
553
  context 'with array toolbar' do
499
554
  it 'returns duplicated array' do
@@ -552,7 +607,7 @@ RSpec.describe CKEditor5::Rails::Presets::PresetBuilder do
552
607
  expect { builder.special_characters }.not_to raise_error
553
608
  end
554
609
 
555
- it 'configures special characters with groups and items' do # rubocop:disable Metrics/BlockLength
610
+ it 'configures special characters with groups and items' do
556
611
  builder.special_characters do
557
612
  group 'Emoji', label: 'Emoticons' do
558
613
  item 'smiley', '😊'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ckeditor5
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.26.2
4
+ version: 1.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mateusz Bagiński
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-02-13 00:00:00.000000000 Z
12
+ date: 2025-02-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -90,6 +90,7 @@ files:
90
90
  - lib/ckeditor5/rails/hooks/importmap.rb
91
91
  - lib/ckeditor5/rails/hooks/simple_form.rb
92
92
  - lib/ckeditor5/rails/plugins.rb
93
+ - lib/ckeditor5/rails/plugins/custom_translations_loader.rb
93
94
  - lib/ckeditor5/rails/plugins/patches/fix_color_picker_race_condition.rb
94
95
  - lib/ckeditor5/rails/plugins/simple_upload_adapter.rb
95
96
  - lib/ckeditor5/rails/plugins/special_characters_bootstrap.rb