twine 0.9.1 → 0.10.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +56 -69
  3. data/lib/twine.rb +11 -3
  4. data/lib/twine/cli.rb +375 -155
  5. data/lib/twine/formatters.rb +0 -5
  6. data/lib/twine/formatters/abstract.rb +43 -43
  7. data/lib/twine/formatters/android.rb +58 -59
  8. data/lib/twine/formatters/apple.rb +3 -3
  9. data/lib/twine/formatters/django.rb +15 -21
  10. data/lib/twine/formatters/flash.rb +17 -20
  11. data/lib/twine/formatters/gettext.rb +11 -15
  12. data/lib/twine/formatters/jquery.rb +8 -11
  13. data/lib/twine/formatters/tizen.rb +4 -4
  14. data/lib/twine/output_processor.rb +15 -15
  15. data/lib/twine/placeholders.rb +26 -6
  16. data/lib/twine/runner.rb +95 -95
  17. data/lib/twine/{stringsfile.rb → twine_file.rb} +53 -48
  18. data/lib/twine/version.rb +1 -1
  19. data/test/{command_test_case.rb → command_test.rb} +5 -5
  20. data/test/fixtures/{consume_loc_drop.zip → consume_localization_archive.zip} +0 -0
  21. data/test/fixtures/formatter_django.po +3 -1
  22. data/test/test_abstract_formatter.rb +40 -40
  23. data/test/test_cli.rb +313 -211
  24. data/test/test_consume_localization_archive.rb +27 -0
  25. data/test/{test_consume_string_file.rb → test_consume_localization_file.rb} +19 -19
  26. data/test/test_formatters.rb +108 -43
  27. data/test/{test_generate_all_string_files.rb → test_generate_all_localization_files.rb} +18 -18
  28. data/test/{test_generate_loc_drop.rb → test_generate_localization_archive.rb} +14 -14
  29. data/test/{test_generate_string_file.rb → test_generate_localization_file.rb} +18 -18
  30. data/test/test_output_processor.rb +26 -26
  31. data/test/test_placeholders.rb +44 -9
  32. data/test/test_twine_definition.rb +111 -0
  33. data/test/test_twine_file.rb +58 -0
  34. data/test/test_validate_twine_file.rb +61 -0
  35. data/test/twine_file_dsl.rb +12 -12
  36. data/test/{twine_test_case.rb → twine_test.rb} +1 -1
  37. metadata +23 -23
  38. data/test/test_consume_loc_drop.rb +0 -27
  39. data/test/test_strings_file.rb +0 -58
  40. data/test/test_strings_row.rb +0 -47
  41. data/test/test_validate_strings_file.rb +0 -61
@@ -0,0 +1,27 @@
1
+ require 'command_test'
2
+
3
+ class TestConsumeLocalizationArchive < CommandTest
4
+ def setup
5
+ super
6
+
7
+ options = {}
8
+ options[:input_path] = fixture_path 'consume_localization_archive.zip'
9
+ options[:output_path] = @output_path
10
+ options[:format] = 'apple'
11
+
12
+ @twine_file = build_twine_file 'en', 'es' do
13
+ add_section 'Section' do
14
+ add_definition key1: 'value1'
15
+ end
16
+ end
17
+
18
+ @runner = Twine::Runner.new(options, @twine_file)
19
+ end
20
+
21
+ def test_consumes_zip_file
22
+ @runner.consume_localization_archive
23
+
24
+ assert @twine_file.definitions_by_key['key1'].translations['en'], 'value1-english'
25
+ assert @twine_file.definitions_by_key['key1'].translations['es'], 'value1-spanish'
26
+ end
27
+ end
@@ -1,6 +1,6 @@
1
- require 'command_test_case'
1
+ require 'command_test'
2
2
 
3
- class TestConsumeStringFile < CommandTestCase
3
+ class TestConsumeLocalizationFile < CommandTest
4
4
  def new_runner(language, file)
5
5
  options = {}
6
6
  options[:output_path] = File.join(@output_dir, file) if file
@@ -8,10 +8,10 @@ class TestConsumeStringFile < CommandTestCase
8
8
  FileUtils.touch options[:input_path]
9
9
  options[:languages] = language if language
10
10
 
11
- @strings = Twine::StringsFile.new
12
- @strings.language_codes.concat KNOWN_LANGUAGES
11
+ @twine_file = Twine::TwineFile.new
12
+ @twine_file.language_codes.concat KNOWN_LANGUAGES
13
13
 
14
- Twine::Runner.new(options, @strings)
14
+ Twine::Runner.new(options, @twine_file)
15
15
  end
16
16
 
17
17
  def prepare_mock_read_formatter(formatter_class)
@@ -22,25 +22,25 @@ class TestConsumeStringFile < CommandTestCase
22
22
  def test_deducts_android_format_from_output_path
23
23
  prepare_mock_read_formatter Twine::Formatters::Android
24
24
 
25
- new_runner('fr', 'fr.xml').consume_string_file
25
+ new_runner('fr', 'fr.xml').consume_localization_file
26
26
  end
27
27
 
28
28
  def test_deducts_apple_format_from_output_path
29
29
  prepare_mock_read_formatter Twine::Formatters::Apple
30
30
 
31
- new_runner('fr', 'fr.strings').consume_string_file
31
+ new_runner('fr', 'fr.strings').consume_localization_file
32
32
  end
33
33
 
34
34
  def test_deducts_jquery_format_from_output_path
35
35
  prepare_mock_read_formatter Twine::Formatters::JQuery
36
36
 
37
- new_runner('fr', 'fr.json').consume_string_file
37
+ new_runner('fr', 'fr.json').consume_localization_file
38
38
  end
39
39
 
40
40
  def test_deducts_gettext_format_from_output_path
41
41
  prepare_mock_read_formatter Twine::Formatters::Gettext
42
42
 
43
- new_runner('fr', 'fr.po').consume_string_file
43
+ new_runner('fr', 'fr.po').consume_localization_file
44
44
  end
45
45
 
46
46
  def test_deducts_language_from_input_path
@@ -48,10 +48,10 @@ class TestConsumeStringFile < CommandTestCase
48
48
  formatter = prepare_mock_formatter Twine::Formatters::Android
49
49
  formatter.expects(:read).with(anything, random_language)
50
50
 
51
- new_runner(nil, "#{random_language}.xml").consume_string_file
51
+ new_runner(nil, "#{random_language}.xml").consume_localization_file
52
52
  end
53
53
 
54
- class TestEncodings < CommandTestCase
54
+ class TestEncodings < CommandTest
55
55
  class DummyFormatter < Twine::Formatters::Abstract
56
56
  attr_reader :content
57
57
 
@@ -75,10 +75,10 @@ class TestConsumeStringFile < CommandTestCase
75
75
  options[:encoding] = encoding if encoding
76
76
  options[:languages] = 'en'
77
77
 
78
- @strings = Twine::StringsFile.new
79
- @strings.language_codes.concat KNOWN_LANGUAGES
78
+ @twine_file = Twine::TwineFile.new
79
+ @twine_file.language_codes.concat KNOWN_LANGUAGES
80
80
 
81
- Twine::Runner.new(options, @strings)
81
+ Twine::Runner.new(options, @twine_file)
82
82
  end
83
83
 
84
84
  def setup
@@ -88,31 +88,31 @@ class TestConsumeStringFile < CommandTestCase
88
88
 
89
89
  def test_reads_utf8
90
90
  formatter = prepare_mock_formatter DummyFormatter
91
- new_runner(fixture_path('enc_utf8.dummy')).consume_string_file
91
+ new_runner(fixture_path('enc_utf8.dummy')).consume_localization_file
92
92
  assert_equal @expected_content, formatter.content
93
93
  end
94
94
 
95
95
  def test_reads_utf16le_bom
96
96
  formatter = prepare_mock_formatter DummyFormatter
97
- new_runner(fixture_path('enc_utf16le_bom.dummy')).consume_string_file
97
+ new_runner(fixture_path('enc_utf16le_bom.dummy')).consume_localization_file
98
98
  assert_equal @expected_content, formatter.content
99
99
  end
100
100
 
101
101
  def test_reads_utf16be_bom
102
102
  formatter = prepare_mock_formatter DummyFormatter
103
- new_runner(fixture_path('enc_utf16be_bom.dummy')).consume_string_file
103
+ new_runner(fixture_path('enc_utf16be_bom.dummy')).consume_localization_file
104
104
  assert_equal @expected_content, formatter.content
105
105
  end
106
106
 
107
107
  def test_reads_utf16le
108
108
  formatter = prepare_mock_formatter DummyFormatter
109
- new_runner(fixture_path('enc_utf16le.dummy'), 'UTF-16LE').consume_string_file
109
+ new_runner(fixture_path('enc_utf16le.dummy'), 'UTF-16LE').consume_localization_file
110
110
  assert_equal @expected_content, formatter.content
111
111
  end
112
112
 
113
113
  def test_reads_utf16be
114
114
  formatter = prepare_mock_formatter DummyFormatter
115
- new_runner(fixture_path('enc_utf16be.dummy'), 'UTF-16BE').consume_string_file
115
+ new_runner(fixture_path('enc_utf16be.dummy'), 'UTF-16BE').consume_localization_file
116
116
  assert_equal @expected_content, formatter.content
117
117
  end
118
118
  end
@@ -1,44 +1,58 @@
1
- require 'twine_test_case'
1
+ require 'twine_test'
2
2
 
3
- class FormatterTest < TwineTestCase
3
+ class FormatterTest < TwineTest
4
4
  def setup(formatter_class)
5
5
  super()
6
6
 
7
7
  @twine_file = build_twine_file 'en' do
8
8
  add_section 'Section 1' do
9
- add_row key1: 'value1-english', comment: 'comment key1'
10
- add_row key2: 'value2-english'
9
+ add_definition key1: 'value1-english', comment: 'comment key1'
10
+ add_definition key2: 'value2-english'
11
11
  end
12
12
 
13
13
  add_section 'Section 2' do
14
- add_row key3: 'value3-english'
15
- add_row key4: 'value4-english', comment: 'comment key4'
14
+ add_definition key3: 'value3-english'
15
+ add_definition key4: 'value4-english', comment: 'comment key4'
16
16
  end
17
17
  end
18
18
 
19
- @strings = Twine::StringsFile.new
19
+ @empty_twine_file = Twine::TwineFile.new
20
20
  @formatter = formatter_class.new
21
- @formatter.strings = @strings
21
+ @formatter.twine_file = @empty_twine_file
22
22
  @formatter.options = { consume_all: true, consume_comments: true }
23
23
  end
24
24
 
25
25
  def assert_translations_read_correctly
26
26
  1.upto(4) do |i|
27
- assert_equal "value#{i}-english", @strings.strings_map["key#{i}"].translations['en']
27
+ assert_equal "value#{i}-english", @empty_twine_file.definitions_by_key["key#{i}"].translations['en']
28
28
  end
29
29
  end
30
30
 
31
31
  def assert_file_contents_read_correctly
32
32
  assert_translations_read_correctly
33
33
 
34
- assert_equal "comment key1", @strings.strings_map["key1"].comment
35
- assert_equal "comment key4", @strings.strings_map["key4"].comment
34
+ assert_equal "comment key1", @empty_twine_file.definitions_by_key["key1"].comment
35
+ assert_equal "comment key4", @empty_twine_file.definitions_by_key["key4"].comment
36
36
  end
37
37
  end
38
38
 
39
39
  class TestAndroidFormatter < FormatterTest
40
40
  def setup
41
41
  super Twine::Formatters::Android
42
+
43
+ @escape_test_values = {
44
+ 'this & that' => 'this &amp; that',
45
+ 'this < that' => 'this &lt; that',
46
+ "it's complicated" => "it\\'s complicated",
47
+ 'a "good" way' => 'a \"good\" way',
48
+ '<b>bold</b>' => '&lt;b>bold&lt;/b>',
49
+ '<a href="target">link</a>' => '&lt;a href=\"target\">link&lt;/a>',
50
+
51
+ '<xliff:g></xliff:g>' => '<xliff:g></xliff:g>',
52
+ '<xliff:g>untouched</xliff:g>' => '<xliff:g>untouched</xliff:g>',
53
+ '<xliff:g id="42">untouched</xliff:g>' => '<xliff:g id="42">untouched</xliff:g>',
54
+ '<xliff:g id="1">first</xliff:g> inbetween <xliff:g id="2">second</xliff:g>' => '<xliff:g id="1">first</xliff:g> inbetween <xliff:g id="2">second</xliff:g>'
55
+ }
42
56
  end
43
57
 
44
58
  def test_read_format
@@ -47,29 +61,52 @@ class TestAndroidFormatter < FormatterTest
47
61
  assert_file_contents_read_correctly
48
62
  end
49
63
 
64
+ def test_read_multiline_translation
65
+ content = <<-EOCONTENT
66
+ <?xml version="1.0" encoding="utf-8"?>
67
+ <resources>
68
+ <string name="foo">This is
69
+ a string</string>
70
+ </resources>
71
+ EOCONTENT
72
+
73
+ io = StringIO.new(content)
74
+
75
+ @formatter.read io, 'en'
76
+
77
+ assert_equal 'This is\n a string', @empty_twine_file.definitions_by_key["foo"].translations['en']
78
+ end
79
+
50
80
  def test_set_translation_converts_leading_spaces
51
81
  @formatter.set_translation_for_key 'key1', 'en', "\u0020value"
52
- assert_equal ' value', @strings.strings_map['key1'].translations['en']
82
+ assert_equal ' value', @empty_twine_file.definitions_by_key['key1'].translations['en']
53
83
  end
54
84
 
55
85
  def test_set_translation_coverts_trailing_spaces
56
86
  @formatter.set_translation_for_key 'key1', 'en', "value\u0020\u0020"
57
- assert_equal 'value ', @strings.strings_map['key1'].translations['en']
87
+ assert_equal 'value ', @empty_twine_file.definitions_by_key['key1'].translations['en']
58
88
  end
59
89
 
60
90
  def test_set_translation_converts_string_placeholders
61
91
  @formatter.set_translation_for_key 'key1', 'en', "value %s"
62
- assert_equal 'value %@', @strings.strings_map['key1'].translations['en']
92
+ assert_equal 'value %@', @empty_twine_file.definitions_by_key['key1'].translations['en']
63
93
  end
64
94
 
65
95
  def test_set_translation_unescapes_at_signs
66
96
  @formatter.set_translation_for_key 'key1', 'en', '\@value'
67
- assert_equal '@value', @strings.strings_map['key1'].translations['en']
97
+ assert_equal '@value', @empty_twine_file.definitions_by_key['key1'].translations['en']
98
+ end
99
+
100
+ def test_set_translation_unescaping
101
+ @escape_test_values.each do |expected, input|
102
+ @formatter.set_translation_for_key 'key1', 'en', input
103
+ assert_equal expected, @empty_twine_file.definitions_by_key['key1'].translations['en']
104
+ end
68
105
  end
69
106
 
70
107
  def test_format_file
71
108
  formatter = Twine::Formatters::Android.new
72
- formatter.strings = @twine_file
109
+ formatter.twine_file = @twine_file
73
110
  assert_equal content('formatter_android.xml'), formatter.format_file('en')
74
111
  end
75
112
 
@@ -85,10 +122,14 @@ class TestAndroidFormatter < FormatterTest
85
122
  assert_equal "value\\u0020", @formatter.format_value('value ')
86
123
  end
87
124
 
88
- def test_format_value_escapes_single_quotes
89
- skip 'not working with ruby 2.0'
90
- # http://stackoverflow.com/questions/18735608/cgiescapehtml-is-escaping-single-quote
91
- assert_equal "not \\'so\\' easy", @formatter.format_value("not 'so' easy")
125
+ def test_format_value_string_placeholder
126
+ assert_equal "The file %s could not be found.", @formatter.format_value("The file %@ could not be found.")
127
+ end
128
+
129
+ def test_format_value_escaping
130
+ @escape_test_values.each do |input, expected|
131
+ assert_equal expected, @formatter.format_value(input)
132
+ end
92
133
  end
93
134
 
94
135
  def test_format_value_escapes_non_resource_identifier_at_signs
@@ -109,10 +150,6 @@ class TestAndroidFormatter < FormatterTest
109
150
  assert_equal 'de-AT', @formatter.determine_language_given_path("res/values-de-rAT")
110
151
  end
111
152
 
112
- def test_maps_laguage_deducted_from_resource_folder
113
- assert_equal 'zh-Hans', @formatter.determine_language_given_path("res/values-zh-rCN")
114
- end
115
-
116
153
  def test_does_not_deduct_language_from_device_capability_resource_folder
117
154
  assert_nil @formatter.determine_language_given_path('res/values-w820dp')
118
155
  end
@@ -120,10 +157,6 @@ class TestAndroidFormatter < FormatterTest
120
157
  def test_output_path_is_prefixed
121
158
  assert_equal 'values-en', @formatter.output_path_for_language('en')
122
159
  end
123
-
124
- def test_output_path_language_mappings
125
- assert_equal 'values-zh-rCN', @formatter.output_path_for_language('zh-Hans')
126
- end
127
160
  end
128
161
 
129
162
  class TestAppleFormatter < FormatterTest
@@ -139,47 +172,47 @@ class TestAppleFormatter < FormatterTest
139
172
 
140
173
  def test_reads_quoted_keys
141
174
  @formatter.read StringIO.new('"key" = "value"'), 'en'
142
- assert_equal 'value', @strings.strings_map['key'].translations['en']
175
+ assert_equal 'value', @empty_twine_file.definitions_by_key['key'].translations['en']
143
176
  end
144
177
 
145
178
  def test_reads_unquoted_keys
146
179
  @formatter.read StringIO.new('key = "value"'), 'en'
147
- assert_equal 'value', @strings.strings_map['key'].translations['en']
180
+ assert_equal 'value', @empty_twine_file.definitions_by_key['key'].translations['en']
148
181
  end
149
182
 
150
183
  def test_ignores_leading_whitespace_before_quoted_keys
151
184
  @formatter.read StringIO.new("\t \"key\" = \"value\""), 'en'
152
- assert_equal 'value', @strings.strings_map['key'].translations['en']
185
+ assert_equal 'value', @empty_twine_file.definitions_by_key['key'].translations['en']
153
186
  end
154
187
 
155
188
  def test_ignores_leading_whitespace_before_unquoted_keys
156
189
  @formatter.read StringIO.new("\t key = \"value\""), 'en'
157
- assert_equal 'value', @strings.strings_map['key'].translations['en']
190
+ assert_equal 'value', @empty_twine_file.definitions_by_key['key'].translations['en']
158
191
  end
159
192
 
160
193
  def test_allows_quotes_in_quoted_keys
161
194
  @formatter.read StringIO.new('"ke\"y" = "value"'), 'en'
162
- assert_equal 'value', @strings.strings_map['ke"y'].translations['en']
195
+ assert_equal 'value', @empty_twine_file.definitions_by_key['ke"y'].translations['en']
163
196
  end
164
197
 
165
198
  def test_does_not_allow_quotes_in_quoted_keys
166
199
  @formatter.read StringIO.new('ke"y = "value"'), 'en'
167
- assert_nil @strings.strings_map['key']
200
+ assert_nil @empty_twine_file.definitions_by_key['key']
168
201
  end
169
202
 
170
203
  def test_allows_equal_signs_in_quoted_keys
171
204
  @formatter.read StringIO.new('"k=ey" = "value"'), 'en'
172
- assert_equal 'value', @strings.strings_map['k=ey'].translations['en']
205
+ assert_equal 'value', @empty_twine_file.definitions_by_key['k=ey'].translations['en']
173
206
  end
174
207
 
175
208
  def test_does_not_allow_equal_signs_in_unquoted_keys
176
209
  @formatter.read StringIO.new('k=ey = "value"'), 'en'
177
- assert_nil @strings.strings_map['key']
210
+ assert_nil @empty_twine_file.definitions_by_key['key']
178
211
  end
179
212
 
180
213
  def test_format_file
181
214
  formatter = Twine::Formatters::Apple.new
182
- formatter.strings = @twine_file
215
+ formatter.twine_file = @twine_file
183
216
  assert_equal content('formatter_apple.strings'), formatter.format_file('en')
184
217
  end
185
218
 
@@ -210,10 +243,24 @@ class TestJQueryFormatter < FormatterTest
210
243
 
211
244
  def test_format_file
212
245
  formatter = Twine::Formatters::JQuery.new
213
- formatter.strings = @twine_file
246
+ formatter.twine_file = @twine_file
214
247
  assert_equal content('formatter_jquery.json'), formatter.format_file('en')
215
248
  end
216
249
 
250
+ def test_empty_sections_are_removed
251
+ @twine_file = build_twine_file 'en' do
252
+ add_section 'Section 1' do
253
+ end
254
+
255
+ add_section 'Section 2' do
256
+ add_definition key: 'value'
257
+ end
258
+ end
259
+ formatter = Twine::Formatters::JQuery.new
260
+ formatter.twine_file = @twine_file
261
+ refute_includes formatter.format_file('en'), ','
262
+ end
263
+
217
264
  def test_format_value_with_newline
218
265
  assert_equal "value\nwith\nline\nbreaks", @formatter.format_value("value\nwith\nline\nbreaks")
219
266
  end
@@ -234,12 +281,12 @@ class TestGettextFormatter < FormatterTest
234
281
  def test_read_with_multiple_line_value
235
282
  @formatter.read content_io('gettext_multiline.po'), 'en'
236
283
 
237
- assert_equal 'multiline\nstring', @strings.strings_map['key1'].translations['en']
284
+ assert_equal 'multiline\nstring', @empty_twine_file.definitions_by_key['key1'].translations['en']
238
285
  end
239
286
 
240
287
  def test_format_file
241
288
  formatter = Twine::Formatters::Gettext.new
242
- formatter.strings = @twine_file
289
+ formatter.twine_file = @twine_file
243
290
  assert_equal content('formatter_gettext.po'), formatter.format_file('en')
244
291
  end
245
292
 
@@ -260,7 +307,7 @@ class TestTizenFormatter < FormatterTest
260
307
 
261
308
  def test_format_file
262
309
  formatter = Twine::Formatters::Tizen.new
263
- formatter.strings = @twine_file
310
+ formatter.twine_file = @twine_file
264
311
  assert_equal content('formatter_tizen.xml'), formatter.format_file('en')
265
312
  end
266
313
 
@@ -279,7 +326,7 @@ class TestDjangoFormatter < FormatterTest
279
326
 
280
327
  def test_format_file
281
328
  formatter = Twine::Formatters::Django.new
282
- formatter.strings = @twine_file
329
+ formatter.twine_file = @twine_file
283
330
  assert_equal content('formatter_django.po'), formatter.format_file('en')
284
331
  end
285
332
  end
@@ -295,9 +342,27 @@ class TestFlashFormatter < FormatterTest
295
342
  assert_file_contents_read_correctly
296
343
  end
297
344
 
345
+ def test_set_translation_converts_placeholders
346
+ @formatter.set_translation_for_key 'key1', 'en', "value {#{rand(10)}}"
347
+ assert_equal 'value %@', @empty_twine_file.definitions_by_key['key1'].translations['en']
348
+ end
349
+
298
350
  def test_format_file
299
351
  formatter = Twine::Formatters::Flash.new
300
- formatter.strings = @twine_file
352
+ formatter.twine_file = @twine_file
301
353
  assert_equal content('formatter_flash.properties'), formatter.format_file('en')
302
354
  end
355
+
356
+ def test_format_value_converts_placeholders
357
+ assert_equal "value {0}", @formatter.format_value('value %d')
358
+ end
359
+
360
+ def test_deducts_language_from_resource_folder
361
+ language = %w(en de fr).sample
362
+ assert_equal language, @formatter.determine_language_given_path("locale/#{language}")
363
+ end
364
+
365
+ def test_deducts_language_and_region_from_resource_folder
366
+ assert_equal 'de-AT', @formatter.determine_language_given_path("locale/de-AT")
367
+ end
303
368
  end