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,37 +1,189 @@
1
1
  require 'csv'
2
2
 
3
- module Translatomatic::ResourceFile
4
- # CSV resource file
5
- class CSV < Base
3
+ module Translatomatic
4
+ module ResourceFile
5
+ # CSV resource file
6
+ class CSV < Base
7
+ # (see Base.extensions)
8
+ def self.extensions
9
+ %w[csv]
10
+ end
6
11
 
7
- # (see Translatomatic::ResourceFile::Base.extensions)
8
- def self.extensions
9
- %w{csv}
10
- end
12
+ # (see Base#set)
13
+ def set(key, value)
14
+ super(key, value)
15
+ if @cellmap.include?(key)
16
+ @cellmap[key].value = value
17
+ else
18
+ add_row(key, value)
19
+ end
20
+ end
21
+
22
+ # (see Base#save)
23
+ def save(target = path, options = {})
24
+ use_headers = @options[:csv_headers]
25
+ csv_options = { write_headers: use_headers }
26
+ csv_options[:headers] = @headers if use_headers
11
27
 
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]
28
+ ::CSV.open(target, 'wb', csv_options) do |csv|
29
+ @rows.each do |row|
30
+ csv << row.collect(&:value)
31
+ end
17
32
  end
18
33
  end
19
- end
20
34
 
21
- private
35
+ private
22
36
 
23
- def init
24
- @rows = []
25
- end
37
+ DEFAULT_KEY_COLUMN = 'key'.freeze
38
+ DEFAULT_VALUE_COLUMN = 'value'.freeze
39
+ DEFAULT_COMMENT_COLUMN = 'comments'.freeze
40
+ CONTEXT_COLUMN = 'tm.context'.freeze
41
+
42
+ define_option :csv_headers, type: :boolean, default: false,
43
+ desc: t('file.csv.headers')
44
+ define_option :csv_translate_columns, type: :array,
45
+ desc: t('file.csv.translate_columns')
46
+ define_option :csv_key_column, default: DEFAULT_KEY_COLUMN,
47
+ desc: t('file.csv.key_column')
48
+ define_option :csv_value_column, default: DEFAULT_VALUE_COLUMN,
49
+ desc: t('file.csv.value_column')
50
+ define_option :csv_comment_column, default: DEFAULT_COMMENT_COLUMN,
51
+ desc: t('file.csv.comment_column')
52
+
53
+ # @private
54
+ Cell = Struct.new(:header, :key, :value, :translate)
26
55
 
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
56
+ def init
57
+ @rows = []
58
+ @cellmap = {} # map of String key -> Cell
59
+ @rownum = 0
60
+ @key_column = option(:csv_key_column, DEFAULT_KEY_COLUMN)
61
+ @value_column = option(:csv_value_column, DEFAULT_VALUE_COLUMN)
62
+ @comments_column = option(:csv_comment_column, DEFAULT_COMMENT_COLUMN)
63
+ @translate = option(:csv_translate_columns)
64
+ end
65
+
66
+ def option(key, default = nil)
67
+ @options[key] || default
68
+ end
69
+
70
+ def add_row(key, value)
71
+ @rows << load_row(@key_column => key, @value_column => value)
72
+ end
73
+
74
+ def load
75
+ contents = read_contents(@path)
76
+ csv_options = { headers: @options[:csv_headers] }
77
+
78
+ @rows = []
79
+ @rownum = 0
80
+
81
+ csv = ::CSV.parse(contents, csv_options)
82
+ csv.each do |row|
83
+ @rows << load_row(row)
84
+ end
85
+
86
+ init_properties
87
+ end
88
+
89
+ # initialise properties and cellmap from @rows
90
+ def init_properties
91
+ @properties = {}
92
+ @cellmap = {}
93
+ @rows.each do |row|
94
+ row.each do |cell|
95
+ @properties[cell.key] = cell.value if cell.translate
96
+ @cellmap[cell.key] = cell
97
+ end
98
+ end
33
99
  end
34
- end
35
100
 
101
+ # @param row [Array<Cell>] row to convert to hash
102
+ def row_to_hash(row)
103
+ hash = {}
104
+ row.each do |cell|
105
+ hash[cell.header] = cell.value
106
+ end
107
+ hash
108
+ end
109
+
110
+ def load_row(row)
111
+ @rownum += 1
112
+ if row.is_a?(::CSV::Row)
113
+ @headers = row.headers
114
+ load_hash_row(row.to_h)
115
+ elsif row.is_a?(Hash)
116
+ load_hash_row(row)
117
+ elsif row.is_a?(Array)
118
+ load_array_row(row)
119
+ else
120
+ raise "invalid row data: #{row}"
121
+ end
122
+ end
123
+
124
+ # row is an array of values
125
+ # @return [Array<Cell>] cells
126
+ def load_array_row(row)
127
+ cells = []
128
+ row.each_with_index do |value, i|
129
+ translate = translate_column?((i + 1).to_s)
130
+ key = "key#{@rownum},#{i + 1}"
131
+ cells << Cell.new("column#{i + 1}", key, value, translate)
132
+ end
133
+ parse_metadata(cells)
134
+ cells
135
+ end
136
+
137
+ # row is a hash of column -> value
138
+ # @return [Array<Cell>] cells
139
+ def load_hash_row(row)
140
+ cells = []
141
+ # add a property for each column
142
+ colnum = 0
143
+ row.each do |column, value|
144
+ colnum += 1
145
+ translate = translate_column?(column)
146
+ key = "key#{@rownum},#{colnum}"
147
+ cells << Cell.new(column, key, value, translate)
148
+ end
149
+ parse_metadata(cells)
150
+ cells
151
+ end
152
+
153
+ def translate_column?(column)
154
+ if @translate.present?
155
+ # translation columns specified
156
+ @translate.include?(column)
157
+ elsif have_target_locale_column?
158
+ # if there is a column matching the target locale, translate that
159
+ # column only.
160
+ column == @target_locale.to_s
161
+ else
162
+ # translate if it's not the key column or comments column
163
+ column != @key_column && column != @comments_column
164
+ end
165
+ end
166
+
167
+ def have_target_locale_column?
168
+ @have_target_locale_column ||= begin
169
+ @target_locale && @headers &&
170
+ @headers.include?(@target_locale.to_s)
171
+ end
172
+ end
173
+
174
+ def parse_metadata(row)
175
+ comments_cell = find_cell(row, @comments_column)
176
+ return unless comments_cell
177
+ @metadata.parse_comment(comments_cell.value)
178
+ row.each do |cell|
179
+ @metadata.assign_key(cell.key, keep_context: true) if cell.translate
180
+ end
181
+ @metadata.clear_context
182
+ end
183
+
184
+ def find_cell(row, column_name)
185
+ row.find { |i| i.header == column_name }
186
+ end
187
+ end
36
188
  end
37
189
  end
@@ -1,55 +1,34 @@
1
- module Translatomatic::ResourceFile
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)
1
+ module Translatomatic
2
+ module ResourceFile
3
+ # HTML resource file
4
+ class HTML < XML
5
+ # (see Base.extensions)
6
+ def self.extensions
7
+ %w[html htm shtml]
25
8
  end
26
- end
27
9
 
28
- # (see Translatomatic::ResourceFile::Base#save)
29
- def save(target = path, options = {})
30
- if @doc
10
+ # (see Base#save)
11
+ def save(target = path, options = {})
12
+ return unless @doc
31
13
  add_created_by unless options[:no_created_by]
32
14
  target.write(@doc.to_html)
33
15
  end
34
- end
35
16
 
36
- private
17
+ private
37
18
 
38
- def read_doc
39
- doc = Nokogiri::HTML(@path.open) do |config|
40
- config.noblanks
19
+ def read_doc
20
+ doc = Nokogiri::HTML(@path.open, &:noblanks)
21
+ parse_error(doc.errors[0]) if doc.errors.present?
22
+ doc
41
23
  end
42
- parse_error(doc.errors[0]) if doc.errors.present?
43
- doc
44
- end
45
24
 
46
- def empty_doc
47
- Nokogiri::HTML("<html><body></body></html>")
48
- end
25
+ def empty_doc
26
+ Nokogiri::HTML('<html><body></body></html>')
27
+ end
49
28
 
50
- def text_nodes_xpath
51
- '//*[not(self::code)]/text()'
29
+ def text_nodes_xpath
30
+ '//*[not(self::code)]/text()|//comment()'
31
+ end
52
32
  end
53
-
54
33
  end
55
34
  end
@@ -0,0 +1,117 @@
1
+ module Translatomatic
2
+ module ResourceFile
3
+ # @private
4
+ module KeyValueSupport
5
+ # (see Base.key_value?)
6
+ def self.key_value?
7
+ true
8
+ end
9
+
10
+ # (see Base#set)
11
+ def set(key, value)
12
+ super(key, value)
13
+ if @map.include?(key)
14
+ @map[key].value = value
15
+ else
16
+ element = Definition.new(key, value)
17
+ @elements << element
18
+ @map[key] = element
19
+ @properties[key] = value
20
+ end
21
+ end
22
+
23
+ # (see Base#save)
24
+ def save(target = path, options = {})
25
+ add_created_by unless options[:no_created_by] || created_by?
26
+ content = @elements.collect { |i| render_element(i) }.join
27
+ content = content.gsub(/[\r\n]+\Z/, '') + "\n"
28
+ target.write(content)
29
+ end
30
+
31
+ private
32
+
33
+ def init
34
+ @elements = [] # key/values or comment elements
35
+ @map = {} # map key to elements
36
+ end
37
+
38
+ def load
39
+ @metadata.reset
40
+ @doc = read_doc
41
+ @elements = init_elements
42
+ @properties = {}
43
+ @elements.each do |element|
44
+ if element.is_a?(Comment)
45
+ @metadata.parse_comment(element.text)
46
+ elsif element.is_a?(Definition)
47
+ @metadata.assign_key(element.key)
48
+ @properties[element.key] = element.value
49
+ @map[element.key] = element
50
+ end
51
+ end
52
+ end
53
+
54
+ # parse document to a list of elements
55
+ def read_doc
56
+ content = read_contents(@path)
57
+ document = parse_doc(content)
58
+ raise t('file.invalid') unless document
59
+ document
60
+ end
61
+
62
+ def parse_doc(_content)
63
+ raise 'parse_doc must be implemented by subclass'
64
+ end
65
+
66
+ def render_element(_element)
67
+ raise 'render_element must be implemented by subclass'
68
+ end
69
+
70
+ def init_elements
71
+ # convert to a list of elements
72
+ @doc.content.collect do |item|
73
+ case item[0]
74
+ when :comment
75
+ content_to_comment(item)
76
+ when :definition
77
+ content_to_definition(item)
78
+ end
79
+ end
80
+ end
81
+
82
+ def content_to_definition(item)
83
+ key = unescape(item[1])
84
+ value = unescape(item[2])
85
+ # remove line continuations
86
+ value = value.gsub(/\\\n\s*/, '')
87
+ Definition.new(key, value)
88
+ end
89
+
90
+ def content_to_comment(item)
91
+ text = unescape(item[1])
92
+ Comment.new(text, item[2])
93
+ end
94
+
95
+ def add_created_by
96
+ @created_by ||= begin
97
+ created_by = Comment.new(created_by)
98
+ @elements.unshift(created_by)
99
+ created_by
100
+ end
101
+ end
102
+
103
+ def escape(value)
104
+ Translatomatic::StringEscaping.escape(value)
105
+ end
106
+
107
+ def unescape(value)
108
+ Translatomatic::StringEscaping.unescape_all(value)
109
+ end
110
+
111
+ # @private
112
+ Definition = Struct.new(:key, :value)
113
+ # @private
114
+ Comment = Struct.new(:text, :type)
115
+ end
116
+ end
117
+ end
@@ -1,50 +1,48 @@
1
1
  require 'kramdown'
2
2
  require 'reverse_markdown'
3
3
 
4
- module Translatomatic::ResourceFile
5
- # Markdown resource file
6
- class Markdown < HTML
7
-
8
- # (see Translatomatic::ResourceFile::Base.extensions)
9
- def self.extensions
10
- %w{md}
11
- end
4
+ module Translatomatic
5
+ module ResourceFile
6
+ # Markdown resource file
7
+ class Markdown < HTML
8
+ # (see Base.extensions)
9
+ def self.extensions
10
+ %w[md]
11
+ end
12
12
 
13
- # (see Translatomatic::ResourceFile::Base#save)
14
- def save(target = path, options = {})
15
- if @doc
16
- add_created_by unless options[:no_created_by]
17
- html = @doc.to_html
18
- # convert html back to markdown
19
- markdown = ReverseMarkdown.convert(html, unknown_tags: :bypass)
20
- target.write(markdown.chomp)
13
+ # (see Base#save)
14
+ def save(target = path, options = {})
15
+ if @doc
16
+ add_created_by unless options[:no_created_by]
17
+ html = @doc.to_html
18
+ # convert html back to markdown
19
+ markdown = ReverseMarkdown.convert(html, unknown_tags: :bypass)
20
+ target.write(markdown.chomp)
21
+ end
21
22
  end
22
- end
23
23
 
24
- private
24
+ private
25
25
 
26
- def add_created_by
27
- @created_by ||= begin
28
- body = @doc.at('body')
29
- body.add_child("<p><i>#{created_by}</i></p>")
26
+ def add_created_by
27
+ @created_by ||= begin
28
+ body = @doc.at('body')
29
+ body.add_child("<p><i>#{created_by}</i></p>")
30
+ end
30
31
  end
31
- end
32
32
 
33
- def read_doc
34
- # read markdown and convert to html
35
- markdown = read_contents(@path)
36
- if markdown.blank?
37
- empty_doc
38
- else
39
- html = Kramdown::Document.new(markdown).to_html
40
- # parse html with nokogiri
41
- doc = Nokogiri::HTML(html) do |config|
42
- config.noblanks
33
+ def read_doc
34
+ # read markdown and convert to html
35
+ markdown = read_contents(@path)
36
+ if markdown.blank?
37
+ empty_doc
38
+ else
39
+ html = Kramdown::Document.new(markdown).to_html
40
+ # parse html with nokogiri
41
+ doc = Nokogiri::HTML(html, &:noblanks)
42
+ parse_error(doc.errors[0]) if doc.errors.present?
43
+ doc
43
44
  end
44
- parse_error(doc.errors[0]) if doc.errors.present?
45
- doc
46
45
  end
47
46
  end
48
-
49
- end # class
50
- end # module
47
+ end
48
+ end