ckeditor5 1.26.2 → 1.27.0

Sign up to get free protection for your applications and to get access to all the features.
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