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 +4 -4
- data/README.md +43 -53
- data/lib/twine/cli.rb +14 -12
- data/lib/twine/formatters/android.rb +23 -7
- data/lib/twine/formatters/apple.rb +3 -1
- data/lib/twine/formatters/django.rb +1 -1
- data/lib/twine/formatters/gettext.rb +2 -4
- data/lib/twine/placeholders.rb +1 -1
- data/lib/twine/runner.rb +9 -2
- data/lib/twine/version.rb +1 -1
- data/test/command_test.rb +2 -2
- data/test/test_cli.rb +45 -0
- data/test/test_formatters.rb +45 -4
- data/test/test_generate_localization_file.rb +18 -0
- data/test/twine_test.rb +1 -1
- metadata +33 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 107f74e29483071d8a72edba115daa2dbea7d1c9
|
4
|
+
data.tar.gz: 4b81eccc076aa40a7649d6779ef4ccac51fbf0dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
data/lib/twine/cli.rb
CHANGED
@@ -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
|
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
|
-
|
230
|
-
Twine::stderr.puts "Invalid command: #{command}" unless command.nil?
|
234
|
+
if command.nil?
|
231
235
|
print_help(args)
|
232
|
-
|
236
|
+
return false
|
237
|
+
elsif not COMMANDS.keys.include? command
|
238
|
+
raise Twine::Error.new "Invalid command: #{command}"
|
233
239
|
end
|
234
240
|
|
235
|
-
|
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
|
-
|
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
|
-
#
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
value
|
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, /&/, '&') { |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, '<') { |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>
|
@@ -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 = /(
|
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 = /(
|
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
|
data/lib/twine/placeholders.rb
CHANGED
@@ -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
|
|
data/lib/twine/runner.rb
CHANGED
@@ -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
|
-
|
286
|
-
|
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
|
data/lib/twine/version.rb
CHANGED
data/test/command_test.rb
CHANGED
@@ -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
|
data/test/test_cli.rb
CHANGED
@@ -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
|
data/test/test_formatters.rb
CHANGED
@@ -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 & that',
|
45
45
|
'this < that' => 'this < that',
|
46
46
|
"it's complicated" => "it\\'s complicated",
|
47
47
|
'a "good" way' => 'a \"good\" way',
|
48
|
-
|
49
|
-
'<
|
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>' => '<span>inline</span>',
|
54
|
+
'<p>paragraph</p>' => '<p>paragraph</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¶m2=3¶m3=%20">link</a>' => '<a href="http://url.com?param=1¶m2=3¶m3=%20">link</a>',
|
60
|
+
|
61
|
+
'<p>escaped</p><![CDATA[]]>' => '<p>escaped</p><![CDATA[]]>',
|
62
|
+
'<![CDATA[]]><p>escaped</p>' => '<![CDATA[]]><p>escaped</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
|
data/test/twine_test.rb
CHANGED
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
|
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-
|
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
|
-
-
|
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.
|
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.
|