i18n-tasks 0.9.22 → 0.9.23

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: 84b3720d5321c4996a0cebe6b17a734445434d5aa549ebcc6771c09e1856bf6b
4
- data.tar.gz: d966fc1e803e5cf34fe1dc61ddd37a10f56f5457b2749142511a38fb377e38ba
3
+ metadata.gz: 8b90d77c0efd6c17b2b659fd19de47f5e6f2a8352f5d0eede4fa0525d481f2ac
4
+ data.tar.gz: 15f983188ee4dc5124823f318d947842d89f3df78890452d62092abdb0ea4836
5
5
  SHA512:
6
- metadata.gz: 2f90d7edb7ca9f661d0d5f8d36f25a19c14d58fdac1a540389f1b3e19c01e12835df8d1478619b8a45bddb42fd4852c91ea59b4a92c0f66ed696432202b331a9
7
- data.tar.gz: 1407da83477f63875c56708aef038ac96d005897e5457b990bcc195d8738dbe1a857490939c2e7530cdb41380374c5291a3b55892cd80442af6ad3eda695a197
6
+ metadata.gz: c5c4c4f9210a7e25dec136bac40fda0575afff686aeb58614f2b2f4bafdd8b9e2f18bd4bf4b9b99b94409a6a34340ee0be009d6fad85361b691e8f6ff5d1266a
7
+ data.tar.gz: bb6d10a390f885e884861caf5c669c29239566e613554a17fd263cd8e89d3bdeb64fc554e7cd61d6ced403568a736dfa15bce159206cd65e0af84d19a5078fc2
data/README.md CHANGED
@@ -22,7 +22,7 @@ i18n-tasks can be used with any project using the ruby [i18n gem][i18n-gem] (def
22
22
  Add i18n-tasks to the Gemfile:
23
23
 
24
24
  ```ruby
25
- gem 'i18n-tasks', '~> 0.9.22'
25
+ gem 'i18n-tasks', '~> 0.9.23'
26
26
  ```
27
27
 
28
28
  Copy the default [configuration file](#configuration):
@@ -11,8 +11,7 @@ require 'i18n/tasks/used_keys'
11
11
  require 'i18n/tasks/ignore_keys'
12
12
  require 'i18n/tasks/missing_keys'
13
13
  require 'i18n/tasks/unused_keys'
14
- require 'i18n/tasks/deepl_translation'
15
- require 'i18n/tasks/google_translation'
14
+ require 'i18n/tasks/translation'
16
15
  require 'i18n/tasks/locale_pathname'
17
16
  require 'i18n/tasks/locale_list'
18
17
  require 'i18n/tasks/string_interpolation'
@@ -32,8 +31,7 @@ module I18n
32
31
  include IgnoreKeys
33
32
  include MissingKeys
34
33
  include UnusedKeys
35
- include DeeplTranslation
36
- include GoogleTranslation
34
+ include Translation
37
35
  include Logging
38
36
  include Configuration
39
37
  include Data
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/tasks/translators/deepl_translator.rb'
4
+ require 'i18n/tasks/translators/google_translator.rb'
5
+
6
+ module I18n::Tasks
7
+ module Translation
8
+ # @param [I18n::Tasks::Tree::Siblings] forest to translate to the locales of its root nodes
9
+ # @param [String] from locale
10
+ # @return [I18n::Tasks::Tree::Siblings] translated forest
11
+ def deepl_translate_forest(forest, from)
12
+ Translators::DeeplTranslator.new(self).translate_forest(forest, from)
13
+ end
14
+
15
+ # @param [I18n::Tasks::Tree::Siblings] forest to translate to the locales of its root nodes
16
+ # @param [String] from locale
17
+ # @return [I18n::Tasks::Tree::Siblings] translated forest
18
+ def google_translate_forest(forest, from)
19
+ Translators::GoogleTranslator.new(self).translate_forest(forest, from)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18n::Tasks
4
+ module Translators
5
+ class BaseTranslator
6
+ # @param [I18n::Tasks::BaseTask] i18n_tasks
7
+ def initialize(i18n_tasks)
8
+ @i18n_tasks = i18n_tasks
9
+ end
10
+
11
+ # @param [I18n::Tasks::Tree::Siblings] forest to translate to the locales of its root nodes
12
+ # @param [String] from locale
13
+ # @return [I18n::Tasks::Tree::Siblings] translated forest
14
+ def translate_forest(forest, from)
15
+ forest.inject @i18n_tasks.empty_forest do |result, root|
16
+ translated = translate_pairs(root.key_values(root: true), to: root.key, from: from)
17
+ result.merge! Data::Tree::Siblings.from_flat_pairs(translated)
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ # @param [Array<[String, Object]>] list of key-value pairs
24
+ # @return [Array<[String, Object]>] translated list
25
+ def translate_pairs(list, opts)
26
+ return [] if list.empty?
27
+ opts = opts.dup
28
+ key_pos = list.each_with_index.inject({}) { |idx, ((k, _v), i)| idx.update(k => i) }
29
+ # copy reference keys as is, instead of translating
30
+ reference_key_vals = list.select { |_k, v| v.is_a? Symbol } || []
31
+ list -= reference_key_vals
32
+ result = list.group_by { |k_v| @i18n_tasks.html_key? k_v[0], opts[:from] }.map do |is_html, list_slice|
33
+ fetch_translations list_slice, opts.merge(is_html ? options_for_html : options_for_plain)
34
+ end.reduce(:+) || []
35
+ result.concat(reference_key_vals)
36
+ result.sort! { |a, b| key_pos[a[0]] <=> key_pos[b[0]] }
37
+ result
38
+ end
39
+
40
+ # @param [Array<[String, Object]>] list of key-value pairs
41
+ # @return [Array<[String, Object]>] translated list
42
+ def fetch_translations(list, opts)
43
+ from_values(list, translate_values(to_values(list), **options_for_translate_values(**opts))).tap do |result|
44
+ fail CommandError, no_results_error_message if result.blank?
45
+ end
46
+ end
47
+
48
+ # @param [Array<[String, Object]>] list of key-value pairs
49
+ # @return [Array<String>] values for translation extracted from list
50
+ def to_values(list)
51
+ list.map { |l| dump_value l[1] }.flatten.compact
52
+ end
53
+
54
+ # @param [Array<[String, Object]>] list
55
+ # @param [Array<String>] translated_values
56
+ # @return [Array<[String, Object]>] translated key-value pairs
57
+ def from_values(list, translated_values)
58
+ keys = list.map(&:first)
59
+ untranslated_values = list.map(&:last)
60
+ keys.zip parse_value(untranslated_values, translated_values.to_enum)
61
+ end
62
+
63
+ # Prepare value for translation.
64
+ # @return [String, Array<String, nil>, nil] value for Google Translate or nil for non-string values
65
+ def dump_value(value)
66
+ case value
67
+ when Array
68
+ # dump recursively
69
+ value.map { |v| dump_value v }
70
+ when String
71
+ replace_interpolations value
72
+ end
73
+ end
74
+
75
+ # Parse translated value from the each_translated enumerator
76
+ # @param [Object] untranslated
77
+ # @param [Enumerator] each_translated
78
+ # @return [Object] final translated value
79
+ def parse_value(untranslated, each_translated)
80
+ case untranslated
81
+ when Array
82
+ # implode array
83
+ untranslated.map { |from| parse_value(from, each_translated) }
84
+ when String
85
+ restore_interpolations untranslated, each_translated.next
86
+ else
87
+ untranslated
88
+ end
89
+ end
90
+
91
+ INTERPOLATION_KEY_RE = /%\{[^}]+}/
92
+ UNTRANSLATABLE_STRING = 'zxzxzx'
93
+
94
+ # @param [String] value
95
+ # @return [String] 'hello, %{name}' => 'hello, <round-trippable string>'
96
+ def replace_interpolations(value)
97
+ i = -1
98
+ value.gsub INTERPOLATION_KEY_RE do
99
+ i += 1
100
+ "#{UNTRANSLATABLE_STRING}#{i}"
101
+ end
102
+ end
103
+
104
+ # @param [String] untranslated
105
+ # @param [String] translated
106
+ # @return [String] 'hello, <round-trippable string>' => 'hello, %{name}'
107
+ def restore_interpolations(untranslated, translated)
108
+ return translated if untranslated !~ INTERPOLATION_KEY_RE
109
+ values = untranslated.scan(INTERPOLATION_KEY_RE)
110
+ translated.gsub(/#{Regexp.escape(UNTRANSLATABLE_STRING)}\d+/i) do |m|
111
+ values[m[UNTRANSLATABLE_STRING.length..-1].to_i]
112
+ end
113
+ rescue StandardError => e
114
+ raise_interpolation_error(untranslated, translated, e)
115
+ end
116
+
117
+ def raise_interpolation_error(untranslated, translated, e)
118
+ fail CommandError.new(e, <<-TEXT.strip)
119
+ Error when restoring interpolations:
120
+ original: "#{untranslated}"
121
+ response: "#{translated}"
122
+ error: #{e.message} (#{e.class.name})
123
+ TEXT
124
+ end
125
+
126
+ # @param [Array<String>] list
127
+ # @param [Hash] options
128
+ # @return [Array<String>]
129
+ # @abstract
130
+ def translate_values(list, **options); end
131
+
132
+ # @param [Hash] options
133
+ # @return [Hash]
134
+ # @abstract
135
+ def options_for_translate_values(options); end
136
+
137
+ # @return [Hash]
138
+ # @abstract
139
+ def options_for_html; end
140
+
141
+ # @return [Hash]
142
+ # @abstract
143
+ def options_for_plain; end
144
+
145
+ # @return [String]
146
+ # @abstract
147
+ def no_results_error_message; end
148
+ end
149
+ end
150
+ end
@@ -1,27 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'deepl'
4
- require 'i18n/tasks/html_keys'
5
- require 'i18n/tasks/base_translator'
6
-
7
- module I18n::Tasks
8
- module DeeplTranslation
9
- # @param [I18n::Tasks::Tree::Siblings] forest to translate to the locales of its root nodes
10
- # @param [String] from locale
11
- # @return [I18n::Tasks::Tree::Siblings] translated forest
12
- def deepl_translate_forest(forest, from)
13
- DeeplTranslator.new(self).translate_forest(forest, from)
14
- end
15
- end
4
+ require 'i18n/tasks/translators/base_translator'
16
5
 
6
+ module I18n::Tasks::Translators
17
7
  class DeeplTranslator < BaseTranslator
18
8
  def initialize(*)
19
9
  super
20
10
  configure_api_key!
21
11
  end
22
12
 
13
+ protected
14
+
23
15
  def translate_values(list, from:, to:, **options)
24
- DeepL.translate(list, from, to, options).map(&:text)
16
+ DeepL.translate(list, to_deepl_compatible_locale(from), to_deepl_compatible_locale(to), options).map(&:text)
25
17
  end
26
18
 
27
19
  def options_for_translate_values(**options)
@@ -58,6 +50,11 @@ module I18n::Tasks
58
50
 
59
51
  private
60
52
 
53
+ # Convert 'es-ES' to 'ES'
54
+ def to_deepl_compatible_locale(locale)
55
+ locale.to_s.split('-', 2).first.upcase
56
+ end
57
+
61
58
  def configure_api_key!
62
59
  api_key = @i18n_tasks.translation_config[:deepl_api_key]
63
60
  fail CommandError, I18n.t('i18n_tasks.deepl_translate.errors.no_api_key') if api_key.blank?
@@ -1,21 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'easy_translate'
4
- require 'i18n/tasks/html_keys'
5
- require 'i18n/tasks/base_translator'
6
-
7
- module I18n::Tasks
8
- module GoogleTranslation
9
- # @param [I18n::Tasks::Tree::Siblings] forest to translate to the locales of its root nodes
10
- # @param [String] from locale
11
- # @return [I18n::Tasks::Tree::Siblings] translated forest
12
- def google_translate_forest(forest, from)
13
- GoogleTranslator.new(self).translate_forest(forest, from)
14
- end
15
- end
4
+ require 'i18n/tasks/translators/base_translator'
16
5
 
6
+ module I18n::Tasks::Translators
17
7
  class GoogleTranslator < BaseTranslator
18
- SUPPORTED_LOCALES_WITH_REGION = %w[zh-CN zh-TW].freeze
8
+ protected
19
9
 
20
10
  def translate_values(list, **options)
21
11
  EasyTranslate.translate(list, options)
@@ -43,6 +33,8 @@ module I18n::Tasks
43
33
 
44
34
  private
45
35
 
36
+ SUPPORTED_LOCALES_WITH_REGION = %w[zh-CN zh-TW].freeze
37
+
46
38
  # Convert 'es-ES' to 'es'
47
39
  def to_google_translate_compatible_locale(locale)
48
40
  return locale unless locale.include?('-') && !SUPPORTED_LOCALES_WITH_REGION.include?(locale)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module I18n
4
4
  module Tasks
5
- VERSION = '0.9.22'
5
+ VERSION = '0.9.23'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.22
4
+ version: 0.9.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - glebm
@@ -276,7 +276,6 @@ files:
276
276
  - i18n-tasks.gemspec
277
277
  - lib/i18n/tasks.rb
278
278
  - lib/i18n/tasks/base_task.rb
279
- - lib/i18n/tasks/base_translator.rb
280
279
  - lib/i18n/tasks/cli.rb
281
280
  - lib/i18n/tasks/command/collection.rb
282
281
  - lib/i18n/tasks/command/commander.rb
@@ -310,8 +309,6 @@ files:
310
309
  - lib/i18n/tasks/data/tree/nodes.rb
311
310
  - lib/i18n/tasks/data/tree/siblings.rb
312
311
  - lib/i18n/tasks/data/tree/traversal.rb
313
- - lib/i18n/tasks/deepl_translation.rb
314
- - lib/i18n/tasks/google_translation.rb
315
312
  - lib/i18n/tasks/html_keys.rb
316
313
  - lib/i18n/tasks/ignore_keys.rb
317
314
  - lib/i18n/tasks/key_pattern_matching.rb
@@ -346,6 +343,10 @@ files:
346
343
  - lib/i18n/tasks/split_key.rb
347
344
  - lib/i18n/tasks/stats.rb
348
345
  - lib/i18n/tasks/string_interpolation.rb
346
+ - lib/i18n/tasks/translation.rb
347
+ - lib/i18n/tasks/translators/base_translator.rb
348
+ - lib/i18n/tasks/translators/deepl_translator.rb
349
+ - lib/i18n/tasks/translators/google_translator.rb
349
350
  - lib/i18n/tasks/unused_keys.rb
350
351
  - lib/i18n/tasks/used_keys.rb
351
352
  - lib/i18n/tasks/version.rb
@@ -1,148 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module I18n::Tasks
4
- class BaseTranslator
5
- # @param [I18n::Tasks::BaseTask] i18n_tasks
6
- def initialize(i18n_tasks)
7
- @i18n_tasks = i18n_tasks
8
- end
9
-
10
- # @param [I18n::Tasks::Tree::Siblings] forest to translate to the locales of its root nodes
11
- # @param [String] from locale
12
- # @return [I18n::Tasks::Tree::Siblings] translated forest
13
- def translate_forest(forest, from)
14
- forest.inject @i18n_tasks.empty_forest do |result, root|
15
- translated = translate_pairs(root.key_values(root: true), to: root.key, from: from)
16
- result.merge! Data::Tree::Siblings.from_flat_pairs(translated)
17
- end
18
- end
19
-
20
- protected
21
-
22
- # @param [Array<[String, Object]>] list of key-value pairs
23
- # @return [Array<[String, Object]>] translated list
24
- def translate_pairs(list, opts)
25
- return [] if list.empty?
26
- opts = opts.dup
27
- key_pos = list.each_with_index.inject({}) { |idx, ((k, _v), i)| idx.update(k => i) }
28
- # copy reference keys as is, instead of translating
29
- reference_key_vals = list.select { |_k, v| v.is_a? Symbol } || []
30
- list -= reference_key_vals
31
- result = list.group_by { |k_v| @i18n_tasks.html_key? k_v[0], opts[:from] }.map do |is_html, list_slice|
32
- fetch_translations list_slice, opts.merge(is_html ? options_for_html : options_for_plain)
33
- end.reduce(:+) || []
34
- result.concat(reference_key_vals)
35
- result.sort! { |a, b| key_pos[a[0]] <=> key_pos[b[0]] }
36
- result
37
- end
38
-
39
- # @param [Array<[String, Object]>] list of key-value pairs
40
- # @return [Array<[String, Object]>] translated list
41
- def fetch_translations(list, opts)
42
- from_values(list, translate_values(to_values(list), **options_for_translate_values(**opts))).tap do |result|
43
- fail CommandError, no_results_error_message if result.blank?
44
- end
45
- end
46
-
47
- # @param [Array<[String, Object]>] list of key-value pairs
48
- # @return [Array<String>] values for translation extracted from list
49
- def to_values(list)
50
- list.map { |l| dump_value l[1] }.flatten.compact
51
- end
52
-
53
- # @param [Array<[String, Object]>] list
54
- # @param [Array<String>] translated_values
55
- # @return [Array<[String, Object]>] translated key-value pairs
56
- def from_values(list, translated_values)
57
- keys = list.map(&:first)
58
- untranslated_values = list.map(&:last)
59
- keys.zip parse_value(untranslated_values, translated_values.to_enum)
60
- end
61
-
62
- # Prepare value for translation.
63
- # @return [String, Array<String, nil>, nil] value for Google Translate or nil for non-string values
64
- def dump_value(value)
65
- case value
66
- when Array
67
- # dump recursively
68
- value.map { |v| dump_value v }
69
- when String
70
- replace_interpolations value
71
- end
72
- end
73
-
74
- # Parse translated value from the each_translated enumerator
75
- # @param [Object] untranslated
76
- # @param [Enumerator] each_translated
77
- # @return [Object] final translated value
78
- def parse_value(untranslated, each_translated)
79
- case untranslated
80
- when Array
81
- # implode array
82
- untranslated.map { |from| parse_value(from, each_translated) }
83
- when String
84
- restore_interpolations untranslated, each_translated.next
85
- else
86
- untranslated
87
- end
88
- end
89
-
90
- INTERPOLATION_KEY_RE = /%\{[^}]+}/
91
- UNTRANSLATABLE_STRING = 'zxzxzx'
92
-
93
- # @param [String] value
94
- # @return [String] 'hello, %{name}' => 'hello, <round-trippable string>'
95
- def replace_interpolations(value)
96
- i = -1
97
- value.gsub INTERPOLATION_KEY_RE do
98
- i += 1
99
- "#{UNTRANSLATABLE_STRING}#{i}"
100
- end
101
- end
102
-
103
- # @param [String] untranslated
104
- # @param [String] translated
105
- # @return [String] 'hello, <round-trippable string>' => 'hello, %{name}'
106
- def restore_interpolations(untranslated, translated)
107
- return translated if untranslated !~ INTERPOLATION_KEY_RE
108
- values = untranslated.scan(INTERPOLATION_KEY_RE)
109
- translated.gsub(/#{Regexp.escape(UNTRANSLATABLE_STRING)}\d+/i) do |m|
110
- values[m[UNTRANSLATABLE_STRING.length..-1].to_i]
111
- end
112
- rescue StandardError => e
113
- raise_interpolation_error(untranslated, translated, e)
114
- end
115
-
116
- def raise_interpolation_error(untranslated, translated, e)
117
- fail CommandError.new(e, <<-TEXT.strip)
118
- Error when restoring interpolations:
119
- original: "#{untranslated}"
120
- response: "#{translated}"
121
- error: #{e.message} (#{e.class.name})
122
- TEXT
123
- end
124
-
125
- # @param [Array<String>] list
126
- # @param [Hash] options
127
- # @return [Array<String>]
128
- # @abstract
129
- def translate_values(list, **options); end
130
-
131
- # @param [Hash] options
132
- # @return [Hash]
133
- # @abstract
134
- def options_for_translate_values(options); end
135
-
136
- # @return [Hash]
137
- # @abstract
138
- def options_for_html; end
139
-
140
- # @return [Hash]
141
- # @abstract
142
- def options_for_plain; end
143
-
144
- # @return [String]
145
- # @abstract
146
- def no_results_error_message; end
147
- end
148
- end