i18n-translators-tools 0.2.3 → 0.2.4
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/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"
|