twine 1.0.2 → 1.1

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.
@@ -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