twine 0.7.0 → 0.8.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 +12 -0
- data/lib/twine.rb +21 -0
- data/lib/twine/cli.rb +68 -91
- data/lib/twine/formatters/abstract.rb +133 -82
- data/lib/twine/formatters/android.rb +49 -67
- data/lib/twine/formatters/apple.rb +33 -47
- data/lib/twine/formatters/django.rb +38 -48
- data/lib/twine/formatters/flash.rb +25 -40
- data/lib/twine/formatters/gettext.rb +37 -44
- data/lib/twine/formatters/jquery.rb +31 -33
- data/lib/twine/formatters/tizen.rb +38 -55
- data/lib/twine/output_processor.rb +57 -0
- data/lib/twine/placeholders.rb +54 -0
- data/lib/twine/runner.rb +78 -115
- data/lib/twine/stringsfile.rb +83 -60
- data/lib/twine/version.rb +1 -1
- data/test/command_test_case.rb +12 -0
- data/test/fixtures/consume_loc_drop.zip +0 -0
- data/test/fixtures/formatter_android.xml +15 -0
- data/test/fixtures/formatter_apple.strings +20 -0
- data/test/fixtures/formatter_django.po +28 -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 +152 -0
- data/test/test_cli.rb +288 -0
- data/test/test_consume_loc_drop.rb +27 -0
- data/test/test_consume_string_file.rb +53 -0
- data/test/test_formatters.rb +236 -0
- data/test/test_generate_all_string_files.rb +44 -0
- data/test/test_generate_loc_drop.rb +44 -0
- data/test/test_generate_string_file.rb +51 -0
- data/test/test_output_processor.rb +85 -0
- data/test/test_placeholders.rb +86 -0
- data/test/test_strings_file.rb +58 -0
- data/test/test_strings_row.rb +47 -0
- data/test/test_validate_strings_file.rb +55 -0
- data/test/twine_file_dsl.rb +46 -0
- data/test/twine_test_case.rb +44 -0
- metadata +80 -37
- data/test/fixtures/en-1.json +0 -5
- data/test/fixtures/en-1.po +0 -16
- data/test/fixtures/en-1.strings +0 -10
- data/test/fixtures/en-2.po +0 -23
- data/test/fixtures/en-3.xml +0 -8
- data/test/fixtures/fr-1.xml +0 -10
- data/test/fixtures/strings-1.txt +0 -17
- data/test/fixtures/strings-2.txt +0 -5
- data/test/fixtures/strings-3.txt +0 -5
- data/test/fixtures/test-json-line-breaks/consumed.txt +0 -5
- data/test/fixtures/test-json-line-breaks/generated.json +0 -3
- data/test/fixtures/test-json-line-breaks/line-breaks.json +0 -3
- data/test/fixtures/test-json-line-breaks/line-breaks.txt +0 -4
- data/test/fixtures/test-output-1.txt +0 -12
- data/test/fixtures/test-output-10.txt +0 -9
- data/test/fixtures/test-output-11.txt +0 -9
- data/test/fixtures/test-output-12.txt +0 -12
- data/test/fixtures/test-output-2.txt +0 -12
- data/test/fixtures/test-output-3.txt +0 -18
- data/test/fixtures/test-output-4.txt +0 -21
- data/test/fixtures/test-output-5.txt +0 -4
- data/test/fixtures/test-output-6.txt +0 -10
- data/test/fixtures/test-output-7.txt +0 -16
- data/test/fixtures/test-output-8.txt +0 -9
- data/test/fixtures/test-output-9.txt +0 -21
- data/test/twine_test.rb +0 -134
@@ -60,50 +60,43 @@ module Twine
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
-
def
|
64
|
-
default_lang =
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
end
|
101
|
-
f.print "msgstr \"#{value}\"\n\n"
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
63
|
+
def format_file(strings, lang)
|
64
|
+
@default_lang = strings.language_codes[0]
|
65
|
+
super
|
66
|
+
end
|
67
|
+
|
68
|
+
def format_header(lang)
|
69
|
+
"msgid \"\"\nmsgstr \"\"\n\"Language: #{lang}\\n\"\n\"X-Generator: Twine #{Twine::VERSION}\\n\"\n"
|
70
|
+
end
|
71
|
+
|
72
|
+
def format_section_header(section)
|
73
|
+
"# SECTION: #{section.name}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def row_pattern
|
77
|
+
"%{comment}%{key}%{base_translation}%{value}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def format_row(row, lang)
|
81
|
+
return nil unless row.translated_string_for_lang(@default_lang)
|
82
|
+
|
83
|
+
super
|
84
|
+
end
|
85
|
+
|
86
|
+
def format_comment(row, lang)
|
87
|
+
"#. \"#{escape_quotes(row.comment)}\"\n" if row.comment
|
88
|
+
end
|
89
|
+
|
90
|
+
def format_key(row, lang)
|
91
|
+
"msgctxt \"#{row.key.dup}\"\n"
|
92
|
+
end
|
93
|
+
|
94
|
+
def format_base_translation(row, lang)
|
95
|
+
"msgid \"#{row.translations[@default_lang]}\"\n"
|
96
|
+
end
|
97
|
+
|
98
|
+
def format_value(row, lang)
|
99
|
+
"msgstr \"#{row.translated_string_for_lang(lang)}\"\n"
|
107
100
|
end
|
108
101
|
end
|
109
102
|
end
|
@@ -35,52 +35,50 @@ module Twine
|
|
35
35
|
open(path) do |io|
|
36
36
|
json = JSON.load(io)
|
37
37
|
json.each do |key, value|
|
38
|
-
value.gsub!("\n","\\n")
|
39
38
|
set_translation_for_key(key, lang, value)
|
40
39
|
end
|
41
40
|
end
|
42
41
|
end
|
43
42
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
rescue LoadError
|
48
|
-
raise Twine::Error.new "You must run 'gem install json' in order to read or write jquery-localize files."
|
49
|
-
end
|
43
|
+
def format_file(strings, lang)
|
44
|
+
"{\n#{super}\n}"
|
45
|
+
end
|
50
46
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
47
|
+
def format_sections(strings, lang)
|
48
|
+
sections = strings.sections.map { |section| format_section(section, lang) }
|
49
|
+
sections.join(",\n\n")
|
50
|
+
end
|
51
|
+
|
52
|
+
def format_section_header(section)
|
53
|
+
end
|
56
54
|
|
57
|
-
|
58
|
-
|
59
|
-
section.rows.each_with_index do |row, ri|
|
60
|
-
if row.matches_tags?(@options[:tags], @options[:untagged])
|
61
|
-
if printed_string
|
62
|
-
f.print ",\n"
|
63
|
-
end
|
55
|
+
def format_section(section, lang)
|
56
|
+
rows = section.rows.dup
|
64
57
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
58
|
+
rows.map! { |row| format_row(row, lang) }
|
59
|
+
rows.compact! # remove nil entries
|
60
|
+
rows.join(",\n")
|
61
|
+
end
|
69
62
|
|
70
|
-
|
71
|
-
|
63
|
+
def key_value_pattern
|
64
|
+
"\"%{key}\":\"%{value}\""
|
65
|
+
end
|
72
66
|
|
73
|
-
|
74
|
-
|
67
|
+
def format_key(key)
|
68
|
+
escape_quotes(key)
|
69
|
+
end
|
75
70
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
end
|
80
|
-
end
|
81
|
-
f.puts "\n}"
|
71
|
+
def format_value(value)
|
72
|
+
escape_quotes(value)
|
73
|
+
end
|
82
74
|
|
75
|
+
def write_file(path, lang)
|
76
|
+
begin
|
77
|
+
require "json"
|
78
|
+
rescue LoadError
|
79
|
+
raise Twine::Error.new "You must run 'gem install json' in order to read or write jquery-localize files."
|
83
80
|
end
|
81
|
+
super
|
84
82
|
end
|
85
83
|
end
|
86
84
|
end
|
@@ -5,6 +5,8 @@ require 'rexml/document'
|
|
5
5
|
module Twine
|
6
6
|
module Formatters
|
7
7
|
class Tizen < Abstract
|
8
|
+
include Twine::Placeholders
|
9
|
+
|
8
10
|
FORMAT_NAME = 'tizen'
|
9
11
|
EXTENSION = '.xml'
|
10
12
|
DEFAULT_FILE_NAME = 'strings.xml'
|
@@ -90,7 +92,7 @@ module Twine
|
|
90
92
|
value = CGI.unescapeHTML(value)
|
91
93
|
value.gsub!('\\\'', '\'')
|
92
94
|
value.gsub!('\\"', '"')
|
93
|
-
value =
|
95
|
+
value = convert_placeholders_from_android_to_twine(value)
|
94
96
|
value.gsub!(/(\\u0020)*|(\\u0020)*\z/) { |spaces| ' ' * (spaces.length / 6) }
|
95
97
|
else
|
96
98
|
value = ""
|
@@ -111,64 +113,45 @@ module Twine
|
|
111
113
|
end
|
112
114
|
end
|
113
115
|
|
114
|
-
def
|
115
|
-
|
116
|
-
|
117
|
-
default_lang = DEFAULT_LANG_CODES[lang]
|
118
|
-
end
|
119
|
-
File.open(path, 'w:UTF-8') do |f|
|
120
|
-
f.puts "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Tizen Strings File -->\n<!-- Generated by Twine #{Twine::VERSION} -->\n<!-- Language: #{lang} -->"
|
121
|
-
f.write '<string_table Bversion="2.0.0.201311071819" Dversion="20120315">'
|
122
|
-
@strings.sections.each do |section|
|
123
|
-
printed_section = false
|
124
|
-
section.rows.each do |row|
|
125
|
-
if row.matches_tags?(@options[:tags], @options[:untagged])
|
126
|
-
if !printed_section
|
127
|
-
f.puts ''
|
128
|
-
if section.name && section.name.length > 0
|
129
|
-
section_name = section.name.gsub('--', '—')
|
130
|
-
f.puts "\t<!-- SECTION: #{section_name} -->"
|
131
|
-
end
|
132
|
-
printed_section = true
|
133
|
-
end
|
116
|
+
def format_header(lang)
|
117
|
+
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Tizen Strings File -->\n<!-- Generated by Twine #{Twine::VERSION} -->\n<!-- Language: #{lang} -->"
|
118
|
+
end
|
134
119
|
|
135
|
-
|
120
|
+
def format_sections(strings, lang)
|
121
|
+
result = '<string_table Bversion="2.0.0.201311071819" Dversion="20120315">'
|
122
|
+
|
123
|
+
result += super + "\n"
|
136
124
|
|
137
|
-
|
138
|
-
|
139
|
-
value = row.translated_string_for_lang(@strings.language_codes[0])
|
140
|
-
end
|
125
|
+
result += '</string_table>'
|
126
|
+
end
|
141
127
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
# Tizen enforces the following rules on the values
|
146
|
-
# 1) apostrophes and quotes must be escaped with a backslash
|
147
|
-
value.gsub!('\'', '\\\\\'')
|
148
|
-
value.gsub!('"', '\\\\"')
|
149
|
-
# 2) HTML escape the string
|
150
|
-
value = CGI.escapeHTML(value)
|
151
|
-
# 3) fix substitutions (e.g. %s/%@)
|
152
|
-
value = androidify_substitutions(value)
|
153
|
-
# 4) replace beginning and end spaces with \0020. Otherwise Tizen strips them.
|
154
|
-
value.gsub!(/\A *| *\z/) { |spaces| '\u0020' * spaces.length }
|
155
|
-
|
156
|
-
comment = row.comment
|
157
|
-
if comment
|
158
|
-
comment = comment.gsub('--', '—')
|
159
|
-
end
|
160
|
-
|
161
|
-
if comment && comment.length > 0
|
162
|
-
f.puts "\t<!-- #{comment} -->\n"
|
163
|
-
end
|
164
|
-
f.puts "\t<text id=\"IDS_#{key.upcase}\">#{value}</text>"
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
128
|
+
def format_section_header(section)
|
129
|
+
"\t<!-- SECTION: #{section.name} -->"
|
130
|
+
end
|
169
131
|
|
170
|
-
|
171
|
-
|
132
|
+
def format_comment(row, lang)
|
133
|
+
"\t<!-- #{row.comment.gsub('--', '—')} -->\n" if row.comment
|
134
|
+
end
|
135
|
+
|
136
|
+
def key_value_pattern
|
137
|
+
"\t<text id=\"IDS_%{key}\">%{value}</text>"
|
138
|
+
end
|
139
|
+
|
140
|
+
def format_key(key)
|
141
|
+
key.upcase
|
142
|
+
end
|
143
|
+
|
144
|
+
def format_value(value)
|
145
|
+
value = escape_quotes(value)
|
146
|
+
# Tizen enforces the following rules on the values
|
147
|
+
# 1) apostrophes and quotes must be escaped with a backslash
|
148
|
+
value.gsub!("'", "\\\\'")
|
149
|
+
# 2) HTML escape the string
|
150
|
+
value = CGI.escapeHTML(value)
|
151
|
+
# 3) fix substitutions (e.g. %s/%@)
|
152
|
+
value = convert_placeholders_from_twine_to_android(value)
|
153
|
+
# 4) replace beginning and end spaces with \0020. Otherwise Tizen strips them.
|
154
|
+
value.gsub(/\A *| *\z/) { |spaces| '\u0020' * spaces.length }
|
172
155
|
end
|
173
156
|
end
|
174
157
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Twine
|
2
|
+
module Processors
|
3
|
+
|
4
|
+
class OutputProcessor
|
5
|
+
def initialize(strings, options)
|
6
|
+
@strings = strings
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def default_language
|
11
|
+
@options[:developer_language] || @strings.language_codes[0]
|
12
|
+
end
|
13
|
+
|
14
|
+
def fallback_languages(language)
|
15
|
+
fallback_mapping = {
|
16
|
+
'zh-TW' => 'zh-Hant' # if we don't have a zh-TW translation, try zh-Hant before en
|
17
|
+
}
|
18
|
+
|
19
|
+
[fallback_mapping[language], default_language].flatten.compact
|
20
|
+
end
|
21
|
+
|
22
|
+
def process(language)
|
23
|
+
result = StringsFile.new
|
24
|
+
|
25
|
+
result.language_codes.concat @strings.language_codes
|
26
|
+
@strings.sections.each do |section|
|
27
|
+
new_section = StringsSection.new section.name
|
28
|
+
|
29
|
+
section.rows.each do |row|
|
30
|
+
next unless row.matches_tags?(@options[:tags], @options[:untagged])
|
31
|
+
|
32
|
+
value = row.translated_string_for_lang(language)
|
33
|
+
|
34
|
+
next if value && @options[:include] == 'untranslated'
|
35
|
+
|
36
|
+
if value.nil? && @options[:include] != 'translated'
|
37
|
+
value = row.translated_string_for_lang(fallback_languages(language))
|
38
|
+
end
|
39
|
+
|
40
|
+
next unless value
|
41
|
+
|
42
|
+
new_row = row.dup
|
43
|
+
new_row.translations[language] = value
|
44
|
+
|
45
|
+
new_section.rows << new_row
|
46
|
+
result.strings_map[new_row.key] = new_row
|
47
|
+
end
|
48
|
+
|
49
|
+
result.sections << new_section
|
50
|
+
end
|
51
|
+
|
52
|
+
return result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Twine
|
2
|
+
module Placeholders
|
3
|
+
extend self
|
4
|
+
|
5
|
+
PLACEHOLDER_FLAGS_WIDTH_PRECISION_LENGTH = '([-+ 0#])?(\d+|\*)?(\.(\d+|\*))?(hh?|ll?|L|z|j|t)?'
|
6
|
+
PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH = '(\d+\$)?' + PLACEHOLDER_FLAGS_WIDTH_PRECISION_LENGTH
|
7
|
+
|
8
|
+
# http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling
|
9
|
+
# http://stackoverflow.com/questions/4414389/android-xml-percent-symbol
|
10
|
+
# https://github.com/mobiata/twine/pull/106
|
11
|
+
def convert_placeholders_from_twine_to_android(input)
|
12
|
+
placeholder_types = '[diufFeEgGxXoscpaA]'
|
13
|
+
|
14
|
+
# %@ -> %s
|
15
|
+
value = input.gsub(/(%#{PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH})@/, '\1s')
|
16
|
+
|
17
|
+
placeholder_syntax = PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH + placeholder_types
|
18
|
+
placeholder_regex = /%#{placeholder_syntax}/
|
19
|
+
|
20
|
+
number_of_placeholders = value.scan(placeholder_regex).size
|
21
|
+
|
22
|
+
return value if number_of_placeholders == 0
|
23
|
+
|
24
|
+
# got placeholders -> need to double single percent signs
|
25
|
+
# % -> %% (but %% -> %%, %d -> %d)
|
26
|
+
single_percent_regex = /([^%])(%)(?!(%|#{placeholder_syntax}))/
|
27
|
+
value.gsub! single_percent_regex, '\1%%'
|
28
|
+
|
29
|
+
return value if number_of_placeholders < 2
|
30
|
+
|
31
|
+
# number placeholders
|
32
|
+
non_numbered_placeholder_regex = /%(#{PLACEHOLDER_FLAGS_WIDTH_PRECISION_LENGTH}#{placeholder_types})/
|
33
|
+
|
34
|
+
number_of_non_numbered_placeholders = value.scan(non_numbered_placeholder_regex).size
|
35
|
+
|
36
|
+
return value if number_of_non_numbered_placeholders == 0
|
37
|
+
|
38
|
+
raise Twine::Error.new("The value \"#{input}\" contains numbered and non-numbered placeholders") if number_of_placeholders != number_of_non_numbered_placeholders
|
39
|
+
|
40
|
+
# %d -> %$1d
|
41
|
+
index = 0
|
42
|
+
value.gsub!(non_numbered_placeholder_regex) { "%#{index += 1}$#{$1}" }
|
43
|
+
|
44
|
+
value
|
45
|
+
end
|
46
|
+
|
47
|
+
def convert_placeholders_from_android_to_twine(input)
|
48
|
+
placeholder_regex = /(%#{PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH})s/
|
49
|
+
|
50
|
+
# %s -> %@
|
51
|
+
input.gsub(placeholder_regex, '\1@')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/twine/runner.rb
CHANGED
@@ -1,30 +1,38 @@
|
|
1
1
|
require 'tmpdir'
|
2
|
+
require 'fileutils'
|
2
3
|
|
3
4
|
Twine::Plugin.new # Initialize plugins first in Runner.
|
4
5
|
|
5
6
|
module Twine
|
6
|
-
VALID_COMMANDS = ['generate-string-file', 'generate-all-string-files', 'consume-string-file', 'consume-all-string-files', 'generate-loc-drop', 'consume-loc-drop', 'validate-strings-file']
|
7
|
-
|
8
7
|
class Runner
|
9
|
-
def initialize(args)
|
10
|
-
@options = {}
|
11
|
-
@args = args
|
12
|
-
end
|
13
|
-
|
14
8
|
def self.run(args)
|
15
|
-
|
16
|
-
|
9
|
+
options = CLI.parse(args)
|
10
|
+
|
11
|
+
strings = StringsFile.new
|
12
|
+
strings.read options[:strings_file]
|
13
|
+
runner = new(options, strings)
|
17
14
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
case options[:command]
|
16
|
+
when 'generate-string-file'
|
17
|
+
runner.generate_string_file
|
18
|
+
when 'generate-all-string-files'
|
19
|
+
runner.generate_all_string_files
|
20
|
+
when 'consume-string-file'
|
21
|
+
runner.consume_string_file
|
22
|
+
when 'consume-all-string-files'
|
23
|
+
runner.consume_all_string_files
|
24
|
+
when 'generate-loc-drop'
|
25
|
+
runner.generate_loc_drop
|
26
|
+
when 'consume-loc-drop'
|
27
|
+
runner.consume_loc_drop
|
28
|
+
when 'validate-strings-file'
|
29
|
+
runner.validate_strings_file
|
30
|
+
end
|
23
31
|
end
|
24
32
|
|
25
|
-
def
|
26
|
-
@
|
27
|
-
@strings
|
33
|
+
def initialize(options = {}, strings = StringsFile.new)
|
34
|
+
@options = options
|
35
|
+
@strings = strings
|
28
36
|
end
|
29
37
|
|
30
38
|
def write_strings_data(path)
|
@@ -34,44 +42,24 @@ module Twine
|
|
34
42
|
@strings.write(path)
|
35
43
|
end
|
36
44
|
|
37
|
-
def execute_command
|
38
|
-
case @options[:command]
|
39
|
-
when 'generate-string-file'
|
40
|
-
generate_string_file
|
41
|
-
when 'generate-all-string-files'
|
42
|
-
generate_all_string_files
|
43
|
-
when 'consume-string-file'
|
44
|
-
consume_string_file
|
45
|
-
when 'consume-all-string-files'
|
46
|
-
consume_all_string_files
|
47
|
-
when 'generate-loc-drop'
|
48
|
-
generate_loc_drop
|
49
|
-
when 'consume-loc-drop'
|
50
|
-
consume_loc_drop
|
51
|
-
when 'validate-strings-file'
|
52
|
-
validate_strings_file
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
45
|
def generate_string_file
|
57
46
|
lang = nil
|
58
|
-
if @options[:languages]
|
59
|
-
lang = @options[:languages][0]
|
60
|
-
end
|
47
|
+
lang = @options[:languages][0] if @options[:languages]
|
61
48
|
|
62
49
|
read_write_string_file(@options[:output_path], false, lang)
|
63
50
|
end
|
64
51
|
|
65
52
|
def generate_all_string_files
|
66
53
|
if !File.directory?(@options[:output_path])
|
67
|
-
|
54
|
+
if @options[:create_folders]
|
55
|
+
FileUtils.mkdir_p(@options[:output_path])
|
56
|
+
else
|
57
|
+
raise Twine::Error.new("Directory does not exist: #{@options[:output_path]}")
|
58
|
+
end
|
68
59
|
end
|
69
60
|
|
70
|
-
format = @options[:format]
|
71
|
-
|
72
|
-
format = determine_format_given_directory(@options[:output_path])
|
73
|
-
end
|
74
|
-
if !format
|
61
|
+
format = @options[:format] || determine_format_given_directory(@options[:output_path])
|
62
|
+
unless format
|
75
63
|
raise Twine::Error.new "Could not determine format given the contents of #{@options[:output_path]}"
|
76
64
|
end
|
77
65
|
|
@@ -101,7 +89,7 @@ module Twine
|
|
101
89
|
begin
|
102
90
|
read_write_string_file(item, true, nil)
|
103
91
|
rescue Twine::Error => e
|
104
|
-
|
92
|
+
Twine::stderr.puts "#{e.message}"
|
105
93
|
end
|
106
94
|
end
|
107
95
|
end
|
@@ -115,23 +103,15 @@ module Twine
|
|
115
103
|
raise Twine::Error.new("File does not exist: #{path}")
|
116
104
|
end
|
117
105
|
|
118
|
-
format = @options[:format]
|
119
|
-
|
120
|
-
format = determine_format_given_path(path)
|
121
|
-
end
|
122
|
-
if !format
|
106
|
+
format = @options[:format] || determine_format_given_path(path)
|
107
|
+
unless format
|
123
108
|
raise Twine::Error.new "Unable to determine format of #{path}"
|
124
109
|
end
|
125
110
|
|
126
111
|
formatter = formatter_for_format(format)
|
127
112
|
|
128
|
-
|
129
|
-
|
130
|
-
end
|
131
|
-
if !lang
|
132
|
-
lang = formatter.determine_language_given_path(path)
|
133
|
-
end
|
134
|
-
if !lang
|
113
|
+
lang = lang || determine_language_given_path(path) || formatter.determine_language_given_path(path)
|
114
|
+
unless lang
|
135
115
|
raise Twine::Error.new "Unable to determine language for #{path}"
|
136
116
|
end
|
137
117
|
|
@@ -147,18 +127,14 @@ module Twine
|
|
147
127
|
end
|
148
128
|
|
149
129
|
def generate_loc_drop
|
150
|
-
|
151
|
-
require 'zip/zip'
|
152
|
-
rescue LoadError
|
153
|
-
raise Twine::Error.new "You must run 'gem install rubyzip' in order to create or consume localization drops."
|
154
|
-
end
|
130
|
+
require_rubyzip
|
155
131
|
|
156
132
|
if File.file?(@options[:output_path])
|
157
133
|
File.delete(@options[:output_path])
|
158
134
|
end
|
159
135
|
|
160
136
|
Dir.mktmpdir do |dir|
|
161
|
-
Zip::
|
137
|
+
Zip::File.open(@options[:output_path], Zip::File::CREATE) do |zipfile|
|
162
138
|
zipfile.mkdir('Locales')
|
163
139
|
|
164
140
|
formatter = formatter_for_format(@options[:format])
|
@@ -176,18 +152,14 @@ module Twine
|
|
176
152
|
end
|
177
153
|
|
178
154
|
def consume_loc_drop
|
155
|
+
require_rubyzip
|
156
|
+
|
179
157
|
if !File.file?(@options[:input_path])
|
180
158
|
raise Twine::Error.new("File does not exist: #{@options[:input_path]}")
|
181
159
|
end
|
182
160
|
|
183
|
-
begin
|
184
|
-
require 'zip/zip'
|
185
|
-
rescue LoadError
|
186
|
-
raise Twine::Error.new "You must run 'gem install rubyzip' in order to create or consume localization drops."
|
187
|
-
end
|
188
|
-
|
189
161
|
Dir.mktmpdir do |dir|
|
190
|
-
Zip::
|
162
|
+
Zip::File.open(@options[:input_path]) do |zipfile|
|
191
163
|
zipfile.each do |entry|
|
192
164
|
if !entry.name.end_with?'/' and !File.basename(entry.name).start_with?'.'
|
193
165
|
real_path = File.join(dir, entry.name)
|
@@ -196,7 +168,7 @@ module Twine
|
|
196
168
|
begin
|
197
169
|
read_write_string_file(real_path, true, nil)
|
198
170
|
rescue Twine::Error => e
|
199
|
-
|
171
|
+
Twine::stderr.puts "#{e.message}"
|
200
172
|
end
|
201
173
|
end
|
202
174
|
end
|
@@ -212,81 +184,72 @@ module Twine
|
|
212
184
|
all_keys = Set.new
|
213
185
|
duplicate_keys = Set.new
|
214
186
|
keys_without_tags = Set.new
|
215
|
-
|
187
|
+
invalid_keys = Set.new
|
188
|
+
valid_key_regex = /^[A-Za-z0-9_]+$/
|
216
189
|
|
217
190
|
@strings.sections.each do |section|
|
218
191
|
section.rows.each do |row|
|
219
192
|
total_strings += 1
|
220
193
|
|
221
|
-
if all_keys.include? row.key
|
222
|
-
|
223
|
-
else
|
224
|
-
all_keys.add(row.key)
|
225
|
-
end
|
194
|
+
duplicate_keys.add(row.key) if all_keys.include? row.key
|
195
|
+
all_keys.add(row.key)
|
226
196
|
|
227
|
-
if row.tags == nil
|
228
|
-
|
229
|
-
|
197
|
+
keys_without_tags.add(row.key) if row.tags == nil or row.tags.length == 0
|
198
|
+
|
199
|
+
invalid_keys << row.key unless row.key =~ valid_key_regex
|
230
200
|
end
|
231
201
|
end
|
232
202
|
|
233
|
-
|
234
|
-
|
235
|
-
|
203
|
+
errors = []
|
204
|
+
join_keys = lambda { |set| set.map { |k| " " + k }.join("\n") }
|
205
|
+
|
206
|
+
unless duplicate_keys.empty?
|
207
|
+
errors << "Found duplicate string key(s):\n#{join_keys.call(duplicate_keys)}"
|
236
208
|
end
|
237
209
|
|
238
210
|
if keys_without_tags.length == total_strings
|
239
211
|
errors << "None of your strings have tags."
|
240
212
|
elsif keys_without_tags.length > 0
|
241
|
-
|
242
|
-
errors << "Found strings(s) without tags:\n #{error_body}"
|
213
|
+
errors << "Found strings without tags:\n#{join_keys.call(keys_without_tags)}"
|
243
214
|
end
|
244
215
|
|
245
|
-
|
246
|
-
|
216
|
+
unless invalid_keys.empty?
|
217
|
+
errors << "Found key(s) with invalid characters:\n#{join_keys.call(invalid_keys)}"
|
247
218
|
end
|
248
219
|
|
249
|
-
|
220
|
+
raise Twine::Error.new errors.join("\n\n") unless errors.empty?
|
221
|
+
|
222
|
+
Twine::stdout.puts "#{@options[:strings_file]} is valid."
|
250
223
|
end
|
251
224
|
|
252
225
|
def determine_language_given_path(path)
|
253
226
|
code = File.basename(path, File.extname(path))
|
254
|
-
if
|
255
|
-
code = nil
|
256
|
-
end
|
257
|
-
|
258
|
-
code
|
227
|
+
return code if @strings.language_codes.include? code
|
259
228
|
end
|
260
229
|
|
261
230
|
def determine_format_given_path(path)
|
262
|
-
|
263
|
-
|
264
|
-
if formatter::EXTENSION == ext
|
265
|
-
return formatter::FORMAT_NAME
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
return
|
231
|
+
formatter = Formatters.formatters.find { |f| f::EXTENSION == File.extname(path) }
|
232
|
+
return formatter::FORMAT_NAME if formatter
|
270
233
|
end
|
271
234
|
|
272
235
|
def determine_format_given_directory(directory)
|
273
|
-
Formatters.formatters.
|
274
|
-
|
275
|
-
return formatter::FORMAT_NAME
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
return
|
236
|
+
formatter = Formatters.formatters.find { |f| f.can_handle_directory?(directory) }
|
237
|
+
return formatter::FORMAT_NAME if formatter
|
280
238
|
end
|
281
239
|
|
282
240
|
def formatter_for_format(format)
|
283
|
-
Formatters.formatters.
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
241
|
+
formatter = Formatters.formatters.find { |f| f::FORMAT_NAME == format }
|
242
|
+
return formatter.new(@strings, @options) if formatter
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
288
246
|
|
289
|
-
|
247
|
+
def require_rubyzip
|
248
|
+
begin
|
249
|
+
require 'zip'
|
250
|
+
rescue LoadError
|
251
|
+
raise Twine::Error.new "You must run 'gem install rubyzip' in order to create or consume localization drops."
|
252
|
+
end
|
290
253
|
end
|
291
254
|
end
|
292
255
|
end
|