translatomatic 0.1.2 → 0.1.3

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/.translatomatic/config.yml +18 -0
  3. data/.travis.yml +33 -33
  4. data/Gemfile +6 -4
  5. data/README.de.md +53 -18
  6. data/README.es.md +55 -20
  7. data/README.fr.md +54 -19
  8. data/README.it.md +58 -23
  9. data/README.ja.md +54 -19
  10. data/README.ko.md +58 -23
  11. data/README.md +167 -141
  12. data/README.ms.md +51 -16
  13. data/README.pt.md +58 -23
  14. data/README.ru.md +53 -18
  15. data/README.sv.md +53 -18
  16. data/README.zh.md +53 -18
  17. data/bin/translatomatic +6 -6
  18. data/bin/travis +24 -26
  19. data/config/locales/translatomatic/de.yml +22 -11
  20. data/config/locales/translatomatic/en.yml +21 -12
  21. data/config/locales/translatomatic/es.yml +22 -11
  22. data/config/locales/translatomatic/fr.yml +22 -12
  23. data/config/locales/translatomatic/it.yml +22 -11
  24. data/config/locales/translatomatic/ja.yml +22 -11
  25. data/config/locales/translatomatic/ko.yml +22 -11
  26. data/config/locales/translatomatic/ms.yml +22 -11
  27. data/config/locales/translatomatic/pt.yml +22 -11
  28. data/config/locales/translatomatic/ru.yml +22 -11
  29. data/config/locales/translatomatic/sv.yml +22 -11
  30. data/config/locales/translatomatic/zh.yml +22 -11
  31. data/db/migrate/201712170000_initial.rb +25 -25
  32. data/lib/translatomatic/cli/base.rb +81 -73
  33. data/lib/translatomatic/cli/config.rb +110 -81
  34. data/lib/translatomatic/cli/main.rb +85 -72
  35. data/lib/translatomatic/cli/translate.rb +141 -106
  36. data/lib/translatomatic/cli.rb +8 -8
  37. data/lib/translatomatic/config.rb +302 -155
  38. data/lib/translatomatic/converter.rb +28 -260
  39. data/lib/translatomatic/database.rb +134 -134
  40. data/lib/translatomatic/define_options.rb +22 -0
  41. data/lib/translatomatic/escaped_unicode.rb +0 -0
  42. data/lib/translatomatic/extractor/base.rb +16 -16
  43. data/lib/translatomatic/extractor/ruby.rb +6 -6
  44. data/lib/translatomatic/extractor.rb +5 -5
  45. data/lib/translatomatic/file_translator.rb +269 -0
  46. data/lib/translatomatic/http_request.rb +162 -162
  47. data/lib/translatomatic/locale.rb +76 -76
  48. data/lib/translatomatic/logger.rb +23 -23
  49. data/lib/translatomatic/model/locale.rb +25 -25
  50. data/lib/translatomatic/model/text.rb +19 -19
  51. data/lib/translatomatic/model.rb +1 -1
  52. data/lib/translatomatic/option.rb +37 -41
  53. data/lib/translatomatic/progress_updater.rb +13 -13
  54. data/lib/translatomatic/resource_file/base.rb +269 -192
  55. data/lib/translatomatic/resource_file/csv.rb +37 -0
  56. data/lib/translatomatic/resource_file/html.rb +54 -47
  57. data/lib/translatomatic/resource_file/markdown.rb +50 -55
  58. data/lib/translatomatic/resource_file/plist.rb +153 -19
  59. data/lib/translatomatic/resource_file/po.rb +107 -0
  60. data/lib/translatomatic/resource_file/properties.rb +91 -90
  61. data/lib/translatomatic/resource_file/resw.rb +50 -30
  62. data/lib/translatomatic/resource_file/subtitle.rb +75 -0
  63. data/lib/translatomatic/resource_file/text.rb +24 -30
  64. data/lib/translatomatic/resource_file/xcode_strings.rb +75 -80
  65. data/lib/translatomatic/resource_file/xml.rb +98 -91
  66. data/lib/translatomatic/resource_file/yaml.rb +94 -116
  67. data/lib/translatomatic/resource_file.rb +87 -78
  68. data/lib/translatomatic/string.rb +188 -188
  69. data/lib/translatomatic/tmx/document.rb +99 -99
  70. data/lib/translatomatic/translation_result.rb +63 -63
  71. data/lib/translatomatic/{converter_stats.rb → translation_stats.rb} +17 -17
  72. data/lib/translatomatic/translator/base.rb +1 -1
  73. data/lib/translatomatic/translator/google.rb +2 -0
  74. data/lib/translatomatic/translator.rb +10 -2
  75. data/lib/translatomatic/util.rb +45 -45
  76. data/lib/translatomatic/version.rb +7 -7
  77. data/lib/translatomatic.rb +52 -49
  78. data/translatomatic.gemspec +3 -2
  79. metadata +25 -5
@@ -1,192 +1,269 @@
1
- # Base class for resource file implementations
2
- # @abstract Subclasses implement different types of resource files
3
- class Translatomatic::ResourceFile::Base
4
-
5
- attr_accessor :locale
6
- attr_accessor :path
7
-
8
- # @return [Hash<String,String>] key -> value properties
9
- attr_reader :properties
10
-
11
- # @return [Array<String>] File extensions supported by this resource file
12
- def self.extensions
13
- raise "extensions must be implemented by subclass"
14
- end
15
-
16
- # Create a new resource file.
17
- # If locale is unspecified, attempts to determine the locale of the file
18
- # automatically, and if that fails, uses the default locale.
19
- # @param path [String] Path to the file
20
- # @param locale [String] Locale of the file contents
21
- # @return [Translatomatic::ResourceFile::Base] the resource file.
22
- def initialize(path, locale = nil)
23
- @path = path.kind_of?(Pathname) ? path : Pathname.new(path)
24
- @locale = Translatomatic::Locale.parse(locale || detect_locale || Translatomatic::Locale.default)
25
- raise t("resource.unknown_locale") unless @locale && @locale.language
26
- @valid = false
27
- @properties = {}
28
- end
29
-
30
- # @return [String] The format of this resource file, e.g. "Properties"
31
- def format
32
- self.class.name.demodulize.downcase.to_sym
33
- end
34
-
35
- # Create a path for the current resource file with a given locale
36
- # @param locale [String] The target locale
37
- # @return [Pathname] The path of this resource file modified for the given locale
38
- def locale_path(locale)
39
- basename = path.sub_ext('').basename.to_s
40
-
41
- extlist = extension_list
42
- if extlist.length >= 2 && loc_idx = find_locale(extlist)
43
- # extension(s) contains locale, replace it
44
- extlist[loc_idx] = locale.to_s
45
- elsif valid_locale?(basename)
46
- # basename is a locale name, replace it
47
- path.dirname + (locale.to_s + path.extname)
48
- else
49
- # remove any underscore and trailing text from basename
50
- deunderscored = basename.sub(/_.*?$/, '')
51
- # add _locale.ext
52
- filename = deunderscored + "_" + locale.to_s + path.extname
53
- path.dirname + filename
54
- end
55
- end
56
-
57
- # Set all properties
58
- # @param properties [Hash<String,String>] New properties
59
- def properties=(properties)
60
- # use set rather that set @properties directly as subclasses override set()
61
- properties.each do |key, value|
62
- set(key, value)
63
- end
64
- end
65
-
66
- # Get the value of a property
67
- # @param key [String] The name of the property
68
- # @return [String] The value of the property
69
- def get(key)
70
- @properties[key]
71
- end
72
-
73
- # Set a property
74
- # @param key [String] The name of the property
75
- # @param value [String] The new value of the property
76
- # @return [String] The new value of the property
77
- def set(key, value)
78
- @properties[key] = value
79
- end
80
-
81
- # Test if the current resource file is valid
82
- # @return true if the current file is valid
83
- def valid?
84
- @valid
85
- end
86
-
87
- # Save the resource file.
88
- # @param target [Pathname] The destination path
89
- # @param options [Hash<Symbol, Object>] Output format options
90
- # @return [void]
91
- def save(target = path, options = {})
92
- raise "save(path) must be implemented by subclass"
93
- end
94
-
95
- # @return [String] String representation of this file
96
- def to_s
97
- path.basename.to_s
98
- end
99
-
100
- # @return [Array<String>] All property values split into sentences
101
- def sentences
102
- sentences = []
103
- properties.values.each do |value|
104
- string = Translatomatic::String.new(value, locale)
105
- sentences += string.sentences
106
- end
107
- sentences
108
- end
109
-
110
- # @return [boolean] true if this resource file supports variable interpolation
111
- def supports_variable_interpolation?
112
- false
113
- end
114
-
115
- # Create an interpolated variable string.
116
- # @return [String] A string representing the interpolated variable, or
117
- # nil if this resource file doesn't support variable interpolation.
118
- def create_variable(name)
119
- return nil unless supports_variable_interpolation?
120
- raise "create_variable(name) must be implemented by subclass"
121
- end
122
-
123
- # @return [Regexp] A regexp used to match interpolated variables
124
- def variable_regex
125
- return nil unless supports_variable_interpolation?
126
- raise "variable_regex must be implemented by subclass"
127
- end
128
-
129
- private
130
-
131
- include Translatomatic::Util
132
-
133
- def created_by
134
- t("resource.created_by", app: "Translatomatic",
135
- version: Translatomatic::VERSION, date: I18n.l(DateTime.now),
136
- locale: locale.language
137
- )
138
- end
139
-
140
- # detect locale from filename
141
- def detect_locale
142
- tag = nil
143
- basename = path.sub_ext('').basename.to_s
144
- directory = path.dirname.basename.to_s
145
- extlist = extension_list
146
-
147
- if basename.match(/_([-\w]{2,})$/i)
148
- # locale after underscore in filename
149
- tag = $1
150
- elsif directory.match(/^([-\w]+)\.lproj$/)
151
- # xcode localized strings
152
- tag = $1
153
- elsif extlist.length >= 2 && loc_idx = find_locale(extlist)
154
- # multiple parts to extension, e.g. index.html.en
155
- tag = extlist[loc_idx]
156
- elsif valid_locale?(basename)
157
- # try to match on entire basename
158
- # (support for rails en.yml)
159
- tag = basename
160
- elsif valid_locale?(path.parent.basename)
161
- # try to match on parent directory, e.g. strings/en-US/text.resw
162
- tag = path.parent.basename
163
- end
164
-
165
- tag ? Translatomatic::Locale.parse(tag, true) : nil
166
- end
167
-
168
- def valid_locale?(tag)
169
- Translatomatic::Locale.new(tag).valid?
170
- end
171
-
172
- # test if the list of strings contains a valid locale
173
- # return the index to the locale, or nil if no locales found
174
- def find_locale(list)
175
- list.find_index { |i| valid_locale?(i) }
176
- end
177
-
178
- # ext_sub() only removes the last extension
179
- def strip_extensions
180
- filename = path.basename.to_s
181
- filename.sub!(/\..*$/, '')
182
- path.parent + filename
183
- end
184
-
185
- # for index.html.de, returns ['html', 'de']
186
- def extension_list
187
- filename = path.basename.to_s
188
- idx = filename.index('.')
189
- idx && idx < filename.length - 1 ? filename[idx + 1..-1].split('.') : []
190
- end
191
-
192
- end
1
+ # Base class for resource file implementations
2
+ # @abstract Subclasses implement different types of resource files
3
+ class Translatomatic::ResourceFile::Base
4
+
5
+ attr_accessor :locale
6
+ attr_accessor :path
7
+
8
+ # @return [Hash<String,String>] key -> value properties
9
+ attr_reader :properties
10
+
11
+ # @return [boolean] True if this resource format is enabled
12
+ def self.enabled?
13
+ true
14
+ end
15
+
16
+ # @return [Array<String>] File extensions supported by this resource file
17
+ def self.extensions
18
+ raise "extensions must be implemented by subclass"
19
+ end
20
+
21
+ # @return [boolean] True if the file format consists of keys and values
22
+ def self.is_key_value?
23
+ false
24
+ end
25
+
26
+ # @return [boolean] true if this resource file supports variable interpolation
27
+ def self.supports_variable_interpolation?
28
+ false
29
+ end
30
+
31
+ # Create a new resource file or load an existing file.
32
+ # If options[:locale] is unspecified, attempts to determine
33
+ # the locale of the file automatically, and if that fails,
34
+ # uses the default locale.
35
+ # Raises an exception if errors were encountered loading the file.
36
+ # @param path [String] Path to the file
37
+ # @param options [Hash<Symbol,String>] Options
38
+ # @return [Translatomatic::ResourceFile::Base] the resource file.
39
+ def initialize(path = nil, options = {})
40
+ raise "expected options hash" if options && !options.kind_of?(Hash)
41
+ raise t("file.unsupported", file: path) unless self.class.enabled?
42
+ @options = options || {}
43
+ @properties = {}
44
+ @path = path.nil? || path.kind_of?(Pathname) ? path : Pathname.new(path)
45
+ update_locale
46
+ init
47
+ load if @path && @path.exist?
48
+ end
49
+
50
+ # Save the resource file.
51
+ # @param target [Pathname] The destination path
52
+ # @param options [Hash<Symbol, Object>] Output format options
53
+ # @return [void]
54
+ def save(target = path, options = {})
55
+ raise "save(path) must be implemented by subclass"
56
+ end
57
+
58
+ # @return [String] The format of this resource file, e.g. "Properties"
59
+ def format
60
+ self.class.name.demodulize.downcase.to_sym
61
+ end
62
+
63
+ # Create a path for the current resource file with a given locale
64
+ # @param locale [String] The target locale
65
+ # @return [Pathname] The path of this resource file modified for the given locale
66
+ def locale_path(locale)
67
+ basename = path.sub_ext('').basename.to_s
68
+
69
+ extlist = extension_list
70
+ if extlist.length >= 2 && loc_idx = find_locale(extlist)
71
+ # extension(s) contains locale, replace it
72
+ extlist[loc_idx] = locale.to_s
73
+ elsif valid_locale?(basename)
74
+ # basename is a locale name, replace it
75
+ path.dirname + (locale.to_s + path.extname)
76
+ else
77
+ # remove any underscore and trailing text from basename
78
+ deunderscored = basename.sub(/_.*?$/, '')
79
+ # add _locale.ext
80
+ filename = deunderscored + "_" + locale.to_s + path.extname
81
+ path.dirname + filename
82
+ end
83
+ end
84
+
85
+ # Set all properties
86
+ # @param properties [Hash<String,String>] New properties
87
+ def properties=(properties)
88
+ # use set rather that set @properties directly as subclasses override set()
89
+ properties.each do |key, value|
90
+ set(key, value)
91
+ end
92
+ end
93
+
94
+ # Get the value of a property
95
+ # @param key [String] The name of the property
96
+ # @return [String] The value of the property
97
+ def get(key)
98
+ @properties[key]
99
+ end
100
+
101
+ # Set a property
102
+ # @param key [String] The name of the property
103
+ # @param value [String] The new value of the property
104
+ # @return [String] The new value of the property
105
+ def set(key, value)
106
+ @properties[key] = value
107
+ end
108
+
109
+ # @return [String] String representation of this file
110
+ def to_s
111
+ path.basename.to_s
112
+ end
113
+
114
+ def type
115
+ self.class.name.demodulize
116
+ end
117
+
118
+ # @return [Array<String>] All property values split into sentences
119
+ def sentences
120
+ sentences = []
121
+ properties.values.each do |value|
122
+ string = Translatomatic::String.new(value, locale)
123
+ sentences += string.sentences
124
+ end
125
+ sentences
126
+ end
127
+
128
+ # Create an interpolated variable string.
129
+ # @return [String] A string representing the interpolated variable, or
130
+ # nil if this resource file doesn't support variable interpolation.
131
+ def create_variable(name)
132
+ return nil unless supports_variable_interpolation?
133
+ raise "create_variable(name) must be implemented by subclass"
134
+ end
135
+
136
+ # @return [Regexp] A regexp used to match interpolated variables
137
+ def variable_regex
138
+ return nil unless supports_variable_interpolation?
139
+ raise "variable_regex must be implemented by subclass"
140
+ end
141
+
142
+ private
143
+
144
+ include Translatomatic::Util
145
+
146
+ # called by constructor before load
147
+ def init
148
+ end
149
+
150
+ # load contents from @path
151
+ def load
152
+ raise "load must be implemented by subclass"
153
+ end
154
+
155
+ def update_locale
156
+ locale = @options[:locale] || detect_locale || Translatomatic::Locale.default
157
+ @locale = Translatomatic::Locale.parse(locale)
158
+ raise t("file.unknown_locale") unless @locale && @locale.language
159
+ end
160
+
161
+ def created_by
162
+ options = {
163
+ app: "Translatomatic",
164
+ version: Translatomatic::VERSION,
165
+ date: I18n.l(DateTime.now),
166
+ }
167
+ # use created by string in current file's locale, fall back to
168
+ # english locale if translation is missing.
169
+ t("file.created_by", options.merge({
170
+ locale: locale.language,
171
+ default: t("file.created_by", options.merge(locale: "en"))
172
+ })
173
+ )
174
+ end
175
+
176
+ def read_contents(path)
177
+ File.read(path.to_s, mode: "r:bom|utf-8")
178
+ end
179
+
180
+ def parsing_error(error)
181
+ raise Exception.new(error)
182
+ end
183
+
184
+ # detect locale from filename
185
+ def detect_locale
186
+ return nil unless path
187
+ tag = nil
188
+ basename = path.sub_ext('').basename.to_s
189
+ directory = path.dirname.basename.to_s
190
+ extlist = extension_list
191
+
192
+ if basename.match(/_([-\w]{2,})$/i)
193
+ # locale after underscore in filename
194
+ tag = $1
195
+ elsif directory.match(/^([-\w]+)\.lproj$/)
196
+ # xcode localized strings
197
+ tag = $1
198
+ elsif extlist.length >= 2 && loc_idx = find_locale(extlist)
199
+ # multiple parts to extension, e.g. index.html.en
200
+ tag = extlist[loc_idx]
201
+ elsif valid_locale?(basename)
202
+ # try to match on entire basename
203
+ # (support for rails en.yml)
204
+ tag = basename
205
+ elsif valid_locale?(path.parent.basename)
206
+ # try to match on parent directory, e.g. strings/en-US/text.resw
207
+ tag = path.parent.basename
208
+ end
209
+
210
+ tag ? Translatomatic::Locale.parse(tag, true) : nil
211
+ end
212
+
213
+ def valid_locale?(tag)
214
+ Translatomatic::Locale.new(tag).valid?
215
+ end
216
+
217
+ # test if the list of strings contains a valid locale
218
+ # return the index to the locale, or nil if no locales found
219
+ def find_locale(list)
220
+ list.find_index { |i| valid_locale?(i) }
221
+ end
222
+
223
+ # ext_sub() only removes the last extension
224
+ def strip_extensions
225
+ filename = path.basename.to_s
226
+ filename.sub!(/\..*$/, '')
227
+ path.parent + filename
228
+ end
229
+
230
+ # for index.html.de, returns ['html', 'de']
231
+ def extension_list
232
+ filename = path.basename.to_s
233
+ idx = filename.index('.')
234
+ idx && idx < filename.length - 1 ? filename[idx + 1..-1].split('.') : []
235
+ end
236
+
237
+ # flatten hash or array of hashes to a hash of key => value pairs
238
+ def flatten(data)
239
+ result = {}
240
+
241
+ if data.kind_of?(Hash)
242
+ data.each do |key, value|
243
+ flatten_add(result, key, value)
244
+ end
245
+ elsif data.kind_of?(Array)
246
+ data.each_with_index do |value, i|
247
+ key = "key" + i.to_s
248
+ flatten_add(result, key, value)
249
+ end
250
+ end
251
+
252
+ result
253
+ end
254
+
255
+ def flatten_add(result, key, value)
256
+ if needs_flatten?(value)
257
+ children = flatten(value)
258
+ children.each do |ck, cv|
259
+ result[key + "." + ck] = cv
260
+ end
261
+ else
262
+ result[key] = value
263
+ end
264
+ end
265
+
266
+ def needs_flatten?(value)
267
+ value.kind_of?(Array) || value.kind_of?(Hash)
268
+ end
269
+ end
@@ -0,0 +1,37 @@
1
+ require 'csv'
2
+
3
+ module Translatomatic::ResourceFile
4
+ # CSV resource file
5
+ class CSV < Base
6
+
7
+ # (see Translatomatic::ResourceFile::Base.extensions)
8
+ def self.extensions
9
+ %w{csv}
10
+ end
11
+
12
+ # (see Translatomatic::ResourceFile::Base#save)
13
+ def save(target = path, options = {})
14
+ ::CSV.open(target, "wb") do |csv|
15
+ @properties.each do |key, value|
16
+ csv << [key, value]
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def init
24
+ @rows = []
25
+ end
26
+
27
+ def load
28
+ contents = read_contents(@path)
29
+ @rows = ::CSV.parse(contents)
30
+ @properties = {}
31
+ @rows.each do |key, value|
32
+ @properties[key] = value
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -1,48 +1,55 @@
1
- module Translatomatic::ResourceFile
1
+ module Translatomatic::ResourceFile
2
2
  # HTML resource file
3
- class HTML < XML
4
-
5
- # (see Translatomatic::ResourceFile::Base.extensions)
6
- def self.extensions
7
- %w{html htm shtml}
8
- end
9
-
10
- # (see Translatomatic::ResourceFile::Base#locale_path)
11
- def locale_path(locale)
12
- extlist = extension_list
13
- if extlist.length >= 2 && loc_idx = find_locale(extlist)
14
- # part of the extension is the locale
15
- # replace that part with the new locale
16
- extlist[loc_idx] = locale.to_s
17
- new_extension = extlist.join(".")
18
- return strip_extensions.sub_ext("." + new_extension)
19
- else
20
- # add locale extension
21
- ext = path.extname
22
- # TODO: need configurable order for locale & ext here?
23
- #path.sub_ext("#{ext}." + locale.to_s)
24
- path.sub_ext("." + locale.to_s + ext)
25
- end
26
- end
27
-
28
- # (see Translatomatic::ResourceFile::Base#save)
29
- def save(target = path, options = {})
30
- if @doc
31
- add_created_by unless options[:no_created_by]
32
- target.write(@doc.to_html)
33
- end
34
- end
35
-
36
- private
37
-
38
- def text_nodes_xpath
39
- '//*[not(self::code)]/text()'
40
- end
41
-
42
- def read_doc(path)
43
- Nokogiri::HTML(path.open) do |config|
44
- config.noblanks
45
- end
46
- end
47
- end
48
- end
3
+ class HTML < XML
4
+
5
+ # (see Translatomatic::ResourceFile::Base.extensions)
6
+ def self.extensions
7
+ %w{html htm shtml}
8
+ end
9
+
10
+ # (see Translatomatic::ResourceFile::Base#locale_path)
11
+ def locale_path(locale)
12
+ extlist = extension_list
13
+ if extlist.length >= 2 && loc_idx = find_locale(extlist)
14
+ # part of the extension is the locale
15
+ # replace that part with the new locale
16
+ extlist[loc_idx] = locale.to_s
17
+ new_extension = extlist.join(".")
18
+ return strip_extensions.sub_ext("." + new_extension)
19
+ else
20
+ # add locale extension
21
+ ext = path.extname
22
+ # TODO: need configurable order for locale & ext here?
23
+ #path.sub_ext("#{ext}." + locale.to_s)
24
+ path.sub_ext("." + locale.to_s + ext)
25
+ end
26
+ end
27
+
28
+ # (see Translatomatic::ResourceFile::Base#save)
29
+ def save(target = path, options = {})
30
+ if @doc
31
+ add_created_by unless options[:no_created_by]
32
+ target.write(@doc.to_html)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def read_doc
39
+ doc = Nokogiri::HTML(@path.open) do |config|
40
+ config.noblanks
41
+ end
42
+ parse_error(doc.errors[0]) if doc.errors.present?
43
+ doc
44
+ end
45
+
46
+ def empty_doc
47
+ Nokogiri::HTML("<html><body></body></html>")
48
+ end
49
+
50
+ def text_nodes_xpath
51
+ '//*[not(self::code)]/text()'
52
+ end
53
+
54
+ end
55
+ end