twine 0.10.1 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6dcdb67ead79faa41f6f4849e7ae0e187ea98b92
4
- data.tar.gz: 11ca78a313a5d092dc107b2100abb95f3ac4cc5b
3
+ metadata.gz: 107f74e29483071d8a72edba115daa2dbea7d1c9
4
+ data.tar.gz: 4b81eccc076aa40a7649d6779ef4ccac51fbf0dc
5
5
  SHA512:
6
- metadata.gz: 0e097325df41ef943e72ecc58e663c5b7261a46103fd7d27f4e187c1f2c1bbdd03e49fb11dd6d2b5534c3bfde939cf28f4725a458c444b935ba519f0aecf219c
7
- data.tar.gz: 71d7c5d2e9c61b7038f69d9780f0f1137314a44be5849152fd39baddfa0312799cc6a887ce13f9b66ba13dca470d398b4c3b6311be80e78ce5c527826583cd3f
6
+ metadata.gz: 3173909e712d67f0e63e2ce0dadf3c6bc39d287208d159f798ee29ed220ab87b69cc49692b0472a9961589eda36ce0f31f2d402aab67c736ab4458d8beddcf4e
7
+ data.tar.gz: a2d07cfa6b89a6650aed7ee25a3cb2944a83c63b8ac1b5918a64f2804e4485e91b1d26ab329e381a3f4892b38dd76902639dab73f9ecb8445c512fd74477e8f3
data/README.md CHANGED
@@ -1,26 +1,15 @@
1
1
  # Twine
2
2
 
3
+ [![Continuous Integration by CircleCI](https://circleci.com/gh/teespring/twine.svg?style=shield)](https://circleci.com/gh/teespring/twine)
4
+
3
5
  Twine is a command line tool for managing your strings and their translations. These are all stored in a master text file and then Twine uses this file to import and export localization files in a variety of types, including iOS and Mac OS X `.strings` files, Android `.xml` files, gettext `.po` files, and [jquery-localize][jquerylocalize] `.json` files. This allows individuals and companies to easily share translations across multiple projects, as well as export localization files in any format the user wants.
4
6
 
5
7
  ## Install
6
8
 
7
- ### As a Gem
8
-
9
9
  Twine is most easily installed as a Gem.
10
10
 
11
11
  $ gem install twine
12
12
 
13
- ### From Source
14
-
15
- You can also run Twine directly from source. However, it requires [rubyzip][rubyzip] in order to create and read standard zip files.
16
-
17
- $ gem install rubyzip
18
- $ git clone git://github.com/mobiata/twine.git
19
- $ cd twine
20
- $ ./twine --help
21
-
22
- Make sure you run the `twine` executable at the root of the project as it properly sets up your Ruby library path. The `bin/twine` executable does not.
23
-
24
13
  ## Twine File Format
25
14
 
26
15
  Twine stores everything in a single file, the Twine data file. The format of this file is a slight variant of the [Git][git] config file format, which itself is based on the old [Windows INI file][INI] format. The entire file is broken up into sections, which are created by placing the section name between two pairs of square brackets. Sections are optional, but they are the recommended way of grouping your definitions into smaller, more manageable chunks.
@@ -48,39 +37,39 @@ If you want a definition to inherit the values of another definition, you can us
48
37
  ### Example
49
38
 
50
39
  ```ini
51
- [[General]]
52
- [yes]
53
- en = Yes
54
- es = Sí
55
- fr = Oui
56
- ja = はい
57
- [no]
58
- en = No
59
- fr = Non
60
- ja = いいえ
61
-
62
- [[Errors]]
63
- [path_not_found_error]
64
- en = The file '%@' could not be found.
65
- tags = app1,app6
66
- comment = An error describing when a path on the filesystem could not be found.
67
- [network_unavailable_error]
68
- en = The network is currently unavailable.
69
- tags = app1
70
- comment = An error describing when the device can not connect to the internet.
71
- [dismiss_error]
72
- ref = yes
73
- en = Dismiss
74
-
75
- [[Escaping Example]]
76
- [list_item_separator]
77
- en = `, `
78
- tags = mytag
79
- comment = A string that should be placed between multiple items in a list. For example: Red, Green, Blue
80
- [grave_accent_quoted_string]
81
- en = ``%@``
82
- tags = myothertag
83
- comment = This string will evaluate to `%@`.
40
+ [[General]]
41
+ [yes]
42
+ en = Yes
43
+ es = Sí
44
+ fr = Oui
45
+ ja = はい
46
+ [no]
47
+ en = No
48
+ fr = Non
49
+ ja = いいえ
50
+
51
+ [[Errors]]
52
+ [path_not_found_error]
53
+ en = The file '%@' could not be found.
54
+ tags = app1,app6
55
+ comment = An error describing when a path on the filesystem could not be found.
56
+ [network_unavailable_error]
57
+ en = The network is currently unavailable.
58
+ tags = app1
59
+ comment = An error describing when the device can not connect to the internet.
60
+ [dismiss_error]
61
+ ref = yes
62
+ en = Dismiss
63
+
64
+ [[Escaping Example]]
65
+ [list_item_separator]
66
+ en = `, `
67
+ tags = mytag
68
+ comment = A string that should be placed between multiple items in a list. For example: Red, Green, Blue
69
+ [grave_accent_quoted_string]
70
+ en = ``%@``
71
+ tags = myothertag
72
+ comment = This string will evaluate to `%@`.
84
73
  ```
85
74
 
86
75
  ## Supported Output Formats
@@ -89,6 +78,7 @@ Twine currently supports the following output formats:
89
78
 
90
79
  * [iOS and OS X String Resources][applestrings] (format: apple)
91
80
  * [Android String Resources][androidstrings] (format: android)
81
+ * Supports [basic styling][androidstyling] with \<b\>, \<i\>, \<u\> and \<a\> links. These tags will *not* be escaped. Use [`getText()`](https://developer.android.com/reference/android/content/res/Resources.html#getText(int)) to read these strings. Also tags inside `<![CDATA[` won't be escaped. See [\#212](https://github.com/scelis/twine/issues/212) for details.
92
82
  * [Gettext PO Files][gettextpo] (format: gettext)
93
83
  * [jquery-localize Language Files][jquerylocalize] (format: jquery)
94
84
  * [Django PO Files][djangopo] (format: django)
@@ -182,11 +172,11 @@ Now, whenever you build your application, Xcode will automatically invoke Twine
182
172
  Add the following task at the top level in app/build.gradle:
183
173
  ```
184
174
  task generateLocalizations {
185
- String script = 'if hash twine 2>/dev/null; then twine generate-localization-file twine.txt ./src/main/res/values/generated_strings.xml; fi'
186
- exec {
187
- executable "sh"
188
- args '-c', script
189
- }
175
+ String script = 'if hash twine 2>/dev/null; then twine generate-localization-file twine.txt ./src/main/res/values/generated_strings.xml; fi'
176
+ exec {
177
+ executable "sh"
178
+ args '-c', script
179
+ }
190
180
  }
191
181
  ```
192
182
 
@@ -196,7 +186,6 @@ Now every time you build your app the localization files are generated from the
196
186
  ## User Interface
197
187
 
198
188
  * [Twine TextMate 2 Bundle](https://github.com/mobiata/twine.tmbundle) — This [TextMate 2](https://github.com/textmate/textmate) bundle will make it easier for you to work with Twine files. In particular, it lets you use code folding to easily collapse and expand both definitions and sections.
199
- * [twine_ui](https://github.com/Daij-Djan/twine_ui) — A user interface for Twine written by [Dominik Pich](https://github.com/Daij-Djan/). Consider using this if you would prefer to use Twine without dropping to a command line.
200
189
 
201
190
  ## Extending Twine
202
191
 
@@ -224,9 +213,10 @@ Many thanks to all of the contributors to the Twine project, including:
224
213
  [INI]: http://en.wikipedia.org/wiki/INI_file
225
214
  [applestrings]: http://developer.apple.com/documentation/Cocoa/Conceptual/LoadingResources/Strings/Strings.html
226
215
  [androidstrings]: http://developer.android.com/guide/topics/resources/string-resource.html
216
+ [androidstyling]: http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling
227
217
  [gettextpo]: http://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/PO-Files.html
228
218
  [jquerylocalize]: https://github.com/coderifous/jquery-localize
229
219
  [djangopo]: https://docs.djangoproject.com/en/dev/topics/i18n/translation/
230
220
  [tizen]: https://developer.tizen.org/documentation/articles/localization
231
221
  [flash]: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/resources/IResourceManager.html#getString()
232
- [printf]: https://en.wikipedia.org/wiki/Printf_format_string
222
+ [printf]: https://en.wikipedia.org/wiki/Printf_format_string
@@ -22,7 +22,7 @@ module Twine
22
22
  create_folders: {
23
23
  switch: ['-r', '--[no-]create-folders'],
24
24
  description: <<-DESC,
25
- This flag may be used to create output folders for all languages, if they don't exist yet.
25
+ This flag may be used to create output folders for all languages, if they don't exist yet.
26
26
  As a result all languages will be exported, not only the ones where an output folder already exists.
27
27
  DESC
28
28
  boolean: true
@@ -220,21 +220,25 @@ module Twine
220
220
  command = args.select { |a| a[0] != '-' }[0]
221
221
  args = args.reject { |a| a == command }
222
222
 
223
+ if args.any? { |a| a == '--version' }
224
+ Twine::stdout.puts "Twine version #{Twine::VERSION}"
225
+ return false
226
+ end
227
+
223
228
  mapped_command = DEPRECATED_COMMAND_MAPPINGS[command]
224
229
  if mapped_command
225
- Twine::stderr.puts "WARNING: Twine commands names have changed. `#{command}` is now `#{mapped_command}`. The old command is deprecated will soon stop working. For more information please check the documentation at https://github.com/mobiata/twine"
230
+ Twine::stderr.puts "WARNING: Twine commands names have changed. `#{command}` is now `#{mapped_command}`. The old command is deprecated and will soon stop working. For more information please check the documentation at https://github.com/mobiata/twine"
226
231
  command = mapped_command
227
232
  end
228
233
 
229
- unless COMMANDS.keys.include? command
230
- Twine::stderr.puts "Invalid command: #{command}" unless command.nil?
234
+ if command.nil?
231
235
  print_help(args)
232
- abort
236
+ return false
237
+ elsif not COMMANDS.keys.include? command
238
+ raise Twine::Error.new "Invalid command: #{command}"
233
239
  end
234
240
 
235
- options = parse_command_options(command, args)
236
-
237
- return options
241
+ parse_command_options(command, args)
238
242
  end
239
243
 
240
244
  private
@@ -352,8 +356,6 @@ module Twine
352
356
  parser.define(*option[:switch]) do |value|
353
357
  if option[:repeated]
354
358
  result[option_name] = (result[option_name] || []) << value
355
- elsif option[:boolean]
356
- result[option_name] = true
357
359
  else
358
360
  result[option_name] = value
359
361
  end
@@ -363,8 +365,8 @@ module Twine
363
365
  end
364
366
 
365
367
  parser.define('-h', '--help', 'Show this message.') do
366
- puts parser.help
367
- exit
368
+ Twine::stdout.puts parser.help
369
+ return false
368
370
  end
369
371
 
370
372
  parser.separator ''
@@ -32,7 +32,7 @@ module Twine
32
32
  # The language is defined by a two-letter ISO 639-1 language code, optionally followed by a two letter ISO 3166-1-alpha-2 region code (preceded by lowercase "r").
33
33
  # see http://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources
34
34
  match = /^values-([a-z]{2}(-r[a-z]{2})?)$/i.match(segment)
35
-
35
+
36
36
  return match[1].sub('-r', '-') if match
37
37
  end
38
38
  end
@@ -41,7 +41,7 @@ module Twine
41
41
  end
42
42
 
43
43
  def output_path_for_language(lang)
44
- "values-#{lang}"
44
+ "values-#{lang}".gsub(/-(\p{Lu})/, '-r\1')
45
45
  end
46
46
 
47
47
  def set_translation_for_key(key, lang, value)
@@ -99,12 +99,28 @@ module Twine
99
99
  "\t<string name=\"%{key}\">%{value}</string>"
100
100
  end
101
101
 
102
+ def gsub_unless(text, pattern, replacement)
103
+ text.gsub(pattern) do |match|
104
+ match_start_position = Regexp.last_match.offset(0)[0]
105
+ yield(text[0, match_start_position]) ? match : replacement
106
+ end
107
+ end
108
+
109
+ # http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling
102
110
  def escape_value(value)
103
- # escape double and single quotes, & signs and tags
104
- value = escape_quotes(value)
105
- value.gsub!("'", "\\\\'")
106
- value.gsub!(/&/, '&amp;')
107
- value.gsub!('<', '&lt;')
111
+ inside_cdata = /<\!\[CDATA\[((?!\]\]>).)*$/ # opening CDATA tag ('<![CDATA[') not followed by a closing tag (']]>')
112
+ inside_opening_anchor_tag = /<a\s?((?!>).)*$/ # anchor tag start ('<a ') not followed by a '>'
113
+
114
+ # escape double and single quotes and & signs
115
+ value = gsub_unless(value, '"', '\\"') { |substring| substring =~ inside_cdata || substring =~ inside_opening_anchor_tag }
116
+ value = gsub_unless(value, "'", "\\'") { |substring| substring =~ inside_cdata }
117
+ value = gsub_unless(value, /&/, '&amp;') { |substring| substring =~ inside_cdata || substring =~ inside_opening_anchor_tag }
118
+
119
+ # escape opening angle brackes unless it's a supported styling tag
120
+ # https://github.com/scelis/twine/issues/212
121
+ # https://stackoverflow.com/questions/3235131/#18199543
122
+ angle_bracket = /<(?!(\/?(b|u|i|a|\!\[CDATA)))/ # matches all `<` but <b>, <u>, <i>, <a> and <![CDATA
123
+ value = gsub_unless(value, angle_bracket, '&lt;') { |substring| substring =~ inside_cdata }
108
124
 
109
125
  # escape non resource identifier @ signs (http://developer.android.com/guide/topics/resources/accessing-resources.html#ResourcesFromXml)
110
126
  resource_identifier_regex = /@(?!([a-z\.]+:)?[a-z+]+\/[a-zA-Z_]+)/ # @[<package_name>:]<resource_type>/<resource_name>
@@ -22,7 +22,9 @@ module Twine
22
22
  path_arr.each do |segment|
23
23
  match = /^(.+)\.lproj$/.match(segment)
24
24
  if match
25
- if match[1] != "Base"
25
+ if match[1] == "Base"
26
+ return @options[:developer_language]
27
+ else
26
28
  return match[1]
27
29
  end
28
30
  end
@@ -16,7 +16,7 @@ module Twine
16
16
  def determine_language_given_path(path)
17
17
  path_arr = path.split(File::SEPARATOR)
18
18
  path_arr.each do |segment|
19
- match = /(..)\.po$/.match(segment)
19
+ match = /([a-z]{2}(-[A-Za-z]{2})?)\.po$/.match(segment)
20
20
  return match[1] if match
21
21
  end
22
22
 
@@ -18,10 +18,8 @@ module Twine
18
18
  def determine_language_given_path(path)
19
19
  path_arr = path.split(File::SEPARATOR)
20
20
  path_arr.each do |segment|
21
- match = /(..)\.po$/.match(segment)
22
- if match
23
- return match[1]
24
- end
21
+ match = /([a-z]{2}(-[A-Za-z]{2})?)\.po$/.match(segment)
22
+ return match[1] if match
25
23
  end
26
24
 
27
25
  return
@@ -3,7 +3,7 @@ module Twine
3
3
  extend self
4
4
 
5
5
  # Note: the ` ` (single space) flag is NOT supported
6
- PLACEHOLDER_FLAGS_WIDTH_PRECISION_LENGTH = '([-+0#])?(\d+|\*)?(\.(\d+|\*))?(hh?|ll?|L|z|j|t)?'
6
+ PLACEHOLDER_FLAGS_WIDTH_PRECISION_LENGTH = '([-+0#])?(\d+|\*)?(\.(\d+|\*))?(hh?|ll?|L|z|j|t|q)?'
7
7
  PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH = '(\d+\$)?' + PLACEHOLDER_FLAGS_WIDTH_PRECISION_LENGTH
8
8
  PLACEHOLDER_TYPES = '[diufFeEgGxXoscpaA]'
9
9
 
@@ -7,6 +7,8 @@ module Twine
7
7
  class Runner
8
8
  def self.run(args)
9
9
  options = CLI.parse(args)
10
+
11
+ return unless options
10
12
 
11
13
  twine_file = TwineFile.new
12
14
  twine_file.read options[:twine_file]
@@ -282,8 +284,13 @@ module Twine
282
284
  end
283
285
 
284
286
  def find_formatter(&block)
285
- formatter = Formatters.formatters.find &block
286
- return nil unless formatter
287
+ formatters = Formatters.formatters.select &block
288
+ if formatters.empty?
289
+ return nil
290
+ elsif formatters.size > 1
291
+ raise Twine::Error.new("Unable to determine format. Candidates are: #{formatters.map(&:format_name).join(', ')}. Please specify the format you want using '--format'")
292
+ end
293
+ formatter = formatters.first
287
294
  formatter.twine_file = @twine_file
288
295
  formatter.options = @options
289
296
  formatter
@@ -1,3 +1,3 @@
1
1
  module Twine
2
- VERSION = '0.10.1'
2
+ VERSION = '1.0'
3
3
  end
@@ -1,13 +1,13 @@
1
1
  require 'twine_test'
2
2
 
3
3
  class CommandTest < TwineTest
4
- def prepare_mock_formatter(formatter_class)
4
+ def prepare_mock_formatter(formatter_class, clear_other_formatters = true)
5
5
  twine_file = Twine::TwineFile.new
6
6
  twine_file.language_codes.concat KNOWN_LANGUAGES
7
7
 
8
8
  formatter = formatter_class.new
9
9
  formatter.twine_file = twine_file
10
- Twine::Formatters.formatters.clear
10
+ Twine::Formatters.formatters.clear if clear_other_formatters
11
11
  Twine::Formatters.formatters << formatter
12
12
  formatter
13
13
  end
@@ -17,14 +17,24 @@ class CLITest < TwineTest
17
17
  raise "you need to implement `parse_with` in your test class"
18
18
  end
19
19
 
20
+ def assert_help
21
+ parse_with '--help'
22
+ assert_equal @options, false
23
+ assert_match /Usage: twine.*Examples:/m, Twine::stdout.string
24
+ end
25
+
20
26
  def assert_option_consume_all
21
27
  parse_with '--consume-all'
22
28
  assert @options[:consume_all]
29
+ parse_with '--no-consume-all'
30
+ refute @options[:consume_all]
23
31
  end
24
32
 
25
33
  def assert_option_consume_comments
26
34
  parse_with '--consume-comments'
27
35
  assert @options[:consume_comments]
36
+ parse_with '--no-consume-comments'
37
+ refute @options[:consume_comments]
28
38
  end
29
39
 
30
40
  def assert_option_developer_language
@@ -99,11 +109,35 @@ class CLITest < TwineTest
99
109
  def assert_option_untagged
100
110
  parse_with '--untagged'
101
111
  assert @options[:untagged]
112
+ parse_with '--no-untagged'
113
+ refute @options[:untagged]
102
114
  end
103
115
 
104
116
  def assert_option_validate
105
117
  parse_with "--validate"
106
118
  assert @options[:validate]
119
+ parse_with "--no-validate"
120
+ refute @options[:validate]
121
+ end
122
+ end
123
+
124
+ class TestCLI < CLITest
125
+ def test_version
126
+ parse "--version"
127
+
128
+ assert_equal @options, false
129
+ assert_equal "Twine version #{Twine::VERSION}\n", Twine::stdout.string
130
+ end
131
+
132
+ def test_help
133
+ parse ""
134
+ assert_match 'Usage: twine', Twine::stdout.string
135
+ end
136
+
137
+ def test_invalid_command
138
+ assert_raises Twine::Error do
139
+ parse "not a command"
140
+ end
107
141
  end
108
142
  end
109
143
 
@@ -133,6 +167,7 @@ class TestGenerateLocalizationFileCLI < CLITest
133
167
  end
134
168
 
135
169
  def test_options
170
+ assert_help
136
171
  assert_option_developer_language
137
172
  assert_option_encoding
138
173
  assert_option_format
@@ -171,6 +206,7 @@ class TestGenerateAllLocalizationFilesCLI < CLITest
171
206
  end
172
207
 
173
208
  def test_options
209
+ assert_help
174
210
  assert_option_developer_language
175
211
  assert_option_encoding
176
212
  assert_option_format
@@ -183,6 +219,8 @@ class TestGenerateAllLocalizationFilesCLI < CLITest
183
219
  def test_option_create_folders
184
220
  parse_with '--create-folders'
185
221
  assert @options[:create_folders]
222
+ parse_with '--no-create-folders'
223
+ refute @options[:create_folders]
186
224
  end
187
225
 
188
226
  def test_option_file_name
@@ -218,6 +256,7 @@ class TestGenerateLocalizationArchiveCLI < CLITest
218
256
  end
219
257
 
220
258
  def test_options
259
+ assert_help
221
260
  assert_option_developer_language
222
261
  assert_option_encoding
223
262
  assert_option_include
@@ -269,6 +308,7 @@ class TestConsumeLocalizationFileCLI < CLITest
269
308
  end
270
309
 
271
310
  def test_options
311
+ assert_help
272
312
  assert_option_consume_all
273
313
  assert_option_consume_comments
274
314
  assert_option_developer_language
@@ -307,6 +347,7 @@ class TestConsumeAllLocalizationFilesCLI < CLITest
307
347
  end
308
348
 
309
349
  def test_options
350
+ assert_help
310
351
  assert_option_consume_all
311
352
  assert_option_consume_comments
312
353
  assert_option_developer_language
@@ -343,6 +384,7 @@ class TestConsumeLocalizationArchiveCLI < CLITest
343
384
  end
344
385
 
345
386
  def test_options
387
+ assert_help
346
388
  assert_option_consume_all
347
389
  assert_option_consume_comments
348
390
  assert_option_developer_language
@@ -388,11 +430,14 @@ class TestValidateTwineFileCLI < CLITest
388
430
  end
389
431
 
390
432
  def test_options
433
+ assert_help
391
434
  assert_option_developer_language
392
435
  end
393
436
 
394
437
  def test_option_pedantic
395
438
  parse "validate-twine-file #{@twine_file_path} --pedantic"
396
439
  assert @options[:pedantic]
440
+ parse "validate-twine-file #{@twine_file_path} --no-pedantic"
441
+ refute @options[:pedantic]
397
442
  end
398
443
  end
@@ -39,14 +39,33 @@ end
39
39
  class TestAndroidFormatter < FormatterTest
40
40
  def setup
41
41
  super Twine::Formatters::Android
42
-
42
+
43
43
  @escape_test_values = {
44
44
  'this & that' => 'this &amp; that',
45
45
  'this < that' => 'this &lt; that',
46
46
  "it's complicated" => "it\\'s complicated",
47
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>',
48
+
49
+ '<b>bold</b>' => '<b>bold</b>',
50
+ '<i>italic</i>' => '<i>italic</i>',
51
+ '<u>underline</u>' => '<u>underline</u>',
52
+
53
+ '<span>inline</span>' => '&lt;span>inline&lt;/span>',
54
+ '<p>paragraph</p>' => '&lt;p>paragraph&lt;/p>',
55
+
56
+ '<a href="target">link</a>' => '<a href="target">link</a>',
57
+ '<a href="target">"link"</a>' => '<a href="target">\"link\"</a>',
58
+ '<a href="target"></a>"out"' => '<a href="target"></a>\"out\"',
59
+ '<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>',
60
+
61
+ '<p>escaped</p><![CDATA[]]>' => '&lt;p>escaped&lt;/p><![CDATA[]]>',
62
+ '<![CDATA[]]><p>escaped</p>' => '<![CDATA[]]>&lt;p>escaped&lt;/p>',
63
+ '<![CDATA[<p>unescaped</p>]]>' => '<![CDATA[<p>unescaped</p>]]>',
64
+ '<![CDATA[]]><![CDATA[<p>unescaped</p>]]>' => '<![CDATA[]]><![CDATA[<p>unescaped</p>]]>',
65
+
66
+ '<![CDATA[&]]>' => '<![CDATA[&]]>',
67
+ '<![CDATA[\']]>' => '<![CDATA[\']]>',
68
+ '<![CDATA["]]>' => '<![CDATA["]]>',
50
69
 
51
70
  '<xliff:g></xliff:g>' => '<xliff:g></xliff:g>',
52
71
  '<xliff:g>untouched</xliff:g>' => '<xliff:g>untouched</xliff:g>',
@@ -157,6 +176,10 @@ class TestAndroidFormatter < FormatterTest
157
176
  def test_output_path_is_prefixed
158
177
  assert_equal 'values-en', @formatter.output_path_for_language('en')
159
178
  end
179
+
180
+ def test_output_path_with_region
181
+ assert_equal 'values-en-rGB', @formatter.output_path_for_language('en-GB')
182
+ end
160
183
  end
161
184
 
162
185
  class TestAppleFormatter < FormatterTest
@@ -170,6 +193,16 @@ class TestAppleFormatter < FormatterTest
170
193
  assert_file_contents_read_correctly
171
194
  end
172
195
 
196
+ def test_deducts_language_from_resource_folder
197
+ language = %w(en de fr).sample
198
+ assert_equal language, @formatter.determine_language_given_path("#{language}.lproj/Localizable.strings")
199
+ end
200
+
201
+ def test_deducts_base_language_from_resource_folder
202
+ @formatter.options = { consume_all: true, consume_comments: true, developer_language: 'en' }
203
+ assert_equal 'en', @formatter.determine_language_given_path('Base.lproj/Localizations.strings')
204
+ end
205
+
173
206
  def test_reads_quoted_keys
174
207
  @formatter.read StringIO.new('"key" = "value"'), 'en'
175
208
  assert_equal 'value', @empty_twine_file.definitions_by_key['key'].translations['en']
@@ -267,7 +300,6 @@ class TestJQueryFormatter < FormatterTest
267
300
  end
268
301
 
269
302
  class TestGettextFormatter < FormatterTest
270
-
271
303
  def setup
272
304
  super Twine::Formatters::Gettext
273
305
  end
@@ -290,6 +322,10 @@ class TestGettextFormatter < FormatterTest
290
322
  assert_equal content('formatter_gettext.po'), formatter.format_file('en')
291
323
  end
292
324
 
325
+ def test_deducts_language_and_region
326
+ language = "en-GB"
327
+ assert_equal language, @formatter.determine_language_given_path("#{language}.po")
328
+ end
293
329
  end
294
330
 
295
331
  class TestTizenFormatter < FormatterTest
@@ -329,6 +365,11 @@ class TestDjangoFormatter < FormatterTest
329
365
  formatter.twine_file = @twine_file
330
366
  assert_equal content('formatter_django.po'), formatter.format_file('en')
331
367
  end
368
+
369
+ def test_deducts_language_and_region
370
+ language = "en-GB"
371
+ assert_equal language, @formatter.determine_language_given_path("#{language}.po")
372
+ end
332
373
  end
333
374
 
334
375
  class TestFlashFormatter < FormatterTest
@@ -41,6 +41,24 @@ class TestGenerateLocalizationFile < CommandTest
41
41
  new_runner('fr', 'fr.po').generate_localization_file
42
42
  end
43
43
 
44
+ def test_deducts_django_format_from_output_path
45
+ prepare_mock_format_file_formatter Twine::Formatters::Django
46
+
47
+ new_runner('fr', 'fr.po').generate_localization_file
48
+ end
49
+
50
+ def test_returns_error_for_ambiguous_output_path
51
+ # both Gettext and Django use .po
52
+ gettext_formatter = prepare_mock_formatter(Twine::Formatters::Gettext)
53
+ gettext_formatter.stubs(:format_file).returns(true)
54
+ django_formatter = prepare_mock_formatter(Twine::Formatters::Django, false)
55
+ django_formatter.stubs(:format_file).returns(true)
56
+
57
+ assert_raises Twine::Error do
58
+ new_runner('fr', 'fr.po').generate_localization_file
59
+ end
60
+ end
61
+
44
62
  def test_deducts_language_from_output_path
45
63
  random_language = KNOWN_LANGUAGES.sample
46
64
  formatter = prepare_mock_formatter Twine::Formatters::Android
@@ -30,7 +30,7 @@ class TwineTest < Minitest::Test
30
30
  end
31
31
 
32
32
  def execute(command)
33
- command += " -o #{@output_path}"
33
+ command += " -o #{@output_path}"
34
34
  Twine::Runner.run(command.split(" "))
35
35
  end
36
36
 
metadata CHANGED
@@ -1,83 +1,97 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: '1.0'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Celis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-19 00:00:00.000000000 Z
11
+ date: 2017-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyzip
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ~>
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: safe_yaml
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ~>
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ~>
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ~>
46
46
  - !ruby/object:Gem::Version
47
47
  version: '10.4'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ~>
53
53
  - !ruby/object:Gem::Version
54
54
  version: '10.4'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: minitest
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ~>
60
60
  - !ruby/object:Gem::Version
61
61
  version: '5.5'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ~>
67
67
  - !ruby/object:Gem::Version
68
68
  version: '5.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest-ci
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: mocha
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
- - - "~>"
87
+ - - ~>
74
88
  - !ruby/object:Gem::Version
75
89
  version: '1.1'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
- - - "~>"
94
+ - - ~>
81
95
  - !ruby/object:Gem::Version
82
96
  version: '1.1'
83
97
  description: |2
@@ -91,13 +105,10 @@ extensions: []
91
105
  extra_rdoc_files: []
92
106
  files:
93
107
  - Gemfile
94
- - LICENSE
95
108
  - README.md
96
- - bin/twine
97
- - lib/twine.rb
109
+ - LICENSE
98
110
  - lib/twine/cli.rb
99
111
  - lib/twine/encoding.rb
100
- - lib/twine/formatters.rb
101
112
  - lib/twine/formatters/abstract.rb
102
113
  - lib/twine/formatters/android.rb
103
114
  - lib/twine/formatters/apple.rb
@@ -106,12 +117,15 @@ files:
106
117
  - lib/twine/formatters/gettext.rb
107
118
  - lib/twine/formatters/jquery.rb
108
119
  - lib/twine/formatters/tizen.rb
120
+ - lib/twine/formatters.rb
109
121
  - lib/twine/output_processor.rb
110
122
  - lib/twine/placeholders.rb
111
123
  - lib/twine/plugin.rb
112
124
  - lib/twine/runner.rb
113
125
  - lib/twine/twine_file.rb
114
126
  - lib/twine/version.rb
127
+ - lib/twine.rb
128
+ - bin/twine
115
129
  - test/command_test.rb
116
130
  - test/fixtures/consume_localization_archive.zip
117
131
  - test/fixtures/enc_utf16be.dummy
@@ -153,17 +167,17 @@ require_paths:
153
167
  - lib
154
168
  required_ruby_version: !ruby/object:Gem::Requirement
155
169
  requirements:
156
- - - ">="
170
+ - - '>='
157
171
  - !ruby/object:Gem::Version
158
172
  version: '2.0'
159
173
  required_rubygems_version: !ruby/object:Gem::Requirement
160
174
  requirements:
161
- - - ">="
175
+ - - '>='
162
176
  - !ruby/object:Gem::Version
163
177
  version: '0'
164
178
  requirements: []
165
179
  rubyforge_project:
166
- rubygems_version: 2.4.5.1
180
+ rubygems_version: 2.0.14.1
167
181
  signing_key:
168
182
  specification_version: 4
169
183
  summary: Manage strings and their translations for your iOS, Android and other projects.