i18n-translators-tools 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
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 'rubygems'
7
+ require 'find'
8
+
9
+ spec = Gem::Specification.new do |s|
10
+ s.platform = Gem::Platform::RUBY
11
+ s.summary = "I18n transation utility which helps to manage files with locales."
12
+ s.email = "pejuko@gmail.com"
13
+ s.authors = ["Petr Kovar"]
14
+ s.name = 'i18n-translators-tools'
15
+ s.version = '0.1'
16
+ s.date = '2010-07-16'
17
+ s.requirements << 'i18n' << 'ya2yaml'
18
+ s.require_path = 'lib'
19
+ s.files = ["bin/i18n-translate", "test/tc_translate.rb", "test/locale/src/default.yml", "test/locale/src/cze.yml", "test/locale/src/cze.rb", "test/locale/src/cze.po", "lib/i18n-translate.rb", "lib/i18n/translate.rb", "lib/i18n/processor.rb", "lib/i18n/processor/yaml.rb", "lib/i18n/processor/ruby.rb", "lib/i18n/processor/gettext.rb", "lib/i18n/backend/translate.rb", "README.md", "i18n-translators-tools.gemspec", "Rakefile"]
20
+ s.executables = ["i18n-translate"]
21
+ s.description = <<EOF
22
+ This package brings you useful utility which can help you to handle locale files
23
+ and translations in your Ruby projects. Read README.md file and run i18n-translate
24
+ without parameters for more information.
25
+ EOF
26
+ end
27
+
@@ -0,0 +1,13 @@
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 'i18n'
7
+
8
+ dir = File.expand_path(File.dirname(__FILE__))
9
+ $:.unshift(dir) unless $:.include?(dir)
10
+
11
+ require 'i18n/backend/translate'
12
+ require 'i18n/translate'
13
+
@@ -0,0 +1,38 @@
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
+ module I18n
7
+
8
+ module Backend
9
+ # It is highly recommended to use Translator wit Fallback plugin
10
+ #
11
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Translator)
12
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
13
+ #
14
+ # notice that Translator have to be included BEFORE Fallback otherwise
15
+ # the fallback will get Hash (even with empty translation) and won't work.
16
+ module Translate
17
+
18
+ # wrapper which can work with both format
19
+ # the simple and the Translator's
20
+ def translate(locale, key, options = {})
21
+ result = super(locale, key, options)
22
+ return result unless result.kind_of?(Hash)
23
+ return nil unless result[:t]
24
+
25
+ tr = result[:t]
26
+ values = options.except(*I18n::Backend::Base::RESERVED_KEYS)
27
+
28
+ tr = resolve(locale, key, tr, options)
29
+ tr = interpolate(locale, tr, values) if values
30
+
31
+ tr
32
+ end
33
+
34
+ end # module Backend::Translator
35
+ end # module Backend
36
+
37
+ end
38
+
@@ -0,0 +1,93 @@
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
+ module I18n::Translate
7
+
8
+ module Processor
9
+ @processors = []
10
+
11
+ class << self
12
+ attr_reader :processors
13
+ end
14
+
15
+ def self.<<(processor)
16
+ @processors << processor
17
+ end
18
+
19
+ def self.read(fname, tr)
20
+ processor = find_processor(fname)
21
+ raise "Unknown file format" unless processor
22
+ worker = processor.new(fname, tr)
23
+ worker.read
24
+ end
25
+
26
+ def self.write(fname, data, tr)
27
+ processor = find_processor(fname)
28
+ raise "Unknown file format `#{fname}'" unless processor
29
+ worker = processor.new(fname, tr)
30
+ worker.write(data)
31
+ end
32
+
33
+ def self.find_processor(fname)
34
+ @processors.each do |processor|
35
+ return processor if processor.can_handle?(fname)
36
+ end
37
+ nil
38
+ end
39
+
40
+
41
+ class Template
42
+ FORMAT = []
43
+
44
+ def self.inherited(processor)
45
+ Processor << processor
46
+ end
47
+
48
+ attr_reader :filename, :translate
49
+
50
+ def initialize(fname, tr)
51
+ @filename = fname
52
+ @translate = tr
53
+ end
54
+
55
+ def read
56
+ data = File.open(@filename, mode("r")){ |f| f.read }
57
+ import(data)
58
+ end
59
+
60
+ def write(data)
61
+ File.open(@filename, mode("w")) {|f| f << export(data)}
62
+ end
63
+
64
+ def self.can_handle?(fname)
65
+ fname =~ %r{\.([^\.]+)$}
66
+ self::FORMAT.include?($1)
67
+ end
68
+
69
+ protected
70
+
71
+ def import(data)
72
+ data
73
+ end
74
+
75
+ def export(data)
76
+ data
77
+ end
78
+
79
+ def mode(m)
80
+ mode = m.dup
81
+ mode << ":" << @translate.options[:encoding] if defined?(Encoding)
82
+ mode
83
+ end
84
+ end
85
+ end
86
+
87
+ end
88
+
89
+
90
+ require 'i18n/processor/yaml'
91
+ require 'i18n/processor/ruby'
92
+ require 'i18n/processor/gettext'
93
+
@@ -0,0 +1,127 @@
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
+ module I18n::Translate::Processor
7
+
8
+ class Gettext < Template
9
+ FORMAT = ['po']
10
+
11
+ protected
12
+
13
+ def import(data)
14
+ hash = {}
15
+
16
+ entry = {}
17
+ key = nil
18
+ last = nil
19
+ data.each_line do |line|
20
+ # empty line starts new entry
21
+ if line =~ %r{^\s*$}
22
+ if not entry.empty? and key
23
+ I18n::Translate.set(key, entry, hash, @translate.options[:separator])
24
+ end
25
+ key = nil
26
+ last = nil
27
+ entry = {}
28
+ next
29
+ end
30
+
31
+ case line
32
+
33
+ # translator's comment
34
+ when %r{^# (.*)$}
35
+ entry["comment"] = $1.to_s.strip
36
+
37
+ # translator's comment
38
+ when %r{^#: (.*)$}
39
+ entry["line"] = $1.to_s.strip
40
+
41
+ # flag
42
+ when %r{^#, (.*)$}
43
+ flags = $1.split(",").compact.map{|x| x.strip}
44
+ fuzzy = flags.delete("fuzzy")
45
+ unless fuzzy
46
+ entry["flag"] = "ok"
47
+ else
48
+ flags.delete_if{|x| not I18n::Translate::FLAGS.include?(x)}
49
+ entry["flag"] = flags.first unless flags.empty?
50
+ end
51
+
52
+ # old default
53
+ when %r{^#\| msgid (.*)$}
54
+ entry["old"] = $1.to_s.strip
55
+
56
+ # key
57
+ when %r{^msgctxt "(.*)"$}
58
+ key = $1.to_s.strip
59
+ last = "key"
60
+
61
+ # default
62
+ when %r{^msgid "(.*)"$}
63
+ last = "default"
64
+ entry[last] = $1.to_s.strip
65
+
66
+ # translation
67
+ when %r{^msgstr "(.*)"$}
68
+ last = "t"
69
+ entry[last] = $1.to_s.strip
70
+
71
+ # string continuation
72
+ when %r{^"(.*)"$}
73
+ if last == "key"
74
+ key = "#{key}#{$1.to_s.strip}"
75
+ elsif last
76
+ entry[last] = "#{entry[last]}#{$1.to_s.strip}"
77
+ end
78
+ end
79
+ end
80
+
81
+ # last line at end of file
82
+ if not entry.empty? and key
83
+ I18n::Translate.set(key, entry, hash, @translate.options[:separator])
84
+ end
85
+
86
+ {@translate.lang => hash}
87
+ end
88
+
89
+
90
+ # this export ignores data
91
+ def export(data)
92
+ str = ""
93
+ keys = I18n::Translate.hash_to_keys(@translate.default).sort
94
+
95
+ keys.each do |key|
96
+ entry = [""]
97
+ value = @translate.find(key, @translate.target)
98
+ next unless value
99
+
100
+ if value.kind_of?(String)
101
+ entry << %~msgctxt #{key.inspect}~
102
+ entry << %~msgid #{@translate.find(key, @translate.default).to_s.inspect}~
103
+ entry << %~msgstr #{value.to_s.inspect}~
104
+ else
105
+ entry << %~# #{value["comment"]}~ unless value["comment"].to_s.empty?
106
+ entry << %~#: #{value["line"]}~ unless value["line"].to_s.empty?
107
+ flags = []
108
+ flags << "fuzzy" if value["fuzzy"]
109
+ flags << value["flag"] unless value["flag"].to_s.strip.empty?
110
+ entry << %~#, #{flags.join(", ")}~ unless flags.empty?
111
+ entry << %~#| msgid #{value["old"]}~ unless value["old"].to_s.empty?
112
+ entry << %~msgctxt #{key.inspect}~
113
+ entry << %~msgid #{value["default"].to_s.inspect}~
114
+ entry << %~msgstr #{value["t"].to_s.inspect}~
115
+ end
116
+
117
+ entry << ""
118
+
119
+ str << entry.join("\n")
120
+ end
121
+
122
+ str
123
+ end
124
+
125
+ end
126
+
127
+ end
@@ -0,0 +1,41 @@
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
+ module I18n::Translate::Processor
7
+
8
+ class Ruby < Template
9
+ FORMAT = ['rb']
10
+
11
+ protected
12
+
13
+ def import(data)
14
+ eval(data)
15
+ end
16
+
17
+ # serialize hash to string
18
+ def export(data, indent=0)
19
+ str = "{\n"
20
+
21
+ data.keys.sort{|k1,k2| k1.to_s <=> k2.to_s}.each_with_index do |k, i|
22
+ str << (" " * (indent+1))
23
+ str << "#{k.inspect} => "
24
+ if data[k].kind_of?(Hash)
25
+ str << export(data[k], indent+1)
26
+ else
27
+ str << data[k].inspect
28
+ end
29
+ str << "," if i < (data.keys.size - 1)
30
+ str << "\n"
31
+ end
32
+
33
+ str << (" " * (indent))
34
+ str << "}"
35
+
36
+ str
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,24 @@
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 'yaml'
7
+ require 'ya2yaml'
8
+
9
+ module I18n::Translate::Processor
10
+ class YAML < Template
11
+ FORMAT = ['yml', 'yaml']
12
+
13
+ protected
14
+
15
+ def import(data)
16
+ ::YAML.load(data)
17
+ end
18
+
19
+ def export(data)
20
+ data.ya2yaml
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,413 @@
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 'fileutils'
7
+ require 'find'
8
+
9
+ require 'i18n/processor'
10
+
11
+ # I18n::Translate introduces new format for translations. To make
12
+ # I18n.t work properly you need to include Translator's backend:
13
+ #
14
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Translate)
15
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
16
+ #
17
+ # notice that Translator have to be included BEFORE Fallback otherwise
18
+ # the fallback will get Hash (even with empty translation) and won't work.
19
+ #
20
+ # It is hightly recommended to use Fallback backend together with Translate.
21
+ # If you have experienced nil or empty translations this can fix the problem.
22
+ #
23
+ # Format of entry:
24
+ #
25
+ # old: old default string
26
+ # default: new default string
27
+ # comment: translator's comments
28
+ # t: translation
29
+ # line: the lines, where is this key used # not implemented yet
30
+ # flag: ok || incomplete || changed || untranslated
31
+ # fuzzy: true # exists only where flag != ok (nice to have when you want
32
+ # edit files manualy)
33
+ #
34
+ # This format is for leaves in the tree hiearchy for plurals it should look like
35
+ #
36
+ # key:
37
+ # one:
38
+ # old:
39
+ # default:
40
+ # t:
41
+ # ...
42
+ # other:
43
+ # old:
44
+ # default:
45
+ # t:
46
+ # ...
47
+ #
48
+ module I18n::Translate
49
+
50
+ FLAGS = %w(ok incomplete changed untranslated)
51
+ FORMATS = %w(yml rb po) # the first one is preferred if :format => auto
52
+
53
+ # returns flat array of all keys e.g. ["system.message.ok", "system.message.error", ...]
54
+ def self.hash_to_keys(hash, separator=".", prefix="")
55
+ res = []
56
+ hash.keys.each do |key|
57
+ str = prefix.empty? ? key : "#{prefix}#{separator}#{key}"
58
+ if hash[key].kind_of?(Hash)
59
+ str = hash_to_keys( hash[key], separator, str )
60
+ end
61
+ res << str
62
+ end
63
+ res.flatten
64
+ end
65
+
66
+ # returns what is stored under key
67
+ def self.find(key, hash, separator=".")
68
+ h = hash
69
+ path = key.to_s.split(separator)
70
+ path.each do |key|
71
+ h = h[key]
72
+ return nil unless h
73
+ end
74
+ h
75
+ end
76
+
77
+ def self.set(key, value, hash, separator=".")
78
+ h = hash
79
+ path = key.to_s.split(separator)
80
+ path[0..-2].each do |chunk|
81
+ h[chunk] ||= {}
82
+ h = h[chunk]
83
+ end
84
+ unless value
85
+ h[path[-1]] = nil
86
+ else
87
+ h[path[-1]] = value
88
+ end
89
+ end
90
+
91
+ # scans :locale_dir for files with valid formats and returns
92
+ # list of files with locales. If block is given then
93
+ # it creates Translate object for each entry and pass it to the block
94
+ def self.scan(opts=Translate::DEFAULT_OPTIONS, &block)
95
+ o = Translate::DEFAULT_OPTIONS.merge(opts)
96
+ o[:exclude] ||= []
97
+
98
+ entries = []
99
+ if o[:deep] == true
100
+ Find.find(o[:locale_dir]) {|e| entries << e}
101
+ else
102
+ entries = Dir[File.join(o[:locale_dir], "*")]
103
+ end
104
+
105
+ locales = []
106
+ entries.each do |entry|
107
+ locale, format = Translate.valid_file?(entry, o[:format])
108
+ if (not format) or (locale == o[:default])
109
+ puts "#{entry}...skipping" if o[:verbose]
110
+ next unless format
111
+ next if locale == o[:default]
112
+ end
113
+
114
+ exclude = false
115
+ o[:exclude].each do |ex|
116
+ if entry =~ %r|#{ex}|
117
+ exclude = true
118
+ break
119
+ end
120
+ end
121
+ puts "#{entry}...excluded" if exclude and o[:verbose]
122
+ next if exclude
123
+
124
+ locales << entry
125
+ dir = File.dirname(entry)
126
+
127
+ if block
128
+ yield Translate.new(locale, o.merge({:format => format, :locale_dir => dir, :default => o[:default]}))
129
+ end
130
+
131
+ end
132
+
133
+ locales
134
+ end
135
+
136
+
137
+
138
+ # it breaks proc and lambdas objects
139
+ class Translate
140
+ DEFAULT_OPTIONS = {
141
+ :separator => ".", # default key separator e.g. "model.article.message.not.found"
142
+ :locale_dir => "locale", # where to search for files
143
+ :default => "default", # default name for file containing default app's key => string
144
+ :force_encoding => true, # in ruby 1.9 forces string encoding
145
+ :encoding => "utf-8", # encoding name to be forced to
146
+ :format => "auto" # auto, rb, yml
147
+ }
148
+
149
+ attr_reader :default, :target, :merge, :options, :lang, :default_file, :lang_file
150
+
151
+ # loads default and lang files
152
+ def initialize(lang, opts={})
153
+ @lang = lang.to_s
154
+ raise "Empty locale" if @lang.empty?
155
+ @options = DEFAULT_OPTIONS.merge(opts)
156
+ @options[:default_format] ||= @options[:format]
157
+
158
+ @default, @default_file = load_locale( @options[:default], @options[:default_format] )
159
+ @target, @lang_file = load_locale( @lang )
160
+
161
+ merge!
162
+ end
163
+
164
+ # check if the file has supported format
165
+ def self.valid_file?(fname, format=Translate::DEFAULT_OPTIONS[:format])
166
+ pattern = ".*?"
167
+ pattern = format if format != "auto"
168
+ fname =~ /\/?([^\/]*?)\.(#{pattern})$/
169
+ locale, format = $1, $2
170
+ if I18n::Translate::FORMATS.include?($2)
171
+ return [locale, format]
172
+ end
173
+ nil
174
+ end
175
+
176
+ # will merge only one key and returns hash
177
+ # {
178
+ # :key => 'key',
179
+ # :default => '', # value set in default file
180
+ # :old_default => '', # value set as old in target file
181
+ # (value from default file from last translation
182
+ # if the field has changed)
183
+ # :old_t => '', # if flag == 'changed' then old_t = t and t = ''
184
+ # :t => '', # value set in target file
185
+ # :line => 'some/file.rb: 44', # name of source file and number of line
186
+ # (a copy in target file from default file)
187
+ # !!! line is unused for now
188
+ # :comment => '' # a comment added by a translator
189
+ # :flag => ok || incomplete || changed || untranslated # set by merging tool except incomplete
190
+ # which is set by translator
191
+ # }
192
+ def [](key)
193
+ d = I18n::Translate.find(key, @default, @options[:separator])
194
+ raise "Translate#[key]: wrong key '#{key}'" unless d
195
+
196
+ entry = {"key" => key, "default" => d}
197
+
198
+ # translation doesn't exist
199
+ trg = I18n::Translate.find(key, @target, @options[:separator])
200
+ if (not trg) or
201
+ (trg.kind_of?(String) and trg.strip.empty?) or
202
+ (trg.kind_of?(Hash) and trg["t"].to_s.strip.empty?)
203
+ entry["old_default"] = ""
204
+ entry["old_t"] = ""
205
+ entry["t"] = ""
206
+ entry["comment"] = trg.kind_of?(Hash) ? trg["comment"].to_s.strip : ""
207
+ entry["flag"] = "untranslated"
208
+ return entry
209
+ end
210
+
211
+ # default has changed => new translation is probably required
212
+ if trg.kind_of?(Hash)
213
+ entry["old_t"] = trg["t"].to_s.strip
214
+ entry["t"] = ""
215
+ entry["comment"] = trg["comment"].to_s.strip
216
+ entry["flag"] = "changed"
217
+
218
+ if d != trg["default"]
219
+ entry["old_default"] = trg["default"].to_s.strip
220
+ return entry
221
+ elsif not trg["old"].to_s.strip.empty?
222
+ entry["old_default"] = trg["old"].to_s.strip
223
+ return entry
224
+ end
225
+ end
226
+
227
+ # nothing has changed
228
+ entry["old_default"] = trg.kind_of?(Hash) ? trg["old"].to_s.strip : ""
229
+ entry["old_t"] = ""
230
+ entry["t"] = trg.kind_of?(Hash) ? trg["t"].to_s.strip : trg.to_s.strip
231
+ entry["comment"] = trg.kind_of?(Hash) ? trg["comment"].to_s.strip : ""
232
+ entry["flag"] = (trg.kind_of?(Hash) and trg["flag"]) ? trg["flag"].to_s.strip : "ok"
233
+
234
+ entry
235
+ end
236
+
237
+ # wrapper for I18n::Translate.find with presets options
238
+ def find(key, hash=@translate, separator=@options[:separator])
239
+ I18n::Translate.find(key, hash, separator)
240
+ end
241
+
242
+ # will create path in @target for 'key' and set the 'value'
243
+ def []=(key, value)
244
+ I18n::Translate.set(key, value, @target, @options[:separator])
245
+ end
246
+
247
+ # merge merged and edited hash into @target
248
+ # translation can be hash or array
249
+ # * array format is the same as self.merge is
250
+ # [ {key => , t =>, ...}, {key =>, ...}, ... ]
251
+ # * hash format is supposed to be the format obtained from web form
252
+ # {:key => {t =>, ...}, :key => {...}, ...}
253
+ def assign(translation)
254
+ translation.each do |transl|
255
+ key, values = nil
256
+ if transl.kind_of?(Hash)
257
+ # merge format: [{key => , t =>, ...}, ...]
258
+ key, values = transl["key"], transl
259
+ elsif transl.kind_of?(Array)
260
+ # web format: {key => {t => }, ...}
261
+ key, values = transl
262
+ end
263
+
264
+ old_t = values["old_t"].to_s.strip
265
+ new_t = values["t"].to_s.strip
266
+ default = values["default"].to_s.strip
267
+ old_default = values["old_default"].to_s.strip
268
+ flag = values["flag"].to_s.strip
269
+ comment = values["comment"].to_s.strip
270
+
271
+ if old_t.respond_to?("force_encoding") and @options[:force_encoding]
272
+ enc = @options[:encoding]
273
+ old_t.force_encoding(enc)
274
+ new_t.force_encoding(enc)
275
+ default.force_encoding(enc)
276
+ old_default.force_encoding(enc)
277
+ flag.force_encoding(enc)
278
+ comment.force_encoding(enc)
279
+ end
280
+
281
+ trg = {
282
+ "comment" => comment,
283
+ "flag" => flag
284
+ }
285
+
286
+ if flag == "ok"
287
+ trg["t"] = new_t.empty? ? old_t : new_t
288
+ trg["default"] = default
289
+ trg["old"] = ""
290
+ else
291
+ trg["t"] = new_t.empty? ? old_t : new_t
292
+ trg["default"] = default
293
+ trg["old"] = old_default
294
+ end
295
+
296
+ # make fallback work
297
+ trg["t"] = nil if trg["t"].empty?
298
+
299
+ # say that this entry is not completed yet
300
+ # useful if you edit files in text editor and serching for next one
301
+ trg["fuzzy"] = true if flag != "ok"
302
+
303
+ self[key] = trg
304
+ end
305
+ end
306
+
307
+ # re-read @target data from the disk and create @merge
308
+ def reload!
309
+ @target, @lang_file = load_locale( @lang )
310
+ merge!
311
+ end
312
+
313
+ # merge @default and @target into list @merge
314
+ def merge!
315
+ @merge = merge_locale
316
+ end
317
+
318
+ # export @target to file
319
+ def export!
320
+ save_locale(@lang)
321
+ end
322
+
323
+ # throw away translators metadata and convert
324
+ # hash to default I18n format
325
+ def strip!
326
+ keys = I18n::Translate.hash_to_keys(@default, @options[:separator])
327
+ keys.each do |key|
328
+ entry = I18n::Translate.find(key, @target, @options[:separator])
329
+ raise "Translate#[key]: wrong key '#{key}'" unless entry
330
+ next unless entry.kind_of?(Hash)
331
+ self[key] = entry["t"]
332
+ end
333
+
334
+ self
335
+ end
336
+
337
+ # returns statistics hash
338
+ # {:total => N, :ok => N, :changed => N, :incomplete => N, :untranslated => N, :fuzzy => N, :progress => N}
339
+ def stat
340
+ stat = {
341
+ :total => @merge.size,
342
+ :ok => @merge.select{|e| e["flag"] == "ok"}.size,
343
+ :changed => @merge.select{|e| e["flag"] == "changed"}.size,
344
+ :incomplete => @merge.select{|e| e["flag"] == "incomplete"}.size,
345
+ :untranslated => @merge.select{|e| e["flag"] == "untranslated"}.size,
346
+ :fuzzy => @merge.select{|e| e["flag"] != "ok"}.size
347
+ }
348
+ stat[:progress] = (stat[:ok].to_f / stat[:total].to_f) * 100
349
+ stat
350
+ end
351
+
352
+ def to_yaml
353
+ trg = {@lang => @target}
354
+ #YAML.dump(trg)
355
+ trg.ya2yaml
356
+ end
357
+
358
+ def to_rb
359
+ trg = {@lang => @target}
360
+ trg.to_rb
361
+ end
362
+
363
+ protected
364
+
365
+ # returns first file for @lang.type
366
+ def file_name(lang, type=@options[:format])
367
+ fname = "#{@options[:locale_dir]}/#{lang}.#{type}"
368
+ if type == "auto"
369
+ pattern = "#{@options[:locale_dir]}/#{lang}.*"
370
+ fname = Dir[pattern].select{|x| Translate.valid_file?(x)}.first
371
+ end
372
+ fname = "#{@options[:locale_dir]}/#{lang}.#{FORMATS.first}" unless fname
373
+ fname
374
+ end
375
+
376
+ # loads locales from .rb or .yml file
377
+ def load_locale(lang, type=@options[:format])
378
+ fname = file_name(lang, type)
379
+
380
+ if File.exists?(fname)
381
+ return [Processor.read(fname, self)[lang], fname]
382
+ else
383
+ STDERR << "Warning: I18n::Translate#load_locale: file `#{fname}' does NOT exists. Creating empty locale.\n"
384
+ end
385
+
386
+ [{}, fname]
387
+ end
388
+
389
+ # save to the first file found as lang.*
390
+ # detects .rb and .yml
391
+ def save_locale(lang)
392
+ fname = file_name(lang)
393
+ backup(fname)
394
+ Processor.write(fname, {@lang => @target}, self)
395
+ end
396
+
397
+ # backup file if file exists
398
+ def backup(fname)
399
+ FileUtils.cp(fname, "#{fname}.bak") if File.exists?(fname)
400
+ end
401
+
402
+ # creates array of hashes as specified in self[] function
403
+ def merge_locale
404
+ keys = I18n::Translate.hash_to_keys(@default, @options[:separator])
405
+ keys.sort!
406
+ keys.inject([]) do |sum, key|
407
+ sum << self[key]
408
+ end
409
+ end
410
+
411
+
412
+ end # class Translate
413
+ end # module DML