twine 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|