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
@@ -5,6 +5,8 @@ require 'rexml/document'
|
|
5
5
|
module Twine
|
6
6
|
module Formatters
|
7
7
|
class Android < Abstract
|
8
|
+
include Twine::Placeholders
|
9
|
+
|
8
10
|
FORMAT_NAME = 'android'
|
9
11
|
EXTENSION = '.xml'
|
10
12
|
DEFAULT_FILE_NAME = 'strings.xml'
|
@@ -17,9 +19,6 @@ module Twine
|
|
17
19
|
'nb' => 'no'
|
18
20
|
# TODO: spanish
|
19
21
|
]
|
20
|
-
DEFAULT_LANG_CODES = Hash[
|
21
|
-
'zh-TW' => 'zh-Hant' # if we don't have a zh-TW translation, try zh-Hant before en
|
22
|
-
]
|
23
22
|
|
24
23
|
def self.can_handle_directory?(path)
|
25
24
|
Dir.entries(path).any? { |item| /^values.*$/.match(item) }
|
@@ -48,6 +47,16 @@ module Twine
|
|
48
47
|
return
|
49
48
|
end
|
50
49
|
|
50
|
+
def set_translation_for_key(key, lang, value)
|
51
|
+
value = CGI.unescapeHTML(value)
|
52
|
+
value.gsub!('\\\'', '\'')
|
53
|
+
value.gsub!('\\"', '"')
|
54
|
+
value = convert_placeholders_from_android_to_twine(value)
|
55
|
+
value.gsub!('\@', '@')
|
56
|
+
value.gsub!(/(\\u0020)*|(\\u0020)*\z/) { |spaces| ' ' * (spaces.length / 6) }
|
57
|
+
super(key, lang, value)
|
58
|
+
end
|
59
|
+
|
51
60
|
def read_file(path, lang)
|
52
61
|
resources_regex = /<resources(?:[^>]*)>(.*)<\/resources>/m
|
53
62
|
key_regex = /<string name="(\w+)">/
|
@@ -65,16 +74,8 @@ module Twine
|
|
65
74
|
if key_match
|
66
75
|
key = key_match[1]
|
67
76
|
value_match = value_regex.match(line)
|
68
|
-
|
69
|
-
|
70
|
-
value = CGI.unescapeHTML(value)
|
71
|
-
value.gsub!('\\\'', '\'')
|
72
|
-
value.gsub!('\\"', '"')
|
73
|
-
value = iosify_substitutions(value)
|
74
|
-
value.gsub!(/(\\u0020)*|(\\u0020)*\z/) { |spaces| ' ' * (spaces.length / 6) }
|
75
|
-
else
|
76
|
-
value = ""
|
77
|
-
end
|
77
|
+
value = value_match ? value_match[1] : ""
|
78
|
+
|
78
79
|
set_translation_for_key(key, lang, value)
|
79
80
|
if comment and comment.length > 0 and !comment.start_with?("SECTION:")
|
80
81
|
set_comment_for_key(key, comment)
|
@@ -91,65 +92,46 @@ module Twine
|
|
91
92
|
end
|
92
93
|
end
|
93
94
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
97
|
-
default_lang = DEFAULT_LANG_CODES[lang]
|
98
|
-
end
|
99
|
-
File.open(path, 'w:UTF-8') do |f|
|
100
|
-
f.puts "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Android Strings File -->\n<!-- Generated by Twine #{Twine::VERSION} -->\n<!-- Language: #{lang} -->"
|
101
|
-
f.write '<resources>'
|
102
|
-
@strings.sections.each do |section|
|
103
|
-
printed_section = false
|
104
|
-
section.rows.each do |row|
|
105
|
-
if row.matches_tags?(@options[:tags], @options[:untagged])
|
106
|
-
if !printed_section
|
107
|
-
f.puts ''
|
108
|
-
if section.name && section.name.length > 0
|
109
|
-
section_name = section.name.gsub('--', '—')
|
110
|
-
f.puts "\t<!-- SECTION: #{section_name} -->"
|
111
|
-
end
|
112
|
-
printed_section = true
|
113
|
-
end
|
95
|
+
def format_header(lang)
|
96
|
+
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Android Strings File -->\n<!-- Generated by Twine #{Twine::VERSION} -->\n<!-- Language: #{lang} -->"
|
97
|
+
end
|
114
98
|
|
115
|
-
|
99
|
+
def format_sections(strings, lang)
|
100
|
+
result = '<resources>'
|
101
|
+
|
102
|
+
result += super + "\n"
|
116
103
|
|
117
|
-
|
118
|
-
|
119
|
-
value = row.translated_string_for_lang(@strings.language_codes[0])
|
120
|
-
end
|
104
|
+
result += '</resources>'
|
105
|
+
end
|
121
106
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
# Android enforces the following rules on the values
|
126
|
-
# 1) apostrophes and quotes must be escaped with a backslash
|
127
|
-
value.gsub!('\'', '\\\\\'')
|
128
|
-
value.gsub!('"', '\\\\"')
|
129
|
-
# 2) HTML escape the string
|
130
|
-
value = CGI.escapeHTML(value)
|
131
|
-
# 3) fix substitutions (e.g. %s/%@)
|
132
|
-
value = androidify_substitutions(value)
|
133
|
-
# 4) replace beginning and end spaces with \0020. Otherwise Android strips them.
|
134
|
-
value.gsub!(/\A *| *\z/) { |spaces| '\u0020' * spaces.length }
|
135
|
-
|
136
|
-
comment = row.comment
|
137
|
-
if comment
|
138
|
-
comment = comment.gsub('--', '—')
|
139
|
-
end
|
140
|
-
|
141
|
-
if comment && comment.length > 0
|
142
|
-
f.puts "\t<!-- #{comment} -->\n"
|
143
|
-
end
|
144
|
-
f.puts "\t<string name=\"#{key}\">#{value}</string>"
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
107
|
+
def format_section_header(section)
|
108
|
+
"\t<!-- SECTION: #{section.name} -->"
|
109
|
+
end
|
149
110
|
|
150
|
-
|
151
|
-
|
111
|
+
def format_comment(row, lang)
|
112
|
+
"\t<!-- #{row.comment.gsub('--', '—')} -->\n" if row.comment
|
152
113
|
end
|
114
|
+
|
115
|
+
def key_value_pattern
|
116
|
+
"\t<string name=\"%{key}\">%{value}</string>"
|
117
|
+
end
|
118
|
+
|
119
|
+
def format_value(value)
|
120
|
+
# Android enforces the following rules on the values
|
121
|
+
# 1) apostrophes and quotes must be escaped with a backslash
|
122
|
+
value = escape_quotes(value)
|
123
|
+
value.gsub!("'", "\\\\'")
|
124
|
+
# 2) HTML escape the string
|
125
|
+
value = CGI.escapeHTML(value)
|
126
|
+
# 3) convert placeholders (e.g. %@ -> %s)
|
127
|
+
value = convert_placeholders_from_twine_to_android(value)
|
128
|
+
# 4) escape non resource identifier @ signs (http://developer.android.com/guide/topics/resources/accessing-resources.html#ResourcesFromXml)
|
129
|
+
resource_identifier_regex = /@(?!([a-z\.]+:)?[a-z+]+\/[a-zA-Z_]+)/ # @[<package_name>:]<resource_type>/<resource_name>
|
130
|
+
value.gsub!(resource_identifier_regex, '\@')
|
131
|
+
# 5) replace beginning and end spaces with \0020. Otherwise Android strips them.
|
132
|
+
value.gsub(/\A *| *\z/) { |spaces| '\u0020' * spaces.length }
|
133
|
+
end
|
134
|
+
|
153
135
|
end
|
154
136
|
end
|
155
137
|
end
|
@@ -27,6 +27,10 @@ module Twine
|
|
27
27
|
return
|
28
28
|
end
|
29
29
|
|
30
|
+
def output_path_for_language(lang)
|
31
|
+
"#{lang}.lproj"
|
32
|
+
end
|
33
|
+
|
30
34
|
def read_file(path, lang)
|
31
35
|
encoding = Twine::Encoding.encoding_for_path(path)
|
32
36
|
sep = nil
|
@@ -64,63 +68,45 @@ module Twine
|
|
64
68
|
key.gsub!('\\"', '"')
|
65
69
|
value = match[2]
|
66
70
|
value.gsub!('\\"', '"')
|
67
|
-
value = iosify_substitutions(value)
|
68
71
|
set_translation_for_key(key, lang, value)
|
69
72
|
if last_comment
|
70
73
|
set_comment_for_key(key, last_comment)
|
71
74
|
end
|
72
75
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
end
|
76
|
+
|
77
|
+
match = /\/\* (.*) \*\//.match(line)
|
78
|
+
if match
|
79
|
+
last_comment = match[1]
|
80
|
+
else
|
81
|
+
last_comment = nil
|
80
82
|
end
|
83
|
+
|
81
84
|
end
|
82
85
|
end
|
83
86
|
end
|
84
87
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
value = value.gsub('"', '\\\\"')
|
108
|
-
|
109
|
-
comment = row.comment
|
110
|
-
if comment
|
111
|
-
comment = comment.gsub('*/', '* /')
|
112
|
-
end
|
113
|
-
|
114
|
-
if comment && comment.length > 0
|
115
|
-
f.print "/* #{comment} */\n"
|
116
|
-
end
|
117
|
-
|
118
|
-
f.print "\"#{key}\" = \"#{value}\";\n"
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
88
|
+
def format_header(lang)
|
89
|
+
"/**\n * Apple Strings File\n * Generated by Twine #{Twine::VERSION}\n * Language: #{lang}\n */"
|
90
|
+
end
|
91
|
+
|
92
|
+
def format_section_header(section)
|
93
|
+
"/********** #{section.name} **********/\n"
|
94
|
+
end
|
95
|
+
|
96
|
+
def key_value_pattern
|
97
|
+
"\"%{key}\" = \"%{value}\";\n"
|
98
|
+
end
|
99
|
+
|
100
|
+
def format_comment(row, lang)
|
101
|
+
"/* #{row.comment.gsub('*/', '* /')} */\n" if row.comment
|
102
|
+
end
|
103
|
+
|
104
|
+
def format_key(key)
|
105
|
+
escape_quotes(key)
|
106
|
+
end
|
107
|
+
|
108
|
+
def format_value(value)
|
109
|
+
escape_quotes(value)
|
124
110
|
end
|
125
111
|
end
|
126
112
|
end
|
@@ -26,7 +26,7 @@ module Twine
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def read_file(path, lang)
|
29
|
-
comment_regex =
|
29
|
+
comment_regex = /#\. *"?(.*)"?$/
|
30
30
|
key_regex = /msgid *"(.*)"$/
|
31
31
|
value_regex = /msgstr *"(.*)"$/m
|
32
32
|
|
@@ -60,14 +60,12 @@ module Twine
|
|
60
60
|
line = Iconv.iconv('UTF-8', encoding, line).join
|
61
61
|
end
|
62
62
|
end
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
else
|
69
|
-
comment = nil
|
63
|
+
|
64
|
+
comment_match = comment_regex.match(line)
|
65
|
+
if comment_match
|
66
|
+
comment = comment_match[1]
|
70
67
|
end
|
68
|
+
|
71
69
|
key_match = key_regex.match(line)
|
72
70
|
if key_match
|
73
71
|
key = key_match[1].gsub('\\"', '"')
|
@@ -83,6 +81,8 @@ module Twine
|
|
83
81
|
if comment and comment.length > 0 and !comment.start_with?("--------- ")
|
84
82
|
set_comment_for_key(key, comment)
|
85
83
|
end
|
84
|
+
key = nil
|
85
|
+
value = nil
|
86
86
|
comment = nil
|
87
87
|
end
|
88
88
|
|
@@ -90,53 +90,43 @@ module Twine
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
-
def
|
94
|
-
default_lang =
|
95
|
-
|
96
|
-
|
97
|
-
f.puts "##\n # Django Strings File\n # Generated by Twine #{Twine::VERSION}\n # Language: #{lang}\n "
|
98
|
-
@strings.sections.each do |section|
|
99
|
-
printed_section = false
|
100
|
-
section.rows.each do |row|
|
101
|
-
if row.matches_tags?(@options[:tags], @options[:untagged])
|
102
|
-
f.puts ''
|
103
|
-
if !printed_section
|
104
|
-
if section.name && section.name.length > 0
|
105
|
-
f.print "#--------- #{section.name} ---------#\n\n"
|
106
|
-
end
|
107
|
-
printed_section = true
|
108
|
-
end
|
109
|
-
|
110
|
-
basetrans = row.translated_string_for_lang(default_lang)
|
93
|
+
def format_file(strings, lang)
|
94
|
+
@default_lang = strings.language_codes[0]
|
95
|
+
super
|
96
|
+
end
|
111
97
|
|
112
|
-
|
113
|
-
|
98
|
+
def format_header(lang)
|
99
|
+
"##\n # Django Strings File\n # Generated by Twine #{Twine::VERSION}\n # Language: #{lang}\n"
|
100
|
+
end
|
114
101
|
|
115
|
-
|
116
|
-
|
117
|
-
|
102
|
+
def format_section_header(section)
|
103
|
+
"#--------- #{section.name} ---------#\n"
|
104
|
+
end
|
118
105
|
|
119
|
-
|
106
|
+
def row_pattern
|
107
|
+
"%{comment}%{base_translation}%{key_value}"
|
108
|
+
end
|
120
109
|
|
121
|
-
|
122
|
-
|
123
|
-
|
110
|
+
def format_base_translation(row, lang)
|
111
|
+
base_translation = row.translations[@default_lang]
|
112
|
+
"# base translation: \"#{base_translation}\"\n" if base_translation
|
113
|
+
end
|
124
114
|
|
125
|
-
|
126
|
-
|
127
|
-
|
115
|
+
def key_value_pattern
|
116
|
+
"msgid \"%{key}\"\n" +
|
117
|
+
"msgstr \"%{value}\"\n"
|
118
|
+
end
|
128
119
|
|
129
|
-
|
130
|
-
|
131
|
-
|
120
|
+
def format_comment(row, lang)
|
121
|
+
"#. #{escape_quotes(row.comment)}\n" if row.comment
|
122
|
+
end
|
132
123
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
end
|
124
|
+
def format_key(key)
|
125
|
+
escape_quotes(key)
|
126
|
+
end
|
127
|
+
|
128
|
+
def format_value(value)
|
129
|
+
escape_quotes(value)
|
140
130
|
end
|
141
131
|
end
|
142
132
|
end
|
@@ -51,59 +51,44 @@ module Twine
|
|
51
51
|
match = /((?:[^"\\]|\\.)+)\s*=\s*((?:[^"\\]|\\.)*)/.match(line)
|
52
52
|
if match
|
53
53
|
key = match[1]
|
54
|
-
value = match[2]
|
54
|
+
value = match[2].strip
|
55
55
|
value.gsub!(/\{[0-9]\}/, '%@')
|
56
56
|
set_translation_for_key(key, lang, value)
|
57
57
|
if last_comment
|
58
58
|
set_comment_for_key(key, last_comment)
|
59
59
|
end
|
60
60
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
61
|
+
|
62
|
+
match = /# *(.*)/.match(line)
|
63
|
+
if match
|
64
|
+
last_comment = match[1]
|
65
|
+
else
|
66
|
+
last_comment = nil
|
68
67
|
end
|
68
|
+
|
69
69
|
end
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
File.open(path, "w:#{encoding}") do |f|
|
77
|
-
f.puts "## Flash Strings File\n## Generated by Twine #{Twine::VERSION}\n## Language: #{lang}\n"
|
78
|
-
@strings.sections.each do |section|
|
79
|
-
printed_section = false
|
80
|
-
section.rows.each do |row|
|
81
|
-
if row.matches_tags?(@options[:tags], @options[:untagged])
|
82
|
-
f.puts ''
|
83
|
-
if !printed_section
|
84
|
-
if section.name && section.name.length > 0
|
85
|
-
f.print "## #{section.name} ##\n\n"
|
86
|
-
end
|
87
|
-
printed_section = true
|
88
|
-
end
|
73
|
+
def format_header(lang)
|
74
|
+
"## Flash Strings File\n## Generated by Twine #{Twine::VERSION}\n## Language: #{lang}"
|
75
|
+
end
|
89
76
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
placeHolderNumber = -1
|
94
|
-
value = value.gsub(/%[d@]/) { placeHolderNumber += 1; '{%d}' % placeHolderNumber }
|
95
|
-
|
96
|
-
comment = row.comment
|
97
|
-
if comment && comment.length > 0
|
98
|
-
f.print "# #{comment}\n"
|
99
|
-
end
|
77
|
+
def format_section_header(section)
|
78
|
+
"## #{section.name} ##\n"
|
79
|
+
end
|
100
80
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
81
|
+
def format_comment(row, lang)
|
82
|
+
"# #{row.comment}\n" if row.comment
|
83
|
+
end
|
84
|
+
|
85
|
+
def key_value_pattern
|
86
|
+
"%{key}=%{value}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def format_value(value)
|
90
|
+
placeHolderNumber = -1
|
91
|
+
value.gsub(/%[d@]/) { placeHolderNumber += 1; '{%d}' % placeHolderNumber }
|
107
92
|
end
|
108
93
|
end
|
109
94
|
end
|