i18n-translators-tools 0.1

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.
@@ -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