twine 1.0.2 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -24,7 +24,7 @@ module Twine
24
24
  # %@ -> %s
25
25
  value = convert_twine_string_placeholder(input)
26
26
 
27
- number_of_placeholders = number_of_twine_placeholders(input)
27
+ number_of_placeholders = number_of_twine_placeholders(value)
28
28
 
29
29
  return value if number_of_placeholders == 0
30
30
 
@@ -72,5 +72,11 @@ module Twine
72
72
  def convert_placeholders_from_flash_to_twine(input)
73
73
  input.gsub /\{\d+\}/, '%@'
74
74
  end
75
+
76
+ # Python supports placeholders in the form of `%(amount)03d`
77
+ # see https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting
78
+ def contains_python_specific_placeholder(input)
79
+ /%\([a-zA-Z0-9_-]+\)#{PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH}#{PLACEHOLDER_TYPES}/.match(input) != nil
80
+ end
75
81
  end
76
82
  end
@@ -1,3 +1,4 @@
1
+ require 'date'
1
2
  require 'safe_yaml/load'
2
3
 
3
4
  SafeYAML::OPTIONS[:suppress_warnings] = true
@@ -56,7 +57,7 @@ module Twine
56
57
  end
57
58
 
58
59
  def join_path *paths
59
- File.expand_path File.join *paths
60
+ File.expand_path File.join(*paths)
60
61
  end
61
62
  end
62
63
  end
@@ -5,6 +5,14 @@ Twine::Plugin.new # Initialize plugins first in Runner.
5
5
 
6
6
  module Twine
7
7
  class Runner
8
+ class NullOutput
9
+ def puts(message)
10
+ end
11
+ def string
12
+ ""
13
+ end
14
+ end
15
+
8
16
  def self.run(args)
9
17
  options = CLI.parse(args)
10
18
 
@@ -35,6 +43,9 @@ module Twine
35
43
  def initialize(options = {}, twine_file = TwineFile.new)
36
44
  @options = options
37
45
  @twine_file = twine_file
46
+ if @options[:quite]
47
+ Twine::stdout = NullOutput.new
48
+ end
38
49
  end
39
50
 
40
51
  def write_twine_data(path)
@@ -76,7 +87,7 @@ module Twine
76
87
  end
77
88
 
78
89
  unless formatter
79
- raise Twine::Error.new "Could not determine format given the contents of #{@options[:output_path]}"
90
+ raise Twine::Error.new "Could not determine format given the contents of #{@options[:output_path]}. Try using `--format`."
80
91
  end
81
92
 
82
93
  file_name = @options[:file_name] || formatter.default_file_name
@@ -90,7 +101,7 @@ module Twine
90
101
 
91
102
  output = formatter.format_file(lang)
92
103
  unless output
93
- Twine::stderr.puts "Skipping file at path #{file_path} since it would not contain any translations."
104
+ Twine::stdout.puts "Skipping file at path #{file_path} since it would not contain any translations."
94
105
  next
95
106
  end
96
107
 
@@ -112,7 +123,7 @@ module Twine
112
123
  file_path = File.join(output_path, file_name)
113
124
  output = formatter.format_file(lang)
114
125
  unless output
115
- Twine::stderr.puts "Skipping file at path #{file_path} since it would not contain any translations."
126
+ Twine::stdout.puts "Skipping file at path #{file_path} since it would not contain any translations."
116
127
  next
117
128
  end
118
129
 
@@ -148,7 +159,7 @@ module Twine
148
159
 
149
160
  output = formatter.format_file(lang)
150
161
  unless output
151
- Twine::stderr.puts "Skipping file #{file_name} since it would not contain any translations."
162
+ Twine::stdout.puts "Skipping file #{file_name} since it would not contain any translations."
152
163
  next
153
164
  end
154
165
 
@@ -197,6 +208,7 @@ module Twine
197
208
  raise Twine::Error.new("File does not exist: #{@options[:input_path]}")
198
209
  end
199
210
 
211
+ error_encountered = false
200
212
  Dir.mktmpdir do |temp_dir|
201
213
  Zip::File.open(@options[:input_path]) do |zipfile|
202
214
  zipfile.each do |entry|
@@ -209,6 +221,7 @@ module Twine
209
221
  read_localization_file(real_path)
210
222
  rescue Twine::Error => e
211
223
  Twine::stderr.puts "#{e.message}"
224
+ error_encountered = true
212
225
  end
213
226
  end
214
227
  end
@@ -216,6 +229,10 @@ module Twine
216
229
 
217
230
  output_path = @options[:output_path] || @options[:twine_file]
218
231
  write_twine_data(output_path)
232
+
233
+ if error_encountered
234
+ raise Twine::Error.new("At least one file could not be consumed")
235
+ end
219
236
  end
220
237
 
221
238
  def validate_twine_file
@@ -224,6 +241,7 @@ module Twine
224
241
  duplicate_keys = Set.new
225
242
  keys_without_tags = Set.new
226
243
  invalid_keys = Set.new
244
+ keys_with_python_only_placeholders = Set.new
227
245
  valid_key_regex = /^[A-Za-z0-9_]+$/
228
246
 
229
247
  @twine_file.sections.each do |section|
@@ -236,6 +254,8 @@ module Twine
236
254
  keys_without_tags.add(definition.key) if definition.tags == nil or definition.tags.length == 0
237
255
 
238
256
  invalid_keys << definition.key unless definition.key =~ valid_key_regex
257
+
258
+ keys_with_python_only_placeholders << definition.key if definition.translations.values.any? { |v| Placeholders.contains_python_specific_placeholder(v) }
239
259
  end
240
260
  end
241
261
 
@@ -258,6 +278,10 @@ module Twine
258
278
  errors << "Found key(s) with invalid characters:\n#{join_keys.call(invalid_keys)}"
259
279
  end
260
280
 
281
+ unless keys_with_python_only_placeholders.empty?
282
+ errors << "Found key(s) with placeholders that are only supported by Python:\n#{join_keys.call(keys_with_python_only_placeholders)}"
283
+ end
284
+
261
285
  raise Twine::Error.new errors.join("\n\n") unless errors.empty?
262
286
 
263
287
  Twine::stdout.puts "#{@options[:twine_file]} is valid."
@@ -277,21 +301,16 @@ module Twine
277
301
  end
278
302
  end
279
303
 
280
- def determine_language_given_path(path)
281
- code = File.basename(path, File.extname(path))
282
- return code if @twine_file.language_codes.include? code
283
- end
284
-
285
304
  def formatter_for_format(format)
286
305
  find_formatter { |f| f.format_name == format }
287
306
  end
288
307
 
289
308
  def find_formatter(&block)
290
- formatters = Formatters.formatters.select &block
309
+ formatters = Formatters.formatters.select(&block)
291
310
  if formatters.empty?
292
311
  return nil
293
312
  elsif formatters.size > 1
294
- raise Twine::Error.new("Unable to determine format. Candidates are: #{formatters.map(&:format_name).join(', ')}. Please specify the format you want using '--format'")
313
+ raise Twine::Error.new("Unable to determine format. Candidates are: #{formatters.map(&:format_name).join(', ')}. Please specify the format you want using `--format`")
295
314
  end
296
315
  formatter = formatters.first
297
316
  formatter.twine_file = @twine_file
@@ -322,12 +341,12 @@ module Twine
322
341
  end
323
342
 
324
343
  unless formatter
325
- raise Twine::Error.new "Unable to determine format of #{path}"
344
+ raise Twine::Error.new "Unable to determine format of #{path}. Try using `--format`."
326
345
  end
327
346
 
328
- lang = lang || determine_language_given_path(path) || formatter.determine_language_given_path(path)
347
+ lang = lang || formatter.determine_language_given_path(path)
329
348
  unless lang
330
- raise Twine::Error.new "Unable to determine language for #{path}"
349
+ raise Twine::Error.new "Unable to determine language for #{path}. Try using `--lang`."
331
350
  end
332
351
 
333
352
  @twine_file.language_codes << lang unless @twine_file.language_codes.include? lang
@@ -190,7 +190,7 @@ module Twine
190
190
 
191
191
  value = write_value(definition, dev_lang, f)
192
192
  if !value && !definition.reference_key
193
- puts "Warning: #{definition.key} does not exist in developer language '#{dev_lang}'"
193
+ Twine::stdout.puts "WARNING: #{definition.key} does not exist in developer language '#{dev_lang}'"
194
194
  end
195
195
 
196
196
  if definition.reference_key
@@ -1,3 +1,3 @@
1
1
  module Twine
2
- VERSION = '1.0.2'
2
+ VERSION = '1.1'
3
3
  end
@@ -1,12 +1,11 @@
1
- ##
2
- # Django Strings File
3
- # Generated by Twine <%= Twine::VERSION %>
4
- # Language: en
1
+ # Django Strings File
2
+ # Generated by Twine <%= Twine::VERSION %>
3
+ # Language: en
5
4
  msgid ""
6
5
  msgstr ""
7
6
  "Content-Type: text/plain; charset=UTF-8\n"
8
7
 
9
- #--------- Section 1 ---------#
8
+ # --------- Section 1 --------- #
10
9
 
11
10
  #. comment key1
12
11
  # base translation: "value1-english"
@@ -18,7 +17,7 @@ msgid "key2"
18
17
  msgstr "value2-english"
19
18
 
20
19
 
21
- #--------- Section 2 ---------#
20
+ # --------- Section 2 --------- #
22
21
 
23
22
  # base translation: "value3-english"
24
23
  msgid "key3"
@@ -1,7 +1,7 @@
1
1
  msgid ""
2
2
  msgstr ""
3
- "Language: en\n"
4
- "X-Generator: Twine <%= Twine::VERSION %>\n"
3
+ "Language: en"
4
+ "X-Generator: Twine <%= Twine::VERSION %>"
5
5
 
6
6
 
7
7
  # SECTION: Section 1
@@ -0,0 +1,10 @@
1
+ msgid ""
2
+ msgstr ""
3
+ "Language: en"
4
+ "X-Generator: Twine <%= Twine::VERSION %>"
5
+
6
+
7
+ # SECTION: Section
8
+ msgctxt "key"
9
+ msgid "foo \"bar\" baz"
10
+ msgstr "foo \"bar\" baz"
@@ -48,6 +48,13 @@ class CLITest < TwineTest
48
48
  assert_equal 'UTF16', @options[:encoding]
49
49
  end
50
50
 
51
+ def assert_option_escape_all_tags
52
+ parse_with "--escape-all-tags"
53
+ assert @options[:escape_all_tags]
54
+ parse_with "--no-escape-all-tags"
55
+ refute @options[:escape_all_tags]
56
+ end
57
+
51
58
  def assert_option_format
52
59
  random_format = Twine::Formatters.formatters.sample.format_name.downcase
53
60
  parse_with "--format #{random_format}"
@@ -82,6 +89,13 @@ class CLITest < TwineTest
82
89
  assert_equal @output_path, @options[:output_path]
83
90
  end
84
91
 
92
+ def assert_option_quiet
93
+ parse_with '--quiet'
94
+ assert @options[:quiet]
95
+ parse_with '--no-quiet'
96
+ refute @options[:quiet]
97
+ end
98
+
85
99
  def assert_option_tags
86
100
  # single tag
87
101
  random_tag = "tag#{rand(100)}"
@@ -156,7 +170,7 @@ class TestGenerateLocalizationFileCLI < CLITest
156
170
 
157
171
  def test_missing_argument
158
172
  assert_raises Twine::Error do
159
- parse "generate-localization-file #{@twine_file}"
173
+ parse "generate-localization-file #{@twine_file_path}"
160
174
  end
161
175
  end
162
176
 
@@ -170,10 +184,12 @@ class TestGenerateLocalizationFileCLI < CLITest
170
184
  assert_help
171
185
  assert_option_developer_language
172
186
  assert_option_encoding
187
+ assert_option_escape_all_tags
173
188
  assert_option_format
174
189
  assert_option_include
175
190
  assert_option_single_language
176
191
  assert_raises(Twine::Error) { assert_option_multiple_languages }
192
+ assert_option_quiet
177
193
  assert_option_tags
178
194
  assert_option_untagged
179
195
  assert_option_validate
@@ -209,8 +225,10 @@ class TestGenerateAllLocalizationFilesCLI < CLITest
209
225
  assert_help
210
226
  assert_option_developer_language
211
227
  assert_option_encoding
228
+ assert_option_escape_all_tags
212
229
  assert_option_format
213
230
  assert_option_include
231
+ assert_option_quiet
214
232
  assert_option_tags
215
233
  assert_option_untagged
216
234
  assert_option_validate
@@ -259,7 +277,9 @@ class TestGenerateLocalizationArchiveCLI < CLITest
259
277
  assert_help
260
278
  assert_option_developer_language
261
279
  assert_option_encoding
280
+ assert_option_escape_all_tags
262
281
  assert_option_include
282
+ assert_option_quiet
263
283
  assert_option_tags
264
284
  assert_option_untagged
265
285
  assert_option_validate
@@ -278,7 +298,7 @@ class TestGenerateLocalizationArchiveCLI < CLITest
278
298
 
279
299
  def test_deprecated_command_prints_warning
280
300
  parse "generate-loc-drop #{@twine_file_path} #{@output_path} --format apple"
281
- assert_match "WARNING: Twine commands names have changed.", Twine::stderr.string
301
+ assert_match "WARNING: Twine commands names have changed.", Twine::stdout.string
282
302
  end
283
303
  end
284
304
 
@@ -317,6 +337,7 @@ class TestConsumeLocalizationFileCLI < CLITest
317
337
  assert_option_single_language
318
338
  assert_raises(Twine::Error) { assert_option_multiple_languages }
319
339
  assert_option_output_path
340
+ assert_option_quiet
320
341
  assert_option_tags
321
342
  end
322
343
  end
@@ -354,6 +375,7 @@ class TestConsumeAllLocalizationFilesCLI < CLITest
354
375
  assert_option_encoding
355
376
  assert_option_format
356
377
  assert_option_output_path
378
+ assert_option_quiet
357
379
  assert_option_tags
358
380
  end
359
381
  end
@@ -391,6 +413,7 @@ class TestConsumeLocalizationArchiveCLI < CLITest
391
413
  assert_option_encoding
392
414
  assert_option_format
393
415
  assert_option_output_path
416
+ assert_option_quiet
394
417
  assert_option_tags
395
418
  end
396
419
 
@@ -401,7 +424,7 @@ class TestConsumeLocalizationArchiveCLI < CLITest
401
424
 
402
425
  def test_deprecated_command_prints_warning
403
426
  parse "consume-loc-drop #{@twine_file_path} #{@input_path}"
404
- assert_match "WARNING: Twine commands names have changed.", Twine::stderr.string
427
+ assert_match "WARNING: Twine commands names have changed.", Twine::stdout.string
405
428
  end
406
429
  end
407
430
 
@@ -432,6 +455,7 @@ class TestValidateTwineFileCLI < CLITest
432
455
  def test_options
433
456
  assert_help
434
457
  assert_option_developer_language
458
+ assert_option_quiet
435
459
  end
436
460
 
437
461
  def test_option_pedantic
@@ -4,24 +4,30 @@ class TestConsumeLocalizationArchive < CommandTest
4
4
  def setup
5
5
  super
6
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
7
  @twine_file = build_twine_file 'en', 'es' do
13
8
  add_section 'Section' do
14
9
  add_definition key1: 'value1'
15
10
  end
16
11
  end
12
+ end
13
+
14
+ def new_runner(options = {})
15
+ options[:input_path] = fixture_path 'consume_localization_archive.zip'
16
+ options[:output_path] = @output_path
17
17
 
18
- @runner = Twine::Runner.new(options, @twine_file)
18
+ Twine::Runner.new(options, @twine_file)
19
19
  end
20
20
 
21
21
  def test_consumes_zip_file
22
- @runner.consume_localization_archive
22
+ new_runner(format: 'android').consume_localization_archive
23
23
 
24
24
  assert @twine_file.definitions_by_key['key1'].translations['en'], 'value1-english'
25
25
  assert @twine_file.definitions_by_key['key1'].translations['es'], 'value1-spanish'
26
26
  end
27
+
28
+ def test_raises_error_if_format_ambiguous
29
+ assert_raises Twine::Error do
30
+ new_runner.consume_localization_archive
31
+ end
32
+ end
27
33
  end
@@ -47,26 +47,87 @@ class TestAndroidFormatter < FormatterTest
47
47
  'a "good" way' => 'a \"good\" way',
48
48
 
49
49
  '<b>bold</b>' => '<b>bold</b>',
50
+ '<em>bold</em>' => '<em>bold</em>',
51
+
50
52
  '<i>italic</i>' => '<i>italic</i>',
53
+ '<cite>italic</cite>' => '<cite>italic</cite>',
54
+ '<dfn>italic</dfn>' => '<dfn>italic</dfn>',
55
+
56
+ '<big>larger</big>' => '<big>larger</big>',
57
+ '<small>smaller</small>' => '<small>smaller</small>',
58
+
59
+ '<font color="#45C1D0">F</font>' => '<font color="#45C1D0">F</font>',
60
+
61
+ '<tt>monospaced</tt>' => '<tt>monospaced</tt>',
62
+
63
+ '<s>strike</s>' => '<s>strike</s>',
64
+ '<strike>strike</strike>' => '<strike>strike</strike>',
65
+ '<del>strike</del>' => '<del>strike</del>',
66
+
51
67
  '<u>underline</u>' => '<u>underline</u>',
52
68
 
69
+ '<super>superscript</super>'=> '<super>superscript</super>',
70
+
71
+ '<sub>subscript</sub>' => '<sub>subscript</sub>',
72
+
73
+ '<ul>bullet point</ul>' => '<ul>bullet point</ul>',
74
+ '<li>bullet point</li>' => '<li>bullet point</li>',
75
+
76
+ '<br>line break' => '<br>line break',
77
+
78
+ '<div>division</div>' => '<div>division</div>',
79
+
80
+ '<span style="color:#45C1D0">inline</span>' => '<span style="color:#45C1D0">inline</span>',
81
+
82
+ '<p>para</p>' => '<p>para</p>',
83
+ '<p dir="ltr">para</p>' => '<p dir="ltr">para</p>',
84
+
53
85
  '<b>%@</b>' => '&lt;b>%s&lt;/b>',
86
+ '<em>%@</em>' => '&lt;em>%s&lt;/em>',
87
+
54
88
  '<i>%@</i>' => '&lt;i>%s&lt;/i>',
89
+ '<cite>%@</cite>' => '&lt;cite>%s&lt;/cite>',
90
+ '<dfn>%@</dfn>' => '&lt;dfn>%s&lt;/dfn>',
91
+
92
+ '<big>%@</big>' => '&lt;big>%s&lt;/big>',
93
+ '<small>%@</small>' => '&lt;small>%s&lt;/small>',
94
+
95
+ '<font color="#45C1D0>%@</font>' => '&lt;font color="#45C1D0>%s&lt;/font>',
96
+
97
+ '<tt>%@</tt>' => '&lt;tt>%s&lt;/tt>',
98
+
99
+ '<s>%@</s>' => '&lt;s>%s&lt;/s>',
100
+ '<strike>%@</strike>' => '&lt;strike>%s&lt;/strike>',
101
+ '<del>%@</del>' => '&lt;del>%s&lt;/del>',
102
+
55
103
  '<u>%@</u>' => '&lt;u>%s&lt;/u>',
56
104
 
57
- '<span>inline</span>' => '&lt;span>inline&lt;/span>',
58
- '<p>paragraph</p>' => '&lt;p>paragraph&lt;/p>',
105
+ '<super>%@</super>' => '&lt;super>%s&lt;/super>',
106
+
107
+ '<sub>%@</sub>' => '&lt;sub>%s&lt;/sub>',
108
+
109
+ '<ul>%@</ul>' => '&lt;ul>%s&lt;/ul>',
110
+ '<li>%@</li>' => '&lt;li>%s&lt;/li>',
111
+
112
+ '<br>%@' => '&lt;br>%s',
113
+
114
+ '<div>%@</div>' => '&lt;div>%s&lt;/div>',
115
+
116
+ '<span style="color:#45C1D0">%@</span>' => '&lt;span style="color:#45C1D0">%s&lt;/span>',
117
+
118
+ '<p>%@</p>' => '&lt;p>%s&lt;/p>',
119
+ '<p dir="ltr">%@</p>' => '&lt;p dir="ltr">%s&lt;/p>',
59
120
 
60
121
  '<a href="target">link</a>' => '<a href="target">link</a>',
61
122
  '<a href="target">"link"</a>' => '<a href="target">\"link\"</a>',
62
123
  '<a href="target"></a>"out"' => '<a href="target"></a>\"out\"',
63
124
  '<a href="http://url.com?param=1&param2=3&param3=%20">link</a>' => '<a href="http://url.com?param=1&param2=3&param3=%20">link</a>',
64
125
 
65
- '<p>escaped</p><![CDATA[]]>' => '&lt;p>escaped&lt;/p><![CDATA[]]>',
66
- '<![CDATA[]]><p>escaped</p>' => '<![CDATA[]]>&lt;p>escaped&lt;/p>',
67
- '<![CDATA[<p>unescaped</p>]]>' => '<![CDATA[<p>unescaped</p>]]>',
68
- '<![CDATA[<p>unescaped with %@</p>]]>' => '<![CDATA[<p>unescaped with %s</p>]]>',
69
- '<![CDATA[]]><![CDATA[<p>unescaped</p>]]>' => '<![CDATA[]]><![CDATA[<p>unescaped</p>]]>',
126
+ '<q>escaped</q><![CDATA[]]>' => '&lt;q>escaped&lt;/q><![CDATA[]]>',
127
+ '<![CDATA[]]><q>escaped</q>' => '<![CDATA[]]>&lt;q>escaped&lt;/q>',
128
+ '<![CDATA[<q>unescaped</q>]]>' => '<![CDATA[<q>unescaped</q>]]>',
129
+ '<![CDATA[<q>unescaped with %@</q>]]>' => '<![CDATA[<q>unescaped with %s</q>]]>',
130
+ '<![CDATA[]]><![CDATA[<q>unescaped</q>]]>' => '<![CDATA[]]><![CDATA[<q>unescaped</q>]]>',
70
131
 
71
132
  '<![CDATA[&]]>' => '<![CDATA[&]]>',
72
133
  '<![CDATA[\']]>' => '<![CDATA[\']]>',
@@ -77,6 +138,11 @@ class TestAndroidFormatter < FormatterTest
77
138
  '<xliff:g id="42">untouched</xliff:g>' => '<xliff:g id="42">untouched</xliff:g>',
78
139
  '<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>'
79
140
  }
141
+ @escape_all_test_values = {
142
+ '<b>bold</b>' => '&lt;b>bold&lt;/b>',
143
+ '<i>italic</i>' => '&lt;i>italic&lt;/i>',
144
+ '<u>underline</u>' => '&lt;u>underline&lt;/u>'
145
+ }
80
146
  end
81
147
 
82
148
  def test_read_format
@@ -101,6 +167,36 @@ class TestAndroidFormatter < FormatterTest
101
167
  assert_equal 'This is\n a string', @empty_twine_file.definitions_by_key["foo"].translations['en']
102
168
  end
103
169
 
170
+ def test_read_html_tags
171
+ content = <<-EOCONTENT
172
+ <?xml version="1.0" encoding="utf-8"?>
173
+ <resources>
174
+ <string name="foo">Hello, <b>BOLD</b></string>
175
+ </resources>
176
+ EOCONTENT
177
+
178
+ io = StringIO.new(content)
179
+
180
+ @formatter.read io, 'en'
181
+
182
+ assert_equal 'Hello, <b>BOLD</b>', @empty_twine_file.definitions_by_key["foo"].translations['en']
183
+ end
184
+
185
+ def test_double_quotes_are_not_modified
186
+ content = <<-EOCONTENT
187
+ <?xml version="1.0" encoding="utf-8"?>
188
+ <resources>
189
+ <string name="foo">Hello, <a href="http://www.foo.com">BOLD</a></string>
190
+ </resources>
191
+ EOCONTENT
192
+
193
+ io = StringIO.new(content)
194
+
195
+ @formatter.read io, 'en'
196
+
197
+ assert_equal 'Hello, <a href="http://www.foo.com">BOLD</a>', @empty_twine_file.definitions_by_key["foo"].translations['en']
198
+ end
199
+
104
200
  def test_set_translation_converts_leading_spaces
105
201
  @formatter.set_translation_for_key 'key1', 'en', "\u0020value"
106
202
  assert_equal ' value', @empty_twine_file.definitions_by_key['key1'].translations['en']
@@ -126,6 +222,11 @@ class TestAndroidFormatter < FormatterTest
126
222
  @formatter.set_translation_for_key 'key1', 'en', input
127
223
  assert_equal expected, @empty_twine_file.definitions_by_key['key1'].translations['en']
128
224
  end
225
+
226
+ @escape_all_test_values.each do |expected, input|
227
+ @formatter.set_translation_for_key 'key1', 'en', input
228
+ assert_equal expected, @empty_twine_file.definitions_by_key['key1'].translations['en']
229
+ end
129
230
  end
130
231
 
131
232
  def test_format_file
@@ -154,6 +255,11 @@ class TestAndroidFormatter < FormatterTest
154
255
  @escape_test_values.each do |input, expected|
155
256
  assert_equal expected, @formatter.format_value(input)
156
257
  end
258
+
259
+ @formatter.options.merge!({ escape_all_tags: true })
260
+ @escape_all_test_values.each do |input, expected|
261
+ assert_equal expected, @formatter.format_value(input)
262
+ end
157
263
  end
158
264
 
159
265
  def test_format_value_escapes_non_resource_identifier_at_signs
@@ -165,8 +271,24 @@ class TestAndroidFormatter < FormatterTest
165
271
  assert_equal identifier, @formatter.format_value(identifier)
166
272
  end
167
273
 
274
+ def test_deducts_language_from_filename
275
+ language = KNOWN_LANGUAGES.sample
276
+ assert_equal language, @formatter.determine_language_given_path("#{language}.xml")
277
+ end
278
+
279
+ def test_recognize_every_twine_language_from_filename
280
+ twine_file = build_twine_file "not-a-lang-code" do
281
+ add_section "Section" do
282
+ add_definition key: "value"
283
+ end
284
+ end
285
+
286
+ @formatter.twine_file = twine_file
287
+ assert_equal "not-a-lang-code", @formatter.determine_language_given_path("not-a-lang-code.xml")
288
+ end
289
+
168
290
  def test_deducts_language_from_resource_folder
169
- language = %w(en de fr).sample
291
+ language = KNOWN_LANGUAGES.sample
170
292
  assert_equal language, @formatter.determine_language_given_path("res/values-#{language}")
171
293
  end
172
294
 
@@ -185,6 +307,13 @@ class TestAndroidFormatter < FormatterTest
185
307
  def test_output_path_with_region
186
308
  assert_equal 'values-en-rGB', @formatter.output_path_for_language('en-GB')
187
309
  end
310
+
311
+ def test_output_path_respects_default_lang
312
+ @formatter.twine_file.language_codes.concat KNOWN_LANGUAGES
313
+ non_default_language = KNOWN_LANGUAGES[1..-1].sample
314
+ assert_equal 'values', @formatter.output_path_for_language(KNOWN_LANGUAGES[0])
315
+ assert_equal "values-#{non_default_language}", @formatter.output_path_for_language(non_default_language)
316
+ end
188
317
  end
189
318
 
190
319
  class TestAppleFormatter < FormatterTest
@@ -198,6 +327,22 @@ class TestAppleFormatter < FormatterTest
198
327
  assert_file_contents_read_correctly
199
328
  end
200
329
 
330
+ def test_deducts_language_from_filename
331
+ language = KNOWN_LANGUAGES.sample
332
+ assert_equal language, @formatter.determine_language_given_path("#{language}.strings")
333
+ end
334
+
335
+ def test_recognize_every_twine_language_from_filename
336
+ twine_file = build_twine_file "not-a-lang-code" do
337
+ add_section "Section" do
338
+ add_definition key: "value"
339
+ end
340
+ end
341
+
342
+ @formatter.twine_file = twine_file
343
+ assert_equal "not-a-lang-code", @formatter.determine_language_given_path("not-a-lang-code.strings")
344
+ end
345
+
201
346
  def test_deducts_language_from_resource_folder
202
347
  language = %w(en de fr).sample
203
348
  assert_equal language, @formatter.determine_language_given_path("#{language}.lproj/Localizable.strings")
@@ -302,6 +447,21 @@ class TestJQueryFormatter < FormatterTest
302
447
  def test_format_value_with_newline
303
448
  assert_equal "value\nwith\nline\nbreaks", @formatter.format_value("value\nwith\nline\nbreaks")
304
449
  end
450
+
451
+ def test_deducts_language_from_filename
452
+ language = KNOWN_LANGUAGES.sample
453
+ assert_equal language, @formatter.determine_language_given_path("#{language}.json")
454
+ end
455
+
456
+ def test_deducts_language_from_extended_filename
457
+ language = KNOWN_LANGUAGES.sample
458
+ assert_equal language, @formatter.determine_language_given_path("something-#{language}.json")
459
+ end
460
+
461
+ def test_deducts_language_from_path
462
+ language = %w(en-GB de fr).sample
463
+ assert_equal language, @formatter.determine_language_given_path("/output/#{language}/#{@formatter.default_file_name}")
464
+ end
305
465
  end
306
466
 
307
467
  class TestGettextFormatter < FormatterTest
@@ -331,6 +491,21 @@ class TestGettextFormatter < FormatterTest
331
491
  language = "en-GB"
332
492
  assert_equal language, @formatter.determine_language_given_path("#{language}.po")
333
493
  end
494
+
495
+ def test_deducts_language_from_path
496
+ language = %w(en-GB de fr).sample
497
+ assert_equal language, @formatter.determine_language_given_path("/output/#{language}/#{@formatter.default_file_name}")
498
+ end
499
+
500
+ def test_quoted_strings
501
+ formatter = Twine::Formatters::Gettext.new
502
+ formatter.twine_file = build_twine_file "not-a-lang-code" do
503
+ add_section "Section" do
504
+ add_definition key: "foo \"bar\" baz"
505
+ end
506
+ end
507
+ assert_equal content('formatter_gettext_quotes.po'), formatter.format_file('en')
508
+ end
334
509
  end
335
510
 
336
511
  class TestTizenFormatter < FormatterTest
@@ -351,7 +526,6 @@ class TestTizenFormatter < FormatterTest
351
526
  formatter.twine_file = @twine_file
352
527
  assert_equal content('formatter_tizen.xml'), formatter.format_file('en')
353
528
  end
354
-
355
529
  end
356
530
 
357
531
  class TestDjangoFormatter < FormatterTest
@@ -375,6 +549,24 @@ class TestDjangoFormatter < FormatterTest
375
549
  language = "en-GB"
376
550
  assert_equal language, @formatter.determine_language_given_path("#{language}.po")
377
551
  end
552
+
553
+ def test_deducts_language_from_path
554
+ language = %w(en-GB de fr).sample
555
+ assert_equal language, @formatter.determine_language_given_path("/output/#{language}/#{@formatter.default_file_name}")
556
+ end
557
+
558
+ def test_ignores_commented_out_strings
559
+ content = <<-EOCONTENT
560
+ #~ msgid "foo"
561
+ #~ msgstr "This should be ignored"
562
+ EOCONTENT
563
+
564
+ io = StringIO.new(content)
565
+
566
+ @formatter.read io, 'en'
567
+
568
+ assert_nil @empty_twine_file.definitions_by_key["foo"]
569
+ end
378
570
  end
379
571
 
380
572
  class TestFlashFormatter < FormatterTest
@@ -405,10 +597,10 @@ class TestFlashFormatter < FormatterTest
405
597
 
406
598
  def test_deducts_language_from_resource_folder
407
599
  language = %w(en de fr).sample
408
- assert_equal language, @formatter.determine_language_given_path("locale/#{language}")
600
+ assert_equal language, @formatter.determine_language_given_path("locale/#{language}/#{@formatter.default_file_name}")
409
601
  end
410
602
 
411
603
  def test_deducts_language_and_region_from_resource_folder
412
- assert_equal 'de-AT', @formatter.determine_language_given_path("locale/de-AT")
604
+ assert_equal 'de-AT', @formatter.determine_language_given_path("locale/de-AT/#{@formatter.default_file_name}")
413
605
  end
414
606
  end