0b6217c6cea174e658df6514515ff55e7acc0366
[packages/precise/mcollective.git] / lib / mcollective / vendor / i18n / lib / i18n / backend / base.rb
1 require 'yaml'
2 require 'i18n/core_ext/hash'
3 require 'i18n/core_ext/kernel/surpress_warnings'
4
5 module I18n
6   module Backend
7     module Base
8       include I18n::Backend::Transliterator
9
10       # Accepts a list of paths to translation files. Loads translations from
11       # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
12       # for details.
13       def load_translations(*filenames)
14         filenames = I18n.load_path if filenames.empty?
15         filenames.flatten.each { |filename| load_file(filename) }
16       end
17
18       # This method receives a locale, a data hash and options for storing translations.
19       # Should be implemented
20       def store_translations(locale, data, options = {})
21         raise NotImplementedError
22       end
23
24       def translate(locale, key, options = {})
25         raise InvalidLocale.new(locale) unless locale
26         entry = key && lookup(locale, key, options[:scope], options)
27
28         if options.empty?
29           entry = resolve(locale, key, entry, options)
30         else
31           count, default = options.values_at(:count, :default)
32           values = options.except(*RESERVED_KEYS)
33           entry = entry.nil? && default ?
34             default(locale, key, default, options) : resolve(locale, key, entry, options)
35         end
36
37         throw(:exception, I18n::MissingTranslation.new(locale, key, options)) if entry.nil?
38         entry = entry.dup if entry.is_a?(String)
39
40         entry = pluralize(locale, entry, count) if count
41         entry = interpolate(locale, entry, values) if values
42         entry
43       end
44
45       # Acts the same as +strftime+, but uses a localized version of the
46       # format string. Takes a key from the date/time formats translations as
47       # a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
48       def localize(locale, object, format = :default, options = {})
49         raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
50
51         if Symbol === format
52           key  = format
53           type = object.respond_to?(:sec) ? 'time' : 'date'
54           options = options.merge(:raise => true, :object => object, :locale => locale)
55           format  = I18n.t(:"#{type}.formats.#{key}", options)
56         end
57
58         # format = resolve(locale, object, format, options)
59         format = format.to_s.gsub(/%[aAbBp]/) do |match|
60           case match
61           when '%a' then I18n.t(:"date.abbr_day_names",                  :locale => locale, :format => format)[object.wday]
62           when '%A' then I18n.t(:"date.day_names",                       :locale => locale, :format => format)[object.wday]
63           when '%b' then I18n.t(:"date.abbr_month_names",                :locale => locale, :format => format)[object.mon]
64           when '%B' then I18n.t(:"date.month_names",                     :locale => locale, :format => format)[object.mon]
65           when '%p' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format) if object.respond_to? :hour
66           end
67         end
68
69         object.strftime(format)
70       end
71
72       # Returns an array of locales for which translations are available
73       # ignoring the reserved translation meta data key :i18n.
74       def available_locales
75         raise NotImplementedError
76       end
77
78       def reload!
79         @skip_syntax_deprecation = false
80       end
81
82       protected
83
84         # The method which actually looks up for the translation in the store.
85         def lookup(locale, key, scope = [], options = {})
86           raise NotImplementedError
87         end
88
89         # Evaluates defaults.
90         # If given subject is an Array, it walks the array and returns the
91         # first translation that can be resolved. Otherwise it tries to resolve
92         # the translation directly.
93         def default(locale, object, subject, options = {})
94           options = options.dup.reject { |key, value| key == :default }
95           case subject
96           when Array
97             subject.each do |item|
98               result = resolve(locale, object, item, options) and return result
99             end and nil
100           else
101             resolve(locale, object, subject, options)
102           end
103         end
104
105         # Resolves a translation.
106         # If the given subject is a Symbol, it will be translated with the
107         # given options. If it is a Proc then it will be evaluated. All other
108         # subjects will be returned directly.
109         def resolve(locale, object, subject, options = {})
110           return subject if options[:resolve] == false
111           result = catch(:exception) do
112             case subject
113             when Symbol
114               I18n.translate(subject, options.merge(:locale => locale, :throw => true))
115             when Proc
116               date_or_time = options.delete(:object) || object
117               resolve(locale, object, subject.call(date_or_time, options))
118             else
119               subject
120             end
121           end
122           result unless result.is_a?(MissingTranslation)
123         end
124
125         # Picks a translation from an array according to English pluralization
126         # rules. It will pick the first translation if count is not equal to 1
127         # and the second translation if it is equal to 1. Other backends can
128         # implement more flexible or complex pluralization rules.
129         def pluralize(locale, entry, count)
130           return entry unless entry.is_a?(Hash) && count
131
132           key = :zero if count == 0 && entry.has_key?(:zero)
133           key ||= count == 1 ? :one : :other
134           raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
135           entry[key]
136         end
137
138         # Interpolates values into a given string.
139         #
140         #   interpolate "file %{file} opened by %%{user}", :file => 'test.txt', :user => 'Mr. X'
141         #   # => "file test.txt opened by %{user}"
142         def interpolate(locale, string, values = {})
143           if string.is_a?(::String) && !values.empty?
144             I18n.interpolate(string, values)
145           else
146             string
147           end
148         end
149
150         # Loads a single translations file by delegating to #load_rb or
151         # #load_yml depending on the file extension and directly merges the
152         # data to the existing translations. Raises I18n::UnknownFileType
153         # for all other file extensions.
154         def load_file(filename)
155           type = File.extname(filename).tr('.', '').downcase
156           raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)
157           data = send(:"load_#{type}", filename)
158           raise InvalidLocaleData.new(filename) unless data.is_a?(Hash)
159           data.each { |locale, d| store_translations(locale, d || {}) }
160         end
161
162         # Loads a plain Ruby translations file. eval'ing the file must yield
163         # a Hash containing translation data with locales as toplevel keys.
164         def load_rb(filename)
165           eval(IO.read(filename), binding, filename)
166         end
167
168         # Loads a YAML translations file. The data must have locales as
169         # toplevel keys.
170         def load_yml(filename)
171           begin
172             YAML.load_file(filename)
173           rescue TypeError
174             nil
175           rescue SyntaxError
176             nil
177           end
178         end
179     end
180   end
181 end