i18n-tasks 0.9.22 → 0.9.23

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: 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