twine 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +5 -5
- data/bin/twine +6 -1
- data/lib/twine/cli.rb +30 -40
- data/lib/twine/formatters/abstract.rb +35 -24
- data/lib/twine/formatters/android.rb +14 -16
- data/lib/twine/formatters/apple.rb +11 -15
- data/lib/twine/runner.rb +20 -18
- data/lib/twine/stringsfile.rb +43 -11
- data/lib/twine/version.rb +1 -1
- data/test/fixtures/en-1.strings +10 -0
- data/test/fixtures/fr-1.xml +9 -0
- data/test/fixtures/strings-1.txt +16 -0
- data/test/fixtures/test-output-1.txt +10 -0
- data/test/fixtures/test-output-2.txt +9 -0
- data/test/fixtures/test-output-3.txt +17 -0
- data/test/fixtures/test-output-4.txt +20 -0
- data/test/twine_test.rb +40 -0
- metadata +26 -6
data/README.md
CHANGED
@@ -70,7 +70,7 @@ Whitepace in this file is mostly ignored. If you absolutely need to put spaces a
|
|
70
70
|
|
71
71
|
## Usage
|
72
72
|
|
73
|
-
Usage: twine COMMAND STRINGS_FILE [INPUT_OR_OUTPUT_PATH] [--lang LANG1,LANG2...] [--
|
73
|
+
Usage: twine COMMAND STRINGS_FILE [INPUT_OR_OUTPUT_PATH] [--lang LANG1,LANG2...] [--tags TAG1,TAG2,TAG3...] [--format FORMAT]
|
74
74
|
|
75
75
|
### Commands
|
76
76
|
|
@@ -78,15 +78,15 @@ Whitepace in this file is mostly ignored. If you absolutely need to put spaces a
|
|
78
78
|
|
79
79
|
This command creates an Apple or Android strings file from the master strings data file.
|
80
80
|
|
81
|
-
$ twine generate-string-file /path/to/strings.txt values-ja.xml --
|
82
|
-
$ twine generate-string-file /path/to/strings.txt Localizable.strings --lang ja --
|
81
|
+
$ twine generate-string-file /path/to/strings.txt values-ja.xml --tags common,app1
|
82
|
+
$ twine generate-string-file /path/to/strings.txt Localizable.strings --lang ja --tags mytag
|
83
83
|
$ twine generate-string-file /path/to/strings.txt all-english.strings --lang en
|
84
84
|
|
85
85
|
#### `generate-all-string-files`
|
86
86
|
|
87
87
|
This command is a convenient way to call `generate-string-file` multiple times. It uses standard Mac OS X, iOS, and Android conventions to figure out exactly which files to create given a parent directory. For example, if you point it to a parent directory containing `en.lproj`, `fr.lproj`, and `ja.lproj` subdirectories, Twine will create a `Localizable.strings` file of the appropriate language in each of them. This is often the command you will want to execute during the build phase of your project.
|
88
88
|
|
89
|
-
$ twine generate-all-string-files /path/to/strings.txt /path/to/project/locales/directory --
|
89
|
+
$ twine generate-all-string-files /path/to/strings.txt /path/to/project/locales/directory --tags common,app1
|
90
90
|
|
91
91
|
#### `consume-string-file`
|
92
92
|
|
@@ -101,7 +101,7 @@ This command slurps all of the strings from a `.strings` or `.xml` file and inco
|
|
101
101
|
This command is a convenient way to generate a zip file containing files created by the `generate-string-file` command. It is often used for creating a single zip containing a large number of strings in all languages which you can then hand off to your translation team.
|
102
102
|
|
103
103
|
$ twine generate-loc-drop /path/to/strings.txt LocDrop1.zip
|
104
|
-
$ twine generate-loc-drop /path/to/strings.txt LocDrop2.zip --lang en,fr,ja,ko --
|
104
|
+
$ twine generate-loc-drop /path/to/strings.txt LocDrop2.zip --lang en,fr,ja,ko --tags common,app1
|
105
105
|
|
106
106
|
#### `consume-loc-drop`
|
107
107
|
|
data/bin/twine
CHANGED
data/lib/twine/cli.rb
CHANGED
@@ -12,8 +12,8 @@ module Twine
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def parse_args
|
15
|
-
parser = OptionParser.new
|
16
|
-
opts.banner = 'Usage: twine COMMAND STRINGS_FILE [INPUT_OR_OUTPUT_PATH] [--lang LANG1,LANG2...] [--
|
15
|
+
parser = OptionParser.new do |opts|
|
16
|
+
opts.banner = 'Usage: twine COMMAND STRINGS_FILE [INPUT_OR_OUTPUT_PATH] [--lang LANG1,LANG2...] [--tags TAG1,TAG2,TAG3...] [--format FORMAT]'
|
17
17
|
opts.separator ''
|
18
18
|
opts.separator 'The purpose of this script is to convert back and forth between multiple data formats, allowing us to treat our strings (and translations) as data stored in a text file. We can then use the data file to create drops for the localization team, consume similar drops returned by the localization team, generate reports on the strings, as well as create iOS and Android string files to ship with our products.'
|
19
19
|
opts.separator ''
|
@@ -36,7 +36,7 @@ module Twine
|
|
36
36
|
opts.on('-l', '--lang LANGUAGES', Array, 'The language code(s) to use for the specified action.') do |langs|
|
37
37
|
@options[:languages] = langs
|
38
38
|
end
|
39
|
-
opts.on('-t', '--
|
39
|
+
opts.on('-t', '--tags TAGS', Array, 'The tag(s) to use for the specified action. Only strings with that tag will be processed.') do |tags|
|
40
40
|
@options[:tags] = tags
|
41
41
|
end
|
42
42
|
opts.on('-f', '--format FORMAT', 'The file format to read or write (iOS, Android). Additional formatters can be placed in the formats/ directory.') do |format|
|
@@ -49,10 +49,16 @@ module Twine
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
if !found_format
|
52
|
-
puts "Invalid format: #{format}"
|
52
|
+
STDERR.puts "Invalid format: #{format}"
|
53
53
|
end
|
54
54
|
@options[:format] = lformat
|
55
55
|
end
|
56
|
+
opts.on('-a', '--all', 'Normally, when consuming a string file, Twine will ignore any string keys that do not exist in your master file. This flag will force those missing strings to be added to your master file.') do |a|
|
57
|
+
@options[:consume_all] = true
|
58
|
+
end
|
59
|
+
opts.on('-o', '--output-file OUTPUT_FILE', 'Write the new strings database to this file instead of replacing the original file. This flag is only useful when running the consume-string-file or consume-loc-drop commands.') do |o|
|
60
|
+
@options[:output_path] = o
|
61
|
+
end
|
56
62
|
opts.on('-h', '--help', 'Show this message.') do |h|
|
57
63
|
puts opts.help
|
58
64
|
exit
|
@@ -64,14 +70,14 @@ module Twine
|
|
64
70
|
opts.separator ''
|
65
71
|
opts.separator 'Examples:'
|
66
72
|
opts.separator ''
|
67
|
-
opts.separator '> twine generate-string-file strings.txt ko.xml --
|
68
|
-
opts.separator '> twine generate-all-string-files strings.txt Resources/Locales/ --
|
73
|
+
opts.separator '> twine generate-string-file strings.txt ko.xml --tags FT'
|
74
|
+
opts.separator '> twine generate-all-string-files strings.txt Resources/Locales/ --tags FT,FB'
|
69
75
|
opts.separator '> twine consume-string-file strings.txt ja.strings'
|
70
|
-
opts.separator '> twine generate-loc-drop strings.txt LocDrop5.zip --
|
76
|
+
opts.separator '> twine generate-loc-drop strings.txt LocDrop5.zip --tags FT,FB --format android --lang de,en,en-GB,ja,ko'
|
71
77
|
opts.separator '> twine consume-loc-drop strings.txt LocDrop5.zip'
|
72
78
|
opts.separator '> twine generate-report strings.txt'
|
73
79
|
end
|
74
|
-
parser.parse!
|
80
|
+
parser.parse! @args
|
75
81
|
|
76
82
|
if @args.length == 0
|
77
83
|
puts parser.help
|
@@ -81,13 +87,11 @@ module Twine
|
|
81
87
|
@options[:command] = @args[0]
|
82
88
|
|
83
89
|
if !VALID_COMMANDS.include? @options[:command]
|
84
|
-
|
85
|
-
exit
|
90
|
+
raise Twine::Error.new "Invalid command: #{@options[:command]}"
|
86
91
|
end
|
87
92
|
|
88
93
|
if @args.length == 1
|
89
|
-
|
90
|
-
exit
|
94
|
+
raise Twine::Error.new 'You must specify your strings file.'
|
91
95
|
end
|
92
96
|
|
93
97
|
@options[:strings_file] = @args[1]
|
@@ -97,68 +101,54 @@ module Twine
|
|
97
101
|
if @args.length == 3
|
98
102
|
@options[:output_path] = @args[2]
|
99
103
|
elsif @args.length > 3
|
100
|
-
|
101
|
-
exit
|
104
|
+
raise Twine::Error.new "Unknown argument: #{@args[3]}"
|
102
105
|
else
|
103
|
-
|
104
|
-
exit
|
106
|
+
raise Twine::Error.new 'Not enough arguments.'
|
105
107
|
end
|
106
108
|
if @options[:languages] and @options[:languages].length > 1
|
107
|
-
|
108
|
-
exit
|
109
|
+
raise Twine::Error.new 'Please only specify a single language for the generate-string-file command.'
|
109
110
|
end
|
110
111
|
when 'generate-all-string-files'
|
111
112
|
if ARGV.length == 3
|
112
113
|
@options[:output_path] = @args[2]
|
113
114
|
elsif @args.length > 3
|
114
|
-
|
115
|
-
exit
|
115
|
+
raise Twine::Error.new "Unknown argument: #{@args[3]}"
|
116
116
|
else
|
117
|
-
|
118
|
-
exit
|
117
|
+
raise Twine::Error.new 'Not enough arguments.'
|
119
118
|
end
|
120
119
|
when 'consume-string-file'
|
121
120
|
if @args.length == 3
|
122
121
|
@options[:input_path] = @args[2]
|
123
122
|
elsif @args.length > 3
|
124
|
-
|
125
|
-
exit
|
123
|
+
raise Twine::Error.new "Unknown argument: #{@args[3]}"
|
126
124
|
else
|
127
|
-
|
128
|
-
exit
|
125
|
+
raise Twine::Error.new 'Not enough arguments.'
|
129
126
|
end
|
130
127
|
if @options[:languages] and @options[:languages].length > 1
|
131
|
-
|
132
|
-
exit
|
128
|
+
raise Twine::Error.new 'Please only specify a single language for the consume-string-file command.'
|
133
129
|
end
|
134
130
|
when 'generate-loc-drop'
|
135
131
|
if @args.length == 3
|
136
132
|
@options[:output_path] = @args[2]
|
137
133
|
elsif @args.length > 3
|
138
|
-
|
139
|
-
exit
|
134
|
+
raise Twine::Error.new "Unknown argument: #{@args[3]}"
|
140
135
|
else
|
141
|
-
|
142
|
-
exit
|
136
|
+
raise Twine::Error.new 'Not enough arguments.'
|
143
137
|
end
|
144
138
|
if !@options[:format]
|
145
|
-
|
146
|
-
exit
|
139
|
+
raise Twine::Error.new 'You must specify a format.'
|
147
140
|
end
|
148
141
|
when 'consume-loc-drop'
|
149
142
|
if @args.length == 3
|
150
143
|
@options[:input_path] = @args[2]
|
151
144
|
elsif @args.length > 3
|
152
|
-
|
153
|
-
exit
|
145
|
+
raise Twine::Error.new "Unknown argument: #{@args[3]}"
|
154
146
|
else
|
155
|
-
|
156
|
-
exit
|
147
|
+
raise Twine::Error.new 'Not enough arguments.'
|
157
148
|
end
|
158
149
|
when 'generate-report'
|
159
150
|
if @args.length > 2
|
160
|
-
|
161
|
-
exit
|
151
|
+
raise Twine::Error.new "Unknown argument: #{@args[2]}"
|
162
152
|
end
|
163
153
|
end
|
164
154
|
end
|
@@ -1,10 +1,41 @@
|
|
1
1
|
module Twine
|
2
2
|
module Formatters
|
3
3
|
class Abstract
|
4
|
+
attr_accessor :strings
|
5
|
+
attr_accessor :options
|
6
|
+
|
4
7
|
def self.can_handle_directory?(path)
|
5
8
|
return false
|
6
9
|
end
|
7
10
|
|
11
|
+
def initialize(strings, options)
|
12
|
+
@strings = strings
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def set_translation_for_key(key, lang, value)
|
17
|
+
if @strings.strings_map.include?(key)
|
18
|
+
@strings.strings_map[key].translations[lang] = value
|
19
|
+
elsif @options[:consume_all]
|
20
|
+
STDERR.puts "Adding new string '#{key}' to strings data file."
|
21
|
+
arr = @strings.sections.select { |s| s.name == 'Uncategorized' }
|
22
|
+
current_section = arr ? arr[0] : nil
|
23
|
+
if !current_section
|
24
|
+
current_section = StringsSection.new('Uncategorized')
|
25
|
+
@strings.sections.insert(0, current_section)
|
26
|
+
end
|
27
|
+
current_row = StringsRow.new(key)
|
28
|
+
current_section.rows << current_row
|
29
|
+
@strings.strings_map[key] = current_row
|
30
|
+
@strings.strings_map[key].translations[lang] = value
|
31
|
+
else
|
32
|
+
STDERR.puts "Warning: '#{key}' not found in strings data file."
|
33
|
+
end
|
34
|
+
if !@strings.language_codes.include?(lang)
|
35
|
+
@strings.add_language_code(lang)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
8
39
|
def default_file_name
|
9
40
|
raise NotImplementedError.new("You must implement default_file_name in your formatter class.")
|
10
41
|
end
|
@@ -13,15 +44,15 @@ module Twine
|
|
13
44
|
raise NotImplementedError.new("You must implement determine_language_given_path in your formatter class.")
|
14
45
|
end
|
15
46
|
|
16
|
-
def read_file(path, lang
|
47
|
+
def read_file(path, lang)
|
17
48
|
raise NotImplementedError.new("You must implement read_file in your formatter class.")
|
18
49
|
end
|
19
50
|
|
20
|
-
def write_file(path, lang
|
51
|
+
def write_file(path, lang)
|
21
52
|
raise NotImplementedError.new("You must implement write_file in your formatter class.")
|
22
53
|
end
|
23
54
|
|
24
|
-
def write_all_files(path
|
55
|
+
def write_all_files(path)
|
25
56
|
if !File.directory?(path)
|
26
57
|
raise Twine::Error.new("Directory does not exist: #{path}")
|
27
58
|
end
|
@@ -29,29 +60,9 @@ module Twine
|
|
29
60
|
Dir.foreach(path) do |item|
|
30
61
|
lang = determine_language_given_path(item)
|
31
62
|
if lang
|
32
|
-
write_file(File.join(path, item, default_file_name), lang
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def row_matches_tags?(row, tags)
|
38
|
-
if tags == nil || tags.length == 0
|
39
|
-
return true
|
40
|
-
end
|
41
|
-
|
42
|
-
if tags != nil && row.tags != nil
|
43
|
-
tags.each do |tag|
|
44
|
-
if row.tags.include? tag
|
45
|
-
return true
|
46
|
-
end
|
63
|
+
write_file(File.join(path, item, default_file_name), lang)
|
47
64
|
end
|
48
65
|
end
|
49
|
-
|
50
|
-
return false
|
51
|
-
end
|
52
|
-
|
53
|
-
def translated_string_for_row_and_lang(row, lang, default_lang)
|
54
|
-
row.translations[lang] || row.translations[default_lang]
|
55
66
|
end
|
56
67
|
end
|
57
68
|
end
|
@@ -21,7 +21,7 @@ module Twine
|
|
21
21
|
def determine_language_given_path(path)
|
22
22
|
path_arr = path.split(File::SEPARATOR)
|
23
23
|
path_arr.each do |segment|
|
24
|
-
match = /^values-(.*)$/.match(
|
24
|
+
match = /^values-(.*)$/.match(segment)
|
25
25
|
if match
|
26
26
|
lang = match[1]
|
27
27
|
lang.sub!('-r', '-')
|
@@ -32,33 +32,31 @@ module Twine
|
|
32
32
|
return
|
33
33
|
end
|
34
34
|
|
35
|
-
def read_file(path, lang
|
35
|
+
def read_file(path, lang)
|
36
36
|
File.open(path, 'r:UTF-8') do |f|
|
37
|
+
current_section = nil
|
37
38
|
doc = REXML::Document.new(f)
|
38
39
|
doc.elements.each('resources/string') do |ele|
|
39
40
|
key = ele.attributes["name"]
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
else
|
46
|
-
puts "#{key} not found in strings data file."
|
47
|
-
end
|
41
|
+
value = ele.text || ''
|
42
|
+
value.gsub!('\\\'', '\'')
|
43
|
+
value.gsub!(/\n/, '')
|
44
|
+
value.gsub!('%s', '%@')
|
45
|
+
set_translation_for_key(key, lang, value)
|
48
46
|
end
|
49
47
|
end
|
50
48
|
end
|
51
49
|
|
52
|
-
def write_file(path, lang
|
53
|
-
default_lang = strings.language_codes[0]
|
50
|
+
def write_file(path, lang)
|
51
|
+
default_lang = @strings.language_codes[0]
|
54
52
|
File.open(path, 'w:UTF-8') do |f|
|
55
53
|
f.puts "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Android Strings File -->\n<!-- Generated by Twine -->\n<!-- Language: #{lang} -->"
|
56
54
|
f.write '<resources>'
|
57
|
-
strings.sections.each do |section|
|
55
|
+
@strings.sections.each do |section|
|
58
56
|
printed_section = false
|
59
57
|
section.rows.each do |row|
|
60
|
-
if
|
61
|
-
|
58
|
+
if row.matches_tags?(options[:tags])
|
59
|
+
if !printed_section
|
62
60
|
f.puts ''
|
63
61
|
if section.name && section.name.length > 0
|
64
62
|
section_name = section.name.gsub('--', '—')
|
@@ -70,7 +68,7 @@ module Twine
|
|
70
68
|
key = row.key
|
71
69
|
key = CGI.escapeHTML(key)
|
72
70
|
|
73
|
-
value =
|
71
|
+
value = row.translated_string_for_lang(lang, default_lang)
|
74
72
|
value.gsub!('\'', '\\\\\'')
|
75
73
|
value.gsub!('%@', '%s')
|
76
74
|
value = CGI.escapeHTML(value)
|
@@ -25,34 +25,30 @@ module Twine
|
|
25
25
|
return
|
26
26
|
end
|
27
27
|
|
28
|
-
def read_file(path, lang
|
28
|
+
def read_file(path, lang)
|
29
29
|
File.open(path, 'r:UTF-8') do |f|
|
30
30
|
while line = f.gets
|
31
|
-
match = /"((?:[^"\\]|\\.)+)"\s*=\s*"((?:[^"\\]|\\.)*)/.match(line)
|
31
|
+
match = /"((?:[^"\\]|\\.)+)"\s*=\s*"((?:[^"\\]|\\.)*)"/.match(line)
|
32
32
|
if match
|
33
33
|
key = match[1]
|
34
34
|
key.gsub!('\\"', '"')
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
strings.strings_map[key].translations[lang] = value
|
39
|
-
else
|
40
|
-
puts "#{key} not found in strings data file."
|
41
|
-
end
|
35
|
+
value = match[2]
|
36
|
+
value.gsub!('\\"', '"')
|
37
|
+
set_translation_for_key(key, lang, value)
|
42
38
|
end
|
43
39
|
end
|
44
40
|
end
|
45
41
|
end
|
46
42
|
|
47
|
-
def write_file(path, lang
|
48
|
-
default_lang = strings.language_codes[0]
|
43
|
+
def write_file(path, lang)
|
44
|
+
default_lang = @strings.language_codes[0]
|
49
45
|
File.open(path, 'w:UTF-8') do |f|
|
50
46
|
f.puts "/**\n * iOS Strings File\n * Generated by Twine\n * Language: #{lang}\n */"
|
51
|
-
strings.sections.each do |section|
|
47
|
+
@strings.sections.each do |section|
|
52
48
|
printed_section = false
|
53
49
|
section.rows.each do |row|
|
54
|
-
if
|
55
|
-
|
50
|
+
if row.matches_tags?(options[:tags])
|
51
|
+
if !printed_section
|
56
52
|
f.puts ''
|
57
53
|
if section.name && section.name.length > 0
|
58
54
|
f.puts "/* #{section.name} */"
|
@@ -63,7 +59,7 @@ module Twine
|
|
63
59
|
key = row.key
|
64
60
|
key = key.gsub('"', '\\\\"')
|
65
61
|
|
66
|
-
value =
|
62
|
+
value = row.translated_string_for_lang(lang, default_lang)
|
67
63
|
value = value.gsub('"', '\\\\"')
|
68
64
|
|
69
65
|
comment = row.comment
|
data/lib/twine/runner.rb
CHANGED
@@ -16,13 +16,8 @@ module Twine
|
|
16
16
|
def run
|
17
17
|
# Parse all CLI arguments.
|
18
18
|
CLI::parse_args(@args, @options)
|
19
|
-
|
20
|
-
|
21
|
-
read_strings_data
|
22
|
-
execute_command
|
23
|
-
rescue Twine::Error => e
|
24
|
-
puts e.message
|
25
|
-
end
|
19
|
+
read_strings_data
|
20
|
+
execute_command
|
26
21
|
end
|
27
22
|
|
28
23
|
def read_strings_data
|
@@ -53,7 +48,7 @@ module Twine
|
|
53
48
|
lang = @options[:languages][0]
|
54
49
|
end
|
55
50
|
|
56
|
-
read_write_string_file(@options[:output_path], false, lang
|
51
|
+
read_write_string_file(@options[:output_path], false, lang)
|
57
52
|
end
|
58
53
|
|
59
54
|
def generate_all_string_files
|
@@ -71,7 +66,7 @@ module Twine
|
|
71
66
|
|
72
67
|
formatter = formatter_for_format(format)
|
73
68
|
|
74
|
-
formatter.write_all_files(@options[:output_path]
|
69
|
+
formatter.write_all_files(@options[:output_path])
|
75
70
|
end
|
76
71
|
|
77
72
|
def consume_string_file
|
@@ -80,15 +75,17 @@ module Twine
|
|
80
75
|
lang = @options[:languages][0]
|
81
76
|
end
|
82
77
|
|
83
|
-
read_write_string_file(@options[:input_path], true, lang
|
84
|
-
@
|
78
|
+
read_write_string_file(@options[:input_path], true, lang)
|
79
|
+
output_path = @options[:output_path] || @options[:strings_file]
|
80
|
+
@strings.write(output_path)
|
85
81
|
end
|
86
82
|
|
87
|
-
def read_write_string_file(path, is_read, lang
|
83
|
+
def read_write_string_file(path, is_read, lang)
|
88
84
|
if is_read && !File.file?(path)
|
89
85
|
raise Twine::Error.new("File does not exist: #{path}")
|
90
86
|
end
|
91
87
|
|
88
|
+
format = @options[:format]
|
92
89
|
if !format
|
93
90
|
format = determine_format_given_path(path)
|
94
91
|
end
|
@@ -108,10 +105,14 @@ module Twine
|
|
108
105
|
raise Twine::Error.new "Unable to determine language for #{path}"
|
109
106
|
end
|
110
107
|
|
108
|
+
if !@strings.language_codes.include? lang
|
109
|
+
@strings.language_codes << lang
|
110
|
+
end
|
111
|
+
|
111
112
|
if is_read
|
112
|
-
formatter.read_file(path, lang
|
113
|
+
formatter.read_file(path, lang)
|
113
114
|
else
|
114
|
-
formatter.write_file(path, lang
|
115
|
+
formatter.write_file(path, lang)
|
115
116
|
end
|
116
117
|
end
|
117
118
|
|
@@ -162,13 +163,14 @@ module Twine
|
|
162
163
|
real_path = File.join(dir, entry.name)
|
163
164
|
FileUtils.mkdir_p(File.dirname(real_path))
|
164
165
|
zipfile.extract(entry.name, real_path)
|
165
|
-
read_write_string_file(real_path, true, nil
|
166
|
+
read_write_string_file(real_path, true, nil)
|
166
167
|
end
|
167
168
|
end
|
168
169
|
end
|
169
170
|
end
|
170
|
-
|
171
|
-
@
|
171
|
+
|
172
|
+
output_path = @options[:output_path] || @options[:strings_file]
|
173
|
+
@strings.write(output_path)
|
172
174
|
end
|
173
175
|
|
174
176
|
def generate_report
|
@@ -255,7 +257,7 @@ module Twine
|
|
255
257
|
def formatter_for_format(format)
|
256
258
|
Formatters::FORMATTERS.each do |formatter|
|
257
259
|
if formatter::FORMAT_NAME == format
|
258
|
-
return formatter.new
|
260
|
+
return formatter.new(@strings, @options)
|
259
261
|
end
|
260
262
|
end
|
261
263
|
|
data/lib/twine/stringsfile.rb
CHANGED
@@ -21,6 +21,28 @@ module Twine
|
|
21
21
|
@tags = nil
|
22
22
|
@translations = {}
|
23
23
|
end
|
24
|
+
|
25
|
+
def matches_tags?(tags)
|
26
|
+
if @tags == nil || @tags.length == 0
|
27
|
+
# This row has no tags. Never match
|
28
|
+
return false
|
29
|
+
elsif tags == nil || tags.length == 0
|
30
|
+
# The user did not specify any tags. Everything passes.
|
31
|
+
return true
|
32
|
+
else
|
33
|
+
tags.each do |tag|
|
34
|
+
if @tags.include? tag
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
def translated_string_for_lang(lang, default_lang=nil)
|
44
|
+
@translations[lang] || @translations[default_lang]
|
45
|
+
end
|
24
46
|
end
|
25
47
|
|
26
48
|
class StringsFile
|
@@ -87,7 +109,7 @@ module Twine
|
|
87
109
|
current_row.tags = value.split(',')
|
88
110
|
else
|
89
111
|
if !@language_codes.include? key
|
90
|
-
|
112
|
+
add_language_code(key)
|
91
113
|
end
|
92
114
|
current_row.translations[key] = value
|
93
115
|
end
|
@@ -99,12 +121,6 @@ module Twine
|
|
99
121
|
raise Twine::Error.new("Unable to parse line #{line_num} of #{path}: #{line}")
|
100
122
|
end
|
101
123
|
end
|
102
|
-
|
103
|
-
# Developer Language
|
104
|
-
dev_lang = @language_codes[0]
|
105
|
-
@language_codes.delete(dev_lang)
|
106
|
-
@language_codes.sort!
|
107
|
-
@language_codes.insert(0, dev_lang)
|
108
124
|
end
|
109
125
|
end
|
110
126
|
|
@@ -120,13 +136,17 @@ module Twine
|
|
120
136
|
f.puts "[[#{section.name}]]"
|
121
137
|
|
122
138
|
section.rows.each do |row|
|
139
|
+
f.puts "\t[#{row.key}]"
|
123
140
|
value = row.translations[dev_lang]
|
124
|
-
if value
|
125
|
-
|
141
|
+
if !value
|
142
|
+
puts "Warning: #{row.key} does not exist in developer language '#{dev_lang}'"
|
143
|
+
else
|
144
|
+
if value[0,1] == ' ' || value[-1,1] == ' ' || (value[0,1] == '`' && value[-1,1] == '`')
|
145
|
+
value = '`' + value + '`'
|
146
|
+
end
|
147
|
+
f.puts "\t\t#{dev_lang} = #{value}"
|
126
148
|
end
|
127
149
|
|
128
|
-
f.puts "\t[#{row.key}]"
|
129
|
-
f.puts "\t\t#{dev_lang} = #{value}"
|
130
150
|
if row.tags && row.tags.length > 0
|
131
151
|
tag_str = row.tags.join(',')
|
132
152
|
f.puts "\t\ttags = #{tag_str}"
|
@@ -147,5 +167,17 @@ module Twine
|
|
147
167
|
end
|
148
168
|
end
|
149
169
|
end
|
170
|
+
|
171
|
+
def add_language_code(code)
|
172
|
+
if @language_codes.length == 0
|
173
|
+
@language_codes << code
|
174
|
+
elsif !@language_codes.include?(code)
|
175
|
+
dev_lang = @language_codes[0]
|
176
|
+
@language_codes << code
|
177
|
+
@language_codes.delete(dev_lang)
|
178
|
+
@language_codes.sort!
|
179
|
+
@language_codes.insert(0, dev_lang)
|
180
|
+
end
|
181
|
+
end
|
150
182
|
end
|
151
183
|
end
|
data/lib/twine/version.rb
CHANGED
@@ -0,0 +1,9 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<!-- Android Strings File -->
|
3
|
+
<!-- Generated by Twine -->
|
4
|
+
<!-- Language: fr -->
|
5
|
+
<resources>
|
6
|
+
<string name="key1">key1-french</string>
|
7
|
+
<string name="key2">key2-french</string>
|
8
|
+
<string name="key3">key3-french</string>
|
9
|
+
</resources>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
[[My Strings]]
|
2
|
+
[key1]
|
3
|
+
en = key1-english
|
4
|
+
tags = tag1
|
5
|
+
es = key1-spanish
|
6
|
+
fr = key1-french
|
7
|
+
[key2]
|
8
|
+
en = key2-english
|
9
|
+
tags = tag2
|
10
|
+
fr = key2-french
|
11
|
+
[key3]
|
12
|
+
en = key3-english
|
13
|
+
tags = tag1,tag2
|
14
|
+
es = key3-spanish
|
15
|
+
[key4]
|
16
|
+
en = key4-english
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<!-- Android Strings File -->
|
3
|
+
<!-- Generated by Twine -->
|
4
|
+
<!-- Language: fr -->
|
5
|
+
<resources>
|
6
|
+
<!-- My Strings -->
|
7
|
+
<string name="key1">key1-french</string>
|
8
|
+
<string name="key2">key2-french</string>
|
9
|
+
<string name="key3">key3-english</string>
|
10
|
+
</resources>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
[[My Strings]]
|
2
|
+
[key1]
|
3
|
+
en = key1-english
|
4
|
+
tags = tag1
|
5
|
+
es = key1-spanish
|
6
|
+
fr = key1-french
|
7
|
+
[key2]
|
8
|
+
en = key2-english
|
9
|
+
tags = tag2
|
10
|
+
fr = key2-french
|
11
|
+
[key3]
|
12
|
+
en = key3-english
|
13
|
+
tags = tag1,tag2
|
14
|
+
es = key3-spanish
|
15
|
+
fr = key3-french
|
16
|
+
[key4]
|
17
|
+
en = key4-english
|
@@ -0,0 +1,20 @@
|
|
1
|
+
[[Uncategorized]]
|
2
|
+
[key5]
|
3
|
+
en = A new string
|
4
|
+
|
5
|
+
[[My Strings]]
|
6
|
+
[key1]
|
7
|
+
en = key1-english
|
8
|
+
tags = tag1
|
9
|
+
es = key1-spanish
|
10
|
+
fr = key1-french
|
11
|
+
[key2]
|
12
|
+
en = key2-english
|
13
|
+
tags = tag2
|
14
|
+
fr = key2-french
|
15
|
+
[key3]
|
16
|
+
en = key3-english
|
17
|
+
tags = tag1,tag2
|
18
|
+
es = key3-spanish
|
19
|
+
[key4]
|
20
|
+
en = key4-english
|
data/test/twine_test.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'twine'
|
3
|
+
|
4
|
+
class TwineTest < Test::Unit::TestCase
|
5
|
+
def test_generate_string_file_1
|
6
|
+
Dir.mktmpdir do |dir|
|
7
|
+
output_path = File.join(dir, 'fr.xml')
|
8
|
+
Twine::Runner.run(%W(generate-string-file test/fixtures/strings-1.txt #{output_path}))
|
9
|
+
assert_equal(File.read('test/fixtures/test-output-1.txt'), File.read(output_path))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_generate_string_file_2
|
14
|
+
Dir.mktmpdir do |dir|
|
15
|
+
output_path = File.join(dir, 'en.strings')
|
16
|
+
Twine::Runner.run(%W(generate-string-file test/fixtures/strings-1.txt #{output_path} -t tag1))
|
17
|
+
assert_equal(File.read('test/fixtures/test-output-2.txt'), File.read(output_path))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_consume_string_file_1
|
22
|
+
Dir.mktmpdir do |dir|
|
23
|
+
output_path = File.join(dir, 'strings.txt')
|
24
|
+
Twine::Runner.run(%W(consume-string-file test/fixtures/strings-1.txt test/fixtures/fr-1.xml -o #{output_path} -l fr))
|
25
|
+
assert_equal(File.read('test/fixtures/test-output-3.txt'), File.read(output_path))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_consume_string_file_2
|
30
|
+
Dir.mktmpdir do |dir|
|
31
|
+
output_path = File.join(dir, 'strings.txt')
|
32
|
+
Twine::Runner.run(%W(consume-string-file test/fixtures/strings-1.txt test/fixtures/en-1.strings -o #{output_path} -l en -a))
|
33
|
+
assert_equal(File.read('test/fixtures/test-output-4.txt'), File.read(output_path))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_generate_report_1
|
38
|
+
Twine::Runner.run(%w(generate-report test/fixtures/strings-1.txt))
|
39
|
+
end
|
40
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: twine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-02-
|
12
|
+
date: 2012-02-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rubyzip
|
16
|
-
requirement: &
|
16
|
+
requirement: &70180596581660 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,7 +21,18 @@ dependencies:
|
|
21
21
|
version: 0.9.5
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70180596581660
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &70180596581180 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.9.2
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70180596581180
|
25
36
|
description: ! " Twine is a command line tool for managing your strings and their
|
26
37
|
translations.\n \n It is geared toward Mac OS X, iOS, and Android developers.\n"
|
27
38
|
email: twine@mobiata.com
|
@@ -43,6 +54,14 @@ files:
|
|
43
54
|
- lib/twine/version.rb
|
44
55
|
- lib/twine.rb
|
45
56
|
- bin/twine
|
57
|
+
- test/fixtures/en-1.strings
|
58
|
+
- test/fixtures/fr-1.xml
|
59
|
+
- test/fixtures/strings-1.txt
|
60
|
+
- test/fixtures/test-output-1.txt
|
61
|
+
- test/fixtures/test-output-2.txt
|
62
|
+
- test/fixtures/test-output-3.txt
|
63
|
+
- test/fixtures/test-output-4.txt
|
64
|
+
- test/twine_test.rb
|
46
65
|
homepage: https://github.com/mobiata/twine
|
47
66
|
licenses: []
|
48
67
|
post_install_message:
|
@@ -54,7 +73,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
54
73
|
requirements:
|
55
74
|
- - ! '>='
|
56
75
|
- !ruby/object:Gem::Version
|
57
|
-
version:
|
76
|
+
version: 1.8.7
|
58
77
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
78
|
none: false
|
60
79
|
requirements:
|
@@ -67,4 +86,5 @@ rubygems_version: 1.8.11
|
|
67
86
|
signing_key:
|
68
87
|
specification_version: 3
|
69
88
|
summary: Manage strings and their translations for your iOS and Android projects.
|
70
|
-
test_files:
|
89
|
+
test_files:
|
90
|
+
- test/twine_test.rb
|