translatomatic 0.1.3 → 0.2.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.
Files changed (139) hide show
  1. checksums.yaml +5 -5
  2. data/.gitattributes +20 -20
  3. data/.gitignore +19 -15
  4. data/.rspec +3 -3
  5. data/.rubocop.yml +28 -0
  6. data/.translatomatic/config.yml +4 -0
  7. data/.travis.yml +4 -6
  8. data/.yardopts +9 -9
  9. data/Gemfile +8 -4
  10. data/Guardfile +4 -5
  11. data/README.de.md +55 -50
  12. data/README.en.md +177 -0
  13. data/README.es.md +53 -48
  14. data/README.fr.md +53 -48
  15. data/README.it.md +54 -49
  16. data/README.ja.md +63 -58
  17. data/README.ko.md +59 -54
  18. data/README.md +17 -13
  19. data/README.ms.md +50 -45
  20. data/README.pt.md +54 -49
  21. data/README.ru.md +57 -52
  22. data/README.sv.md +51 -46
  23. data/README.zh.md +60 -55
  24. data/Rakefile +3 -3
  25. data/TODO.txt +6 -0
  26. data/bin/console +3 -3
  27. data/bin/translatomatic +4 -2
  28. data/config/i18n-tasks.yml +130 -0
  29. data/config/locales/translatomatic/de.yml +141 -99
  30. data/config/locales/translatomatic/en.yml +129 -89
  31. data/config/locales/translatomatic/es.yml +136 -99
  32. data/config/locales/translatomatic/fr.yml +139 -100
  33. data/config/locales/translatomatic/it.yml +135 -97
  34. data/config/locales/translatomatic/ja.yml +137 -98
  35. data/config/locales/translatomatic/ko.yml +138 -98
  36. data/config/locales/translatomatic/ms.yml +138 -100
  37. data/config/locales/translatomatic/pt.yml +137 -101
  38. data/config/locales/translatomatic/ru.yml +136 -98
  39. data/config/locales/translatomatic/sv.yml +134 -96
  40. data/config/locales/translatomatic/zh.yml +136 -97
  41. data/db/migrate/201712170000_initial.rb +2 -3
  42. data/lib/translatomatic.rb +40 -25
  43. data/lib/translatomatic/cli.rb +5 -1
  44. data/lib/translatomatic/cli/base.rb +61 -58
  45. data/lib/translatomatic/cli/common_options.rb +14 -11
  46. data/lib/translatomatic/cli/config.rb +96 -91
  47. data/lib/translatomatic/cli/database.rb +85 -23
  48. data/lib/translatomatic/cli/main.rb +158 -104
  49. data/lib/translatomatic/cli/thor.rb +29 -0
  50. data/lib/translatomatic/cli/translate.rb +134 -157
  51. data/lib/translatomatic/config.rb +10 -301
  52. data/lib/translatomatic/config/display.rb +78 -0
  53. data/lib/translatomatic/config/files.rb +60 -0
  54. data/lib/translatomatic/config/location_settings.rb +133 -0
  55. data/lib/translatomatic/config/options.rb +68 -0
  56. data/lib/translatomatic/config/selector.rb +127 -0
  57. data/lib/translatomatic/config/settings.rb +148 -0
  58. data/lib/translatomatic/converter.rb +40 -28
  59. data/lib/translatomatic/database.rb +127 -110
  60. data/lib/translatomatic/define_options.rb +4 -5
  61. data/lib/translatomatic/escaped_unicode.rb +86 -76
  62. data/lib/translatomatic/extractor.rb +5 -2
  63. data/lib/translatomatic/extractor/base.rb +12 -12
  64. data/lib/translatomatic/extractor/ruby.rb +7 -6
  65. data/lib/translatomatic/file_translator.rb +101 -244
  66. data/lib/translatomatic/flattenation.rb +39 -0
  67. data/lib/translatomatic/http.rb +13 -0
  68. data/lib/translatomatic/http/client.rb +144 -0
  69. data/lib/translatomatic/http/exception.rb +43 -0
  70. data/lib/translatomatic/http/file_param.rb +27 -0
  71. data/lib/translatomatic/http/param.rb +37 -0
  72. data/lib/translatomatic/http/request.rb +91 -0
  73. data/lib/translatomatic/i18n.rb +43 -0
  74. data/lib/translatomatic/locale.rb +71 -59
  75. data/lib/translatomatic/logger.rb +43 -28
  76. data/lib/translatomatic/metadata.rb +58 -0
  77. data/lib/translatomatic/model.rb +4 -2
  78. data/lib/translatomatic/model/locale.rb +5 -5
  79. data/lib/translatomatic/model/text.rb +5 -5
  80. data/lib/translatomatic/option.rb +57 -34
  81. data/lib/translatomatic/path_utils.rb +126 -0
  82. data/lib/translatomatic/progress_updater.rb +13 -16
  83. data/lib/translatomatic/provider.rb +101 -0
  84. data/lib/translatomatic/provider/base.rb +136 -0
  85. data/lib/translatomatic/provider/frengly.rb +55 -0
  86. data/lib/translatomatic/provider/google.rb +78 -0
  87. data/lib/translatomatic/provider/google_web.rb +50 -0
  88. data/lib/translatomatic/provider/microsoft.rb +144 -0
  89. data/lib/translatomatic/provider/my_memory.rb +75 -0
  90. data/lib/translatomatic/provider/yandex.rb +61 -0
  91. data/lib/translatomatic/resource_file.rb +59 -53
  92. data/lib/translatomatic/resource_file/base.rb +171 -237
  93. data/lib/translatomatic/resource_file/csv.rb +176 -24
  94. data/lib/translatomatic/resource_file/html.rb +21 -42
  95. data/lib/translatomatic/resource_file/key_value_support.rb +117 -0
  96. data/lib/translatomatic/resource_file/markdown.rb +36 -38
  97. data/lib/translatomatic/resource_file/plist.rb +121 -126
  98. data/lib/translatomatic/resource_file/po.rb +104 -82
  99. data/lib/translatomatic/resource_file/properties.rb +48 -77
  100. data/lib/translatomatic/resource_file/properties.treetop +87 -0
  101. data/lib/translatomatic/resource_file/resw.rb +56 -41
  102. data/lib/translatomatic/resource_file/subtitle.rb +86 -54
  103. data/lib/translatomatic/resource_file/text.rb +18 -18
  104. data/lib/translatomatic/resource_file/xcode_strings.rb +32 -63
  105. data/lib/translatomatic/resource_file/xcode_strings.treetop +85 -0
  106. data/lib/translatomatic/resource_file/xml.rb +94 -81
  107. data/lib/translatomatic/resource_file/yaml.rb +54 -68
  108. data/lib/translatomatic/retry_executor.rb +37 -0
  109. data/lib/translatomatic/slurp.rb +32 -0
  110. data/lib/translatomatic/string_batcher.rb +50 -0
  111. data/lib/translatomatic/string_escaping.rb +61 -0
  112. data/lib/translatomatic/text.rb +263 -0
  113. data/lib/translatomatic/text_collection.rb +66 -0
  114. data/lib/translatomatic/tmx.rb +5 -3
  115. data/lib/translatomatic/tmx/document.rb +107 -82
  116. data/lib/translatomatic/tmx/translation_unit.rb +19 -18
  117. data/lib/translatomatic/translation.rb +8 -28
  118. data/lib/translatomatic/translation/collection.rb +199 -0
  119. data/lib/translatomatic/translation/fetcher.rb +123 -0
  120. data/lib/translatomatic/translation/munging.rb +112 -0
  121. data/lib/translatomatic/translation/result.rb +50 -0
  122. data/lib/translatomatic/translation/sharer.rb +32 -0
  123. data/lib/translatomatic/translation/stats.rb +44 -0
  124. data/lib/translatomatic/translator.rb +91 -88
  125. data/lib/translatomatic/type_cast.rb +63 -0
  126. data/lib/translatomatic/util.rb +37 -33
  127. data/lib/translatomatic/version.rb +2 -2
  128. data/translatomatic.gemspec +57 -46
  129. metadata +136 -59
  130. data/lib/translatomatic/http_request.rb +0 -162
  131. data/lib/translatomatic/string.rb +0 -188
  132. data/lib/translatomatic/translation_result.rb +0 -86
  133. data/lib/translatomatic/translation_stats.rb +0 -31
  134. data/lib/translatomatic/translator/base.rb +0 -128
  135. data/lib/translatomatic/translator/frengly.rb +0 -62
  136. data/lib/translatomatic/translator/google.rb +0 -37
  137. data/lib/translatomatic/translator/microsoft.rb +0 -41
  138. data/lib/translatomatic/translator/my_memory.rb +0 -68
  139. data/lib/translatomatic/translator/yandex.rb +0 -56
@@ -1,5 +1,8 @@
1
- # Classes to extract strings from files
2
- module Translatomatic::Extractor; end
1
+ module Translatomatic
2
+ # Classes to extract strings from files
3
+ module Extractor
4
+ end
5
+ end
3
6
 
4
7
  require 'translatomatic/extractor/base'
5
8
  require 'translatomatic/extractor/ruby'
@@ -1,16 +1,16 @@
1
- module Translatomatic::Extractor
2
- # Base class for string extraction functionality
3
- class Base
1
+ module Translatomatic
2
+ module Extractor
3
+ # Base class for string extraction functionality
4
+ class Base
5
+ def initialize(path)
6
+ @path = path.is_a?(Pathname) ? path : Pathname.new(path)
7
+ @contents = @path.read
8
+ end
4
9
 
5
- def initialize(path)
6
- @path = path.kind_of?(Pathname) ? path : Pathname.new(path)
7
- @contents = @path.read
10
+ # @return [Array<String>] All strings found
11
+ def extract
12
+ @contents.scan(/\"(.*?[^\\])"|'(.*?[^\\])'/).flatten.compact
13
+ end
8
14
  end
9
-
10
- # @return [Array<String>] All strings found
11
- def extract
12
- @contents.scan(/\"(.*?[^\\])"|'(.*?[^\\])'/).flatten.compact
13
- end
14
-
15
15
  end
16
16
  end
@@ -1,6 +1,7 @@
1
- module Translatomatic::Extractor
2
- # Class to extract strings from ruby code
3
- class Ruby < Base
4
-
5
- end # class
6
- end # module
1
+ module Translatomatic
2
+ module Extractor
3
+ # Class to extract strings from ruby code
4
+ class Ruby < Base
5
+ end
6
+ end
7
+ end
@@ -1,269 +1,126 @@
1
- # The file translator ties together functionality from translators,
2
- # resource files, and the database to convert files from one
3
- # language to another.
4
- class Translatomatic::FileTranslator
5
-
6
- # @return [Array<Translatomatic::Model::Text>] A list of translations saved to the database
7
- attr_reader :db_translations
8
-
9
- # Create a converter to translate files
10
- #
11
- # @param options [Hash<Symbol,Object>] converter and/or translator options.
12
- def initialize(options = {})
13
- @dry_run = options[:dry_run]
14
- @listener = options[:listener]
15
- @translators = Translatomatic::Translator.resolve(options[:translator], options)
16
- raise t("file_translator.translator_required") if @translators.empty?
17
- @translators.each { |i| i.listener = @listener } if @listener
18
-
19
- # use database by default if we're connected to a database
20
- use_db = options.include?(:use_database) ? options[:use_database] : true
21
- @use_db = use_db && ActiveRecord::Base.connected?
22
- log.debug(t("file_translator.database_disabled")) unless @use_db
23
-
24
- @db_translations = []
25
- @translations = {} # map of original text to Translation
26
- end
27
-
28
- # @return [Translatomatic::TranslationStats] Translation statistics
29
- def stats
30
- @stats ||= Translatomatic::TranslationStats.new(@translations)
31
- end
32
-
33
- # Translate properties of source_file to the target locale.
34
- # Does not write changes to disk.
35
- #
36
- # @param file [String, Translatomatic::ResourceFile] File to translate
37
- # @param to_locale [String] The target locale, e.g. "fr"
38
- # @return [Translatomatic::ResourceFile] The translated resource file
39
- def translate(file, to_locale)
40
- file = resource_file(file)
41
- to_locale = parse_locale(to_locale)
42
-
43
- # do nothing if target language is the same as source language
44
- return file if file.locale.language == to_locale.language
45
-
46
- result = Translatomatic::TranslationResult.new(file, to_locale)
47
-
48
- # translate using strings from the database first
49
- each_translator(result) { translate_properties_with_db(result) }
50
- # send remaining unknown strings to translator
51
- each_translator(result) { translate_properties_with_translator(result) }
52
-
53
- log.debug(t("file_translator.stats", from_db: stats.from_db,
54
- from_translator: stats.from_translator,
55
- untranslated: result.untranslated.length))
56
- @listener.untranslated_texts(result.untranslated) if @listener
57
-
58
- file.properties = result.properties
59
- file.locale = to_locale
60
- file
61
- end
62
-
63
- # Translates a resource file and writes results to a target resource file.
64
- # The path of the target resource file is automatically determined.
65
- #
66
- # @param source [Translatomatic::ResourceFile] The source
67
- # @param to_locale [String] The target locale, e.g. "fr"
68
- # @return [Translatomatic::ResourceFile] The translated resource file
69
- def translate_to_file(source, to_locale)
70
- # Automatically determines the target filename based on target locale.
71
- source = resource_file(source)
72
- target = Translatomatic::ResourceFile.load(source.path)
73
- target.path = source.locale_path(to_locale)
74
-
75
- log.info(t("file_translator.translating", source: source,
76
- source_locale: source.locale, target: target, target_locale: to_locale))
77
- translate(target, to_locale)
78
- unless @dry_run
79
- target.path.parent.mkpath
80
- target.save
1
+ module Translatomatic
2
+ # Translates resource files from one language to another.
3
+ class FileTranslator
4
+ # @return [Translatomatic::Translator] Translator object
5
+ attr_reader :translator
6
+
7
+ # Create a new FileTranslator instance
8
+ # @param options [Hash<Symbol,Object>] converter and/or
9
+ # provider options.
10
+ def initialize(options = {})
11
+ @dry_run = options[:dry_run]
12
+ @in_place = options[:in_place]
13
+ @translator = Translator.new(options)
81
14
  end
82
- target
83
- end
84
-
85
- private
86
-
87
- include Translatomatic::Util
88
- include Translatomatic::DefineOptions
89
15
 
90
- define_options(
91
- { name: :dry_run, type: :boolean, aliases: "-n",
92
- desc: t("file_translator.dry_run"),
93
- command_line_only: true
94
- },
95
- { name: :use_database, type: :boolean, default: true,
96
- desc: t("file_translator.use_database")
97
- }
98
- )
16
+ # Translate properties of file to the target locale.
17
+ # Does not write changes to disk.
18
+ # @param file [String, Translatomatic::ResourceFile] File to translate
19
+ # @param to_locale [String] The target locale, e.g. "fr"
20
+ # @return [Translatomatic::ResourceFile] The translated resource file
21
+ def translate(file, to_locale)
22
+ file = resource_file(file)
23
+ to_locale = build_locale(to_locale)
24
+
25
+ # do nothing if target language is the same as source language
26
+ return file if file.locale.language == to_locale.language
27
+
28
+ texts = texts_from_file(file)
29
+ unless @dry_run
30
+ collection = @translator.translate(texts, to_locale)
31
+ update_properties(file, to_locale, collection)
32
+ end
33
+ file
34
+ end
99
35
 
100
- def each_translator(result)
101
- @translators.each do |translator|
102
- break if result.untranslated.empty?
103
- @current_translator = translator
104
- yield
36
+ # Translates a resource file and writes results to a target
37
+ # resource file. The path of the target resource file is
38
+ # automagically determined.
39
+ # @param source [ResourceFile] The source
40
+ # @param to_locale [String] The target locale, e.g. "fr"
41
+ # @return [ResourceFile] The translated resource file
42
+ def translate_to_file(source, to_locale)
43
+ target = translation_target_file(source, to_locale)
44
+ return source unless target
45
+ translate(target, to_locale)
46
+ save_resource_file(target)
105
47
  end
106
- end
107
48
 
108
- # Attempt to restore interpolated variable names in the translation.
109
- # If variable names cannot be restored, sets the translation result to nil.
110
- # @param result [Translatomatic::TranslationResult] translation result
111
- # @param translation [Translatomatic::Translation] translation
112
- # @return [void]
113
- def restore_variables(result, translation)
114
- file = result.file
115
- return unless file.class.supports_variable_interpolation?
49
+ private
50
+
51
+ include Translatomatic::Util
52
+ include Translatomatic::DefineOptions
116
53
 
117
- # find variables in the original string
118
- variables = string_variables(translation.original, file.locale, file)
119
- # find variables in the translated string
120
- translated_variables = string_variables(translation.result, result.to_locale, file)
54
+ define_option :in_place, type: :boolean, command_line_only: true,
55
+ default: false,
56
+ desc: t('file_translator.in_place')
121
57
 
122
- if variables.length == translated_variables.length
123
- # we can restore variables. sort by largest offset first.
124
- # not using translation() method as that adds to @translations hash.
125
- conversions = variables.zip(translated_variables).collect {
126
- |v1, v2| Translatomatic::Translation.new(v1, v2)
127
- }
128
- conversions.sort_by! { |t| -t.original.offset }
129
- conversions.each do |conversion|
130
- v1 = conversion.original
131
- v2 = conversion.result
132
- translation.result[v2.offset, v2.length] = v1.value
58
+ # Update file properties from the given translation collection
59
+ def update_properties(file, to_locale, collection)
60
+ value_map = init_value_map(file)
61
+ file.properties.each_value do |value|
62
+ keys = value_map[value.to_s]
63
+ translation = collection.get(value, to_locale) # best translation
64
+ new_value = translation ? translation.result.to_s : nil
65
+ keys.each { |key| file.set(key, new_value) }
133
66
  end
134
- else
135
- # unable to restore interpolated variable names
136
- log.debug("#{@current_translator.name}: unable to restore variables: #{translation.result}")
137
- translation.result = nil # mark result as invalid
67
+ file.locale = to_locale
138
68
  end
139
- end
140
69
 
141
- def string_variables(value, locale, file)
142
- string(value, locale).substrings(file.variable_regex)
143
- end
144
-
145
- def resource_file(path)
146
- if path.kind_of?(Translatomatic::ResourceFile::Base)
147
- path
148
- else
149
- file = Translatomatic::ResourceFile.load(path)
150
- raise t("file.unsupported", file: path) unless file
70
+ def save_resource_file(file)
71
+ unless @dry_run
72
+ file.path.parent.mkpath
73
+ file.save
74
+ end
151
75
  file
152
76
  end
153
- end
154
77
 
155
- # update result with translations from the database.
156
- def translate_properties_with_db(result)
157
- db_texts = []
158
- unless database_disabled?
159
- translations = []
160
- untranslated = hashify(result.untranslated)
161
- db_texts = find_database_translations(result, result.untranslated.to_a)
162
- db_texts.each do |db_text|
163
- from_text = db_text.from_text.value
164
- if untranslated[from_text]
165
- translation = translation(untranslated[from_text], db_text.value, true)
166
- restore_variables(result, translation)
167
- translations << translation
168
- end
78
+ # Create a target file to write for the given source file.
79
+ # @param source [ResourceFile] Source resource file
80
+ # @param to_locale [Locale] Target locale
81
+ # @return [ResourceFile] Target file, or nil if we shouldn't translate
82
+ def translation_target_file(source, to_locale)
83
+ if @in_place
84
+ source
85
+ else
86
+ target_path = source.locale_path(to_locale)
87
+ # don't overwrite source unless using @in_place
88
+ return nil if target_path == source.path
89
+ target = resource_file(source.path, source.options)
90
+ target.path = target_path
91
+ target
169
92
  end
170
-
171
- result.update_strings(translations)
172
- @listener.translated_texts(db_texts) if @listener
173
93
  end
174
- db_texts
175
- end
176
-
177
- # update result with translations from the translator.
178
- def translate_properties_with_translator(result)
179
- untranslated = result.untranslated.to_a.select { |i| translatable?(i) }
180
- translated = []
181
- if !untranslated.empty? && !@dry_run
182
- untranslated_strings = untranslated.collect { |i| i.to_s }
183
- log.debug("translating: #{untranslated_strings}")
184
- translated = @current_translator.translate(untranslated_strings,
185
- result.from_locale, result.to_locale
186
- )
187
94
 
188
- # create list of translations, filtering out invalid translations
189
- translations = []
190
- untranslated.zip(translated).each do |from, to|
191
- translation = translation(from, to, false)
192
- restore_variables(result, translation)
193
- translations << translation
95
+ def texts_from_file(file)
96
+ texts = []
97
+ file.properties.each do |key, value|
98
+ text = build_text(value, file.locale)
99
+ text.preserve_regex = file.variable_regex
100
+ text.context = file.get_context(key)
101
+ texts << text
194
102
  end
103
+ texts
104
+ end
195
105
 
196
- result.update_strings(translations)
197
- unless database_disabled?
198
- save_database_translations(result, translations)
106
+ # set up a mapping from property value -> key list
107
+ def init_value_map(file)
108
+ value_map = {}
109
+ file.properties.each do |key, value|
110
+ keylist = (value_map[value.to_s] ||= [])
111
+ keylist << key
199
112
  end
113
+ value_map
200
114
  end
201
- translated
202
- end
203
-
204
- def translation(from, to, from_database = false)
205
- translator = @current_translator.name
206
- t = Translatomatic::Translation.new(from, to, translator, from_database)
207
- @translations[from] = t
208
- t
209
- end
210
-
211
- def database_disabled?
212
- !@use_db
213
- end
214
115
 
215
- def parse_locale(locale)
216
- Translatomatic::Locale.parse(locale)
217
- end
218
-
219
- def translatable?(string)
220
- # don't translate numbers
221
- string && !string.match(/\A\s*\z/) && !string.match(/\A[\d,]+\z/)
222
- end
223
-
224
- def save_database_translations(result, translations)
225
- ActiveRecord::Base.transaction do
226
- from = db_locale(result.from_locale)
227
- to = db_locale(result.to_locale)
228
- translations.each do |translation|
229
- next if translation.result.nil? # skip invalid translations
230
- save_database_translation(from, to, translation)
116
+ def resource_file(path, options = {})
117
+ if path.is_a?(Translatomatic::ResourceFile::Base)
118
+ path
119
+ else
120
+ file = Translatomatic::ResourceFile.load(path, options)
121
+ raise t('file.unsupported', file: path) unless file
122
+ file
231
123
  end
232
124
  end
233
125
  end
234
-
235
- def save_database_translation(from_locale, to_locale, translation)
236
- original_text = Translatomatic::Model::Text.find_or_create_by!(
237
- locale: from_locale,
238
- value: translation.original.to_s
239
- )
240
-
241
- text = Translatomatic::Model::Text.find_or_create_by!(
242
- locale: to_locale,
243
- value: translation.result.to_s,
244
- from_text: original_text,
245
- translator: @current_translator.name
246
- )
247
- @db_translations += [original_text, text]
248
- text
249
- end
250
-
251
- def find_database_translations(result, untranslated)
252
- from = db_locale(result.from_locale)
253
- to = db_locale(result.to_locale)
254
-
255
- Translatomatic::Model::Text.where({
256
- locale: to,
257
- translator: @current_translator.name,
258
- from_texts_texts: {
259
- locale_id: from,
260
- # convert untranslated set to strings
261
- value: untranslated.collect { |i| i.to_s }
262
- }
263
- }).joins(:from_text)
264
- end
265
-
266
- def db_locale(locale)
267
- Translatomatic::Model::Locale.from_tag(locale)
268
- end
269
126
  end
@@ -0,0 +1,39 @@
1
+ module Translatomatic
2
+ # Methods to flatten data
3
+ module Flattenation
4
+ private
5
+
6
+ # flatten hash or array of hashes to a hash of key => value pairs
7
+ def flatten(data)
8
+ result = {}
9
+
10
+ if data.is_a?(Hash)
11
+ data.each do |key, value|
12
+ flatten_add(result, key, value)
13
+ end
14
+ elsif data.is_a?(Array)
15
+ data.each_with_index do |value, i|
16
+ key = 'key' + i.to_s
17
+ flatten_add(result, key, value)
18
+ end
19
+ end
20
+
21
+ result
22
+ end
23
+
24
+ def flatten_add(result, key, value)
25
+ if needs_flatten?(value)
26
+ children = flatten(value)
27
+ children.each do |ck, cv|
28
+ result[key + '.' + ck] = cv
29
+ end
30
+ else
31
+ result[key] = value
32
+ end
33
+ end
34
+
35
+ def needs_flatten?(value)
36
+ value.is_a?(Array) || value.is_a?(Hash)
37
+ end
38
+ end
39
+ end