i18n-translators-tools 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +7 -2
- data/Rakefile +8 -2
- data/bin/i18n-translate +6 -1
- data/i18n-translators-tools.gemspec +20 -7
- data/lib/i18n/backend/translate.rb +2 -2
- data/lib/i18n/processor.rb +1 -1
- data/lib/i18n/processor/gettext.rb +1 -1
- data/lib/i18n/processor/gettext_strscan.rb +190 -0
- data/lib/i18n/processor/properties.rb +33 -17
- data/lib/i18n/processor/ts.rb +3 -2
- data/lib/i18n/translate.rb +55 -7
- data/test/all.rb +8 -1
- data/test/processor.rb +1 -1
- data/test/tc_backend_properties.rb +16 -0
- data/test/tc_processor_ts.rb +1 -1
- metadata +25 -16
data/README.md
CHANGED
@@ -2,7 +2,10 @@ I18n translation and locales management utility
|
|
2
2
|
===============================================
|
3
3
|
|
4
4
|
This package brings you useful utility and library which can help you to handle
|
5
|
-
locale files and translations in your Ruby projects.
|
5
|
+
locale files and translations in your Ruby projects. It is build upon i18n
|
6
|
+
library and extends it's simple format so you can simply track field changes
|
7
|
+
or keep translator's notes. Conversion back to simple format is possible and as
|
8
|
+
simple as call 'i18n-translate strip'.
|
6
9
|
|
7
10
|
|
8
11
|
Interesting features
|
@@ -11,6 +14,8 @@ Interesting features
|
|
11
14
|
* no database required
|
12
15
|
* merging and changes propagation (adding, removing and changed default text)
|
13
16
|
keeping default file untouched
|
17
|
+
* hard/soft merging (hard deletes extra keys from target, soft set them
|
18
|
+
obsolete; default is soft)
|
14
19
|
* creating new locale file based on default file
|
15
20
|
* converting from one format to another (yml <=> rb <=> po <=> ts <=> properties)
|
16
21
|
* statistics
|
@@ -275,7 +280,7 @@ New format looks like:
|
|
275
280
|
file: "file parsed from reference"
|
276
281
|
line: "line parsed from po reference"
|
277
282
|
translation: "translation itself"
|
278
|
-
flag: "one of (ok || incomplete || changed || untranslated)"
|
283
|
+
flag: "one of (ok || incomplete || changed || untranslated || obsolete)"
|
279
284
|
fuzzy: true # exists only where flag != ok (nice to have when you want
|
280
285
|
edit files manually)
|
281
286
|
|
data/Rakefile
CHANGED
@@ -8,7 +8,7 @@ require 'rake/testtask'
|
|
8
8
|
require 'rake/gempackagetask'
|
9
9
|
require 'rake/clean'
|
10
10
|
|
11
|
-
CLEAN << "coverage" << "pkg" << "README.html" << "CHANGELOG.html"
|
11
|
+
CLEAN << "coverage" << "pkg" << "README.html" << "CHANGELOG.html" << '*.rbc'
|
12
12
|
|
13
13
|
task :default => [:test, :doc, :gem]
|
14
14
|
Rake::TestTask.new(:test) do |t|
|
@@ -23,6 +23,12 @@ task :rcov do |t|
|
|
23
23
|
system "rcov --exclude .rvm,lib/ruby --sort coverage --text-summary --text-coverage-diff -o coverage test/all.rb"
|
24
24
|
end
|
25
25
|
|
26
|
+
desc "Benchmark processors"
|
27
|
+
task :bench do |t|
|
28
|
+
system "ruby benchmark/gettext_scan.rb"
|
29
|
+
system "ruby benchmark/gettext_reg.rb"
|
30
|
+
end
|
31
|
+
|
26
32
|
begin
|
27
33
|
require 'bluecloth'
|
28
34
|
|
@@ -47,5 +53,5 @@ begin
|
|
47
53
|
build_document("CHANGELOG.md")
|
48
54
|
end
|
49
55
|
|
50
|
-
rescue
|
56
|
+
rescue LoadError
|
51
57
|
end
|
data/bin/i18n-translate
CHANGED
@@ -51,6 +51,8 @@ options:
|
|
51
51
|
--quiet, -q -- show less information
|
52
52
|
--verbose, -v -- shows more information during
|
53
53
|
locales processing
|
54
|
+
--hard, -h -- deletes from target files all missing keys in default
|
55
|
+
normaly those keys are kept but flag is set to obsolete
|
54
56
|
|
55
57
|
|
56
58
|
CONFIG FILE
|
@@ -184,7 +186,8 @@ opts = GetoptLong.new(
|
|
184
186
|
["--encoding", "-e", GetoptLong::REQUIRED_ARGUMENT],
|
185
187
|
["--verbose", "-v", GetoptLong::NO_ARGUMENT],
|
186
188
|
["--quiet", "-q", GetoptLong::NO_ARGUMENT],
|
187
|
-
["--locale", "-l", GetoptLong::REQUIRED_ARGUMENT]
|
189
|
+
["--locale", "-l", GetoptLong::REQUIRED_ARGUMENT],
|
190
|
+
["--hard", "-h", GetoptLong::NO_ARGUMENT]
|
188
191
|
)
|
189
192
|
|
190
193
|
|
@@ -214,6 +217,8 @@ opts.each do |opt, val|
|
|
214
217
|
tmp[optkey] = val
|
215
218
|
when :deep
|
216
219
|
tmp[optkey] = val
|
220
|
+
when :hard
|
221
|
+
tmp[:merge] = "hard"
|
217
222
|
when :verbose
|
218
223
|
tmp[:verbose] = true
|
219
224
|
tmp[:quiet] = false
|
@@ -13,9 +13,9 @@ spec = Gem::Specification.new do |s|
|
|
13
13
|
s.email = "pejuko@gmail.com"
|
14
14
|
s.authors = ["Petr Kovar"]
|
15
15
|
s.name = 'i18n-translators-tools'
|
16
|
-
s.version = '0.2.
|
16
|
+
s.version = '0.2.4'
|
17
17
|
s.date = Time.now.strftime("%Y-%m-%d")
|
18
|
-
s.add_dependency('i18n', '>= 0.
|
18
|
+
s.add_dependency('i18n', '>= 0.5.0')
|
19
19
|
s.add_dependency('ya2yaml')
|
20
20
|
s.require_path = 'lib'
|
21
21
|
s.files = ["bin/i18n-translate", "README.md", "i18n-translators-tools.gemspec", "Rakefile"]
|
@@ -37,7 +37,7 @@ Supported formats:
|
|
37
37
|
|
38
38
|
Backends:
|
39
39
|
* Extended format. i18n-translators-tools brings extended format
|
40
|
-
I18n::Backend::Simple.send(:include, I18n::Backend::
|
40
|
+
I18n::Backend::Simple.send(:include, I18n::Backend::Translate)
|
41
41
|
* Gettext po
|
42
42
|
I18n::Backend::Simple.send(:include, I18n::Backend::PO)
|
43
43
|
* QT Linguist TS
|
@@ -64,9 +64,19 @@ Changelog:
|
|
64
64
|
v0.2.3
|
65
65
|
* fix: hash_to_keys can work with enhanced format => default can be in
|
66
66
|
enchanced format
|
67
|
+
* default file can be now in enhanced format. if translation field is missing
|
67
68
|
* i18n-translate <source file> <target file>
|
68
69
|
automaticlay perform convert action from one file to another
|
69
70
|
|
71
|
+
v0.2.4
|
72
|
+
* enhanced support for java properties
|
73
|
+
* hard/soft merges. hard deletes deleted keys in target and soft set them to
|
74
|
+
obsolete
|
75
|
+
* processors now generate keys list from provided data and not from @tr.default
|
76
|
+
* flag obsolete added
|
77
|
+
* delete function
|
78
|
+
* i18n-0.5.0 compatibility (for older i18n user v0.2.3)
|
79
|
+
|
70
80
|
For more information read README.md and CHANGELOG.md
|
71
81
|
|
72
82
|
-----------------------------------------------------------------------------
|
@@ -77,10 +87,13 @@ http://github.com/pejuko/i18n-translators-tools
|
|
77
87
|
EOF
|
78
88
|
s.description = <<EOF
|
79
89
|
This package brings you useful utility and library which can help you to handle
|
80
|
-
locale files and translations in your Ruby projects.
|
81
|
-
|
82
|
-
|
83
|
-
|
90
|
+
locale files and translations in your Ruby projects.
|
91
|
+
It is build upon i18n library and extends it's simple format so you can simply
|
92
|
+
track field changes or keep translator's notes. Conversion back to simple format
|
93
|
+
is possible and as simple as call 'i18n-translate strip'. Offers also built-in
|
94
|
+
simple console editor. Supported formats are YAML, Ruby, Gettext po,
|
95
|
+
QT Linguist TS and Java Properties. Read README.md file and run i18n-translate
|
96
|
+
without parameters for more information.
|
84
97
|
EOF
|
85
98
|
end
|
86
99
|
|
@@ -8,7 +8,7 @@ module I18n
|
|
8
8
|
module Backend
|
9
9
|
# It is highly recommended to use Translator wit Fallback plugin
|
10
10
|
#
|
11
|
-
# I18n::Backend::Simple.send(:include, I18n::Backend::
|
11
|
+
# I18n::Backend::Simple.send(:include, I18n::Backend::Translate)
|
12
12
|
# I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
|
13
13
|
#
|
14
14
|
# notice that Translator have to be included BEFORE Fallback otherwise
|
@@ -28,7 +28,7 @@ module I18n
|
|
28
28
|
|
29
29
|
return nil if tr.to_s.empty?
|
30
30
|
|
31
|
-
values = options.except(*
|
31
|
+
values = options.except(*RESERVED_KEYS)
|
32
32
|
|
33
33
|
tr = resolve(locale, key, tr, options)
|
34
34
|
tr = interpolate(locale, tr, values) if values
|
data/lib/i18n/processor.rb
CHANGED
@@ -121,7 +121,7 @@ module I18n::Translate
|
|
121
121
|
# converts inspected string back into normal string
|
122
122
|
def uninspect(str)
|
123
123
|
return nil unless str
|
124
|
-
str.gsub(%r!\\([\\#"abefnrstvx]|u\d{4}|u\{[^\}]+\}|\d{1,3}|x\d{1,2}|cx|C-[a-zA-Z]|M-[a-zA-Z])!) do |m|
|
124
|
+
str.gsub(%r!\\([\\#"abefnrstvx]|u\d{4}|u\{[^\}]+\}|\d{1,3}|x\d{1,2}|cx|C-[a-zA-Z]|M-[a-zA-Z]| |=|:)!) do |m|
|
125
125
|
repl = ""
|
126
126
|
if ['\\', '#', '"'].include?($1)
|
127
127
|
repl = $1
|
@@ -117,7 +117,7 @@ module I18n::Translate::Processor
|
|
117
117
|
def export(data)
|
118
118
|
target = data[@translate.lang]
|
119
119
|
str = ""
|
120
|
-
keys = I18n::Translate.hash_to_keys(
|
120
|
+
keys = I18n::Translate.hash_to_keys(target).sort
|
121
121
|
|
122
122
|
str << %~msgid ""\n~
|
123
123
|
str << %~msgstr ""\n~
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# vi: fenc=utf-8:expandtab:ts=2:sw=2:sts=2
|
3
|
+
#
|
4
|
+
# @author: Petr Kovar <pejuko@gmail.com>
|
5
|
+
|
6
|
+
require 'strscan'
|
7
|
+
|
8
|
+
module I18n::Translate::Processor
|
9
|
+
|
10
|
+
class GettextScanner < Template
|
11
|
+
FORMAT = ['po', 'pot']
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def import(data)
|
16
|
+
hash = {}
|
17
|
+
|
18
|
+
entry = {}
|
19
|
+
key = nil
|
20
|
+
last = nil
|
21
|
+
|
22
|
+
|
23
|
+
s = StringScanner.new(data)
|
24
|
+
|
25
|
+
until s.eos?
|
26
|
+
|
27
|
+
# empty line starts new entry
|
28
|
+
if s.scan /\n\s*\n/
|
29
|
+
if not entry.empty? and key
|
30
|
+
key = entry["default"].dup unless key
|
31
|
+
I18n::Translate.set(key, entry, hash, @translate.options[:separator])
|
32
|
+
end
|
33
|
+
key = last = nil
|
34
|
+
entry = {}
|
35
|
+
next
|
36
|
+
end
|
37
|
+
|
38
|
+
# skip end of line
|
39
|
+
if s.scan /\n/
|
40
|
+
next
|
41
|
+
end
|
42
|
+
|
43
|
+
# comments
|
44
|
+
if s.scan /#/
|
45
|
+
# translator's comment
|
46
|
+
if s.scan %r{\s+}
|
47
|
+
entry["comment"] = s.scan(/.*?$/).to_s.strip
|
48
|
+
|
49
|
+
# extracted comment
|
50
|
+
elsif s.scan %r{\.\s+}
|
51
|
+
entry["extracted_comment"] = s.scan(/.*?$/).to_s.strip
|
52
|
+
|
53
|
+
# reference
|
54
|
+
elsif s.scan %r{:\s+}
|
55
|
+
entry["reference"] = s.scan(/.*?$/).to_s.strip
|
56
|
+
if entry["reference"] =~ %r{(.*):(\d+)}
|
57
|
+
entry["file"] = $1.to_s.strip
|
58
|
+
entry["line"] = $2.to_s.strip
|
59
|
+
end
|
60
|
+
|
61
|
+
# flag
|
62
|
+
elsif s.scan %r{,\s+}
|
63
|
+
flags = s.scan(/.*?$/).split(",").compact.map{|x| x.strip}
|
64
|
+
fuzzy = flags.delete("fuzzy")
|
65
|
+
unless fuzzy
|
66
|
+
entry["flag"] = "ok"
|
67
|
+
else
|
68
|
+
flags.delete_if{|x| not I18n::Translate::FLAGS.include?(x)}
|
69
|
+
entry["flag"] = flags.first unless flags.empty?
|
70
|
+
entry["fuzzy"] = true
|
71
|
+
end
|
72
|
+
|
73
|
+
# old default
|
74
|
+
elsif s.scan %r{\| msgid\s+"}
|
75
|
+
match = s.scan(%r{.*"$}).to_s[0..-2]
|
76
|
+
entry["old_default"] = match
|
77
|
+
# expect that this entry has no key
|
78
|
+
# if does, will be overwriten later
|
79
|
+
key = entry["old_default"].dup
|
80
|
+
end
|
81
|
+
end # end of scan for comments
|
82
|
+
|
83
|
+
# key (context)
|
84
|
+
if s.scan %r{msgctxt\s+"}
|
85
|
+
key = get_string(s)
|
86
|
+
last = "key"
|
87
|
+
|
88
|
+
# default
|
89
|
+
elsif s.scan %r{msgid\s+"}
|
90
|
+
match = get_string(s)
|
91
|
+
if match.empty?
|
92
|
+
last = "po-header"
|
93
|
+
else
|
94
|
+
last = "default"
|
95
|
+
entry[last] = uninspect(match)
|
96
|
+
key = entry[last].dup unless key
|
97
|
+
end
|
98
|
+
|
99
|
+
# translation
|
100
|
+
elsif s.scan %r{msgstr\s+"}
|
101
|
+
match = get_string(s)
|
102
|
+
last = "translation" unless last == "po-header"
|
103
|
+
entry[last] = uninspect(match)
|
104
|
+
|
105
|
+
# string continuation
|
106
|
+
elsif s.scan %r{"}
|
107
|
+
match = get_string(s)
|
108
|
+
if last == "key"
|
109
|
+
key = "#{key}#{match}"
|
110
|
+
elsif last == "po-header"
|
111
|
+
case match
|
112
|
+
when %r{Content-Type: text/plain; charset=(.*)}
|
113
|
+
enc = uninspect($1.to_s).strip
|
114
|
+
@translate.options[:encoding] = enc unless enc.empty?
|
115
|
+
when %r{X-Language: (.*)}
|
116
|
+
# skip language is set from filename
|
117
|
+
end
|
118
|
+
elsif last
|
119
|
+
entry[last] = "#{entry[last]}#{uninspect(match)}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# last line at end of file
|
125
|
+
if not entry.empty? and key
|
126
|
+
I18n::Translate.set(key, entry, hash, @translate.options[:separator])
|
127
|
+
end
|
128
|
+
|
129
|
+
{@translate.lang => hash}
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
def export(data)
|
134
|
+
target = data[@translate.lang]
|
135
|
+
str = ""
|
136
|
+
keys = I18n::Translate.hash_to_keys(@translate.default).sort
|
137
|
+
|
138
|
+
str << %~msgid ""\n~
|
139
|
+
str << %~msgstr ""\n~
|
140
|
+
str << %~"Content-Type: text/plain; charset=#{@translate.options[:encoding]}\\n"\n~
|
141
|
+
str << %~"X-Language: #{@translate.lang}\\n"\n~
|
142
|
+
keys.each do |key|
|
143
|
+
entry = [""]
|
144
|
+
value = @translate.find(key, target)
|
145
|
+
next unless value
|
146
|
+
|
147
|
+
if value.kind_of?(String)
|
148
|
+
# leave out msgctxt if using po strings as a key
|
149
|
+
default = @translate.find(key, @translate.default)
|
150
|
+
entry << %~msgctxt #{key.inspect}~ if key != default
|
151
|
+
entry << %~msgid #{default.to_s.inspect}~
|
152
|
+
entry << %~msgstr #{value.to_s.inspect}~
|
153
|
+
else
|
154
|
+
entry << %~# #{value["comment"].to_s.strip}~ unless value["comment"].to_s.strip.empty?
|
155
|
+
entry << %~#. #{value["extracted_comment"].to_s.strip}~ unless value["extracted_comment"].to_s.strip.empty?
|
156
|
+
if not value["reference"].to_s.strip.empty?
|
157
|
+
entry << %~#: #{value["reference"].to_s.strip}~
|
158
|
+
elsif value["file"] or value["line"]
|
159
|
+
entry << %~#: #{value["file"].to_s.strip}:#{value["line"].to_s.strip}~
|
160
|
+
end
|
161
|
+
key_default = nil
|
162
|
+
key_default = value["default"] if value["default"] == key
|
163
|
+
key_default = value["old_default"] if value["old_default"] == key
|
164
|
+
flags = []
|
165
|
+
flags << "fuzzy" if (not value["flag"].nil?) and (value["flag"] != "ok")
|
166
|
+
flags << value["flag"] unless value["flag"].to_s.strip.empty?
|
167
|
+
entry << %~#, #{flags.join(", ")}~ unless flags.empty?
|
168
|
+
entry << %~#| msgid #{value["old_default"].to_s.inspect}~ unless value["old_default"].to_s.empty?
|
169
|
+
entry << %~msgctxt #{key.inspect}~ if key != key_default
|
170
|
+
entry << %~msgid #{value["default"].to_s.inspect}~
|
171
|
+
entry << %~msgstr #{value["translation"].to_s.inspect}~
|
172
|
+
end
|
173
|
+
|
174
|
+
entry << ""
|
175
|
+
|
176
|
+
str << entry.join("\n")
|
177
|
+
end
|
178
|
+
|
179
|
+
str
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
def get_string scanner
|
185
|
+
scanner.scan(%r{.*?"$}).to_s[0..-2]
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
@@ -8,6 +8,13 @@ module I18n::Translate::Processor
|
|
8
8
|
class Properties < Template
|
9
9
|
FORMAT = ['properties']
|
10
10
|
|
11
|
+
WHITE_SPACE = / |\n|\t|\f|\r|\\u0020|\\u0009|\\u000C/
|
12
|
+
ASSIGN = /(?:#{WHITE_SPACE})*?(?:=|:)(?:#{WHITE_SPACE})*/
|
13
|
+
KEY = /^((?:[^\\:=]|\\:|\\=|\\ )+?)/m
|
14
|
+
VALUE_MULTILINE = /(.*?(?:\\\\)*)\\$/
|
15
|
+
VALUE = /(.*?(?:\\\\)*$)/
|
16
|
+
VALUE_END = /([^=]+?(?:\\\\)*$)/
|
17
|
+
|
11
18
|
protected
|
12
19
|
|
13
20
|
def import(data)
|
@@ -15,44 +22,53 @@ module I18n::Translate::Processor
|
|
15
22
|
|
16
23
|
key = nil
|
17
24
|
value = nil
|
25
|
+
status = :first
|
18
26
|
line_number = 0
|
19
27
|
data.each_line do |line|
|
20
28
|
line_number += 1
|
21
|
-
# skip empty line and comments
|
22
|
-
next if (line =~ %r{
|
29
|
+
# skip empty line and comments (first non white character is # or !)
|
30
|
+
next if (line =~ %r{^(#{WHITE_SPACE})*$}) or (line =~ %r{^(#{WHITE_SPACE})*(#|!).*})
|
23
31
|
|
24
|
-
case line
|
25
32
|
# multiline string
|
26
|
-
|
27
|
-
|
28
|
-
|
33
|
+
if line[%r{#{KEY}#{ASSIGN}#{VALUE_MULTILINE}}] and (status == :first)
|
34
|
+
status = :inside
|
35
|
+
key = $1.to_s
|
36
|
+
value = $2.to_s
|
29
37
|
|
30
38
|
# continuous multiline string
|
31
|
-
|
32
|
-
value << $1.to_s
|
39
|
+
elsif line[%r{^(?:#{WHITE_SPACE})*#{VALUE_MULTILINE}}] and (status == :inside)
|
40
|
+
value << $1.to_s
|
33
41
|
|
34
42
|
# end of continuous string
|
35
|
-
|
43
|
+
elsif line[%r{^(?:#{WHITE_SPACE})*#{VALUE_END}$}] and (status == :inside)
|
36
44
|
value << $1.to_s.strip
|
37
|
-
I18n::Translate.set(key, uninspect(value), hash, @translate.options[:separator])
|
45
|
+
I18n::Translate.set(uninspect(key), uninspect(value), hash, @translate.options[:separator])
|
38
46
|
value = nil
|
47
|
+
status = :first
|
39
48
|
|
40
49
|
# simple key = value
|
41
|
-
|
50
|
+
elsif line[%r{#{KEY}#{ASSIGN}#{VALUE}}]
|
42
51
|
key, value = $1.to_s.strip, $2.to_s.strip
|
43
|
-
I18n::Translate.set(key, uninspect(value), hash, @translate.options[:separator])
|
52
|
+
I18n::Translate.set(uninspect(key), uninspect(value), hash, @translate.options[:separator])
|
53
|
+
|
54
|
+
# empty key
|
55
|
+
elsif line[/#{KEY}$/]
|
56
|
+
key = $1.to_s.strip
|
57
|
+
value = ""
|
58
|
+
I18n::Translate.set(uninspect(key), uninspect(value), hash, @translate.options[:separator])
|
59
|
+
else
|
60
|
+
puts "*** not match: '#{line}'"
|
44
61
|
end
|
45
62
|
end
|
46
63
|
|
47
64
|
{@lang => hash}
|
48
65
|
end
|
49
66
|
|
50
|
-
|
51
67
|
# this export ignores data
|
52
68
|
def export(data)
|
53
69
|
target = data[@translate.lang]
|
54
70
|
str = ""
|
55
|
-
keys = I18n::Translate.hash_to_keys(
|
71
|
+
keys = I18n::Translate.hash_to_keys(target).sort
|
56
72
|
|
57
73
|
keys.each do |key|
|
58
74
|
value = @translate.find(key, target)
|
@@ -61,13 +77,13 @@ module I18n::Translate::Processor
|
|
61
77
|
|
62
78
|
|
63
79
|
if value.kind_of?(String)
|
64
|
-
entry = value
|
80
|
+
entry = value
|
65
81
|
else
|
66
|
-
entry = value["translation"].to_s
|
82
|
+
entry = value["translation"].to_s
|
67
83
|
end
|
68
84
|
|
69
85
|
# create record in format: key = value
|
70
|
-
str << key << " = " << entry.gsub("\n", "\\n") << "\n"
|
86
|
+
str << key.gsub(/( |:|=)/){|m| "\\#{m}"} << " = " << entry.gsub("\n", "\\n") << "\n"
|
71
87
|
end
|
72
88
|
|
73
89
|
str
|
data/lib/i18n/processor/ts.rb
CHANGED
@@ -53,6 +53,7 @@ module I18n::Translate::Processor
|
|
53
53
|
entry["comment"] = get(message, "translatorcomment").to_s.strip
|
54
54
|
entry["translation"] = get(message, "translation")
|
55
55
|
fuzzy = get(message, "translation", "type").to_s.strip
|
56
|
+
entry["flag"] = fuzzy if fuzzy == "obsolete"
|
56
57
|
entry["fuzzy"] = true unless fuzzy.empty?
|
57
58
|
flag = get(message, "extra-po-flags").to_s.strip
|
58
59
|
entry["flag"] = flag unless flag.empty?
|
@@ -77,7 +78,7 @@ module I18n::Translate::Processor
|
|
77
78
|
<TS version="2.0" language="#{@translate.lang}">
|
78
79
|
EOF
|
79
80
|
|
80
|
-
keys = I18n::Translate.hash_to_keys(
|
81
|
+
keys = I18n::Translate.hash_to_keys(target).sort
|
81
82
|
keys.each do |key|
|
82
83
|
value = @translate.find(key, target)
|
83
84
|
next unless value
|
@@ -94,7 +95,7 @@ EOF
|
|
94
95
|
</context>
|
95
96
|
EOF
|
96
97
|
else
|
97
|
-
fuzzy = ((value["flag"] == "ok") or value["flag"].to_s.strip.empty?) ? "" : %~ type="unfinished"~
|
98
|
+
fuzzy = ((value["flag"] == "ok") or value["flag"].to_s.strip.empty?) ? "" : (value["flag"] == "obsolete") ? %~ type="obsolete"~ : %~ type="unfinished"~
|
98
99
|
xml += <<EOF
|
99
100
|
<context>
|
100
101
|
<name>#{::CGI.escapeHTML(key)}</name>
|
data/lib/i18n/translate.rb
CHANGED
@@ -28,7 +28,7 @@ require 'find'
|
|
28
28
|
# extracted_comment: po compatibility
|
29
29
|
# file: file where it is # po compatibility
|
30
30
|
# line: the lines, where is this key used # po compatibility
|
31
|
-
# flag: ok || incomplete || changed || untranslated
|
31
|
+
# flag: ok || incomplete || changed || untranslated || obsolete
|
32
32
|
# fuzzy: true # exists only where flag != ok (nice to have when you want
|
33
33
|
# edit files manualy)
|
34
34
|
#
|
@@ -48,7 +48,7 @@ require 'find'
|
|
48
48
|
#
|
49
49
|
module I18n::Translate
|
50
50
|
|
51
|
-
FLAGS = %w(ok incomplete changed untranslated)
|
51
|
+
FLAGS = %w(ok incomplete changed untranslated obsolete)
|
52
52
|
#FORMATS = %w(yml rb po pot ts properties) # the first one is preferred if :format => auto
|
53
53
|
# function I18n::Translate::Processor.init will register known
|
54
54
|
# formats
|
@@ -103,12 +103,26 @@ module I18n::Translate
|
|
103
103
|
h = h[chunk]
|
104
104
|
end
|
105
105
|
unless value
|
106
|
-
h
|
106
|
+
h.delete(path[-1])
|
107
107
|
else
|
108
108
|
h[path[-1]] = value
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
+
def self.delete(key, hash, separator=".")
|
113
|
+
path = key.split(separator)
|
114
|
+
set(key, nil, hash, separator)
|
115
|
+
i = path.size - 1
|
116
|
+
while i >= 0
|
117
|
+
k = path[0..i].join(separator)
|
118
|
+
trg = find(k, hash, separator)
|
119
|
+
if trg and trg.kind_of?(Hash) and trg.empty?
|
120
|
+
set(k, nil, hash, separator)
|
121
|
+
end
|
122
|
+
i -= 1
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
112
126
|
# scans :locale_dir for files with valid formats and returns
|
113
127
|
# list of files with locales. If block is given then
|
114
128
|
# it creates Translate object for each entry and pass it to the block
|
@@ -175,7 +189,8 @@ module I18n::Translate
|
|
175
189
|
:default => "default", # default name for file containing default app's key => string
|
176
190
|
:force_encoding => true, # in ruby 1.9 forces string encoding
|
177
191
|
:encoding => "utf-8", # encoding name to be forced to
|
178
|
-
:format => "auto"
|
192
|
+
:format => "auto", # auto, rb, yml
|
193
|
+
:merge => "soft", # hard or soft: hard strips old keys from target and soft set it to obsolete
|
179
194
|
}
|
180
195
|
|
181
196
|
attr_reader :default, :target, :merge, :options, :lang, :default_file, :lang_file
|
@@ -225,8 +240,9 @@ module I18n::Translate
|
|
225
240
|
# 'old_translation' => '', # if flag == 'changed' then old_translation = t and t = ''
|
226
241
|
# 'translation' => '', # value set in target file
|
227
242
|
# 'comment' => '' # a comment added by a translator
|
228
|
-
# 'flag' => ok || incomplete || changed || untranslated
|
229
|
-
#
|
243
|
+
# 'flag' => ok || incomplete || changed || untranslated || obsolete
|
244
|
+
# # set by merging tool except incomplete
|
245
|
+
# which is set by translator
|
230
246
|
# # other keys helded for compatibility with other formats
|
231
247
|
# }
|
232
248
|
def [](key)
|
@@ -279,6 +295,11 @@ module I18n::Translate
|
|
279
295
|
I18n::Translate.find(key, hash, separator)
|
280
296
|
end
|
281
297
|
|
298
|
+
def delete(key)
|
299
|
+
I18n::Translate.delete(key, @default, @options[:separator])
|
300
|
+
I18n::Translate.delete(key, @target, @options[:separator])
|
301
|
+
end
|
302
|
+
|
282
303
|
# will create path in @target for 'key' and set the 'value'
|
283
304
|
def []=(key, value)
|
284
305
|
I18n::Translate.set(key, value, @target, @options[:separator])
|
@@ -346,6 +367,32 @@ module I18n::Translate
|
|
346
367
|
|
347
368
|
self[key] = trg
|
348
369
|
end
|
370
|
+
obsolete!
|
371
|
+
end
|
372
|
+
|
373
|
+
def obsolete!(merge = @options[:merge])
|
374
|
+
def_keys = I18n::Translate.hash_to_keys(@default, @options[:separator]).sort
|
375
|
+
trg_keys = I18n::Translate.hash_to_keys(@target, @options[:separator]).sort
|
376
|
+
|
377
|
+
obsolete_keys = trg_keys - def_keys
|
378
|
+
obsolete_keys.each do |key|
|
379
|
+
if merge == "hard"
|
380
|
+
I18n::Translate.delete(key, @target, @options[:separator])
|
381
|
+
next
|
382
|
+
end
|
383
|
+
|
384
|
+
trg = find(key)
|
385
|
+
next unless trg
|
386
|
+
|
387
|
+
if trg.kind_of?(String)
|
388
|
+
trg = {"translation" => trg, "flag" => "obsolete"}
|
389
|
+
else
|
390
|
+
trg["flag"] = "obsolete"
|
391
|
+
trg["fuzzy"] = true
|
392
|
+
end
|
393
|
+
|
394
|
+
I18n::Translate.set(key, trg, @target, @options[:separator])
|
395
|
+
end
|
349
396
|
end
|
350
397
|
|
351
398
|
# re-read @target data from the disk and create @merge
|
@@ -380,7 +427,7 @@ module I18n::Translate
|
|
380
427
|
end
|
381
428
|
|
382
429
|
# returns statistics hash
|
383
|
-
# {:total => N, :ok => N, :changed => N, :incomplete => N, :untranslated => N, :fuzzy => N, :progress => N}
|
430
|
+
# {:total => N, :ok => N, :changed => N, :obsolete => N, :incomplete => N, :untranslated => N, :fuzzy => N, :progress => N}
|
384
431
|
def stat
|
385
432
|
stat = {
|
386
433
|
:total => @merge.size,
|
@@ -388,6 +435,7 @@ module I18n::Translate
|
|
388
435
|
:changed => @merge.select{|e| e["flag"] == "changed"}.size,
|
389
436
|
:incomplete => @merge.select{|e| e["flag"] == "incomplete"}.size,
|
390
437
|
:untranslated => @merge.select{|e| e["flag"] == "untranslated"}.size,
|
438
|
+
:obsolete => @merge.select{|e| e["flag"] == "obsolete"}.size,
|
391
439
|
:fuzzy => @merge.select{|e| e["flag"] != "ok"}.size
|
392
440
|
}
|
393
441
|
stat[:progress] = (stat[:ok].to_f / stat[:total].to_f) * 100
|
data/test/all.rb
CHANGED
@@ -8,6 +8,7 @@ $KCODE='UTF8'
|
|
8
8
|
require 'test/unit'
|
9
9
|
require 'rubygems'
|
10
10
|
require 'yaml'
|
11
|
+
require 'pp'
|
11
12
|
|
12
13
|
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
13
14
|
require 'lib/i18n-translate'
|
@@ -64,7 +65,13 @@ def diff(src, trg)
|
|
64
65
|
src.keys.each { |key| puts "src key: #{key}" unless trg.keys.include?(key) }
|
65
66
|
trg.keys.each { |key| puts "trg key: #{key}" unless src.keys.include?(key) }
|
66
67
|
src.keys.each do |key, value|
|
67
|
-
|
68
|
+
if src[key] != trg[key]
|
69
|
+
print "#{key}: "
|
70
|
+
pp src[key]
|
71
|
+
puts "!="
|
72
|
+
pp trg[key]
|
73
|
+
end
|
74
|
+
#puts "#{key} #{src[key].inspect} != #{trg[key].inspect}" if src[key] != trg[key]
|
68
75
|
end
|
69
76
|
elsif src.kind_of?(Array) and trg.kind_of?(Array)
|
70
77
|
src.each {|k| puts "not in trg '#{k}'" unless trg.include?(k)}
|
data/test/processor.rb
CHANGED
@@ -10,7 +10,7 @@ module I18n::Test
|
|
10
10
|
def __prepare(processor, file)
|
11
11
|
# prepare object with correct data from yaml
|
12
12
|
@tr = I18n::Translate::Translate.new('cze', {:locale_dir => $src_dir, :default_format => 'yml', :format => 'yml'})
|
13
|
-
@tr.assign(@tr.
|
13
|
+
@tr.assign(@tr.merge)
|
14
14
|
|
15
15
|
# prepare reader and writer
|
16
16
|
@src_file = File.join($src_dir, file)
|
@@ -13,4 +13,20 @@ class TestBackendProperties < Test::Unit::TestCase
|
|
13
13
|
|
14
14
|
include I18n::Test::Backend
|
15
15
|
|
16
|
+
def test_1000_colon_as_assign_operator
|
17
|
+
assert_equal( "apple, banana, pear, cantaloupe, watermelon, kiwi, mango", I18n.t("fruits") )
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_1010_space_at_key_begining
|
21
|
+
assert_equal( "Beauty", I18n.t("Truth") )
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_1020_escaped_operators_in_key
|
25
|
+
assert_equal( "operators", I18n.t("esc=aped:operators") )
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_1030_key_without_assign_and_value
|
29
|
+
assert_equal( "translation missing: cze.empty_key", I18n.t("empty_key") )
|
30
|
+
end
|
31
|
+
|
16
32
|
end
|
data/test/tc_processor_ts.rb
CHANGED
@@ -12,7 +12,7 @@ class TestProcessorTS < Test::Unit::TestCase
|
|
12
12
|
|
13
13
|
include I18n::Test::Processor
|
14
14
|
|
15
|
-
def
|
15
|
+
def test_1010_read_po_to_ts
|
16
16
|
t = I18n::Translate::Translate.new('cze', {:locale_dir => $src_dir, :default_format => 'yml', :format => 'yml'})
|
17
17
|
file = File.join($src_dir, 'po_to_ts.ts')
|
18
18
|
reader = I18n::Translate::Processor::TS.new(file, t)
|
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: i18n-translators-tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 17
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 0
|
8
7
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
8
|
+
- 4
|
9
|
+
version: 0.2.4
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Petr Kovar
|
@@ -15,7 +14,7 @@ autorequire:
|
|
15
14
|
bindir: bin
|
16
15
|
cert_chain: []
|
17
16
|
|
18
|
-
date: 2010-
|
17
|
+
date: 2010-11-29 00:00:00 +01:00
|
19
18
|
default_executable:
|
20
19
|
dependencies:
|
21
20
|
- !ruby/object:Gem::Dependency
|
@@ -26,12 +25,11 @@ dependencies:
|
|
26
25
|
requirements:
|
27
26
|
- - ">="
|
28
27
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 13
|
30
28
|
segments:
|
31
29
|
- 0
|
32
|
-
-
|
33
|
-
-
|
34
|
-
version: 0.
|
30
|
+
- 5
|
31
|
+
- 0
|
32
|
+
version: 0.5.0
|
35
33
|
type: :runtime
|
36
34
|
version_requirements: *id001
|
37
35
|
- !ruby/object:Gem::Dependency
|
@@ -42,7 +40,6 @@ dependencies:
|
|
42
40
|
requirements:
|
43
41
|
- - ">="
|
44
42
|
- !ruby/object:Gem::Version
|
45
|
-
hash: 3
|
46
43
|
segments:
|
47
44
|
- 0
|
48
45
|
version: "0"
|
@@ -50,10 +47,13 @@ dependencies:
|
|
50
47
|
version_requirements: *id002
|
51
48
|
description: |
|
52
49
|
This package brings you useful utility and library which can help you to handle
|
53
|
-
locale files and translations in your Ruby projects.
|
54
|
-
|
55
|
-
|
56
|
-
|
50
|
+
locale files and translations in your Ruby projects.
|
51
|
+
It is build upon i18n library and extends it's simple format so you can simply
|
52
|
+
track field changes or keep translator's notes. Conversion back to simple format
|
53
|
+
is possible and as simple as call 'i18n-translate strip'. Offers also built-in
|
54
|
+
simple console editor. Supported formats are YAML, Ruby, Gettext po,
|
55
|
+
QT Linguist TS and Java Properties. Read README.md file and run i18n-translate
|
56
|
+
without parameters for more information.
|
57
57
|
|
58
58
|
email: pejuko@gmail.com
|
59
59
|
executables:
|
@@ -74,6 +74,7 @@ files:
|
|
74
74
|
- lib/i18n/backend/translate.rb
|
75
75
|
- lib/i18n/processor/properties.rb
|
76
76
|
- lib/i18n/processor/ts.rb
|
77
|
+
- lib/i18n/processor/gettext_strscan.rb
|
77
78
|
- lib/i18n/processor/ruby.rb
|
78
79
|
- lib/i18n/processor/yaml.rb
|
79
80
|
- lib/i18n/processor/gettext.rb
|
@@ -124,7 +125,7 @@ post_install_message: |
|
|
124
125
|
|
125
126
|
Backends:
|
126
127
|
* Extended format. i18n-translators-tools brings extended format
|
127
|
-
I18n::Backend::Simple.send(:include, I18n::Backend::
|
128
|
+
I18n::Backend::Simple.send(:include, I18n::Backend::Translate)
|
128
129
|
* Gettext po
|
129
130
|
I18n::Backend::Simple.send(:include, I18n::Backend::PO)
|
130
131
|
* QT Linguist TS
|
@@ -151,9 +152,19 @@ post_install_message: |
|
|
151
152
|
v0.2.3
|
152
153
|
* fix: hash_to_keys can work with enhanced format => default can be in
|
153
154
|
enchanced format
|
155
|
+
* default file can be now in enhanced format. if translation field is missing
|
154
156
|
* i18n-translate <source file> <target file>
|
155
157
|
automaticlay perform convert action from one file to another
|
156
158
|
|
159
|
+
v0.2.4
|
160
|
+
* enhanced support for java properties
|
161
|
+
* hard/soft merges. hard deletes deleted keys in target and soft set them to
|
162
|
+
obsolete
|
163
|
+
* processors now generate keys list from provided data and not from @tr.default
|
164
|
+
* flag obsolete added
|
165
|
+
* delete function
|
166
|
+
* i18n-0.5.0 compatibility (for older i18n user v0.2.3)
|
167
|
+
|
157
168
|
For more information read README.md and CHANGELOG.md
|
158
169
|
|
159
170
|
-----------------------------------------------------------------------------
|
@@ -171,7 +182,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
171
182
|
requirements:
|
172
183
|
- - ">="
|
173
184
|
- !ruby/object:Gem::Version
|
174
|
-
hash: 3
|
175
185
|
segments:
|
176
186
|
- 0
|
177
187
|
version: "0"
|
@@ -180,7 +190,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
190
|
requirements:
|
181
191
|
- - ">="
|
182
192
|
- !ruby/object:Gem::Version
|
183
|
-
hash: 3
|
184
193
|
segments:
|
185
194
|
- 0
|
186
195
|
version: "0"
|