twine 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/twine +1 -1
- data/lib/twine.rb +1 -0
- data/lib/twine/cli.rb +10 -3
- data/lib/twine/encoding.rb +20 -0
- data/lib/twine/formatters/abstract.rb +1 -1
- data/lib/twine/formatters/android.rb +56 -21
- data/lib/twine/formatters/apple.rb +32 -4
- data/lib/twine/runner.rb +1 -1
- data/lib/twine/stringsfile.rb +13 -2
- data/lib/twine/version.rb +1 -1
- data/test/twine_test.rb +1 -1
- metadata +7 -6
data/bin/twine
CHANGED
data/lib/twine.rb
CHANGED
data/lib/twine/cli.rb
CHANGED
@@ -25,7 +25,7 @@ module Twine
|
|
25
25
|
opts.separator ''
|
26
26
|
opts.separator 'consume-string-file -- 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.'
|
27
27
|
opts.separator ''
|
28
|
-
opts.separator 'generate-loc-drop -- 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.'
|
28
|
+
opts.separator 'generate-loc-drop -- 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. This command assumes that --all has been specified on the command line.'
|
29
29
|
opts.separator ''
|
30
30
|
opts.separator 'consume-loc-drop -- Consumes an archive of translated files. This archive should be in the same format as the one created by the generate-loc-drop command.'
|
31
31
|
opts.separator ''
|
@@ -53,8 +53,14 @@ module Twine
|
|
53
53
|
end
|
54
54
|
@options[:format] = lformat
|
55
55
|
end
|
56
|
-
opts.on('-a', '--all', 'Normally, when consuming a string file, Twine will ignore any string keys that do not exist in your master file. This flag will
|
57
|
-
@options[:
|
56
|
+
opts.on('-a', '--all', 'Normally, when consuming a string file, Twine will ignore any string keys that do not exist in your master file. This flag will also cause any Android string files that are generated to include strings that have not yet been translated for the current language.') do |a|
|
57
|
+
@options[:consume_generate_all] = true
|
58
|
+
end
|
59
|
+
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 files. For example, you could use this to write Apple .strings files in UTF-16. This flag currently only works with Apple .strings files and is currently only supported in Ruby 1.9.3 or greater.') do |e|
|
60
|
+
if !"".respond_to?(:encode)
|
61
|
+
raise Twine::Error.new "The --encoding flag is only supported on Ruby 1.9.3 or greater."
|
62
|
+
end
|
63
|
+
@options[:output_encoding] = e
|
58
64
|
end
|
59
65
|
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 running the consume-string-file or consume-loc-drop commands.') do |o|
|
60
66
|
@options[:output_path] = o
|
@@ -128,6 +134,7 @@ module Twine
|
|
128
134
|
raise Twine::Error.new 'Please only specify a single language for the consume-string-file command.'
|
129
135
|
end
|
130
136
|
when 'generate-loc-drop'
|
137
|
+
@options[:consume_generate_all] = true
|
131
138
|
if @args.length == 3
|
132
139
|
@options[:output_path] = @args[2]
|
133
140
|
elsif @args.length > 3
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Twine
|
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
|
+
|
17
|
+
'UTF-8'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -16,7 +16,7 @@ module Twine
|
|
16
16
|
def set_translation_for_key(key, lang, value)
|
17
17
|
if @strings.strings_map.include?(key)
|
18
18
|
@strings.strings_map[key].translations[lang] = value
|
19
|
-
elsif @options[:
|
19
|
+
elsif @options[:consume_generate_all]
|
20
20
|
STDERR.puts "Adding new string '#{key}' to strings data file."
|
21
21
|
arr = @strings.sections.select { |s| s.name == 'Uncategorized' }
|
22
22
|
current_section = arr ? arr[0] : nil
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require 'cgi'
|
4
3
|
require 'rexml/document'
|
5
4
|
|
6
5
|
module Twine
|
@@ -9,9 +8,21 @@ module Twine
|
|
9
8
|
FORMAT_NAME = 'android'
|
10
9
|
EXTENSION = '.xml'
|
11
10
|
DEFAULT_FILE_NAME = 'strings.xml'
|
11
|
+
LANG_CODES = Hash[
|
12
|
+
'zh' => 'zh-Hans',
|
13
|
+
'zh-rCN' => 'zh-Hans',
|
14
|
+
'zh-rHK' => 'zh-Hant',
|
15
|
+
'en-rGB' => 'en-UK',
|
16
|
+
'in' => 'id',
|
17
|
+
'nb' => 'no'
|
18
|
+
# TODO: spanish
|
19
|
+
]
|
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
|
+
]
|
12
23
|
|
13
24
|
def self.can_handle_directory?(path)
|
14
|
-
Dir.entries(path).any? { |item| /^values
|
25
|
+
Dir.entries(path).any? { |item| /^values.*$/.match(item) }
|
15
26
|
end
|
16
27
|
|
17
28
|
def default_file_name
|
@@ -21,11 +32,16 @@ module Twine
|
|
21
32
|
def determine_language_given_path(path)
|
22
33
|
path_arr = path.split(File::SEPARATOR)
|
23
34
|
path_arr.each do |segment|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
35
|
+
if segment == 'values'
|
36
|
+
return @strings.language_codes[0]
|
37
|
+
else
|
38
|
+
match = /^values-(.*)$/.match(segment)
|
39
|
+
if match
|
40
|
+
lang = match[1]
|
41
|
+
lang = LANG_CODES.fetch(lang, lang)
|
42
|
+
lang.sub!('-r', '-')
|
43
|
+
return lang
|
44
|
+
end
|
29
45
|
end
|
30
46
|
end
|
31
47
|
|
@@ -40,22 +56,28 @@ module Twine
|
|
40
56
|
key = ele.attributes["name"]
|
41
57
|
value = ele.text || ''
|
42
58
|
value.gsub!('\\\'', '\'')
|
59
|
+
value.gsub!('\\"', '"')
|
43
60
|
value.gsub!(/\n/, '')
|
44
|
-
value.gsub!(
|
61
|
+
value.gsub!(/%([0-9\$]*)s/, '%\1@')
|
62
|
+
value.gsub!('<', '<')
|
63
|
+
value.gsub!('&', '&')
|
45
64
|
set_translation_for_key(key, lang, value)
|
46
65
|
end
|
47
66
|
end
|
48
67
|
end
|
49
68
|
|
50
69
|
def write_file(path, lang)
|
51
|
-
default_lang =
|
70
|
+
default_lang = nil
|
71
|
+
if DEFAULT_LANG_CODES.has_key?(lang)
|
72
|
+
default_lang = DEFAULT_LANG_CODES[lang]
|
73
|
+
end
|
52
74
|
File.open(path, 'w:UTF-8') do |f|
|
53
75
|
f.puts "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Android Strings File -->\n<!-- Generated by Twine -->\n<!-- Language: #{lang} -->"
|
54
76
|
f.write '<resources>'
|
55
77
|
@strings.sections.each do |section|
|
56
78
|
printed_section = false
|
57
79
|
section.rows.each do |row|
|
58
|
-
if row.matches_tags?(options[:tags])
|
80
|
+
if row.matches_tags?(@options[:tags])
|
59
81
|
if !printed_section
|
60
82
|
f.puts ''
|
61
83
|
if section.name && section.name.length > 0
|
@@ -66,22 +88,35 @@ module Twine
|
|
66
88
|
end
|
67
89
|
|
68
90
|
key = row.key
|
69
|
-
key = CGI.escapeHTML(key)
|
70
91
|
|
71
92
|
value = row.translated_string_for_lang(lang, default_lang)
|
72
|
-
value
|
73
|
-
|
74
|
-
value = CGI.escapeHTML(value)
|
75
|
-
|
76
|
-
comment = row.comment
|
77
|
-
if comment
|
78
|
-
comment = comment.gsub('--', '—')
|
93
|
+
if !value && @options[:consume_generate_all]
|
94
|
+
value = row.translated_string_for_lang(@strings.language_codes[0])
|
79
95
|
end
|
80
96
|
|
81
|
-
if
|
82
|
-
|
97
|
+
if value # if values is nil, there was no appropriate translation, so let Android handle the defaulting
|
98
|
+
value = String.new(value) # use a copy to prevent modifying the original
|
99
|
+
|
100
|
+
# Android enforces the following rules on the values
|
101
|
+
# 1) apostrophes and quotes must be escaped with a backslash
|
102
|
+
value.gsub!('\'', '\\\\\'')
|
103
|
+
value.gsub!('"', '\\\\"')
|
104
|
+
# 2) ampersand and less-than must be in XML-escaped form
|
105
|
+
value.gsub!('&', '&')
|
106
|
+
value.gsub!('<', '<')
|
107
|
+
# 3) use "s" instead of "@" for substituting strings
|
108
|
+
value.gsub!(/%([0-9\$]*)@/, '%\1s')
|
109
|
+
|
110
|
+
comment = row.comment
|
111
|
+
if comment
|
112
|
+
comment = comment.gsub('--', '—')
|
113
|
+
end
|
114
|
+
|
115
|
+
if comment && comment.length > 0
|
116
|
+
f.puts "\t<!-- #{comment} -->\n"
|
117
|
+
end
|
118
|
+
f.puts "\t<string name=\"#{key}\">#{value}</string>"
|
83
119
|
end
|
84
|
-
f.puts "\t<string name=\"#{key}\">#{value}</string>"
|
85
120
|
end
|
86
121
|
end
|
87
122
|
end
|
@@ -26,8 +26,35 @@ module Twine
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def read_file(path, lang)
|
29
|
-
|
30
|
-
|
29
|
+
encoding = Twine::Encoding.encoding_for_path(path)
|
30
|
+
sep = nil
|
31
|
+
if !encoding.respond_to?(:encode)
|
32
|
+
# This code is not necessary in 1.9.3 and does not work as it did in 1.8.7.
|
33
|
+
if encoding.end_with? 'LE'
|
34
|
+
sep = "\x0a\x00"
|
35
|
+
elsif encoding.end_with? 'BE'
|
36
|
+
sep = "\x00\x0a"
|
37
|
+
else
|
38
|
+
sep = "\n"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if encoding.index('UTF-16')
|
43
|
+
mode = "rb:#{encoding}"
|
44
|
+
else
|
45
|
+
mode = "r:#{encoding}"
|
46
|
+
end
|
47
|
+
|
48
|
+
File.open(path, mode) do |f|
|
49
|
+
while line = (sep) ? f.gets(sep) : f.gets
|
50
|
+
if encoding.index('UTF-16')
|
51
|
+
if line.respond_to? :encode!
|
52
|
+
line.encode!('UTF-8')
|
53
|
+
else
|
54
|
+
require 'iconv'
|
55
|
+
line = Iconv.iconv('UTF-8', encoding, line).join
|
56
|
+
end
|
57
|
+
end
|
31
58
|
match = /"((?:[^"\\]|\\.)+)"\s*=\s*"((?:[^"\\]|\\.)*)"/.match(line)
|
32
59
|
if match
|
33
60
|
key = match[1]
|
@@ -42,12 +69,13 @@ module Twine
|
|
42
69
|
|
43
70
|
def write_file(path, lang)
|
44
71
|
default_lang = @strings.language_codes[0]
|
45
|
-
|
72
|
+
encoding = @options[:output_encoding] || 'UTF-8'
|
73
|
+
File.open(path, "w:#{encoding}") do |f|
|
46
74
|
f.puts "/**\n * iOS Strings File\n * Generated by Twine\n * Language: #{lang}\n */"
|
47
75
|
@strings.sections.each do |section|
|
48
76
|
printed_section = false
|
49
77
|
section.rows.each do |row|
|
50
|
-
if row.matches_tags?(options[:tags])
|
78
|
+
if row.matches_tags?(@options[:tags])
|
51
79
|
if !printed_section
|
52
80
|
f.puts ''
|
53
81
|
if section.name && section.name.length > 0
|
data/lib/twine/runner.rb
CHANGED
@@ -137,7 +137,7 @@ module Twine
|
|
137
137
|
file_name = lang + formatter.class::EXTENSION
|
138
138
|
real_path = File.join(dir, file_name)
|
139
139
|
zip_path = File.join('Locales', file_name)
|
140
|
-
formatter.write_file(real_path, lang
|
140
|
+
formatter.write_file(real_path, lang)
|
141
141
|
zipfile.add(zip_path, real_path)
|
142
142
|
end
|
143
143
|
end
|
data/lib/twine/stringsfile.rb
CHANGED
@@ -41,7 +41,18 @@ module Twine
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def translated_string_for_lang(lang, default_lang=nil)
|
44
|
-
@translations[lang]
|
44
|
+
if @translations[lang]
|
45
|
+
return @translations[lang]
|
46
|
+
elsif default_lang.respond_to?("each")
|
47
|
+
default_lang.each do |def_lang|
|
48
|
+
if @translations[def_lang]
|
49
|
+
return @translations[def_lang]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
return nil
|
53
|
+
else
|
54
|
+
return @translations[default_lang]
|
55
|
+
end
|
45
56
|
end
|
46
57
|
end
|
47
58
|
|
@@ -94,7 +105,7 @@ module Twine
|
|
94
105
|
parsed = true
|
95
106
|
end
|
96
107
|
else
|
97
|
-
match = /^([^=]+)=(
|
108
|
+
match = /^([^=]+)=(.*)$/.match(line)
|
98
109
|
if match
|
99
110
|
key = match[1].strip
|
100
111
|
value = match[2].strip
|
data/lib/twine/version.rb
CHANGED
data/test/twine_test.rb
CHANGED
@@ -5,7 +5,7 @@ class TwineTest < Test::Unit::TestCase
|
|
5
5
|
def test_generate_string_file_1
|
6
6
|
Dir.mktmpdir do |dir|
|
7
7
|
output_path = File.join(dir, 'fr.xml')
|
8
|
-
Twine::Runner.run(%W(generate-string-file test/fixtures/strings-1.txt #{output_path}))
|
8
|
+
Twine::Runner.run(%W(generate-string-file test/fixtures/strings-1.txt #{output_path} --all))
|
9
9
|
assert_equal(File.read('test/fixtures/test-output-1.txt'), File.read(output_path))
|
10
10
|
end
|
11
11
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: twine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-02-
|
12
|
+
date: 2012-02-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rubyzip
|
16
|
-
requirement: &
|
16
|
+
requirement: &70216088301260 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 0.9.5
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70216088301260
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &70216088300680 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: 0.9.2
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70216088300680
|
36
36
|
description: ! " Twine is a command line tool for managing your strings and their
|
37
37
|
translations.\n \n It is geared toward Mac OS X, iOS, and Android developers.\n"
|
38
38
|
email: twine@mobiata.com
|
@@ -45,6 +45,7 @@ files:
|
|
45
45
|
- README.md
|
46
46
|
- LICENSE
|
47
47
|
- lib/twine/cli.rb
|
48
|
+
- lib/twine/encoding.rb
|
48
49
|
- lib/twine/formatters/abstract.rb
|
49
50
|
- lib/twine/formatters/android.rb
|
50
51
|
- lib/twine/formatters/apple.rb
|