traduco 0.9.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.
- data/Gemfile +2 -0
- data/LICENSE +33 -0
- data/README.md +182 -0
- data/bin/traduco +8 -0
- data/library/traduco.rb +10 -0
- data/library/traduco/cli.rb +186 -0
- data/library/traduco/encoding.rb +20 -0
- data/library/traduco/formatters.rb +12 -0
- data/library/traduco/formatters/abstract.rb +164 -0
- data/library/traduco/formatters/android.rb +152 -0
- data/library/traduco/formatters/apple.rb +125 -0
- data/library/traduco/formatters/flash.rb +110 -0
- data/library/traduco/formatters/gettext.rb +98 -0
- data/library/traduco/formatters/jquery.rb +96 -0
- data/library/traduco/l10nfile.rb +209 -0
- data/library/traduco/runner.rb +301 -0
- data/test/fixtures/en-1.json +12 -0
- data/test/fixtures/en-1.po +16 -0
- data/test/fixtures/en-1.strings +10 -0
- data/test/fixtures/en-2.po +23 -0
- data/test/fixtures/fr-1.xml +10 -0
- data/test/fixtures/strings-1.txt +17 -0
- data/test/fixtures/strings-2.txt +5 -0
- data/test/fixtures/strings-3.txt +5 -0
- data/test/fixtures/test-output-1.txt +12 -0
- data/test/fixtures/test-output-2.txt +12 -0
- data/test/fixtures/test-output-3.txt +18 -0
- data/test/fixtures/test-output-4.txt +21 -0
- data/test/fixtures/test-output-5.txt +11 -0
- data/test/fixtures/test-output-6.txt +10 -0
- data/test/fixtures/test-output-7.txt +14 -0
- data/test/fixtures/test-output-8.txt +9 -0
- data/test/fixtures/test-output-9.txt +21 -0
- data/test/traduco_test.rb +98 -0
- metadata +125 -0
@@ -0,0 +1,301 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
|
3
|
+
module Traduco
|
4
|
+
VALID_COMMANDS = ['generate-string-file', 'generate-all-string-files', 'consume-string-file', 'consume-all-string-files', 'generate-loc-drop', 'consume-loc-drop', 'generate-report']
|
5
|
+
|
6
|
+
class Runner
|
7
|
+
def initialize(args)
|
8
|
+
@options = {}
|
9
|
+
@args = args
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.run(args)
|
13
|
+
new(args).run
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
# Parse all CLI arguments.
|
18
|
+
CLI::parse_args(@args, @options)
|
19
|
+
read_strings_data
|
20
|
+
execute_command
|
21
|
+
end
|
22
|
+
|
23
|
+
def read_strings_data
|
24
|
+
@strings = StringsFile.new
|
25
|
+
@strings.read @options[:strings_file]
|
26
|
+
end
|
27
|
+
|
28
|
+
def write_strings_data(path)
|
29
|
+
if @options[:developer_language]
|
30
|
+
@strings.set_developer_language_code(@options[:developer_language])
|
31
|
+
end
|
32
|
+
@strings.write(path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def execute_command
|
36
|
+
case @options[:command]
|
37
|
+
when 'generate-string-file'
|
38
|
+
generate_string_file
|
39
|
+
when 'generate-all-string-files'
|
40
|
+
generate_all_string_files
|
41
|
+
when 'consume-string-file'
|
42
|
+
consume_string_file
|
43
|
+
when 'consume-all-string-files'
|
44
|
+
consume_all_string_files
|
45
|
+
when 'generate-loc-drop'
|
46
|
+
generate_loc_drop
|
47
|
+
when 'consume-loc-drop'
|
48
|
+
consume_loc_drop
|
49
|
+
when 'generate-report'
|
50
|
+
generate_report
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def generate_string_file
|
55
|
+
lang = nil
|
56
|
+
if @options[:languages]
|
57
|
+
lang = @options[:languages][0]
|
58
|
+
end
|
59
|
+
|
60
|
+
read_write_string_file(@options[:output_path], false, lang)
|
61
|
+
end
|
62
|
+
|
63
|
+
def generate_all_string_files
|
64
|
+
if !File.directory?(@options[:output_path])
|
65
|
+
raise Traduco::Error.new("Directory does not exist: #{@options[:output_path]}")
|
66
|
+
end
|
67
|
+
|
68
|
+
format = @options[:format]
|
69
|
+
if !format
|
70
|
+
format = determine_format_given_directory(@options[:output_path])
|
71
|
+
end
|
72
|
+
if !format
|
73
|
+
raise Traduco::Error.new "Could not determine format given the contents of #{@options[:output_path]}"
|
74
|
+
end
|
75
|
+
|
76
|
+
formatter = formatter_for_format(format)
|
77
|
+
|
78
|
+
formatter.write_all_files(@options[:output_path])
|
79
|
+
end
|
80
|
+
|
81
|
+
def consume_string_file
|
82
|
+
lang = nil
|
83
|
+
if @options[:languages]
|
84
|
+
lang = @options[:languages][0]
|
85
|
+
end
|
86
|
+
|
87
|
+
read_write_string_file(@options[:input_path], true, lang)
|
88
|
+
output_path = @options[:output_path] || @options[:strings_file]
|
89
|
+
write_strings_data(output_path)
|
90
|
+
end
|
91
|
+
|
92
|
+
def consume_all_string_files
|
93
|
+
if !File.directory?(@options[:input_path])
|
94
|
+
raise Traduco::Error.new("Directory does not exist: #{@options[:output_path]}")
|
95
|
+
end
|
96
|
+
|
97
|
+
Dir.glob(File.join(@options[:input_path], "**/*")) do |item|
|
98
|
+
if File.file?(item)
|
99
|
+
begin
|
100
|
+
read_write_string_file(item, true, nil)
|
101
|
+
rescue Traduco::Error => e
|
102
|
+
STDERR.puts "#{e.message}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
output_path = @options[:output_path] || @options[:strings_file]
|
108
|
+
write_strings_data(output_path)
|
109
|
+
end
|
110
|
+
|
111
|
+
def read_write_string_file(path, is_read, lang)
|
112
|
+
if is_read && !File.file?(path)
|
113
|
+
raise Traduco::Error.new("File does not exist: #{path}")
|
114
|
+
end
|
115
|
+
|
116
|
+
format = @options[:format]
|
117
|
+
if !format
|
118
|
+
format = determine_format_given_path(path)
|
119
|
+
end
|
120
|
+
if !format
|
121
|
+
raise Traduco::Error.new "Unable to determine format of #{path}"
|
122
|
+
end
|
123
|
+
|
124
|
+
formatter = formatter_for_format(format)
|
125
|
+
|
126
|
+
if !lang
|
127
|
+
lang = determine_language_given_path(path)
|
128
|
+
end
|
129
|
+
if !lang
|
130
|
+
lang = formatter.determine_language_given_path(path)
|
131
|
+
end
|
132
|
+
if !lang
|
133
|
+
raise Traduco::Error.new "Unable to determine language for #{path}"
|
134
|
+
end
|
135
|
+
|
136
|
+
if !@strings.language_codes.include? lang
|
137
|
+
@strings.language_codes << lang
|
138
|
+
end
|
139
|
+
|
140
|
+
if is_read
|
141
|
+
formatter.read_file(path, lang)
|
142
|
+
else
|
143
|
+
formatter.write_file(path, lang)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def generate_loc_drop
|
148
|
+
begin
|
149
|
+
require 'zip/zip'
|
150
|
+
rescue LoadError
|
151
|
+
raise Traduco::Error.new "You must run 'gem install rubyzip' in order to create or consume localization drops."
|
152
|
+
end
|
153
|
+
|
154
|
+
if File.file?(@options[:output_path])
|
155
|
+
File.delete(@options[:output_path])
|
156
|
+
end
|
157
|
+
|
158
|
+
Dir.mktmpdir do |dir|
|
159
|
+
Zip::ZipFile.open(@options[:output_path], Zip::ZipFile::CREATE) do |zipfile|
|
160
|
+
zipfile.mkdir('Locales')
|
161
|
+
|
162
|
+
formatter = formatter_for_format(@options[:format])
|
163
|
+
@strings.language_codes.each do |lang|
|
164
|
+
if @options[:languages] == nil || @options[:languages].length == 0 || @options[:languages].include?(lang)
|
165
|
+
file_name = lang + formatter.class::EXTENSION
|
166
|
+
real_path = File.join(dir, file_name)
|
167
|
+
zip_path = File.join('Locales', file_name)
|
168
|
+
formatter.write_file(real_path, lang)
|
169
|
+
zipfile.add(zip_path, real_path)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def consume_loc_drop
|
177
|
+
if !File.file?(@options[:input_path])
|
178
|
+
raise Traduco::Error.new("File does not exist: #{@options[:input_path]}")
|
179
|
+
end
|
180
|
+
|
181
|
+
begin
|
182
|
+
require 'zip/zip'
|
183
|
+
rescue LoadError
|
184
|
+
raise Traduco::Error.new "You must run 'gem install rubyzip' in order to create or consume localization drops."
|
185
|
+
end
|
186
|
+
|
187
|
+
Dir.mktmpdir do |dir|
|
188
|
+
Zip::ZipFile.open(@options[:input_path]) do |zipfile|
|
189
|
+
zipfile.each do |entry|
|
190
|
+
if !entry.name.end_with?'/' and !File.basename(entry.name).start_with?'.'
|
191
|
+
real_path = File.join(dir, entry.name)
|
192
|
+
FileUtils.mkdir_p(File.dirname(real_path))
|
193
|
+
zipfile.extract(entry.name, real_path)
|
194
|
+
begin
|
195
|
+
read_write_string_file(real_path, true, nil)
|
196
|
+
rescue Traduco::Error => e
|
197
|
+
STDERR.puts "#{e.message}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
output_path = @options[:output_path] || @options[:strings_file]
|
205
|
+
write_strings_data(output_path)
|
206
|
+
end
|
207
|
+
|
208
|
+
def generate_report
|
209
|
+
total_strings = 0
|
210
|
+
strings_per_lang = {}
|
211
|
+
all_keys = Set.new
|
212
|
+
duplicate_keys = Set.new
|
213
|
+
keys_without_tags = Set.new
|
214
|
+
@strings.language_codes.each do |code|
|
215
|
+
strings_per_lang[code] = 0
|
216
|
+
end
|
217
|
+
|
218
|
+
@strings.sections.each do |section|
|
219
|
+
section.rows.each do |row|
|
220
|
+
total_strings += 1
|
221
|
+
|
222
|
+
if all_keys.include? row.key
|
223
|
+
duplicate_keys.add(row.key)
|
224
|
+
else
|
225
|
+
all_keys.add(row.key)
|
226
|
+
end
|
227
|
+
|
228
|
+
row.translations.each_key do |code|
|
229
|
+
strings_per_lang[code] += 1
|
230
|
+
end
|
231
|
+
|
232
|
+
if row.tags == nil || row.tags.length == 0
|
233
|
+
keys_without_tags.add(row.key)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Print the report.
|
239
|
+
puts "Total number of strings = #{total_strings}"
|
240
|
+
@strings.language_codes.each do |code|
|
241
|
+
puts "#{code}: #{strings_per_lang[code]}"
|
242
|
+
end
|
243
|
+
|
244
|
+
if duplicate_keys.length > 0
|
245
|
+
puts "\nDuplicate string keys:"
|
246
|
+
duplicate_keys.each do |key|
|
247
|
+
puts key
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
if keys_without_tags.length == total_strings
|
252
|
+
puts "\nNone of your strings have tags."
|
253
|
+
elsif keys_without_tags.length > 0
|
254
|
+
puts "\nStrings without tags:"
|
255
|
+
keys_without_tags.each do |key|
|
256
|
+
puts key
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def determine_language_given_path(path)
|
262
|
+
code = File.basename(path, File.extname(path))
|
263
|
+
if !@strings.language_codes.include? code
|
264
|
+
code = nil
|
265
|
+
end
|
266
|
+
|
267
|
+
code
|
268
|
+
end
|
269
|
+
|
270
|
+
def determine_format_given_path(path)
|
271
|
+
ext = File.extname(path)
|
272
|
+
Formatters::FORMATTERS.each do |formatter|
|
273
|
+
if formatter::EXTENSION == ext
|
274
|
+
return formatter::FORMAT_NAME
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
return
|
279
|
+
end
|
280
|
+
|
281
|
+
def determine_format_given_directory(directory)
|
282
|
+
Formatters::FORMATTERS.each do |formatter|
|
283
|
+
if formatter.can_handle_directory?(directory)
|
284
|
+
return formatter::FORMAT_NAME
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
return
|
289
|
+
end
|
290
|
+
|
291
|
+
def formatter_for_format(format)
|
292
|
+
Formatters::FORMATTERS.each do |formatter|
|
293
|
+
if formatter::FORMAT_NAME == format
|
294
|
+
return formatter.new(@strings, @options)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
return
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
msgid ""
|
2
|
+
msgstr ""
|
3
|
+
"Language: en\n"
|
4
|
+
"X-Generator: Twine\n"
|
5
|
+
|
6
|
+
msgctxt "key1"
|
7
|
+
msgid "key1-english"
|
8
|
+
msgstr "key1-english"
|
9
|
+
|
10
|
+
msgctxt "key3"
|
11
|
+
msgid "key3-english"
|
12
|
+
msgstr ""
|
13
|
+
|
14
|
+
msgctxt "key5"
|
15
|
+
msgid "A new string"
|
16
|
+
msgstr "A new string"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
msgid ""
|
2
|
+
msgstr ""
|
3
|
+
"Language: en\n"
|
4
|
+
"X-Generator: Twine\n"
|
5
|
+
|
6
|
+
msgctxt "key1"
|
7
|
+
msgid "key1-english"
|
8
|
+
msgstr "key1-english"
|
9
|
+
|
10
|
+
msgctxt "key3"
|
11
|
+
msgid "key3-english"
|
12
|
+
msgstr ""
|
13
|
+
|
14
|
+
msgctxt "key4"
|
15
|
+
msgid "key4"
|
16
|
+
"multiline"
|
17
|
+
msgstr "A multi"
|
18
|
+
"line string\n"
|
19
|
+
"can occur"
|
20
|
+
|
21
|
+
msgctxt "key5"
|
22
|
+
msgid "A new string"
|
23
|
+
msgstr "A new string"
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<!-- Android Strings File -->
|
3
|
+
<!-- Generated by Twine -->
|
4
|
+
<!-- Language: fr -->
|
5
|
+
<resources>
|
6
|
+
<!-- This is a comment -->
|
7
|
+
<string name="key1">key1-french</string>
|
8
|
+
<string name="key2">key2-french</string>
|
9
|
+
<string name="key3">key3-french</string>
|
10
|
+
</resources>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
[[My Strings]]
|
2
|
+
[key1]
|
3
|
+
en = key1-english
|
4
|
+
tags = tag1
|
5
|
+
comment = This is a comment
|
6
|
+
es = key1-spanish
|
7
|
+
fr = key1-french
|
8
|
+
[key2]
|
9
|
+
en = key2-english
|
10
|
+
tags = tag2
|
11
|
+
fr = key2-french
|
12
|
+
[key3]
|
13
|
+
en = key3-english
|
14
|
+
tags = tag1,tag2
|
15
|
+
es = key3-spanish
|
16
|
+
[key4]
|
17
|
+
en = key4-english
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<!-- Android Strings File -->
|
3
|
+
<!-- Generated by Twine <%= Twine::VERSION %> -->
|
4
|
+
<!-- Language: fr -->
|
5
|
+
<resources>
|
6
|
+
<!-- SECTION: My Strings -->
|
7
|
+
<!-- This is a comment -->
|
8
|
+
<string name="key1">key1-french</string>
|
9
|
+
<string name="key2">key2-french</string>
|
10
|
+
<string name="key3">key3-english</string>
|
11
|
+
<string name="key4">key4-english</string>
|
12
|
+
</resources>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
[[My Strings]]
|
2
|
+
[key1]
|
3
|
+
en = key1-english
|
4
|
+
tags = tag1
|
5
|
+
comment = This is a comment
|
6
|
+
es = key1-spanish
|
7
|
+
fr = key1-french
|
8
|
+
[key2]
|
9
|
+
en = key2-english
|
10
|
+
tags = tag2
|
11
|
+
fr = key2-french
|
12
|
+
[key3]
|
13
|
+
en = key3-english
|
14
|
+
tags = tag1,tag2
|
15
|
+
es = key3-spanish
|
16
|
+
fr = key3-french
|
17
|
+
[key4]
|
18
|
+
en = key4-english
|