yury-twine 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/LICENSE +30 -0
- data/README.md +230 -0
- data/bin/twine +7 -0
- data/lib/twine.rb +36 -0
- data/lib/twine/cli.rb +200 -0
- data/lib/twine/encoding.rb +22 -0
- data/lib/twine/formatters.rb +20 -0
- data/lib/twine/formatters/abstract.rb +187 -0
- data/lib/twine/formatters/android.rb +254 -0
- data/lib/twine/formatters/apple.rb +328 -0
- data/lib/twine/output_processor.rb +57 -0
- data/lib/twine/placeholders.rb +54 -0
- data/lib/twine/plugin.rb +62 -0
- data/lib/twine/runner.rb +332 -0
- data/lib/twine/twine_file.rb +266 -0
- data/lib/twine/version.rb +3 -0
- data/test/command_test.rb +14 -0
- data/test/fixtures/consume_loc_drop.zip +0 -0
- 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/fixtures/formatter_android.xml +15 -0
- data/test/fixtures/formatter_apple.strings +20 -0
- data/test/fixtures/formatter_django.po +30 -0
- data/test/fixtures/formatter_flash.properties +15 -0
- data/test/fixtures/formatter_gettext.po +26 -0
- data/test/fixtures/formatter_jquery.json +7 -0
- data/test/fixtures/formatter_tizen.xml +15 -0
- data/test/fixtures/gettext_multiline.po +10 -0
- data/test/fixtures/twine_accent_values.txt +13 -0
- data/test/test_abstract_formatter.rb +165 -0
- data/test/test_cli.rb +304 -0
- data/test/test_consume_loc_drop.rb +27 -0
- data/test/test_consume_localization_file.rb +119 -0
- data/test/test_formatters.rb +363 -0
- data/test/test_generate_all_localization_files.rb +102 -0
- data/test/test_generate_loc_drop.rb +80 -0
- data/test/test_generate_localization_file.rb +91 -0
- data/test/test_output_processor.rb +85 -0
- data/test/test_placeholders.rb +84 -0
- data/test/test_twine_definition.rb +111 -0
- data/test/test_twine_file.rb +58 -0
- data/test/test_validate_twine_file.rb +61 -0
- data/test/twine_file_dsl.rb +46 -0
- data/test/twine_test.rb +48 -0
- metadata +179 -0
data/lib/twine/runner.rb
ADDED
@@ -0,0 +1,332 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
Twine::Plugin.new # Initialize plugins first in Runner.
|
5
|
+
|
6
|
+
module Twine
|
7
|
+
class Runner
|
8
|
+
def self.run(args)
|
9
|
+
options = CLI.parse(args)
|
10
|
+
|
11
|
+
twine_file = TwineFile.new
|
12
|
+
twine_file.read options[:twine_file]
|
13
|
+
runner = new(options, twine_file)
|
14
|
+
|
15
|
+
case options[:command]
|
16
|
+
when 'generate-localization-file'
|
17
|
+
runner.generate_localization_file
|
18
|
+
when 'generate-all-localization-files'
|
19
|
+
runner.generate_all_localization_files
|
20
|
+
when 'consume-localization-file'
|
21
|
+
runner.consume_localization_file
|
22
|
+
when 'consume-all-localization-files'
|
23
|
+
runner.consume_all_localization_files
|
24
|
+
when 'generate-loc-drop'
|
25
|
+
runner.generate_loc_drop
|
26
|
+
when 'consume-loc-drop'
|
27
|
+
runner.consume_loc_drop
|
28
|
+
when 'validate-twine-file'
|
29
|
+
runner.validate_twine_file
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(options = {}, twine_file = TwineFile.new)
|
34
|
+
@options = options
|
35
|
+
@twine_file = twine_file
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_twine_data(path)
|
39
|
+
if @options[:developer_language]
|
40
|
+
@twine_file.set_developer_language_code(@options[:developer_language])
|
41
|
+
end
|
42
|
+
@twine_file.write(path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def generate_localization_file
|
46
|
+
validate_twine_file if @options[:validate]
|
47
|
+
|
48
|
+
lang = nil
|
49
|
+
lang = @options[:languages][0] if @options[:languages]
|
50
|
+
|
51
|
+
formatter, lang = prepare_read_write(@options[:output_path], lang)
|
52
|
+
output = formatter.format_file(lang)
|
53
|
+
|
54
|
+
raise Twine::Error.new "Nothing to generate! The resulting file would not contain any translations." unless output
|
55
|
+
|
56
|
+
IO.write(@options[:output_path], output, encoding: encoding)
|
57
|
+
end
|
58
|
+
|
59
|
+
def generate_all_localization_files
|
60
|
+
validate_twine_file if @options[:validate]
|
61
|
+
|
62
|
+
if !File.directory?(@options[:output_path])
|
63
|
+
if @options[:create_folders]
|
64
|
+
FileUtils.mkdir_p(@options[:output_path])
|
65
|
+
else
|
66
|
+
raise Twine::Error.new("Directory does not exist: #{@options[:output_path]}")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
formatter_for_directory = find_formatter { |f| f.can_handle_directory?(@options[:output_path]) }
|
71
|
+
formatter = formatter_for_format(@options[:format]) || formatter_for_directory
|
72
|
+
|
73
|
+
unless formatter
|
74
|
+
raise Twine::Error.new "Could not determine format given the contents of #{@options[:output_path]}"
|
75
|
+
end
|
76
|
+
|
77
|
+
file_name = @options[:file_name] || formatter.default_file_name
|
78
|
+
if @options[:create_folders]
|
79
|
+
@twine_file.language_codes.each do |lang|
|
80
|
+
output_path = File.join(@options[:output_path], formatter.output_path_for_language(lang))
|
81
|
+
|
82
|
+
FileUtils.mkdir_p(output_path)
|
83
|
+
|
84
|
+
file_path = File.join(output_path, file_name)
|
85
|
+
|
86
|
+
output = formatter.format_file(lang)
|
87
|
+
unless output
|
88
|
+
Twine::stderr.puts "Skipping file at path #{file_path} since it would not contain any translations."
|
89
|
+
next
|
90
|
+
end
|
91
|
+
|
92
|
+
IO.write(file_path, output, encoding: encoding)
|
93
|
+
end
|
94
|
+
else
|
95
|
+
language_found = false
|
96
|
+
Dir.foreach(@options[:output_path]) do |item|
|
97
|
+
next if item == "." or item == ".."
|
98
|
+
|
99
|
+
output_path = File.join(@options[:output_path], item)
|
100
|
+
next unless File.directory?(output_path)
|
101
|
+
|
102
|
+
lang = formatter.determine_language_given_path(output_path)
|
103
|
+
next unless lang
|
104
|
+
|
105
|
+
language_found = true
|
106
|
+
|
107
|
+
file_path = File.join(output_path, file_name)
|
108
|
+
output = formatter.format_file(lang)
|
109
|
+
unless output
|
110
|
+
Twine::stderr.puts "Skipping file at path #{file_path} since it would not contain any translations."
|
111
|
+
next
|
112
|
+
end
|
113
|
+
|
114
|
+
IO.write(file_path, output, encoding: encoding)
|
115
|
+
end
|
116
|
+
|
117
|
+
unless language_found
|
118
|
+
raise Twine::Error.new("Failed to generate any files: No languages found at #{@options[:output_path]}")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
def consume_localization_file
|
125
|
+
lang = nil
|
126
|
+
if @options[:languages]
|
127
|
+
lang = @options[:languages][0]
|
128
|
+
end
|
129
|
+
|
130
|
+
read_localization_file(@options[:input_path], lang)
|
131
|
+
output_path = @options[:output_path] || @options[:twine_file]
|
132
|
+
write_twine_data(output_path)
|
133
|
+
end
|
134
|
+
|
135
|
+
def consume_all_localization_files
|
136
|
+
if !File.directory?(@options[:input_path])
|
137
|
+
raise Twine::Error.new("Directory does not exist: #{@options[:output_path]}")
|
138
|
+
end
|
139
|
+
|
140
|
+
Dir.glob(File.join(@options[:input_path], "**/*")) do |item|
|
141
|
+
if File.file?(item)
|
142
|
+
begin
|
143
|
+
read_localization_file(item)
|
144
|
+
rescue Twine::Error => e
|
145
|
+
Twine::stderr.puts "#{e.message}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
output_path = @options[:output_path] || @options[:twine_file]
|
151
|
+
write_twine_data(output_path)
|
152
|
+
end
|
153
|
+
|
154
|
+
def generate_loc_drop
|
155
|
+
validate_twine_file if @options[:validate]
|
156
|
+
|
157
|
+
require_rubyzip
|
158
|
+
|
159
|
+
if File.file?(@options[:output_path])
|
160
|
+
File.delete(@options[:output_path])
|
161
|
+
end
|
162
|
+
|
163
|
+
Dir.mktmpdir do |temp_dir|
|
164
|
+
Zip::File.open(@options[:output_path], Zip::File::CREATE) do |zipfile|
|
165
|
+
zipfile.mkdir('Locales')
|
166
|
+
|
167
|
+
formatter = formatter_for_format(@options[:format])
|
168
|
+
@twine_file.language_codes.each do |lang|
|
169
|
+
if @options[:languages] == nil || @options[:languages].length == 0 || @options[:languages].include?(lang)
|
170
|
+
file_name = lang + formatter.extension
|
171
|
+
temp_path = File.join(temp_dir, file_name)
|
172
|
+
zip_path = File.join('Locales', file_name)
|
173
|
+
|
174
|
+
output = formatter.format_file(lang)
|
175
|
+
unless output
|
176
|
+
Twine::stderr.puts "Skipping file #{file_name} since it would not contain any translations."
|
177
|
+
next
|
178
|
+
end
|
179
|
+
|
180
|
+
IO.write(temp_path, output, encoding: encoding)
|
181
|
+
zipfile.add(zip_path, temp_path)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def consume_loc_drop
|
189
|
+
require_rubyzip
|
190
|
+
|
191
|
+
if !File.file?(@options[:input_path])
|
192
|
+
raise Twine::Error.new("File does not exist: #{@options[:input_path]}")
|
193
|
+
end
|
194
|
+
|
195
|
+
Dir.mktmpdir do |temp_dir|
|
196
|
+
Zip::File.open(@options[:input_path]) do |zipfile|
|
197
|
+
zipfile.each do |entry|
|
198
|
+
next if entry.name.end_with? '/' or File.basename(entry.name).start_with? '.'
|
199
|
+
|
200
|
+
real_path = File.join(temp_dir, entry.name)
|
201
|
+
FileUtils.mkdir_p(File.dirname(real_path))
|
202
|
+
zipfile.extract(entry.name, real_path)
|
203
|
+
begin
|
204
|
+
read_localization_file(real_path)
|
205
|
+
rescue Twine::Error => e
|
206
|
+
Twine::stderr.puts "#{e.message}"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
output_path = @options[:output_path] || @options[:twine_file]
|
213
|
+
write_twine_data(output_path)
|
214
|
+
end
|
215
|
+
|
216
|
+
def validate_twine_file
|
217
|
+
total_definitions = 0
|
218
|
+
all_keys = Set.new
|
219
|
+
duplicate_keys = Set.new
|
220
|
+
keys_without_tags = Set.new
|
221
|
+
invalid_keys = Set.new
|
222
|
+
valid_key_regex = /^[A-Za-z0-9_]+$/
|
223
|
+
|
224
|
+
@twine_file.sections.each do |section|
|
225
|
+
section.definitions.each do |definition|
|
226
|
+
total_definitions += 1
|
227
|
+
|
228
|
+
duplicate_keys.add(definition.key) if all_keys.include? definition.key
|
229
|
+
all_keys.add(definition.key)
|
230
|
+
|
231
|
+
keys_without_tags.add(definition.key) if definition.tags == nil or definition.tags.length == 0
|
232
|
+
|
233
|
+
invalid_keys << definition.key unless definition.key =~ valid_key_regex
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
errors = []
|
238
|
+
join_keys = lambda { |set| set.map { |k| " " + k }.join("\n") }
|
239
|
+
|
240
|
+
unless duplicate_keys.empty?
|
241
|
+
errors << "Found duplicate key(s):\n#{join_keys.call(duplicate_keys)}"
|
242
|
+
end
|
243
|
+
|
244
|
+
if @options[:pedantic]
|
245
|
+
if keys_without_tags.length == total_definitions
|
246
|
+
errors << "None of your definitions have tags."
|
247
|
+
elsif keys_without_tags.length > 0
|
248
|
+
errors << "Found definitions without tags:\n#{join_keys.call(keys_without_tags)}"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
unless invalid_keys.empty?
|
253
|
+
errors << "Found key(s) with invalid characters:\n#{join_keys.call(invalid_keys)}"
|
254
|
+
end
|
255
|
+
|
256
|
+
raise Twine::Error.new errors.join("\n\n") unless errors.empty?
|
257
|
+
|
258
|
+
Twine::stdout.puts "#{@options[:twine_file]} is valid."
|
259
|
+
end
|
260
|
+
|
261
|
+
private
|
262
|
+
|
263
|
+
def encoding
|
264
|
+
@options[:output_encoding] || 'UTF-8'
|
265
|
+
end
|
266
|
+
|
267
|
+
def require_rubyzip
|
268
|
+
begin
|
269
|
+
require 'zip'
|
270
|
+
rescue LoadError
|
271
|
+
raise Twine::Error.new "You must run 'gem install rubyzip' in order to create or consume localization drops."
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def determine_language_given_path(path)
|
276
|
+
code = File.basename(path, File.extname(path))
|
277
|
+
return code if @twine_file.language_codes.include? code
|
278
|
+
end
|
279
|
+
|
280
|
+
def formatter_for_format(format)
|
281
|
+
find_formatter { |f| f.format_name == format }
|
282
|
+
end
|
283
|
+
|
284
|
+
def find_formatter(&block)
|
285
|
+
formatter = Formatters.formatters.find &block
|
286
|
+
return nil unless formatter
|
287
|
+
formatter.twine_file = @twine_file
|
288
|
+
formatter.options = @options
|
289
|
+
formatter
|
290
|
+
end
|
291
|
+
|
292
|
+
def read_localization_file(path, lang = nil)
|
293
|
+
unless File.file?(path)
|
294
|
+
raise Twine::Error.new("File does not exist: #{path}")
|
295
|
+
end
|
296
|
+
|
297
|
+
formatter, lang = prepare_read_write(path, lang)
|
298
|
+
|
299
|
+
encoding = @options[:encoding] || Twine::Encoding.encoding_for_path(path)
|
300
|
+
|
301
|
+
IO.open(IO.sysopen(path, 'rb'), 'rb', external_encoding: encoding, internal_encoding: 'UTF-8') do |io|
|
302
|
+
io.read(2) if Twine::Encoding.has_bom?(path)
|
303
|
+
formatter.read(io, lang)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def prepare_read_write(path, lang)
|
308
|
+
formatter_for_path = find_formatter { |f| f.extension == File.extname(path) }
|
309
|
+
formatter = formatter_for_format(@options[:format]) || formatter_for_path
|
310
|
+
|
311
|
+
unless formatter
|
312
|
+
if !path.include?("Localizable.stringsdict")
|
313
|
+
raise Twine::Error.new "Unable to determine format of #{path}"
|
314
|
+
else
|
315
|
+
raise Twine::Error.new "Ignoring for now #{path}"
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
if formatter.can_handle_file?(path)
|
320
|
+
lang = lang || determine_language_given_path(path) || formatter.determine_language_given_path(path)
|
321
|
+
unless lang
|
322
|
+
raise Twine::Error.new "Unable to determine language for #{path}"
|
323
|
+
end
|
324
|
+
|
325
|
+
@twine_file.language_codes << lang unless @twine_file.language_codes.include? lang
|
326
|
+
else
|
327
|
+
raise Twine::Error.new "Ignoring #{path}"
|
328
|
+
end
|
329
|
+
return formatter, lang
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
module Twine
|
2
|
+
class TwineDefinition
|
3
|
+
attr_reader :key
|
4
|
+
attr_accessor :comment
|
5
|
+
attr_accessor :ios_comment
|
6
|
+
attr_accessor :tags
|
7
|
+
attr_reader :translations
|
8
|
+
attr_accessor :reference
|
9
|
+
attr_accessor :reference_key
|
10
|
+
|
11
|
+
def initialize(key)
|
12
|
+
@key = key
|
13
|
+
@comment = nil
|
14
|
+
@ios_comment = nil
|
15
|
+
@tags = nil
|
16
|
+
@translations = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def comment
|
20
|
+
raw_comment || (reference.comment if reference)
|
21
|
+
end
|
22
|
+
|
23
|
+
def raw_comment
|
24
|
+
@comment
|
25
|
+
end
|
26
|
+
|
27
|
+
def ios_comment
|
28
|
+
raw_ios_comment || (reference.ios_comment if reference)
|
29
|
+
end
|
30
|
+
|
31
|
+
def raw_ios_comment
|
32
|
+
@ios_comment
|
33
|
+
end
|
34
|
+
|
35
|
+
# [['tag1', 'tag2'], ['~tag3']] == (tag1 OR tag2) AND (!tag3)
|
36
|
+
def matches_tags?(tags, include_untagged)
|
37
|
+
if tags == nil || tags.empty? # The user did not specify any tags. Everything passes.
|
38
|
+
return true
|
39
|
+
elsif @tags == nil # This definition has no tags -> check reference (if any)
|
40
|
+
return reference ? reference.matches_tags?(tags, include_untagged) : include_untagged
|
41
|
+
elsif @tags.empty?
|
42
|
+
return include_untagged
|
43
|
+
else
|
44
|
+
return tags.all? do |set|
|
45
|
+
regular_tags, negated_tags = set.partition { |tag| tag[0] != '~' }
|
46
|
+
negated_tags.map! { |tag| tag[1..-1] }
|
47
|
+
matches_regular_tags = (!regular_tags.empty? && !(regular_tags & @tags).empty?)
|
48
|
+
matches_negated_tags = (!negated_tags.empty? && (negated_tags & @tags).empty?)
|
49
|
+
matches_regular_tags or matches_negated_tags
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
|
56
|
+
def translation_for_lang(lang)
|
57
|
+
translation = [lang].flatten.map { |l| @translations[l] }.first
|
58
|
+
|
59
|
+
translation = reference.translation_for_lang(lang) if translation.nil? && reference
|
60
|
+
|
61
|
+
return translation
|
62
|
+
end
|
63
|
+
|
64
|
+
# Twine adds a copy of the dev language's translation if there is no definition provided for the language,
|
65
|
+
# which is useful in the main Localizable.strings file because iOS doesn't auto use the Base language's
|
66
|
+
# value, but we don't want that behaviour in our plurals
|
67
|
+
def translation_for_lang_or_nil(lang, dev_lang)
|
68
|
+
translation = [lang].flatten.map { |l| @translations[l] }.first
|
69
|
+
|
70
|
+
# translation never comes back as nil because Twine fills with the dev_lang string
|
71
|
+
if lang != dev_lang
|
72
|
+
[lang].flatten.map do |l|
|
73
|
+
if @translations[l] == @translations[dev_lang]
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
return translation
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class TwineSection
|
84
|
+
attr_reader :name
|
85
|
+
attr_reader :definitions
|
86
|
+
|
87
|
+
def initialize(name)
|
88
|
+
@name = name
|
89
|
+
@definitions = []
|
90
|
+
end
|
91
|
+
|
92
|
+
def is_uncategorized
|
93
|
+
return @name == 'Uncategorized'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class TwineFile
|
98
|
+
attr_reader :sections
|
99
|
+
attr_reader :definitions_by_key
|
100
|
+
attr_reader :language_codes
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def match_key(text)
|
105
|
+
match = /^\[(.+)\]$/.match(text)
|
106
|
+
return match[1] if match
|
107
|
+
end
|
108
|
+
|
109
|
+
public
|
110
|
+
|
111
|
+
def initialize
|
112
|
+
@sections = []
|
113
|
+
@definitions_by_key = {}
|
114
|
+
@language_codes = []
|
115
|
+
end
|
116
|
+
|
117
|
+
def add_language_code(code)
|
118
|
+
if @language_codes.length == 0
|
119
|
+
@language_codes << code
|
120
|
+
elsif !@language_codes.include?(code)
|
121
|
+
dev_lang = @language_codes[0]
|
122
|
+
@language_codes << code
|
123
|
+
@language_codes.delete(dev_lang)
|
124
|
+
@language_codes.sort!
|
125
|
+
@language_codes.insert(0, dev_lang)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def set_developer_language_code(code)
|
130
|
+
@language_codes.delete(code)
|
131
|
+
@language_codes.insert(0, code)
|
132
|
+
end
|
133
|
+
|
134
|
+
def read(path)
|
135
|
+
if !File.file?(path)
|
136
|
+
file = File.new(path, 'w:UTF-8')
|
137
|
+
file.close
|
138
|
+
end
|
139
|
+
|
140
|
+
File.open(path, 'r:UTF-8') do |f|
|
141
|
+
line_num = 0
|
142
|
+
current_section = nil
|
143
|
+
current_definition = nil
|
144
|
+
while line = f.gets
|
145
|
+
parsed = false
|
146
|
+
line.strip!
|
147
|
+
line_num += 1
|
148
|
+
|
149
|
+
if line.length == 0
|
150
|
+
next
|
151
|
+
end
|
152
|
+
|
153
|
+
if line.length > 4 && line[0, 2] == '[['
|
154
|
+
match = /^\[\[(.+)\]\]$/.match(line)
|
155
|
+
if match
|
156
|
+
current_section = TwineSection.new(match[1])
|
157
|
+
@sections << current_section
|
158
|
+
parsed = true
|
159
|
+
end
|
160
|
+
elsif line.length > 2 && line[0, 1] == '['
|
161
|
+
key = match_key(line)
|
162
|
+
if key
|
163
|
+
current_definition = TwineDefinition.new(key)
|
164
|
+
@definitions_by_key[current_definition.key] = current_definition
|
165
|
+
if !current_section
|
166
|
+
current_section = TwineSection.new('')
|
167
|
+
@sections << current_section
|
168
|
+
end
|
169
|
+
current_section.definitions << current_definition
|
170
|
+
parsed = true
|
171
|
+
end
|
172
|
+
else
|
173
|
+
match = /^([^=]+)=(.*)$/.match(line)
|
174
|
+
if match
|
175
|
+
key = match[1].strip
|
176
|
+
value = match[2].strip
|
177
|
+
|
178
|
+
value = value[1..-2] if value[0] == '`' && value[-1] == '`'
|
179
|
+
case key
|
180
|
+
when 'ios'
|
181
|
+
current_definition.ios_comment = value
|
182
|
+
when 'comment'
|
183
|
+
current_definition.comment = value
|
184
|
+
when 'tags'
|
185
|
+
current_definition.tags = value.split(',')
|
186
|
+
when 'ref'
|
187
|
+
current_definition.reference_key = value if value
|
188
|
+
else
|
189
|
+
if !@language_codes.include? key
|
190
|
+
add_language_code(key)
|
191
|
+
end
|
192
|
+
current_definition.translations[key] = value
|
193
|
+
end
|
194
|
+
parsed = true
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
if !parsed
|
199
|
+
raise Twine::Error.new("Unable to parse line #{line_num} of #{path}: #{line}")
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# resolve_references
|
205
|
+
@definitions_by_key.each do |key, definition|
|
206
|
+
next unless definition.reference_key
|
207
|
+
definition.reference = @definitions_by_key[definition.reference_key]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def write(path)
|
212
|
+
dev_lang = @language_codes[0]
|
213
|
+
|
214
|
+
File.open(path, 'w:UTF-8') do |f|
|
215
|
+
@sections.each do |section|
|
216
|
+
if f.pos > 0
|
217
|
+
f.puts ''
|
218
|
+
end
|
219
|
+
|
220
|
+
f.puts "[[#{section.name}]]"
|
221
|
+
|
222
|
+
section.definitions.each do |definition|
|
223
|
+
f.puts "\t[#{definition.key}]"
|
224
|
+
|
225
|
+
value = write_value(definition, dev_lang, f)
|
226
|
+
if !value && !definition.reference_key
|
227
|
+
puts "Warning: #{definition.key} does not exist in developer language '#{dev_lang}'"
|
228
|
+
end
|
229
|
+
|
230
|
+
if definition.reference_key
|
231
|
+
f.puts "\t\tref = #{definition.reference_key}"
|
232
|
+
end
|
233
|
+
if definition.tags && definition.tags.length > 0
|
234
|
+
tag_str = definition.tags.join(',')
|
235
|
+
f.puts "\t\ttags = #{tag_str}"
|
236
|
+
end
|
237
|
+
if definition.raw_comment and definition.raw_comment.length > 0
|
238
|
+
f.puts "\t\tcomment = #{definition.raw_comment}"
|
239
|
+
end
|
240
|
+
if definition.raw_ios_comment and definition.raw_ios_comment.length > 0
|
241
|
+
f.puts "\t\tios = #{definition.raw_ios_comment}"
|
242
|
+
end
|
243
|
+
@language_codes[1..-1].each do |lang|
|
244
|
+
write_value(definition, lang, f)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
def write_value(definition, language, file)
|
254
|
+
value = definition.translations[language]
|
255
|
+
return nil unless value
|
256
|
+
|
257
|
+
if value[0] == ' ' || value[-1] == ' ' || (value[0] == '`' && value[-1] == '`')
|
258
|
+
value = '`' + value + '`'
|
259
|
+
end
|
260
|
+
|
261
|
+
file.puts "\t\t#{language} = #{value}"
|
262
|
+
return value
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
end
|