twine 0.8.1 → 0.9.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 +3 -3
- data/lib/twine/cli.rb +63 -48
- data/lib/twine/encoding.rb +16 -14
- data/lib/twine/formatters/abstract.rb +9 -48
- data/lib/twine/formatters/android.rb +30 -26
- data/lib/twine/formatters/apple.rb +20 -48
- data/lib/twine/formatters/django.rb +23 -55
- data/lib/twine/formatters/flash.rb +21 -47
- data/lib/twine/formatters/gettext.rb +25 -26
- data/lib/twine/formatters/jquery.rb +8 -8
- data/lib/twine/formatters/tizen.rb +27 -29
- data/lib/twine/output_processor.rb +2 -2
- data/lib/twine/runner.rb +81 -21
- data/lib/twine/version.rb +1 -1
- data/test/fixtures/enc_utf16be.dummy +0 -0
- data/test/fixtures/enc_utf16be_bom.dummy +0 -0
- data/test/fixtures/enc_utf16le.dummy +0 -0
- data/test/fixtures/enc_utf16le_bom.dummy +0 -0
- data/test/fixtures/enc_utf8.dummy +2 -0
- data/test/test_cli.rb +3 -15
- data/test/test_consume_loc_drop.rb +1 -1
- data/test/test_consume_string_file.rb +73 -7
- data/test/test_formatters.rb +96 -38
- data/test/test_generate_all_string_files.rb +43 -17
- data/test/test_generate_loc_drop.rb +19 -13
- data/test/test_generate_string_file.rb +17 -8
- data/test/test_output_processor.rb +2 -2
- data/test/test_strings_file.rb +2 -2
- data/test/twine_file_dsl.rb +1 -1
- data/test/twine_test_case.rb +6 -7
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 09e04e3949a2a98ab2483771f9def4c71942edbc
|
4
|
+
data.tar.gz: eaf82148761e31028a7918faaab00b721f41c661
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e55fbf7a14149928f4ebfaa20f44ceef593152a7233d73b2e29ccbb0b88e5a6fd25086bfc9af04a7721513b9297487f8302b4947d3afdac5cc0e614648a203e2
|
7
|
+
data.tar.gz: b722d1aa76cb00b135ae13b1081bc14f488c9bc3da42527046e19096edf796f65df250e086f7c923af43bea72cd9a5154372f90af213570f5c44001634601a58
|
data/README.md
CHANGED
@@ -100,7 +100,7 @@ If you would like to enable twine to create language files in another format, cr
|
|
100
100
|
|
101
101
|
#### `generate-string-file`
|
102
102
|
|
103
|
-
This command creates an Apple or Android strings file from the master strings data file.
|
103
|
+
This command creates an Apple or Android strings file from the master strings data file. If the output file would not contain any translations, twine will exit with an error.
|
104
104
|
|
105
105
|
$ twine generate-string-file /path/to/strings.txt values-ja.xml --tags common,app1
|
106
106
|
$ twine generate-string-file /path/to/strings.txt Localizable.strings --lang ja --tags mytag
|
@@ -108,7 +108,7 @@ This command creates an Apple or Android strings file from the master strings da
|
|
108
108
|
|
109
109
|
#### `generate-all-string-files`
|
110
110
|
|
111
|
-
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.
|
111
|
+
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. However, files that would not contain any translations will not be created; instead warnings will be logged to `stderr`. This is often the command you will want to execute during the build phase of your project.
|
112
112
|
|
113
113
|
$ twine generate-all-string-files /path/to/strings.txt /path/to/project/locales/directory --tags common,app1
|
114
114
|
|
@@ -128,7 +128,7 @@ This command reads in a folder containing many `.strings` or `.xml` files. These
|
|
128
128
|
|
129
129
|
#### `generate-loc-drop`
|
130
130
|
|
131
|
-
This command is a convenient way to generate a zip file containing files created by the `generate-string-file` command.
|
131
|
+
This command is a convenient way to generate a zip file containing files created by the `generate-string-file` command. If a file would not contain any translated strings, it is skipped and a warning is logged to `stderr`. This command can be used to create a single zip containing a large number of strings in all languages which you can then hand off to your translation team.
|
132
132
|
|
133
133
|
$ twine generate-loc-drop /path/to/strings.txt LocDrop1.zip
|
134
134
|
$ twine generate-loc-drop /path/to/strings.txt LocDrop2.zip --lang en,fr,ja,ko --tags common,app1
|
data/lib/twine/cli.rb
CHANGED
@@ -13,93 +13,103 @@ module Twine
|
|
13
13
|
}
|
14
14
|
|
15
15
|
def self.parse(args)
|
16
|
-
options = {}
|
16
|
+
options = { include: :all }
|
17
|
+
|
17
18
|
parser = OptionParser.new do |opts|
|
18
19
|
opts.banner = 'Usage: twine COMMAND STRINGS_FILE [INPUT_OR_OUTPUT_PATH] [--lang LANG1,LANG2...] [--tags TAG1,TAG2,TAG3...] [--format FORMAT]'
|
19
20
|
opts.separator ''
|
20
|
-
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, and create formatted string files to ship with your products.
|
21
|
+
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, and create formatted string files to ship with your products.'
|
21
22
|
opts.separator ''
|
22
23
|
opts.separator 'Commands:'
|
23
24
|
opts.separator ''
|
24
|
-
opts.separator 'generate-string-file
|
25
|
+
opts.separator '- generate-string-file'
|
26
|
+
opts.separator ' Generates a string file in a certain LANGUAGE given a particular FORMAT. This script will attempt to guess both the language and the format given the filename and extension. For example, "ko.xml" will generate a Korean language file for Android.'
|
25
27
|
opts.separator ''
|
26
|
-
opts.separator 'generate-all-string-files
|
28
|
+
opts.separator '- generate-all-string-files'
|
29
|
+
opts.separator ' Generates all the string files necessary for a given project. The parent directory to all of the locale-specific directories in your project should be specified as the INPUT_OR_OUTPUT_PATH. This command will most often be executed by your build script so that each build always contains the most recent strings.'
|
27
30
|
opts.separator ''
|
28
|
-
opts.separator 'consume-string-file
|
31
|
+
opts.separator '- consume-string-file'
|
32
|
+
opts.separator ' Slurps all of the strings from a translated strings file into the specified STRINGS_FILE. If you have some files returned to you by your translators you can use this command to incorporate all of their changes. This script will attempt to guess both the language and the format given the filename and extension. For example, "ja.strings" will assume that the file is a Japanese iOS strings file.'
|
29
33
|
opts.separator ''
|
30
|
-
opts.separator 'consume-all-string-files
|
34
|
+
opts.separator '- consume-all-string-files'
|
35
|
+
opts.separator ' Slurps all of the strings from a directory into the specified STRINGS_FILE. If you have some files returned to you by your translators you can use this command to incorporate all of their changes. This script will attempt to guess both the language and the format given the filename and extension. For example, "ja.strings" will assume that the file is a Japanese iOS strings file.'
|
31
36
|
opts.separator ''
|
32
|
-
opts.separator '
|
37
|
+
opts.separator '- generate-loc-drop'
|
38
|
+
opts.separator ' Generates a zip archive of strings files in any format. The purpose of this command is to create a very simple archive that can be handed off to a translation team. The translation team can unzip the archive, translate all of the strings in the archived files, zip everything back up, and then hand that final archive back to be consumed by the consume-loc-drop command.'
|
33
39
|
opts.separator ''
|
34
|
-
opts.separator '
|
40
|
+
opts.separator '- consume-loc-drop'
|
41
|
+
opts.separator ' Consumes an archive of translated files. This archive should be in the same format as the one created by the generate-loc-drop command.'
|
35
42
|
opts.separator ''
|
36
|
-
opts.separator 'validate-strings-file
|
43
|
+
opts.separator '- validate-strings-file'
|
44
|
+
opts.separator ' Validates that the given strings file is parseable, contains no duplicates, and that every string has a tag. Exits with a non-zero exit code if those criteria are not met.'
|
37
45
|
opts.separator ''
|
38
46
|
opts.separator 'General Options:'
|
39
47
|
opts.separator ''
|
40
|
-
opts.on('-l', '--lang LANGUAGES', Array, 'The language code(s) to use for the specified action.') do |
|
41
|
-
options[:languages] =
|
48
|
+
opts.on('-l', '--lang LANGUAGES', Array, 'The language code(s) to use for the specified action.') do |l|
|
49
|
+
options[:languages] = l
|
42
50
|
end
|
43
|
-
opts.on('-t', '--tags
|
44
|
-
|
51
|
+
opts.on('-t', '--tags TAG1,TAG2,TAG3', Array, 'The tag(s) to use for the specified action. Only strings with that tag will be processed. Omit this option to match',
|
52
|
+
' all strings in the strings data file.') do |t|
|
53
|
+
options[:tags] = t
|
45
54
|
end
|
46
|
-
opts.on('-u', '--untagged', 'If you have specified tags using the --tags flag, then only those tags will be selected. If you also want to select
|
47
|
-
|
55
|
+
opts.on('-u', '--[no-]untagged', 'If you have specified tags using the --tags flag, then only those tags will be selected. If you also want to select',
|
56
|
+
' all strings that are untagged, then you can specify this option to do so.') do |u|
|
57
|
+
options[:untagged] = u
|
48
58
|
end
|
49
|
-
formats = Formatters.formatters.map(&:format_name)
|
50
|
-
opts.on('-f', '--format FORMAT', "The file format to read or write (#{formats.join(', ')}).
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
options[:format] = format.downcase
|
59
|
+
formats = Formatters.formatters.map(&:format_name).map(&:downcase)
|
60
|
+
opts.on('-f', '--format FORMAT', formats, "The file format to read or write: (#{formats.join(', ')}).",
|
61
|
+
" Additional formatters can be placed in the formats/ directory.") do |f|
|
62
|
+
options[:format] = f
|
55
63
|
end
|
56
|
-
opts.on('-a', '--consume-all', 'Normally, when consuming a string file, Twine will ignore any string keys that do not exist in your master file.') do |a|
|
64
|
+
opts.on('-a', '--[no-]consume-all', 'Normally, when consuming a string file, Twine will ignore any string keys that do not exist in your master file.') do |a|
|
57
65
|
options[:consume_all] = true
|
58
66
|
end
|
59
|
-
opts.on('-i', '--include SET',
|
67
|
+
opts.on('-i', '--include SET', [:all, :translated, :untranslated],
|
68
|
+
"This flag will determine which strings are included when generating strings files. It's possible values:",
|
60
69
|
" all: All strings both translated and untranslated for the specified language are included. This is the default value.",
|
61
70
|
" translated: Only translated strings are included.",
|
62
|
-
" untranslated: Only untranslated strings are included.") do |
|
63
|
-
|
64
|
-
raise Twine::Error.new "Invalid include flag: #{set}"
|
65
|
-
end
|
66
|
-
options[:include] = set.downcase
|
67
|
-
end
|
68
|
-
unless options[:include]
|
69
|
-
options[:include] = 'all'
|
71
|
+
" untranslated: Only untranslated strings are included.") do |i|
|
72
|
+
options[:include] = i
|
70
73
|
end
|
71
|
-
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
|
74
|
+
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',
|
75
|
+
' running the consume-string-file or consume-loc-drop commands.') do |o|
|
72
76
|
options[:output_path] = o
|
73
77
|
end
|
74
78
|
opts.on('-n', '--file-name FILE_NAME', 'When running the generate-all-string-files command, this flag may be used to overwrite the default file name of the format.') do |n|
|
75
79
|
options[:file_name] = n
|
76
80
|
end
|
77
|
-
opts.on('-r', '--create-folders', "When running the generate-all-string-files command, this flag may be used to create output folders for all languages,
|
78
|
-
|
81
|
+
opts.on('-r', '--[no-]create-folders', "When running the generate-all-string-files command, this flag may be used to create output folders for all languages,",
|
82
|
+
" if they don't exist yet. As a result all languages will be exported, not only the ones where an output folder already",
|
83
|
+
" exists.") do |r|
|
84
|
+
options[:create_folders] = r
|
79
85
|
end
|
80
|
-
opts.on('-d', '--developer-language LANG', 'When writing the strings data file, set the specified language as the "developer language". In practice, this just
|
86
|
+
opts.on('-d', '--developer-language LANG', 'When writing the strings data file, set the specified language as the "developer language". In practice, this just',
|
87
|
+
' means that this language will appear first in the strings data file. When generating files this language will be',
|
88
|
+
' used as default language and its translations will be used if a key is not localized for the output language.') do |d|
|
81
89
|
options[:developer_language] = d
|
82
90
|
end
|
83
|
-
opts.on('-c', '--consume-comments', 'Normally, when consuming a string file, Twine will ignore all comments in the file. With this flag set, any comments
|
84
|
-
|
91
|
+
opts.on('-c', '--[no-]consume-comments', 'Normally, when consuming a string file, Twine will ignore all comments in the file. With this flag set, any comments',
|
92
|
+
' encountered will be read and parsed into the strings data file. This is especially useful when creating your first',
|
93
|
+
' strings data file from an existing project.') do |c|
|
94
|
+
options[:consume_comments] = c
|
85
95
|
end
|
86
|
-
opts.on('-e', '--encoding ENCODING', 'Twine defaults to encoding all output files in UTF-8. This flag will tell Twine to use an alternate encoding for these
|
87
|
-
|
88
|
-
|
89
|
-
|
96
|
+
opts.on('-e', '--encoding ENCODING', 'Twine defaults to encoding all output files in UTF-8. This flag will tell Twine to use an alternate encoding for these',
|
97
|
+
' files. For example, you could use this to write Apple .strings files in UTF-16. When reading files, Twine does its best',
|
98
|
+
" to determine the encoding automatically. However, if the files are UTF-16 without BOM, you need to specify if it's",
|
99
|
+
' UTF-16LE or UTF16-BE.') do |e|
|
90
100
|
options[:output_encoding] = e
|
91
101
|
end
|
92
|
-
opts.on('--validate', 'Validate the strings file before formatting it') do
|
93
|
-
options[:validate] =
|
102
|
+
opts.on('--[no-]validate', 'Validate the strings file before formatting it.') do |validate|
|
103
|
+
options[:validate] = validate
|
94
104
|
end
|
95
|
-
opts.on('-p', '--pedantic', 'When validating a strings file, perform additional checks that go beyond pure validity (like presence of tags)') do
|
96
|
-
options[:pedantic] =
|
105
|
+
opts.on('-p', '--[no-]pedantic', 'When validating a strings file, perform additional checks that go beyond pure validity (like presence of tags).') do |p|
|
106
|
+
options[:pedantic] = p
|
97
107
|
end
|
98
108
|
opts.on('-h', '--help', 'Show this message.') do |h|
|
99
109
|
puts opts.help
|
100
110
|
exit
|
101
111
|
end
|
102
|
-
opts.on('--version', 'Print the version number and exit.') do
|
112
|
+
opts.on('--version', 'Print the version number and exit.') do
|
103
113
|
puts "Twine version #{Twine::VERSION}"
|
104
114
|
exit
|
105
115
|
end
|
@@ -114,11 +124,16 @@ module Twine
|
|
114
124
|
opts.separator '> twine consume-loc-drop strings.txt LocDrop5.zip'
|
115
125
|
opts.separator '> twine validate-strings-file strings.txt'
|
116
126
|
end
|
117
|
-
|
127
|
+
begin
|
128
|
+
parser.parse! args
|
129
|
+
rescue OptionParser::ParseError => e
|
130
|
+
Twine::stderr.puts e.message
|
131
|
+
exit false
|
132
|
+
end
|
118
133
|
|
119
134
|
if args.length == 0
|
120
135
|
puts parser.help
|
121
|
-
exit
|
136
|
+
exit false
|
122
137
|
end
|
123
138
|
|
124
139
|
number_of_needed_arguments = NEEDED_COMMAND_ARGUMENTS[args[0]]
|
data/lib/twine/encoding.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
module Twine
|
2
2
|
module Encoding
|
3
|
-
def self.encoding_for_path path
|
4
|
-
File.open(path, 'rb') do |f|
|
5
|
-
begin
|
6
|
-
a = f.readbyte
|
7
|
-
b = f.readbyte
|
8
|
-
if (a == 0xfe && b == 0xff)
|
9
|
-
return 'UTF-16BE'
|
10
|
-
elsif (a == 0xff && b == 0xfe)
|
11
|
-
return 'UTF-16LE'
|
12
|
-
end
|
13
|
-
rescue EOFError
|
14
|
-
end
|
15
|
-
end
|
16
3
|
|
17
|
-
|
4
|
+
def self.bom(path)
|
5
|
+
first_bytes = IO.binread(path, 2)
|
6
|
+
return nil unless first_bytes
|
7
|
+
first_bytes = first_bytes.codepoints.map.to_a
|
8
|
+
return 'UTF-16BE' if first_bytes == [0xFE, 0xFF]
|
9
|
+
return 'UTF-16LE' if first_bytes == [0xFF, 0xFE]
|
10
|
+
rescue EOFError
|
11
|
+
return nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.has_bom?(path)
|
15
|
+
!bom(path).nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.encoding_for_path(path)
|
19
|
+
bom(path) || 'UTF-8'
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
@@ -83,15 +83,20 @@ module Twine
|
|
83
83
|
lang
|
84
84
|
end
|
85
85
|
|
86
|
-
def
|
87
|
-
raise NotImplementedError.new("You must implement
|
86
|
+
def read(io, lang)
|
87
|
+
raise NotImplementedError.new("You must implement read in your formatter class.")
|
88
88
|
end
|
89
89
|
|
90
|
-
def format_file(
|
90
|
+
def format_file(lang)
|
91
|
+
output_processor = Processors::OutputProcessor.new(@strings, @options)
|
92
|
+
processed_strings = output_processor.process(lang)
|
93
|
+
|
94
|
+
return nil if processed_strings.strings_map.empty?
|
95
|
+
|
91
96
|
header = format_header(lang)
|
92
97
|
result = ""
|
93
98
|
result += header + "\n" if header
|
94
|
-
result += format_sections(
|
99
|
+
result += format_sections(processed_strings, lang)
|
95
100
|
end
|
96
101
|
|
97
102
|
def format_header(lang)
|
@@ -153,50 +158,6 @@ module Twine
|
|
153
158
|
def escape_quotes(text)
|
154
159
|
text.gsub('"', '\\\\"')
|
155
160
|
end
|
156
|
-
|
157
|
-
def write_file(path, lang)
|
158
|
-
output_processor = Processors::OutputProcessor.new(@strings, @options)
|
159
|
-
processed_strings = output_processor.process(lang)
|
160
|
-
|
161
|
-
encoding = @options[:output_encoding] || 'UTF-8'
|
162
|
-
File.open(path, "w:#{encoding}") do |f|
|
163
|
-
f.puts format_file(processed_strings, lang)
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
def write_all_files(path)
|
168
|
-
file_name = @options[:file_name] || default_file_name
|
169
|
-
if @options[:create_folders]
|
170
|
-
@strings.language_codes.each do |lang|
|
171
|
-
output_path = File.join(path, output_path_for_language(lang))
|
172
|
-
|
173
|
-
FileUtils.mkdir_p(output_path)
|
174
|
-
|
175
|
-
file_path = File.join(output_path, file_name)
|
176
|
-
write_file(file_path, lang)
|
177
|
-
end
|
178
|
-
else
|
179
|
-
language_written = false
|
180
|
-
Dir.foreach(path) do |item|
|
181
|
-
next if item == "." or item == ".."
|
182
|
-
|
183
|
-
item = File.join(path, item)
|
184
|
-
next unless File.directory?(item)
|
185
|
-
|
186
|
-
lang = determine_language_given_path(item)
|
187
|
-
next unless lang
|
188
|
-
|
189
|
-
file_path = File.join(item, file_name)
|
190
|
-
write_file(file_path, lang)
|
191
|
-
language_written = true
|
192
|
-
end
|
193
|
-
|
194
|
-
if !language_written
|
195
|
-
raise Twine::Error.new("Failed to generate any files: No languages found at #{path}")
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
161
|
end
|
201
162
|
end
|
202
163
|
end
|
@@ -7,11 +7,11 @@ module Twine
|
|
7
7
|
class Android < Abstract
|
8
8
|
include Twine::Placeholders
|
9
9
|
|
10
|
-
|
11
|
-
'zh' => 'zh-Hans',
|
10
|
+
LANG_MAPPINGS = Hash[
|
12
11
|
'zh-rCN' => 'zh-Hans',
|
13
12
|
'zh-rHK' => 'zh-Hant',
|
14
13
|
'en-rGB' => 'en-UK',
|
14
|
+
'zh' => 'zh-Hans',
|
15
15
|
'in' => 'id',
|
16
16
|
'nb' => 'no'
|
17
17
|
# TODO: spanish
|
@@ -39,10 +39,12 @@ module Twine
|
|
39
39
|
if segment == 'values'
|
40
40
|
return @strings.language_codes[0]
|
41
41
|
else
|
42
|
-
|
42
|
+
# 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").
|
43
|
+
# see http://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources
|
44
|
+
match = /^values-([a-z]{2}(-r[a-z]{2})?)$/i.match(segment)
|
43
45
|
if match
|
44
46
|
lang = match[1]
|
45
|
-
lang =
|
47
|
+
lang = LANG_MAPPINGS.fetch(lang, lang)
|
46
48
|
lang.sub!('-r', '-')
|
47
49
|
return lang
|
48
50
|
end
|
@@ -52,6 +54,10 @@ module Twine
|
|
52
54
|
return
|
53
55
|
end
|
54
56
|
|
57
|
+
def output_path_for_language(lang)
|
58
|
+
"values-" + (LANG_MAPPINGS.key(lang) || lang)
|
59
|
+
end
|
60
|
+
|
55
61
|
def set_translation_for_key(key, lang, value)
|
56
62
|
value = CGI.unescapeHTML(value)
|
57
63
|
value.gsub!('\\\'', '\'')
|
@@ -62,7 +68,7 @@ module Twine
|
|
62
68
|
super(key, lang, value)
|
63
69
|
end
|
64
70
|
|
65
|
-
def
|
71
|
+
def read(io, lang)
|
66
72
|
resources_regex = /<resources(?:[^>]*)>(.*)<\/resources>/m
|
67
73
|
key_regex = /<string name="(\w+)">/
|
68
74
|
comment_regex = /<!-- (.*) -->/
|
@@ -71,27 +77,25 @@ module Twine
|
|
71
77
|
value = nil
|
72
78
|
comment = nil
|
73
79
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
set_comment_for_key(key, comment)
|
87
|
-
end
|
88
|
-
comment = nil
|
89
|
-
end
|
90
|
-
|
91
|
-
comment_match = comment_regex.match(line)
|
92
|
-
if comment_match
|
93
|
-
comment = comment_match[1]
|
80
|
+
content_match = resources_regex.match(io.read)
|
81
|
+
if content_match
|
82
|
+
for line in content_match[1].split(/\r?\n/)
|
83
|
+
key_match = key_regex.match(line)
|
84
|
+
if key_match
|
85
|
+
key = key_match[1]
|
86
|
+
value_match = value_regex.match(line)
|
87
|
+
value = value_match ? value_match[1] : ""
|
88
|
+
|
89
|
+
set_translation_for_key(key, lang, value)
|
90
|
+
if comment and comment.length > 0 and !comment.start_with?("SECTION:")
|
91
|
+
set_comment_for_key(key, comment)
|
94
92
|
end
|
93
|
+
comment = nil
|
94
|
+
end
|
95
|
+
|
96
|
+
comment_match = comment_regex.match(line)
|
97
|
+
if comment_match
|
98
|
+
comment = comment_match[1]
|
95
99
|
end
|
96
100
|
end
|
97
101
|
end
|
@@ -106,7 +110,7 @@ module Twine
|
|
106
110
|
|
107
111
|
result += super + "\n"
|
108
112
|
|
109
|
-
result +=
|
113
|
+
result += "</resources>\n"
|
110
114
|
end
|
111
115
|
|
112
116
|
def format_section_header(section)
|
@@ -35,56 +35,28 @@ module Twine
|
|
35
35
|
"#{lang}.lproj"
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
if
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
if encoding.index('UTF-16')
|
53
|
-
mode = "rb:#{encoding}"
|
54
|
-
else
|
55
|
-
mode = "r:#{encoding}"
|
56
|
-
end
|
57
|
-
|
58
|
-
File.open(path, mode) do |f|
|
59
|
-
last_comment = nil
|
60
|
-
while line = (sep) ? f.gets(sep) : f.gets
|
61
|
-
if encoding.index('UTF-16')
|
62
|
-
if line.respond_to? :encode!
|
63
|
-
line.encode!('UTF-8')
|
64
|
-
else
|
65
|
-
require 'iconv'
|
66
|
-
line = Iconv.iconv('UTF-8', encoding, line).join
|
67
|
-
end
|
68
|
-
end
|
69
|
-
match = /"((?:[^"\\]|\\.)+)"\s*=\s*"((?:[^"\\]|\\.)*)"/.match(line)
|
70
|
-
if match
|
71
|
-
key = match[1]
|
72
|
-
key.gsub!('\\"', '"')
|
73
|
-
value = match[2]
|
74
|
-
value.gsub!('\\"', '"')
|
75
|
-
set_translation_for_key(key, lang, value)
|
76
|
-
if last_comment
|
77
|
-
set_comment_for_key(key, last_comment)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
match = /\/\* (.*) \*\//.match(line)
|
82
|
-
if match
|
83
|
-
last_comment = match[1]
|
84
|
-
else
|
85
|
-
last_comment = nil
|
38
|
+
def read(io, lang)
|
39
|
+
last_comment = nil
|
40
|
+
while line = io.gets
|
41
|
+
# matches a `key = "value"` line, where key may be quoted or unquoted. The former may also contain escaped characters
|
42
|
+
match = /^\s*((?:"(?:[^"\\]|\\.)+")|(?:[^"\s=]+))\s*=\s*"((?:[^"\\]|\\.)*)"/.match(line)
|
43
|
+
if match
|
44
|
+
key = match[1]
|
45
|
+
key = key[1..-2] if key[0] == '"' and key[-1] == '"'
|
46
|
+
key.gsub!('\\"', '"')
|
47
|
+
value = match[2]
|
48
|
+
value.gsub!('\\"', '"')
|
49
|
+
set_translation_for_key(key, lang, value)
|
50
|
+
if last_comment
|
51
|
+
set_comment_for_key(key, last_comment)
|
86
52
|
end
|
53
|
+
end
|
87
54
|
|
55
|
+
match = /\/\* (.*) \*\//.match(line)
|
56
|
+
if match
|
57
|
+
last_comment = match[1]
|
58
|
+
else
|
59
|
+
last_comment = nil
|
88
60
|
end
|
89
61
|
end
|
90
62
|
end
|