diff options
author | rick <technoweenie@gmail.com> | 2008-08-26 11:53:33 -0700 |
---|---|---|
committer | rick <technoweenie@gmail.com> | 2008-08-26 11:53:33 -0700 |
commit | 0aef9d1a2651fa0acd2adcd2de308eeb0ec8cdd2 (patch) | |
tree | 1a782151632dd80c8a18c3960536bdf8643debe3 /activesupport/lib/active_support/vendor/i18n-0.0.1 | |
parent | 0a6d75dedd79407376aae1f01302164dfd3e44b6 (diff) | |
parent | 229eedfda87a7706dbb5e3e51af8707b3adae375 (diff) | |
download | rails-0aef9d1a2651fa0acd2adcd2de308eeb0ec8cdd2.tar.gz rails-0aef9d1a2651fa0acd2adcd2de308eeb0ec8cdd2.tar.bz2 rails-0aef9d1a2651fa0acd2adcd2de308eeb0ec8cdd2.zip |
Merge branch 'master' of git@github.com:rails/rails
Diffstat (limited to 'activesupport/lib/active_support/vendor/i18n-0.0.1')
3 files changed, 438 insertions, 0 deletions
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb new file mode 100755 index 0000000000..9e347d94e9 --- /dev/null +++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb @@ -0,0 +1,192 @@ +# Authors:: Matt Aimonetti (http://railsontherun.com/), +# Sven Fuchs (http://www.artweb-design.de), +# Joshua Harvey (http://www.workingwithrails.com/person/759-joshua-harvey), +# Saimon Moore (http://saimonmoore.net), +# Stephan Soller (http://www.arkanis-development.de/) +# Copyright:: Copyright (c) 2008 The Ruby i18n Team +# License:: MIT +require 'i18n/backend/simple' +require 'i18n/exceptions' + +module I18n + @@backend = nil + @@default_locale = 'en-US' + @@exception_handler = :default_exception_handler + + class << self + # Returns the current backend. Defaults to +Backend::Simple+. + def backend + @@backend ||= Backend::Simple.new + end + + # Sets the current backend. Used to set a custom backend. + def backend=(backend) + @@backend = backend + end + + # Returns the current default locale. Defaults to 'en-US' + def default_locale + @@default_locale + end + + # Sets the current default locale. Used to set a custom default locale. + def default_locale=(locale) + @@default_locale = locale + end + + # Returns the current locale. Defaults to I18n.default_locale. + def locale + Thread.current[:locale] ||= default_locale + end + + # Sets the current locale pseudo-globally, i.e. in the Thread.current hash. + def locale=(locale) + Thread.current[:locale] = locale + end + + # Sets the exception handler. + def exception_handler=(exception_handler) + @@exception_handler = exception_handler + end + + # Allow client libraries to pass a block that populates the translation + # storage. Decoupled for backends like a db backend that persist their + # translations, so the backend can decide whether/when to yield or not. + def populate(&block) + backend.populate(&block) + end + + # Allows client libraries to pass arguments that specify a source for + # translation data to be loaded by the backend. The backend defines + # acceptable sources. + # E.g. the provided SimpleBackend accepts a list of paths to translation + # files which are either named *.rb and contain plain Ruby Hashes or are + # named *.yml and contain YAML data.) + def load_translations(*args) + backend.load_translations(*args) + end + + # Stores translations for the given locale in the backend. + def store_translations(locale, data) + backend.store_translations locale, data + end + + # Translates, pluralizes and interpolates a given key using a given locale, + # scope, and default, as well as interpolation values. + # + # *LOOKUP* + # + # Translation data is organized as a nested hash using the upper-level keys + # as namespaces. <em>E.g.</em>, ActionView ships with the translation: + # <tt>:date => {:formats => {:short => "%b %d"}}</tt>. + # + # Translations can be looked up at any level of this hash using the key argument + # and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt> + # returns the whole translations hash <tt>{:formats => {:short => "%b %d"}}</tt>. + # + # Key can be either a single key or a dot-separated key (both Strings and Symbols + # work). <em>E.g.</em>, the short format can be looked up using both: + # I18n.t 'date.formats.short' + # I18n.t :'date.formats.short' + # + # Scope can be either a single key, a dot-separated key or an array of keys + # or dot-separated keys. Keys and scopes can be combined freely. So these + # examples will all look up the same short date format: + # I18n.t 'date.formats.short' + # I18n.t 'formats.short', :scope => 'date' + # I18n.t 'short', :scope => 'date.formats' + # I18n.t 'short', :scope => %w(date formats) + # + # *INTERPOLATION* + # + # Translations can contain interpolation variables which will be replaced by + # values passed to #translate as part of the options hash, with the keys matching + # the interpolation variable names. + # + # <em>E.g.</em>, with a translation <tt>:foo => "foo {{bar}}"</tt> the option + # value for the key +bar+ will be interpolated into the translation: + # I18n.t :foo, :bar => 'baz' # => 'foo baz' + # + # *PLURALIZATION* + # + # Translation data can contain pluralized translations. Pluralized translations + # are arrays of singluar/plural versions of translations like <tt>['Foo', 'Foos']</tt>. + # + # Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English + # pluralization rules. Other algorithms can be supported by custom backends. + # + # This returns the singular version of a pluralized translation: + # I18n.t :foo, :count => 1 # => 'Foo' + # + # These both return the plural version of a pluralized translation: + # I18n.t :foo, :count => 0 # => 'Foos' + # I18n.t :foo, :count => 2 # => 'Foos' + # + # The <tt>:count</tt> option can be used both for pluralization and interpolation. + # <em>E.g.</em>, with the translation + # <tt>:foo => ['{{count}} foo', '{{count}} foos']</tt>, count will + # be interpolated to the pluralized translation: + # I18n.t :foo, :count => 1 # => '1 foo' + # + # *DEFAULTS* + # + # This returns the translation for <tt>:foo</tt> or <tt>default</tt> if no translation was found: + # I18n.t :foo, :default => 'default' + # + # This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no + # translation for <tt>:foo</tt> was found: + # I18n.t :foo, :default => :bar + # + # Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> + # or <tt>default</tt> if no translations for <tt>:foo</tt> and <tt>:bar</tt> were found. + # I18n.t :foo, :default => [:bar, 'default'] + # + # <b>BULK LOOKUP</b> + # + # This returns an array with the translations for <tt>:foo</tt> and <tt>:bar</tt>. + # I18n.t [:foo, :bar] + # + # Can be used with dot-separated nested keys: + # I18n.t [:'baz.foo', :'baz.bar'] + # + # Which is the same as using a scope option: + # I18n.t [:foo, :bar], :scope => :baz + def translate(key, options = {}) + locale = options.delete(:locale) || I18n.locale + backend.translate locale, key, options + rescue I18n::ArgumentError => e + raise e if options[:raise] + send @@exception_handler, e, locale, key, options + end + alias :t :translate + + # Localizes certain objects, such as dates and numbers to local formatting. + def localize(object, options = {}) + locale = options[:locale] || I18n.locale + format = options[:format] || :default + backend.localize(locale, object, format) + end + alias :l :localize + + protected + # Handles exceptions raised in the backend. All exceptions except for + # MissingTranslationData exceptions are re-raised. When a MissingTranslationData + # was caught and the option :raise is not set the handler returns an error + # message string containing the key/scope. + def default_exception_handler(exception, locale, key, options) + return exception.message if MissingTranslationData === exception + raise exception + end + + # Merges the given locale, key and scope into a single array of keys. + # Splits keys that contain dots into multiple keys. Makes sure all + # keys are Symbols. + def normalize_translation_keys(locale, key, scope) + keys = [locale] + Array(scope) + [key] + keys = keys.map{|k| k.to_s.split(/\./) } + keys.flatten.map{|k| k.to_sym} + end + end +end + + diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb new file mode 100644 index 0000000000..a53f7fe772 --- /dev/null +++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb @@ -0,0 +1,193 @@ +require 'strscan' + +module I18n + module Backend + class Simple + # Allow client libraries to pass a block that populates the translation + # storage. Decoupled for backends like a db backend that persist their + # translations, so the backend can decide whether/when to yield or not. + def populate(&block) + yield + end + + # Accepts a list of paths to translation files. Loads translations from + # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml + # for details. + def load_translations(*filenames) + filenames.each {|filename| load_file filename } + end + + # Stores translations for the given locale in memory. + # This uses a deep merge for the translations hash, so existing + # translations will be overwritten by new ones only at the deepest + # level of the hash. + def store_translations(locale, data) + merge_translations(locale, data) + end + + def translate(locale, key, options = {}) + raise InvalidLocale.new(locale) if locale.nil? + return key.map{|k| translate locale, k, options } if key.is_a? Array + + reserved = :scope, :default + count, scope, default = options.values_at(:count, *reserved) + options.delete(:default) + values = options.reject{|name, value| reserved.include? name } + + entry = lookup(locale, key, scope) || default(locale, default, options) || raise(I18n::MissingTranslationData.new(locale, key, options)) + entry = pluralize locale, entry, count + entry = interpolate locale, entry, values + entry + end + + # Acts the same as +strftime+, but returns a localized version of the + # formatted date string. Takes a key from the date/time formats + # translations as a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>). + def localize(locale, object, format = :default) + raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime) + + type = object.respond_to?(:sec) ? 'time' : 'date' + formats = translate(locale, :"#{type}.formats") + format = formats[format.to_sym] if formats && formats[format.to_sym] + # TODO raise exception unless format found? + format = format.to_s.dup + + format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday]) + format.gsub!(/%A/, translate(locale, :"date.day_names")[object.wday]) + format.gsub!(/%b/, translate(locale, :"date.abbr_month_names")[object.mon]) + format.gsub!(/%B/, translate(locale, :"date.month_names")[object.mon]) + format.gsub!(/%p/, translate(locale, :"time.#{object.hour < 12 ? :am : :pm}")) if object.respond_to? :hour + object.strftime(format) + end + + protected + + def translations + @translations ||= {} + end + + # Looks up a translation from the translations hash. Returns nil if + # eiher key is nil, or locale, scope or key do not exist as a key in the + # nested translations hash. Splits keys or scopes containing dots + # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as + # <tt>%w(currency format)</tt>. + def lookup(locale, key, scope = []) + return unless key + keys = I18n.send :normalize_translation_keys, locale, key, scope + keys.inject(translations){|result, k| result[k.to_sym] or return nil } + end + + # Evaluates a default translation. + # If the given default is a String it is used literally. If it is a Symbol + # it will be translated with the given options. If it is an Array the first + # translation yielded will be returned. + # + # <em>I.e.</em>, <tt>default(locale, [:foo, 'default'])</tt> will return +default+ if + # <tt>translate(locale, :foo)</tt> does not yield a result. + def default(locale, default, options = {}) + case default + when String then default + when Symbol then translate locale, default, options + when Array then default.each do |obj| + result = default(locale, obj, options.dup) and return result + end and nil + end + rescue MissingTranslationData + nil + end + + # Picks a translation from an array according to English pluralization + # rules. It will pick the first translation if count is not equal to 1 + # and the second translation if it is equal to 1. Other backends can + # implement more flexible or complex pluralization rules. + def pluralize(locale, entry, count) + return entry unless entry.is_a?(Hash) and count + # raise InvalidPluralizationData.new(entry, count) unless entry.is_a?(Hash) + key = :zero if count == 0 && entry.has_key?(:zero) + key ||= count == 1 ? :one : :other + raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key) + entry[key] + end + + # Interpolates values into a given string. + # + # interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X' + # # => "file test.txt opened by {{user}}" + # + # Note that you have to double escape the <tt>\\</tt> when you want to escape + # the <tt>{{...}}</tt> key in a string (once for the string and once for the + # interpolation). + def interpolate(locale, string, values = {}) + return string if !string.is_a?(String) + + string = string.gsub(/%d/, '{{count}}').gsub(/%s/, '{{value}}') + if string.respond_to?(:force_encoding) + original_encoding = string.encoding + string.force_encoding(Encoding::BINARY) + end + s = StringScanner.new(string) + + while s.skip_until(/\{\{/) + s.string[s.pos - 3, 1] = '' and next if s.pre_match[-1, 1] == '\\' + start_pos = s.pos - 2 + key = s.scan_until(/\}\}/)[0..-3] + end_pos = s.pos - 1 + + raise ReservedInterpolationKey.new(key, string) if %w(scope default).include?(key) + raise MissingInterpolationArgument.new(key, string) unless values.has_key? key.to_sym + + s.string[start_pos..end_pos] = values[key.to_sym].to_s + s.unscan + end + + result = s.string + result.force_encoding(original_encoding) if original_encoding + result + end + + # Loads a single translations file by delegating to #load_rb or + # #load_yml depending on the file extension and directly merges the + # data to the existing translations. Raises I18n::UnknownFileType + # for all other file extensions. + def load_file(filename) + type = File.extname(filename).tr('.', '').downcase + raise UnknownFileType.new(type, filename) unless respond_to? :"load_#{type}" + data = send :"load_#{type}", filename # TODO raise a meaningful exception if this does not yield a Hash + data.each{|locale, d| merge_translations locale, d } + end + + # Loads a plain Ruby translations file. eval'ing the file must yield + # a Hash containing translation data with locales as toplevel keys. + def load_rb(filename) + eval IO.read(filename), binding, filename + end + + # Loads a YAML translations file. The data must have locales as + # toplevel keys. + def load_yml(filename) + YAML::load IO.read(filename) + end + + # Deep merges the given translations hash with the existing translations + # for the given locale + def merge_translations(locale, data) + locale = locale.to_sym + translations[locale] ||= {} + data = deep_symbolize_keys data + + # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809 + merger = proc{|key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 } + translations[locale].merge! data, &merger + end + + # Return a new hash with all keys and nested keys converted to symbols. + def deep_symbolize_keys(hash) + hash.inject({}){|result, (key, value)| + value = deep_symbolize_keys(value) if value.is_a? Hash + result[(key.to_sym rescue key) || key] = value + result + } + end + end + end +end diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/exceptions.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/exceptions.rb new file mode 100644 index 0000000000..0f3eff1071 --- /dev/null +++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/exceptions.rb @@ -0,0 +1,53 @@ +module I18n + class ArgumentError < ::ArgumentError; end + + class InvalidLocale < ArgumentError + attr_reader :locale + def initialize(locale) + @locale = locale + super "#{locale.inspect} is not a valid locale" + end + end + + class MissingTranslationData < ArgumentError + attr_reader :locale, :key, :options + def initialize(locale, key, options) + @key, @locale, @options = key, locale, options + keys = I18n.send(:normalize_translation_keys, locale, key, options[:scope]) + keys << 'no key' if keys.size < 2 + super "translation missing: #{keys.join(', ')}" + end + end + + class InvalidPluralizationData < ArgumentError + attr_reader :entry, :count + def initialize(entry, count) + @entry, @count = entry, count + super "translation data #{entry.inspect} can not be used with :count => #{count}" + end + end + + class MissingInterpolationArgument < ArgumentError + attr_reader :key, :string + def initialize(key, string) + @key, @string = key, string + super "interpolation argument #{key} missing in #{string.inspect}" + end + end + + class ReservedInterpolationKey < ArgumentError + attr_reader :key, :string + def initialize(key, string) + @key, @string = key, string + super "reserved key #{key.inspect} used in #{string.inspect}" + end + end + + class UnknownFileType < ArgumentError + attr_reader :type, :filename + def initialize(type, filename) + @type, @filename = type, filename + super "can not load translations from #{filename}, the file type #{type} is not known" + end + end +end
\ No newline at end of file |