twine 0.8.1 → 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.
- 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)
|