twine 0.8.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/lib/twine/cli.rb +63 -48
- data/lib/twine/encoding.rb +16 -14
- data/lib/twine/formatters/abstract.rb +9 -48
- data/lib/twine/formatters/android.rb +30 -26
- data/lib/twine/formatters/apple.rb +20 -48
- data/lib/twine/formatters/django.rb +23 -55
- data/lib/twine/formatters/flash.rb +21 -47
- data/lib/twine/formatters/gettext.rb +25 -26
- data/lib/twine/formatters/jquery.rb +8 -8
- data/lib/twine/formatters/tizen.rb +27 -29
- data/lib/twine/output_processor.rb +2 -2
- data/lib/twine/runner.rb +81 -21
- data/lib/twine/version.rb +1 -1
- data/test/fixtures/enc_utf16be.dummy +0 -0
- data/test/fixtures/enc_utf16be_bom.dummy +0 -0
- data/test/fixtures/enc_utf16le.dummy +0 -0
- data/test/fixtures/enc_utf16le_bom.dummy +0 -0
- data/test/fixtures/enc_utf8.dummy +2 -0
- data/test/test_cli.rb +3 -15
- data/test/test_consume_loc_drop.rb +1 -1
- data/test/test_consume_string_file.rb +73 -7
- data/test/test_formatters.rb +96 -38
- data/test/test_generate_all_string_files.rb +43 -17
- data/test/test_generate_loc_drop.rb +19 -13
- data/test/test_generate_string_file.rb +17 -8
- data/test/test_output_processor.rb +2 -2
- data/test/test_strings_file.rb +2 -2
- data/test/twine_file_dsl.rb +1 -1
- data/test/twine_test_case.rb +6 -7
- metadata +7 -2
@@ -29,73 +29,41 @@ module Twine
|
|
29
29
|
return
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
32
|
+
def read(io, lang)
|
33
33
|
comment_regex = /#\. *"?(.*)"?$/
|
34
34
|
key_regex = /msgid *"(.*)"$/
|
35
35
|
value_regex = /msgstr *"(.*)"$/m
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
sep = "\x0a\x00"
|
43
|
-
elsif encoding.end_with? 'BE'
|
44
|
-
sep = "\x00\x0a"
|
45
|
-
else
|
46
|
-
sep = "\n"
|
37
|
+
last_comment = nil
|
38
|
+
while line = io.gets
|
39
|
+
comment_match = comment_regex.match(line)
|
40
|
+
if comment_match
|
41
|
+
comment = comment_match[1]
|
47
42
|
end
|
48
|
-
end
|
49
|
-
|
50
|
-
if encoding.index('UTF-16')
|
51
|
-
mode = "rb:#{encoding}"
|
52
|
-
else
|
53
|
-
mode = "r:#{encoding}"
|
54
|
-
end
|
55
|
-
|
56
|
-
File.open(path, mode) do |f|
|
57
|
-
last_comment = nil
|
58
|
-
while line = (sep) ? f.gets(sep) : f.gets
|
59
|
-
if encoding.index('UTF-16')
|
60
|
-
if line.respond_to? :encode!
|
61
|
-
line.encode!('UTF-8')
|
62
|
-
else
|
63
|
-
require 'iconv'
|
64
|
-
line = Iconv.iconv('UTF-8', encoding, line).join
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
comment_match = comment_regex.match(line)
|
69
|
-
if comment_match
|
70
|
-
comment = comment_match[1]
|
71
|
-
end
|
72
|
-
|
73
|
-
key_match = key_regex.match(line)
|
74
|
-
if key_match
|
75
|
-
key = key_match[1].gsub('\\"', '"')
|
76
|
-
end
|
77
|
-
value_match = value_regex.match(line)
|
78
|
-
if value_match
|
79
|
-
value = value_match[1].gsub(/"\n"/, '').gsub('\\"', '"')
|
80
|
-
end
|
81
43
|
|
44
|
+
key_match = key_regex.match(line)
|
45
|
+
if key_match
|
46
|
+
key = key_match[1].gsub('\\"', '"')
|
47
|
+
end
|
48
|
+
value_match = value_regex.match(line)
|
49
|
+
if value_match
|
50
|
+
value = value_match[1].gsub(/"\n"/, '').gsub('\\"', '"')
|
51
|
+
end
|
82
52
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
key = nil
|
89
|
-
value = nil
|
90
|
-
comment = nil
|
53
|
+
if key and key.length > 0 and value and value.length > 0
|
54
|
+
set_translation_for_key(key, lang, value)
|
55
|
+
if comment and comment.length > 0 and !comment.start_with?("--------- ")
|
56
|
+
set_comment_for_key(key, comment)
|
91
57
|
end
|
92
|
-
|
58
|
+
key = nil
|
59
|
+
value = nil
|
60
|
+
comment = nil
|
93
61
|
end
|
94
62
|
end
|
95
63
|
end
|
96
64
|
|
97
|
-
def format_file(
|
98
|
-
@default_lang = strings.language_codes[0]
|
65
|
+
def format_file(lang)
|
66
|
+
@default_lang = @strings.language_codes[0]
|
99
67
|
result = super
|
100
68
|
@default_lang = nil
|
101
69
|
result
|
@@ -21,57 +21,31 @@ module Twine
|
|
21
21
|
return
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
24
|
+
def read(io, lang)
|
25
|
+
last_comment = nil
|
26
|
+
while line = io.gets
|
27
|
+
match = /((?:[^"\\]|\\.)+)\s*=\s*((?:[^"\\]|\\.)*)/.match(line)
|
28
|
+
if match
|
29
|
+
key = match[1]
|
30
|
+
value = match[2].strip
|
31
|
+
value.gsub!(/\{[0-9]\}/, '%@')
|
32
|
+
set_translation_for_key(key, lang, value)
|
33
|
+
if last_comment
|
34
|
+
set_comment_for_key(key, last_comment)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
match = /# *(.*)/.match(line)
|
39
|
+
if match
|
40
|
+
last_comment = match[1]
|
33
41
|
else
|
34
|
-
|
42
|
+
last_comment = nil
|
35
43
|
end
|
36
44
|
end
|
45
|
+
end
|
37
46
|
|
38
|
-
|
39
|
-
|
40
|
-
else
|
41
|
-
mode = "r:#{encoding}"
|
42
|
-
end
|
43
|
-
|
44
|
-
File.open(path, mode) do |f|
|
45
|
-
last_comment = nil
|
46
|
-
while line = (sep) ? f.gets(sep) : f.gets
|
47
|
-
if encoding.index('UTF-16')
|
48
|
-
if line.respond_to? :encode!
|
49
|
-
line.encode!('UTF-8')
|
50
|
-
else
|
51
|
-
require 'iconv'
|
52
|
-
line = Iconv.iconv('UTF-8', encoding, line).join
|
53
|
-
end
|
54
|
-
end
|
55
|
-
match = /((?:[^"\\]|\\.)+)\s*=\s*((?:[^"\\]|\\.)*)/.match(line)
|
56
|
-
if match
|
57
|
-
key = match[1]
|
58
|
-
value = match[2].strip
|
59
|
-
value.gsub!(/\{[0-9]\}/, '%@')
|
60
|
-
set_translation_for_key(key, lang, value)
|
61
|
-
if last_comment
|
62
|
-
set_comment_for_key(key, last_comment)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
match = /# *(.*)/.match(line)
|
67
|
-
if match
|
68
|
-
last_comment = match[1]
|
69
|
-
else
|
70
|
-
last_comment = nil
|
71
|
-
end
|
72
|
-
|
73
|
-
end
|
74
|
-
end
|
47
|
+
def format_sections(strings, lang)
|
48
|
+
super + "\n"
|
75
49
|
end
|
76
50
|
|
77
51
|
def format_header(lang)
|
@@ -31,40 +31,39 @@ module Twine
|
|
31
31
|
return
|
32
32
|
end
|
33
33
|
|
34
|
-
def
|
34
|
+
def read(io, lang)
|
35
35
|
comment_regex = /#.? *"(.*)"$/
|
36
36
|
key_regex = /msgctxt *"(.*)"$/
|
37
37
|
value_regex = /msgstr *"(.*)"$/m
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
end
|
61
|
-
comment = nil
|
38
|
+
|
39
|
+
while item = io.gets("\n\n")
|
40
|
+
key = nil
|
41
|
+
value = nil
|
42
|
+
comment = nil
|
43
|
+
|
44
|
+
comment_match = comment_regex.match(item)
|
45
|
+
if comment_match
|
46
|
+
comment = comment_match[1]
|
47
|
+
end
|
48
|
+
key_match = key_regex.match(item)
|
49
|
+
if key_match
|
50
|
+
key = key_match[1].gsub('\\"', '"')
|
51
|
+
end
|
52
|
+
value_match = value_regex.match(item)
|
53
|
+
if value_match
|
54
|
+
value = value_match[1].gsub(/"\n"/, '').gsub('\\"', '"')
|
55
|
+
end
|
56
|
+
if key and key.length > 0 and value and value.length > 0
|
57
|
+
set_translation_for_key(key, lang, value)
|
58
|
+
if comment and comment.length > 0 and !comment.start_with?("SECTION:")
|
59
|
+
set_comment_for_key(key, comment)
|
62
60
|
end
|
61
|
+
comment = nil
|
63
62
|
end
|
64
63
|
end
|
65
64
|
end
|
66
65
|
|
67
|
-
def format_file(
|
66
|
+
def format_file(lang)
|
68
67
|
@default_lang = strings.language_codes[0]
|
69
68
|
result = super
|
70
69
|
@default_lang = nil
|
@@ -29,23 +29,23 @@ module Twine
|
|
29
29
|
return
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
32
|
+
def read(io, lang)
|
33
33
|
begin
|
34
34
|
require "json"
|
35
35
|
rescue LoadError
|
36
36
|
raise Twine::Error.new "You must run 'gem install json' in order to read or write jquery-localize files."
|
37
37
|
end
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
set_translation_for_key(key, lang, value)
|
43
|
-
end
|
39
|
+
json = JSON.load(io)
|
40
|
+
json.each do |key, value|
|
41
|
+
set_translation_for_key(key, lang, value)
|
44
42
|
end
|
45
43
|
end
|
46
44
|
|
47
|
-
def format_file(
|
48
|
-
|
45
|
+
def format_file(lang)
|
46
|
+
result = super
|
47
|
+
return result unless result
|
48
|
+
"{\n#{super}\n}\n"
|
49
49
|
end
|
50
50
|
|
51
51
|
def format_sections(strings, lang)
|
@@ -49,7 +49,7 @@ module Twine
|
|
49
49
|
return
|
50
50
|
end
|
51
51
|
|
52
|
-
def
|
52
|
+
def read(io, lang)
|
53
53
|
resources_regex = /<resources(?:[^>]*)>(.*)<\/resources>/m
|
54
54
|
key_regex = /<string name="(\w+)">/
|
55
55
|
comment_regex = /<!-- (.*) -->/
|
@@ -58,35 +58,33 @@ module Twine
|
|
58
58
|
value = nil
|
59
59
|
comment = nil
|
60
60
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
value = ""
|
78
|
-
end
|
79
|
-
set_translation_for_key(key, lang, value)
|
80
|
-
if comment and comment.length > 0 and !comment.start_with?("SECTION:")
|
81
|
-
set_comment_for_key(key, comment)
|
82
|
-
end
|
83
|
-
comment = nil
|
61
|
+
content_match = resources_regex.match(io.read)
|
62
|
+
if content_match
|
63
|
+
for line in content_match[1].split(/\r?\n/)
|
64
|
+
key_match = key_regex.match(line)
|
65
|
+
if key_match
|
66
|
+
key = key_match[1]
|
67
|
+
value_match = value_regex.match(line)
|
68
|
+
if value_match
|
69
|
+
value = value_match[1]
|
70
|
+
value = CGI.unescapeHTML(value)
|
71
|
+
value.gsub!('\\\'', '\'')
|
72
|
+
value.gsub!('\\"', '"')
|
73
|
+
value = convert_placeholders_from_android_to_twine(value)
|
74
|
+
value.gsub!(/(\\u0020)*|(\\u0020)*\z/) { |spaces| ' ' * (spaces.length / 6) }
|
75
|
+
else
|
76
|
+
value = ""
|
84
77
|
end
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
comment = comment_match[1]
|
78
|
+
set_translation_for_key(key, lang, value)
|
79
|
+
if comment and comment.length > 0 and !comment.start_with?("SECTION:")
|
80
|
+
set_comment_for_key(key, comment)
|
89
81
|
end
|
82
|
+
comment = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
comment_match = comment_regex.match(line)
|
86
|
+
if comment_match
|
87
|
+
comment = comment_match[1]
|
90
88
|
end
|
91
89
|
end
|
92
90
|
end
|
@@ -101,7 +99,7 @@ module Twine
|
|
101
99
|
|
102
100
|
result += super + "\n"
|
103
101
|
|
104
|
-
result +=
|
102
|
+
result += "</string_table>\n"
|
105
103
|
end
|
106
104
|
|
107
105
|
def format_section_header(section)
|
@@ -31,9 +31,9 @@ module Twine
|
|
31
31
|
|
32
32
|
value = row.translated_string_for_lang(language)
|
33
33
|
|
34
|
-
next if value && @options[:include] ==
|
34
|
+
next if value && @options[:include] == :untranslated
|
35
35
|
|
36
|
-
if value.nil? && @options[:include] !=
|
36
|
+
if value.nil? && @options[:include] != :translated
|
37
37
|
value = row.translated_string_for_lang(fallback_languages(language))
|
38
38
|
end
|
39
39
|
|
data/lib/twine/runner.rb
CHANGED
@@ -48,7 +48,12 @@ module Twine
|
|
48
48
|
lang = nil
|
49
49
|
lang = @options[:languages][0] if @options[:languages]
|
50
50
|
|
51
|
-
|
51
|
+
formatter, lang = prepare_read_write(@options[:output_path], lang)
|
52
|
+
output = formatter.format_file(lang)
|
53
|
+
|
54
|
+
raise Twine::Error.new "Nothing to generate! The resulting file would not contain any strings." unless output
|
55
|
+
|
56
|
+
IO.write(@options[:output_path], output, encoding: encoding)
|
52
57
|
end
|
53
58
|
|
54
59
|
def generate_all_string_files
|
@@ -69,7 +74,51 @@ module Twine
|
|
69
74
|
raise Twine::Error.new "Could not determine format given the contents of #{@options[:output_path]}"
|
70
75
|
end
|
71
76
|
|
72
|
-
|
77
|
+
file_name = @options[:file_name] || formatter.default_file_name
|
78
|
+
if @options[:create_folders]
|
79
|
+
@strings.language_codes.each do |lang|
|
80
|
+
output_path = File.join(@options[:output_path], formatter.output_path_for_language(lang))
|
81
|
+
|
82
|
+
FileUtils.mkdir_p(output_path)
|
83
|
+
|
84
|
+
file_path = File.join(output_path, file_name)
|
85
|
+
|
86
|
+
output = formatter.format_file(lang)
|
87
|
+
unless output
|
88
|
+
Twine::stderr.puts "Skipping file at path #{file_path} since it would not contain any strings."
|
89
|
+
next
|
90
|
+
end
|
91
|
+
|
92
|
+
IO.write(file_path, output, encoding: encoding)
|
93
|
+
end
|
94
|
+
else
|
95
|
+
language_found = false
|
96
|
+
Dir.foreach(@options[:output_path]) do |item|
|
97
|
+
next if item == "." or item == ".."
|
98
|
+
|
99
|
+
output_path = File.join(@options[:output_path], item)
|
100
|
+
next unless File.directory?(output_path)
|
101
|
+
|
102
|
+
lang = formatter.determine_language_given_path(output_path)
|
103
|
+
next unless lang
|
104
|
+
|
105
|
+
language_found = true
|
106
|
+
|
107
|
+
file_path = File.join(output_path, file_name)
|
108
|
+
output = formatter.format_file(lang)
|
109
|
+
unless output
|
110
|
+
Twine::stderr.puts "Skipping file at path #{file_path} since it would not contain any strings."
|
111
|
+
next
|
112
|
+
end
|
113
|
+
|
114
|
+
IO.write(file_path, output, encoding: encoding)
|
115
|
+
end
|
116
|
+
|
117
|
+
unless language_found
|
118
|
+
raise Twine::Error.new("Failed to generate any files: No languages found at #{@options[:output_path]}")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
73
122
|
end
|
74
123
|
|
75
124
|
def consume_string_file
|
@@ -111,7 +160,7 @@ module Twine
|
|
111
160
|
File.delete(@options[:output_path])
|
112
161
|
end
|
113
162
|
|
114
|
-
Dir.mktmpdir do |
|
163
|
+
Dir.mktmpdir do |temp_dir|
|
115
164
|
Zip::File.open(@options[:output_path], Zip::File::CREATE) do |zipfile|
|
116
165
|
zipfile.mkdir('Locales')
|
117
166
|
|
@@ -119,10 +168,17 @@ module Twine
|
|
119
168
|
@strings.language_codes.each do |lang|
|
120
169
|
if @options[:languages] == nil || @options[:languages].length == 0 || @options[:languages].include?(lang)
|
121
170
|
file_name = lang + formatter.extension
|
122
|
-
|
171
|
+
temp_path = File.join(temp_dir, file_name)
|
123
172
|
zip_path = File.join('Locales', file_name)
|
124
|
-
|
125
|
-
|
173
|
+
|
174
|
+
output = formatter.format_file(lang)
|
175
|
+
unless output
|
176
|
+
Twine::stderr.puts "Skipping file #{file_name} since it would not contain any strings."
|
177
|
+
next
|
178
|
+
end
|
179
|
+
|
180
|
+
IO.write(temp_path, output, encoding: encoding)
|
181
|
+
zipfile.add(zip_path, temp_path)
|
126
182
|
end
|
127
183
|
end
|
128
184
|
end
|
@@ -136,18 +192,18 @@ module Twine
|
|
136
192
|
raise Twine::Error.new("File does not exist: #{@options[:input_path]}")
|
137
193
|
end
|
138
194
|
|
139
|
-
Dir.mktmpdir do |
|
195
|
+
Dir.mktmpdir do |temp_dir|
|
140
196
|
Zip::File.open(@options[:input_path]) do |zipfile|
|
141
197
|
zipfile.each do |entry|
|
142
|
-
if
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
198
|
+
next if entry.name.end_with? '/' or File.basename(entry.name).start_with? '.'
|
199
|
+
|
200
|
+
real_path = File.join(temp_dir, entry.name)
|
201
|
+
FileUtils.mkdir_p(File.dirname(real_path))
|
202
|
+
zipfile.extract(entry.name, real_path)
|
203
|
+
begin
|
204
|
+
read_string_file(real_path)
|
205
|
+
rescue Twine::Error => e
|
206
|
+
Twine::stderr.puts "#{e.message}"
|
151
207
|
end
|
152
208
|
end
|
153
209
|
end
|
@@ -204,6 +260,10 @@ module Twine
|
|
204
260
|
|
205
261
|
private
|
206
262
|
|
263
|
+
def encoding
|
264
|
+
@options[:output_encoding] || 'UTF-8'
|
265
|
+
end
|
266
|
+
|
207
267
|
def require_rubyzip
|
208
268
|
begin
|
209
269
|
require 'zip'
|
@@ -236,12 +296,12 @@ module Twine
|
|
236
296
|
|
237
297
|
formatter, lang = prepare_read_write(path, lang)
|
238
298
|
|
239
|
-
|
240
|
-
end
|
299
|
+
encoding = @options[:encoding] || Twine::Encoding.encoding_for_path(path)
|
241
300
|
|
242
|
-
|
243
|
-
|
244
|
-
|
301
|
+
IO.open(IO.sysopen(path, 'rb'), 'rb', external_encoding: encoding, internal_encoding: 'UTF-8') do |io|
|
302
|
+
io.read(2) if Twine::Encoding.has_bom?(path)
|
303
|
+
formatter.read(io, lang)
|
304
|
+
end
|
245
305
|
end
|
246
306
|
|
247
307
|
def prepare_read_write(path, lang)
|