traduco 0.9.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.
- data/Gemfile +2 -0
- data/LICENSE +33 -0
- data/README.md +182 -0
- data/bin/traduco +8 -0
- data/library/traduco.rb +10 -0
- data/library/traduco/cli.rb +186 -0
- data/library/traduco/encoding.rb +20 -0
- data/library/traduco/formatters.rb +12 -0
- data/library/traduco/formatters/abstract.rb +164 -0
- data/library/traduco/formatters/android.rb +152 -0
- data/library/traduco/formatters/apple.rb +125 -0
- data/library/traduco/formatters/flash.rb +110 -0
- data/library/traduco/formatters/gettext.rb +98 -0
- data/library/traduco/formatters/jquery.rb +96 -0
- data/library/traduco/l10nfile.rb +209 -0
- data/library/traduco/runner.rb +301 -0
- data/test/fixtures/en-1.json +12 -0
- data/test/fixtures/en-1.po +16 -0
- data/test/fixtures/en-1.strings +10 -0
- data/test/fixtures/en-2.po +23 -0
- data/test/fixtures/fr-1.xml +10 -0
- data/test/fixtures/strings-1.txt +17 -0
- data/test/fixtures/strings-2.txt +5 -0
- data/test/fixtures/strings-3.txt +5 -0
- data/test/fixtures/test-output-1.txt +12 -0
- data/test/fixtures/test-output-2.txt +12 -0
- data/test/fixtures/test-output-3.txt +18 -0
- data/test/fixtures/test-output-4.txt +21 -0
- data/test/fixtures/test-output-5.txt +11 -0
- data/test/fixtures/test-output-6.txt +10 -0
- data/test/fixtures/test-output-7.txt +14 -0
- data/test/fixtures/test-output-8.txt +9 -0
- data/test/fixtures/test-output-9.txt +21 -0
- data/test/traduco_test.rb +98 -0
- metadata +125 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
module Traduco
|
2
|
+
module Formatters
|
3
|
+
class Flash < Abstract
|
4
|
+
FORMAT_NAME = 'flash'
|
5
|
+
EXTENSION = '.properties'
|
6
|
+
DEFAULT_FILE_NAME = 'resources.properties'
|
7
|
+
|
8
|
+
def self.can_handle_directory?(path)
|
9
|
+
return false
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_file_name
|
13
|
+
return DEFAULT_FILE_NAME
|
14
|
+
end
|
15
|
+
|
16
|
+
def determine_language_given_path(path)
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
def read_file(path, lang)
|
21
|
+
encoding = Traduco::Encoding.encoding_for_path(path)
|
22
|
+
sep = nil
|
23
|
+
if !encoding.respond_to?(:encode)
|
24
|
+
# This code is not necessary in 1.9.3 and does not work as it did in 1.8.7.
|
25
|
+
if encoding.end_with? 'LE'
|
26
|
+
sep = "\x0a\x00"
|
27
|
+
elsif encoding.end_with? 'BE'
|
28
|
+
sep = "\x00\x0a"
|
29
|
+
else
|
30
|
+
sep = "\n"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
if encoding.index('UTF-16')
|
35
|
+
mode = "rb:#{encoding}"
|
36
|
+
else
|
37
|
+
mode = "r:#{encoding}"
|
38
|
+
end
|
39
|
+
|
40
|
+
File.open(path, mode) do |f|
|
41
|
+
last_comment = nil
|
42
|
+
while line = (sep) ? f.gets(sep) : f.gets
|
43
|
+
if encoding.index('UTF-16')
|
44
|
+
if line.respond_to? :encode!
|
45
|
+
line.encode!('UTF-8')
|
46
|
+
else
|
47
|
+
require 'iconv'
|
48
|
+
line = Iconv.iconv('UTF-8', encoding, line).join
|
49
|
+
end
|
50
|
+
end
|
51
|
+
match = /((?:[^"\\]|\\.)+)\s*=\s*((?:[^"\\]|\\.)*)/.match(line)
|
52
|
+
if match
|
53
|
+
key = match[1]
|
54
|
+
value = match[2]
|
55
|
+
value.gsub!(/\{[0-9]\}/, '%@')
|
56
|
+
set_translation_for_key(key, lang, value)
|
57
|
+
if last_comment
|
58
|
+
set_comment_for_key(key, last_comment)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
if @options[:consume_comments]
|
62
|
+
match = /#(.*)/.match(line)
|
63
|
+
if match
|
64
|
+
last_comment = match[1]
|
65
|
+
else
|
66
|
+
last_comment = nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def write_file(path, lang)
|
74
|
+
default_lang = @strings.language_codes[0]
|
75
|
+
encoding = @options[:output_encoding] || 'UTF-8'
|
76
|
+
File.open(path, "w:#{encoding}") do |f|
|
77
|
+
f.puts "## Flash Strings File\n## Generated by Traduco #{Traduco::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
|
89
|
+
|
90
|
+
key = row.key
|
91
|
+
value = row.translated_string_for_lang(lang, default_lang)
|
92
|
+
if value
|
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
|
100
|
+
|
101
|
+
f.print "#{key}=#{value}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Traduco
|
2
|
+
module Formatters
|
3
|
+
class Gettext < Abstract
|
4
|
+
FORMAT_NAME = 'gettext'
|
5
|
+
EXTENSION = '.po'
|
6
|
+
DEFAULT_FILE_NAME = 'strings.po'
|
7
|
+
|
8
|
+
def self.can_handle_directory?(path)
|
9
|
+
Dir.entries(path).any? { |item| /^.+\.po$/.match(item) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_file_name
|
13
|
+
return DEFAULT_FILE_NAME
|
14
|
+
end
|
15
|
+
|
16
|
+
def determine_language_given_path(path)
|
17
|
+
path_arr = path.split(File::SEPARATOR)
|
18
|
+
path_arr.each do |segment|
|
19
|
+
match = /(..)\.po$/.match(segment)
|
20
|
+
if match
|
21
|
+
return match[1]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_file(path, lang)
|
29
|
+
comment_regex = /#.? *"(.*)"$/
|
30
|
+
key_regex = /msgctxt *"(.*)"$/
|
31
|
+
value_regex = /msgstr *"(.*)"$/m
|
32
|
+
File.open(path, 'r:UTF-8') do |f|
|
33
|
+
while item = f.gets("\n\n")
|
34
|
+
key = nil
|
35
|
+
value = nil
|
36
|
+
comment = nil
|
37
|
+
|
38
|
+
comment_match = comment_regex.match(item)
|
39
|
+
if comment_match
|
40
|
+
comment = comment_match[1]
|
41
|
+
end
|
42
|
+
key_match = key_regex.match(item)
|
43
|
+
if key_match
|
44
|
+
key = key_match[1].gsub('\\"', '"')
|
45
|
+
end
|
46
|
+
value_match = value_regex.match(item)
|
47
|
+
if value_match
|
48
|
+
value = value_match[1].gsub(/"\n"/, '').gsub('\\"', '"')
|
49
|
+
end
|
50
|
+
if key and key.length > 0 and value and value.length > 0
|
51
|
+
set_translation_for_key(key, lang, value)
|
52
|
+
if comment and comment.length > 0
|
53
|
+
set_comment_for_key(key, comment)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_file(path, lang)
|
61
|
+
default_lang = @strings.language_codes[0]
|
62
|
+
encoding = @options[:output_encoding] || 'UTF-8'
|
63
|
+
File.open(path, "w:#{encoding}") do |f|
|
64
|
+
f.puts "msgid \"\"\nmsgstr \"\"\n\"Language: #{lang}\\n\"\n\"X-Generator: Traduco #{Traduco::VERSION}\\n\"\n\n"
|
65
|
+
@strings.sections.each do |section|
|
66
|
+
printed_section = false
|
67
|
+
section.rows.each do |row|
|
68
|
+
if row.matches_tags?(@options[:tags], @options[:untagged])
|
69
|
+
basetrans = row.translated_string_for_lang(default_lang)
|
70
|
+
|
71
|
+
if basetrans
|
72
|
+
key = row.key
|
73
|
+
key = key.gsub('"', '\\\\"')
|
74
|
+
|
75
|
+
comment = row.comment
|
76
|
+
if comment
|
77
|
+
comment = comment.gsub('"', '\\\\"')
|
78
|
+
end
|
79
|
+
|
80
|
+
if comment && comment.length > 0
|
81
|
+
f.print "#. \"#{comment}\"\n"
|
82
|
+
end
|
83
|
+
|
84
|
+
f.print "msgctxt \"#{key}\"\nmsgid \"#{basetrans}\"\n"
|
85
|
+
value = row.translated_string_for_lang(lang)
|
86
|
+
if value
|
87
|
+
value = value.gsub('"', '\\\\"')
|
88
|
+
end
|
89
|
+
f.print "msgstr \"#{value}\"\n\n"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Traduco
|
2
|
+
module Formatters
|
3
|
+
class JQuery < Abstract
|
4
|
+
FORMAT_NAME = 'jquery'
|
5
|
+
EXTENSION = '.json'
|
6
|
+
DEFAULT_FILE_NAME = 'localize.json'
|
7
|
+
|
8
|
+
def self.can_handle_directory?(path)
|
9
|
+
Dir.entries(path).any? { |item| /^.+\.json$/.match(item) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_file_name
|
13
|
+
return DEFAULT_FILE_NAME
|
14
|
+
end
|
15
|
+
|
16
|
+
def determine_language_given_path(path)
|
17
|
+
path_arr = path.split(File::SEPARATOR)
|
18
|
+
path_arr.each do |segment|
|
19
|
+
match = /^((.+)-)?([^-]+)\.json$/.match(segment)
|
20
|
+
if match
|
21
|
+
return match[3]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_file(path, lang)
|
29
|
+
begin
|
30
|
+
require "json"
|
31
|
+
rescue LoadError
|
32
|
+
raise Traduco::Error.new "You must run 'gem install json' in order to read or write jquery-localize files."
|
33
|
+
end
|
34
|
+
|
35
|
+
open(path) do |io|
|
36
|
+
json = JSON.load(io)
|
37
|
+
json.each do |key, value|
|
38
|
+
set_translation_for_key(key, lang, value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def write_file(path, lang)
|
44
|
+
begin
|
45
|
+
require "json"
|
46
|
+
rescue LoadError
|
47
|
+
raise Traduco::Error.new "You must run 'gem install json' in order to read or write jquery-localize files."
|
48
|
+
end
|
49
|
+
|
50
|
+
default_lang = @strings.language_codes[0]
|
51
|
+
encoding = @options[:output_encoding] || 'UTF-8'
|
52
|
+
File.open(path, "w:#{encoding}") do |f|
|
53
|
+
f.puts "/**\n * JQuery Language File\n * Generated by Traduco\n * Language: #{lang}\n */"
|
54
|
+
f.puts "{"
|
55
|
+
|
56
|
+
@strings.sections.each_with_index do |section, si|
|
57
|
+
printed_section = false
|
58
|
+
section.rows.each_with_index do |row, ri|
|
59
|
+
if row.matches_tags?(@options[:tags], @options[:untagged])
|
60
|
+
if !printed_section
|
61
|
+
f.puts ''
|
62
|
+
if section.name && section.name.length > 0
|
63
|
+
f.puts "/* #{section.name} */"
|
64
|
+
end
|
65
|
+
printed_section = true
|
66
|
+
end
|
67
|
+
|
68
|
+
key = row.key
|
69
|
+
key = key.gsub('"', '\\\\"')
|
70
|
+
|
71
|
+
value = row.translated_string_for_lang(lang, default_lang)
|
72
|
+
value = value.gsub('"', '\\\\"')
|
73
|
+
|
74
|
+
comment = row.comment
|
75
|
+
if comment
|
76
|
+
comment = comment.gsub('*/', '* /')
|
77
|
+
end
|
78
|
+
|
79
|
+
f.print "\"#{key}\":\"#{value}\","
|
80
|
+
|
81
|
+
if comment && comment.length > 0
|
82
|
+
f.print " /* #{comment} */\n"
|
83
|
+
else
|
84
|
+
f.print "\n"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
f.seek(-2, IO::SEEK_CUR)
|
90
|
+
f.puts "\n}"
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
module Traduco
|
2
|
+
class StringsSection
|
3
|
+
attr_reader :name
|
4
|
+
attr_reader :rows
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name = name
|
8
|
+
@rows = []
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class StringsRow
|
13
|
+
attr_reader :key
|
14
|
+
attr_accessor :comment
|
15
|
+
attr_accessor :tags
|
16
|
+
attr_reader :translations
|
17
|
+
|
18
|
+
def initialize(key)
|
19
|
+
@key = key
|
20
|
+
@comment = nil
|
21
|
+
@tags = nil
|
22
|
+
@translations = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def matches_tags?(tags, include_untagged)
|
26
|
+
if tags == nil || tags.length == 0
|
27
|
+
# The user did not specify any tags. Everything passes.
|
28
|
+
return true
|
29
|
+
elsif @tags == nil || @tags.length == 0
|
30
|
+
# This row has no tags.
|
31
|
+
return (include_untagged) ? true : false
|
32
|
+
else
|
33
|
+
tags.each do |tag|
|
34
|
+
if @tags.include? tag
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
def translated_string_for_lang(lang, default_lang=nil)
|
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
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class StringsFile
|
60
|
+
attr_reader :sections
|
61
|
+
attr_reader :strings_map
|
62
|
+
attr_reader :language_codes
|
63
|
+
|
64
|
+
def initialize
|
65
|
+
@sections = []
|
66
|
+
@strings_map = {}
|
67
|
+
@language_codes = []
|
68
|
+
end
|
69
|
+
|
70
|
+
def read(path)
|
71
|
+
if !File.file?(path)
|
72
|
+
raise Traduco::Error.new("File does not exist: #{path}")
|
73
|
+
end
|
74
|
+
|
75
|
+
File.open(path, 'r:UTF-8') do |f|
|
76
|
+
line_num = 0
|
77
|
+
current_section = nil
|
78
|
+
current_row = nil
|
79
|
+
while line = f.gets
|
80
|
+
parsed = false
|
81
|
+
line.strip!
|
82
|
+
line_num += 1
|
83
|
+
|
84
|
+
if line.length == 0
|
85
|
+
next
|
86
|
+
end
|
87
|
+
|
88
|
+
# comment
|
89
|
+
if line[0, 1] == '#'
|
90
|
+
next
|
91
|
+
end
|
92
|
+
|
93
|
+
# section
|
94
|
+
if line.length > 4 && line[0, 2] == '[['
|
95
|
+
match = /^\[\[(.+)\]\]$/.match(line)
|
96
|
+
if match
|
97
|
+
current_section = StringsSection.new(match[1])
|
98
|
+
@sections << current_section
|
99
|
+
parsed = true
|
100
|
+
end
|
101
|
+
# string
|
102
|
+
elsif line.length > 2 && line[0, 1] == '['
|
103
|
+
match = /^\[(.+)\]$/.match(line)
|
104
|
+
if match
|
105
|
+
current_row = StringsRow.new(match[1])
|
106
|
+
@strings_map[current_row.key] = current_row
|
107
|
+
if !current_section
|
108
|
+
current_section = StringsSection.new('')
|
109
|
+
@sections << current_section
|
110
|
+
end
|
111
|
+
current_section.rows << current_row
|
112
|
+
parsed = true
|
113
|
+
end
|
114
|
+
# translation, comment or other
|
115
|
+
else
|
116
|
+
match = /^([^=]+)=(.*)$/.match(line)
|
117
|
+
if match
|
118
|
+
key = match[1].strip
|
119
|
+
value = match[2].strip
|
120
|
+
if value[0,1] == '`' && value[-1,1] == '`'
|
121
|
+
value = value[1..-2]
|
122
|
+
end
|
123
|
+
|
124
|
+
case key
|
125
|
+
when "comment"
|
126
|
+
current_row.comment = value
|
127
|
+
when 'tags'
|
128
|
+
current_row.tags = value.split(',')
|
129
|
+
else
|
130
|
+
if !@language_codes.include? key
|
131
|
+
add_language_code(key)
|
132
|
+
end
|
133
|
+
current_row.translations[key] = value
|
134
|
+
end
|
135
|
+
parsed = true
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
if !parsed
|
140
|
+
raise Traduco::Error.new("Unable to parse line #{line_num} of #{path}: #{line}")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def write(path)
|
147
|
+
dev_lang = @language_codes[0]
|
148
|
+
|
149
|
+
File.open(path, 'w:UTF-8') do |f|
|
150
|
+
@sections.each do |section|
|
151
|
+
if f.pos > 0
|
152
|
+
f.puts ''
|
153
|
+
end
|
154
|
+
|
155
|
+
f.puts "[[#{section.name}]]"
|
156
|
+
|
157
|
+
section.rows.each do |row|
|
158
|
+
f.puts "\t[#{row.key}]"
|
159
|
+
value = row.translations[dev_lang]
|
160
|
+
if !value
|
161
|
+
puts "Warning: #{row.key} does not exist in developer language '#{dev_lang}'"
|
162
|
+
else
|
163
|
+
if value[0,1] == ' ' || value[-1,1] == ' ' || (value[0,1] == '`' && value[-1,1] == '`')
|
164
|
+
value = '`' + value + '`'
|
165
|
+
end
|
166
|
+
f.puts "\t\t#{dev_lang} = #{value}"
|
167
|
+
end
|
168
|
+
|
169
|
+
if row.tags && row.tags.length > 0
|
170
|
+
tag_str = row.tags.join(',')
|
171
|
+
f.puts "\t\ttags = #{tag_str}"
|
172
|
+
end
|
173
|
+
if row.comment && row.comment.length > 0
|
174
|
+
f.puts "\t\tcomment = #{row.comment}"
|
175
|
+
end
|
176
|
+
@language_codes[1..-1].each do |lang|
|
177
|
+
value = row.translations[lang]
|
178
|
+
if value
|
179
|
+
if value[0,1] == ' ' || value[-1,1] == ' ' || (value[0,1] == '`' && value[-1,1] == '`')
|
180
|
+
value = '`' + value + '`'
|
181
|
+
end
|
182
|
+
f.puts "\t\t#{lang} = #{value}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def add_language_code(code)
|
191
|
+
if @language_codes.length == 0
|
192
|
+
@language_codes << code
|
193
|
+
elsif !@language_codes.include?(code)
|
194
|
+
dev_lang = @language_codes[0]
|
195
|
+
@language_codes << code
|
196
|
+
@language_codes.delete(dev_lang)
|
197
|
+
@language_codes.sort!
|
198
|
+
@language_codes.insert(0, dev_lang)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def set_developer_language_code(code)
|
203
|
+
if @language_codes.include?(code)
|
204
|
+
@language_codes.delete(code)
|
205
|
+
end
|
206
|
+
@language_codes.insert(0, code)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|