half_a_minute: "medio minuto"
less_than_x_seconds:
one: "menos de 1 segundo"
- other: "menos de {{count}} segundos"
+ other: "menos de %{count} segundos"
x_seconds:
one: "1 segundo"
- other: "{{count}} segundos"
+ other: "%{count} segundos"
less_than_x_minutes:
one: "menos de 1 minuto"
- other: "menos de {{count}} minutos"
+ other: "menos de %{count} minutos"
x_minutes:
one: "1 minuto"
- other: "{{count}} minutos"
+ other: "%{count} minutos"
about_x_hours:
one: "cerca de 1 hora"
- other: "cerca de {{count}} horas"
+ other: "cerca de %{count} horas"
x_days:
one: "1 día"
- other: "{{count}} días"
+ other: "%{count} días"
about_x_months:
one: "cerca de 1 mes"
- other: "cerca de {{count}} meses"
+ other: "cerca de %{count} meses"
x_months:
one: "1 mes"
- other: "{{count}} meses"
+ other: "%{count} meses"
about_x_years:
- other: "cerca de {{count}} años"
+ other: "cerca de %{count} años"
one: "cerca de 1 año"
over_x_years:
one: "más de 1 año"
- other: "más de {{count}} años"
+ other: "más de %{count} años"
prompts:
hour: 'Hora'
minute: 'Minuto'
template:
header:
one: "{{model}} no pudo guardarse debido a 1 error"
- other: "{{model}} no pudo guardarse debido a {{count}} errores"
+ other: "{{model}} no pudo guardarse debido a %{count} errores"
body: "Revise que los siguientes campos sean válidos:"
messages:
record_invalid: "Falla de validación: {{errors}}"
empty: "no puede estar vacío"
not_a_number: "no es un número"
taken: "ya ha sido tomado"
- less_than: "debe ser menor que {{count}}"
- less_than_or_equal_to: "debe ser menor o igual que {{count}}"
- greater_than: "debe ser mayor que {{count}}"
- greater_than_or_equal_to: "debe ser mayor o igual que {{count}}"
+ less_than: "debe ser menor que %{count}"
+ less_than_or_equal_to: "debe ser menor o igual que %{count}"
+ greater_than: "debe ser mayor que %{count}"
+ greater_than_or_equal_to: "debe ser mayor o igual que %{count}"
too_short:
one: "es demasiado corto (mínimo 1 caracter)"
- other: "es demasiado corto (mínimo {{count}} caracteres)"
+ other: "es demasiado corto (mínimo %{count} caracteres)"
too_long:
one: "es demasiado largo (máximo 1 caracter)"
- other: "es demasiado largo (máximo {{count}} caracteres)"
- equal_to: "debe ser igual a {{count}}"
+ other: "es demasiado largo (máximo %{count} caracteres)"
+ equal_to: "debe ser igual a %{count}"
wrong_length:
one: "longitud errónea (debe ser de 1 caracter)"
- other: "longitud errónea (debe ser de {{count}} caracteres)"
+ other: "longitud errónea (debe ser de %{count} caracteres)"
even: "debe ser un número par"
odd: "debe ser un número non"
\ No newline at end of file
template:
header:
one: "Errore batek ezinezkoa egin du {{model}} hau gordetzea"
- other: "{{count}} errorek ezinezkoa egiten dute {{model}} hau gordetzea"
+ other: "%{count} errorek ezinezkoa egiten dute {{model}} hau gordetzea"
body: "Arazoak egon dira ondoko eremuekin:"
messages:
wrong_length: 'idadi ya herufi hazilingani (inatakiwa %{count})'
not_a_number: 'inaruhusiwa namba tu'
not_an_integer: 'inaruhusiwa namba tu'
- greater_than: 'z/iwe zaidi ya {{count}}'
- greater_than_or_equal_to: 'z/iwe sawa ama zaidi ya {{count}}'
- equal_to: 'z/iwe sawa na {{count}}'
- less_than: 'z/isizidi {{count}}'
- less_than_or_equal_to: 'z/iwe sawa na, ama chini ya {{count}}'
+ greater_than: 'z/iwe zaidi ya %{count}'
+ greater_than_or_equal_to: 'z/iwe sawa ama zaidi ya %{count}'
+ equal_to: 'z/iwe sawa na %{count}'
+ less_than: 'z/isizidi %{count}'
+ less_than_or_equal_to: 'z/iwe sawa na, ama chini ya %{count}'
odd: 'z/iwe witiri'
even: 'z/iwe shufwa'
--- /dev/null
+--- !ruby/object:Gem::Specification
+name: i18n
+version: !ruby/object:Gem::Version
+ prerelease: false
+ segments:
+ - 0
+ - 4
+ - 2
+ version: 0.4.2
+platform: ruby
+authors:
+ - Sven Fuchs
+ - Joshua Harvey
+ - Matt Aimonetti
+ - Stephan Soller
+ - Saimon Moore
+autorequire:
+bindir: bin
+cert_chain: []
+
+date: 2010-10-26 00:00:00 +02:00
+default_executable:
+dependencies: []
+
+description: New wave Internationalization support for Ruby.
+email: rails-i18n@googlegroups.com
+executables: []
+
+extensions: []
+
+extra_rdoc_files: []
+
+files:
+ - lib/i18n.rb
+ - lib/i18n/backend.rb
+ - lib/i18n/backend/active_record.rb
+ - lib/i18n/backend/active_record/missing.rb
+ - lib/i18n/backend/active_record/store_procs.rb
+ - lib/i18n/backend/active_record/translation.rb
+ - lib/i18n/backend/base.rb
+ - lib/i18n/backend/cache.rb
+ - lib/i18n/backend/cascade.rb
+ - lib/i18n/backend/chain.rb
+ - lib/i18n/backend/cldr.rb
+ - lib/i18n/backend/fallbacks.rb
+ - lib/i18n/backend/flatten.rb
+ - lib/i18n/backend/gettext.rb
+ - lib/i18n/backend/interpolation_compiler.rb
+ - lib/i18n/backend/key_value.rb
+ - lib/i18n/backend/memoize.rb
+ - lib/i18n/backend/metadata.rb
+ - lib/i18n/backend/pluralization.rb
+ - lib/i18n/backend/simple.rb
+ - lib/i18n/backend/transliterator.rb
+ - lib/i18n/config.rb
+ - lib/i18n/core_ext/hash.rb
+ - lib/i18n/core_ext/string/interpolate.rb
+ - lib/i18n/exceptions.rb
+ - lib/i18n/gettext.rb
+ - lib/i18n/gettext/helpers.rb
+ - lib/i18n/gettext/po_parser.rb
+ - lib/i18n/locale.rb
+ - lib/i18n/locale/fallbacks.rb
+ - lib/i18n/locale/tag.rb
+ - lib/i18n/locale/tag/parents.rb
+ - lib/i18n/locale/tag/rfc4646.rb
+ - lib/i18n/locale/tag/simple.rb
+ - lib/i18n/version.rb
+ - README.textile
+ - MIT-LICENSE
+ - CHANGELOG.textile
+has_rdoc: true
+homepage: http://github.com/svenfuchs/i18n
+licenses: []
+
+post_install_message:
+rdoc_options: []
+
+require_paths:
+ - lib
+required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ segments:
+ - 0
+ version: "0"
+required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ segments:
+ - 1
+ - 3
+ - 5
+ version: 1.3.5
+requirements: []
+
+rubyforge_project: "[none]"
+rubygems_version: 1.3.6
+signing_key:
+specification_version: 3
+summary: New wave Internationalization support for Ruby
+test_files: []
+
--- /dev/null
+h1. Changelog
+
+h2. 0.4.2 (2010-10-26)
+
+* "Improve UTF8 handling":http://github.com/svenfuchs/i18n/commit/e8d5820a3b08eeca28de1a2b9c8a6ad2b9e6476c
+* "Expose I18n::VERSION":http://github.com/svenfuchs/i18n/commit/b832037bac94c7144f45f3ff5e3b4e4089781726
+* "Better deprecation output":http://github.com/svenfuchs/i18n/commit/2bee924464b8a9c33d3d7852eb1c8423aa38cc25
+
+h2. 0.4.1 (2010-06-05)
+
+* "Fix interpolation failure on Ruby 1.9":http://github.com/svenfuchs/i18n/commit/8d45bedb11c4136c00e853d104b00a8e67ec4894
+
+h2. 0.4.0 (2010-05-27)
+
+* "The localization proc also receives the object as option":http://github.com/svenfuchs/i18n/commit/4a8cd9fa660daaa3078e24c5851353ca377d9213
+
+h2. 0.4.0.beta1 (2010-05-03)
+
+* "Renamed Fast backend to Memoize backend":http://github.com/svenfuchs/i18n/commit/f7f7dc12c00a19d3876223771e14f8671ff313cd
+
+* "Deprecate {{}} as interpolation syntax":http://github.com/svenfuchs/i18n/commit/8894ee521ef5788c415b625a6daf522af4c416e0
+
+* "Allow nil translation to be stored again":http://github.com/svenfuchs/i18n/commit/f2074f1e82d10c2e9a801c8cc2f2a0c7c30703ba
+
+h2. 0.4.0.beta (2010-04-30)
+
+* "Added a KeyValue backend":http://github.com/svenfuchs/i18n/commit/28ca5f53ade7f545f8c0804e93564d4686b416a4
+
+* "Added transliteration support":http://github.com/svenfuchs/i18n/commit/928fdb4794959e779e90f360eb01ba043672d8d5
+
+* "Create Flatten backend module to aid handling flatten translations":http://github.com/svenfuchs/i18n/commit/2ec9d6998aa8facd7b15a3ef47a96cf2471cd8a1
+
+* "Decouple the external separator (used when storing translations) from the internal separator in Fast and ActiveRecord backends":http://github.com/svenfuchs/i18n/commit/274cb4daa0ca5e3b2bd23b45eb7f9fc58f75a79d
+
+h2. 0.3.7 (2010-04-17)
+
+* "Speed up I18n.normalize_keys by caching reused normalizations and producing less garbage":http://github.com/svenfuchs/i18n/commit/819dac0fea9c29e6545801aa107e63e355728cd4
+
+h2. 0.3.6 (2010-03-23)
+
+* "Move gettext po parser to lib":http://github.com/svenfuchs/i18n/commit/b2f038663b55727ac2327e6f07a46ba5d69d600c
+
+* "Move I18n configuration to I18n.config":http://github.com/svenfuchs/i18n/commit/4a7baea86663ead8c681008c3e80a622f0546b07
+
+h2. 0.3.5 (2010-02-26)
+
+* "Delegate I18n.normalize_translation_keys to I18n.normalize_keys and deprecate
+the former":http://github.com/svenfuchs/i18n/commit/7284b04d5f5dd9679cb68875515cdd0cdfc96fef
+
+h2. 0.3.4 (2010-02-25)
+
+* "Rename I18n.normalize_translation_keys to I18n.normalize_keys and finally make it public":http://github.com/svenfuchs/i18n/commit/20b05fe5802df6c90fb70a4e3760b2b851b791b3
+
+* "Added CLDR supoprt":http://github.com/svenfuchs/i18n/commit/860eadf671a231e7f5dffb1bb27fa318ff7a8786
+
+h2. 0.3.3 (2009-12-29)
+
+* "Use lib/i18n/version":http://github.com/svenfuchs/i18n/commit/ff426c8e7a2438b814cb303adadec292dacb752e
+
+* "Added a benchmark suite":http://github.com/svenfuchs/i18n/commit/f9b5b9b113097724638bdab96862ffa404e67e70
+
+* "Ensure links can be handled recursively":http://github.com/svenfuchs/i18n/commit/2c50bd209f3fc24fe9dfa694c81be64340f09b7d
+
+* "Make sure we can lookup false values as translation data":http://github.com/svenfuchs/i18n/commit/561c82ba4b8921d03bfdf56cb2d0c2f287629001
+
+* "Added Fast backend module":http://github.com/svenfuchs/i18n/commit/bd2f09f0a251ca793b0e8ecc7e32177a2f091c23
+
+* "Added InterpolationCompiler backend module":http://github.com/svenfuchs/i18n/commit/91810887d1abfb28996a9183bc9004678290d28b
+
+h2. 0.3.2 (2009-12-12)
+
+* "Added Cascade backend":http://github.com/svenfuchs/i18n/commit/8009aef293e9ef8564c9005090d8380feabcaf6f
+
+h2. 0.3.1 (2009-12-11)
+
+* "Add PoParser to gemspec":http://github.com/svenfuchs/i18n/commit/d6b2763f39c932f66adb039b96882a472f883c51
+* "Enable custom separators for ActiveRecord backend":http://github.com/svenfuchs/i18n/commit/9341d3fcfc951cc31807ba672d2b5d90909ef3e5
+* "Pass interpolation values to interpolation procs":http://github.com/svenfuchs/i18n/commit/39c2ed8fbad645671cd5520ce7ad0aeefe2b0cca
+* "Fix that ngettext supports keys with dots":http://github.com/svenfuchs/i18n/commit/7362a43c34364d500de8899cfcca6bf1a5e6d1c8
+
+h2. 0.3.0 (2009-11-30)
+
+* "Gettext backend and helpers":http://github.com/svenfuchs/i18n/commit/35a1740d2f10b808548af352006950da4017e374
+* "Metadata module":http://github.com/svenfuchs/i18n/commit/2677208555179b36fcbe958c0e8bc642cf5bc020
+* "Basic ActiveRecord backend":http://github.com/svenfuchs/i18n/commit/786632d0b42de423ecf0969622efc87f1691e2a2
+* "Set encoding to UTF8 for all files":http://github.com/svenfuchs/i18n/commit/9be3d4a311b5bf583eec5d39986176cc40c112f2
+* "Chain backend":http://github.com/svenfuchs/i18n/commit/08259ffb88b3005403648d77bc1cbca0b92f3cf5
+* "Backend/cache implementation":http://github.com/svenfuchs/i18n/commit/e7bf15351cd2e27f5972eb40e65a5dd6f4a0feed
+* "Pluralization module":http://github.com/svenfuchs/i18n/commit/9ca4c9ed52d4706566a6abeb2d78722dcc5d4764
+* "add and adapt Globalize2 fallback implementation":http://github.com/svenfuchs/i18n/commit/1b37a303b27d6222b17162804b06323e5628768f
+* "move Simple backend implementation to a Base backend class and extend Simple from Base.":http://github.com/svenfuchs/i18n/commit/32ddc80a04e6aa247f6d6613bde7f78c73396cb4
+
+h2. 0.2.0 (2009-07-12)
+
+* "Allow using Ruby 1.9 syntax for string interpolation (API addition)":http://github.com/svenfuchs/i18n/commit/c6e0b06d512f2af57199a843a1d8a40241b32861
+* "Allow configuring the default scope separator, allow to pass a custom scope separator(API addition)":http://github.com/svenfuchs/i18n/commit/5b75bfbc348061adc11e3790187a187275bfd471 (e.g. I18n.t(:'foo|bar', :separator => '|')
+* "Pass :format option to #translate for #localize more useful lambda support":http://github.com/svenfuchs/i18n/commit/e277711b3c844fe7589b8d3f9af0f7d1b969a273
+* "Refactor Simple backend #resolve to #default and #resolve for more consistency. Now allows to pass lambdas as defaults and re-resolve Symbols":http://github.com/svenfuchs/i18n/commit/8c4ce3d923ce5fa73e973fe28217e18165549aba
+* "Add lambda support to #translate (API addition)":http://github.com/svenfuchs/i18n/commit/c90e62d8f7d3d5b78f34cfe328d871b58884f115
+* "Add lambda support to #localize (API addition)":http://github.com/svenfuchs/i18n/commit/9d390afcf33f3f469bb95e6888147152f6cc7442
+
+h2. 0.1.3 (2009-02-27)
+
+* "Remove unnecessary string encoding handling in the i18n simple backend which made the backend break on Ruby 1.9":http://github.com/svenfuchs/i18n/commit/4c3a970783861a94f2e89f46714fb3434e4f4f8d
+
+h2. 0.1.2 (2009-01-09)
+
+* "added #available_locales (returns an array of locales for which translations are available)":http://github.com/svenfuchs/i18n/commit/411f8fe7c8f3f89e9b6b921fa62ed66cb92f3af4
+* "flatten load_path before using it so that a nested array of paths won't throw up":http://github.com/svenfuchs/i18n/commit/d473a068a2b90aba98135deb225d6eb6d8104d70
+
+h2. 0.1.1 (2008-11-20)
+
+* "Use :'en' as a default locale (in favor of :'en-US')":http://github.com/svenfuchs/i18n/commit/c4b10b246aecf7da78cb2568dd0d2ab7e6b8a230
+* "Add #reload! to Simple backend":http://github.com/svenfuchs/i18n/commit/36dd2bd9973b9e1559728749a9daafa44693e964
+
+h2. 0.1.0 (2008-10-25)
+
+* "Fix Simple backend to distinguish false from nil values":http://github.com/svenfuchs/i18n/commit/39d9a47da14b5f3ba126af48923af8c30e135166
+* "Add #load_path to public api, add initialize to simple backend and remove #load_translations from public api":http://github.com/svenfuchs/i18n/commit/c4c5649e6bc8f020f1aaf5a5470bde048e22c82d
+* "Speed up Backend::Simple#interpolate":http://github.com/svenfuchs/i18n/commit/9e1ac6bf8833304e036323ec9932b9f33c468a35
+* "Remove #populate and #store_translations from public API":http://github.com/svenfuchs/i18n/commit/f4e514a80be7feb509f66824ee311905e2940900
+* "Use :other instead of :many as a plural key":http://github.com/svenfuchs/i18n/commit/0f8f20a2552bf6a2aa758d8fdd62a7154e4a1bf6
+* "Use a class instead of a module for Simple backend":http://github.com/svenfuchs/i18n/commit/08f051aa61320c17debde24a83268bc74e33b995
+* "Make Simple backend #interpolate deal with non-ASCII string encodings":http://github.com/svenfuchs/i18n/commit/d84a3f3f55543c084d5dc5d1fed613b8df148789
+* "Fix default arrays of non-existant keys returning the default array":http://github.com/svenfuchs/i18n/commit/6c04ca86c87f97dc78f07c2a4023644e5ba8b839
+
+h2. Initial implementation (June/July 2008)
+
+Initial implementation by "Sven Fuchs":http://www.workingwithrails.com/person/9963-sven-fuchs based on previous discussion/consensus of the rails-i18n team (alphabetical order) and many others:
+
+* "Matt Aimonetti":http://railsontherun.com
+* "Sven Fuchs":http://www.workingwithrails.com/person/9963-sven-fuchs
+* "Joshua Harvey":http://www.workingwithrails.com/person/759-joshua-harvey
+* "Saimon Moore":http://saimonmoore.net
+* "Stephan Soller":http://www.arkanis-development.de
+
+h2. More information
+
+* "Homepage":http://rails-i18n.org
+* "Wiki":http://rails-i18n.org/wiki
+* "Mailinglist":http://groups.google.com/group/rails-i18n
+* "About the project/history":http://www.artweb-design.de/2008/7/18/finally-ruby-on-rails-gets-internationalized
+* "Initial API Intro":http://www.artweb-design.de/2008/7/18/the-ruby-on-rails-i18n-core-api
\ No newline at end of file
--- /dev/null
+Copyright (c) 2008 The Ruby I18n team
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
--- /dev/null
+h1. Ruby I18n
+
+Ruby Internationalization and localization solution.
+
+Features:
+
+* translation and localization
+* interpolation of values to translations (Ruby 1.9 compatible syntax)
+* pluralization (CLDR compatible)
+* customizable transliteration to ASCII
+* flexible defaults
+* bulk lookup
+* lambdas as translation data
+* custom key/scope separator
+* custom exception handlers
+* extensible architecture with a swappable backend
+
+Pluggable features:
+
+* Cache
+* Pluralization: lambda pluralizers stored as translation data
+* Locale fallbacks, RFC4647 compliant (optionally: RFC4646 locale validation)
+* Gettext support
+* Translation metadata
+
+Alternative backends:
+
+* Chain
+* ActiveRecord (optionally: ActiveRecord::Missing and ActiveRecord::StoreProcs)
+* KeyValue (uses active_support/json and cannot store procs)
+
+For more information and lots of resources see: "http://ruby-i18n.org/wiki":http://ruby-i18n.org/wiki
+
+h2. Installation
+
+gem install i18n
+
+h4. Rails version warning
+
+On Rails < 2.3.6 the method I18n.localize will fail with MissingInterpolationArgument (issue "20":http://github.com/svenfuchs/i18n/issues/issue/20). Upgrade to Rails 2.3.6 or higher (2.3.8 preferably) is recommended.
+
+h3. Installation on Rails < 2.3.5 (deprecated)
+
+Up to version 2.3.4 Rails will not accept i18n gems > 0.1.3. There is an unpacked
+gem inside of active_support/lib/vendor which gets loaded unless gem 'i18n', '~> 0.1.3'.
+This requirement is relaxed in "6da03653":http://github.com/rails/rails/commit/6da03653
+
+The new i18n gem can be loaded from vendor/plugins like this:
+
+<pre>
+ def reload_i18n!
+ raise "Move to i18n version 0.2.0 or greater" if Rails.version > "2.3.4"
+
+ $:.grep(/i18n/).each { |path| $:.delete(path) }
+ I18n::Backend.send :remove_const, "Simple"
+ $: << Rails.root.join('vendor', 'plugins', 'i18n', 'lib').to_s
+ end
+</pre>
+
+Then you can `reload_i18n!` inside an i18n initializer.
+
+h2. Tests
+
+You can run tests both with
+
+* `rake test` or just `rake`
+* run any test file directly, e.g. `ruby test/api/simple_test.rb`
+* run all tests with `ruby test/all.rb`
+
+You can parametrize the test suite for using different sets of dependencies by
+using:
+
+.pre `ruby test/all.rb -w DEPENDENCIES`
+
+... where DEPENDENCIES is a comma-separated list of:
+
+* r23 or rails-2.3.x
+* r3 or rails-3.x
+* no-rails
+* sqlite
+* mysql
+
+So, e.g. this would run the test suite against Rails 2.3.x using mysql:
+
+.pre `ruby test/all.rb -w r23,mysql`
+
+The structure of the test suite is a bit unusual as it uses modules to reuse
+particular tests in different test cases.
+
+The reason for this is that we need to enforce the I18n API across various
+combinations of extensions. E.g. the Simple backend alone needs to support
+the same API as any combination of feature and/or optimization modules included
+to the Simple backend. We test this by reusing the same API defition (implemented
+as test methods) in test cases with different setups.
+
+You can find the test cases that enforce the API in test/api. And you can find
+the API definition test methods in test/api/tests.
+
+All other test cases (e.g. as defined in test/backend, test/core\_ext) etc.
+follow the usual test setup and should be easy to grok.
+
+h2. Authors
+
+* "Sven Fuchs":http://www.artweb-design.de
+* "Joshua Harvey":http://www.workingwithrails.com/person/759-joshua-harvey
+* "Stephan Soller":http://www.arkanis-development.de
+* "Saimon Moore":http://saimonmoore.net
+* "Matt Aimonetti":http://railsontherun.com
+
+h2. Contributors
+
+http://github.com/svenfuchs/i18n/contributors
+
+h2. License
+
+MIT License. See the included MIT-LICENSE file.
--- /dev/null
+# encoding: utf-8
+
+# Authors:: Sven Fuchs (http://www.artweb-design.de),
+# Joshua Harvey (http://www.workingwithrails.com/person/759-joshua-harvey),
+# Stephan Soller (http://www.arkanis-development.de/),
+# Saimon Moore (http://saimonmoore.net),
+# Matt Aimonetti (http://railsontherun.com/)
+# Copyright:: Copyright (c) 2008 The Ruby i18n Team
+# License:: MIT
+require 'i18n/version'
+require 'i18n/exceptions'
+require 'i18n/core_ext/string/interpolate'
+
+module I18n
+ autoload :Backend, 'i18n/backend'
+ autoload :Config, 'i18n/config'
+ autoload :Gettext, 'i18n/gettext'
+ autoload :Locale, 'i18n/locale'
+
+ class << self
+ # Gets I18n configuration object.
+ def config
+ Thread.current[:i18n_config] ||= I18n::Config.new
+ end
+
+ # Sets I18n configuration object.
+ def config=(value)
+ Thread.current[:i18n_config] = value
+ end
+
+ # Write methods which delegates to the configuration object
+ %w(locale backend default_locale available_locales default_separator
+ exception_handler load_path).each do |method|
+ module_eval <<-DELEGATORS, __FILE__, __LINE__ + 1
+ def #{method}
+ config.#{method}
+ end
+
+ def #{method}=(value)
+ config.#{method} = (value)
+ end
+ DELEGATORS
+ end
+
+ # Tells the backend to reload translations. Used in situations like the
+ # Rails development environment. Backends can implement whatever strategy
+ # is useful.
+ def reload!
+ config.backend.reload!
+ 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']
+ #
+ # *BULK LOOKUP*
+ #
+ # 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
+ #
+ # *LAMBDAS*
+ #
+ # Both translations and defaults can be given as Ruby lambdas. Lambdas will be
+ # called and passed the key and options.
+ #
+ # E.g. assuming the key <tt>:salutation</tt> resolves to:
+ # lambda { |key, options| options[:gender] == 'm' ? "Mr. %{options[:name]}" : "Mrs. %{options[:name]}" }
+ #
+ # Then <tt>I18n.t(:salutation, :gender => 'w', :name => 'Smith') will result in "Mrs. Smith".
+ #
+ # It is recommended to use/implement lambdas in an "idempotent" way. E.g. when
+ # a cache layer is put in front of I18n.translate it will generate a cache key
+ # from the argument values passed to #translate. Therefor your lambdas should
+ # always return the same translations/values per unique combination of argument
+ # values.
+ def translate(*args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ key = args.shift
+ backend = config.backend
+ locale = options.delete(:locale) || config.locale
+ raises = options.delete(:raise)
+
+ raise I18n::ArgumentError if key.is_a?(String) && key.empty?
+
+ if key.is_a?(Array)
+ key.map { |k| backend.translate(locale, k, options) }
+ else
+ backend.translate(locale, key, options)
+ end
+ rescue I18n::ArgumentError => exception
+ raise exception if raises
+ handle_exception(exception, locale, key, options)
+ end
+ alias :t :translate
+
+ def translate!(key, options={})
+ translate(key, options.merge(:raise => true))
+ end
+ alias :t! :translate!
+
+ # Transliterates UTF-8 characters to ASCII. By default this method will
+ # transliterate only Latin strings to an ASCII approximation:
+ #
+ # I18n.transliterate("Ærøskøbing")
+ # # => "AEroskobing"
+ #
+ # I18n.transliterate("日本語")
+ # # => "???"
+ #
+ # It's also possible to add support for per-locale transliterations. I18n
+ # expects transliteration rules to be stored at
+ # <tt>i18n.transliterate.rule</tt>.
+ #
+ # Transliteration rules can either be a Hash or a Proc. Procs must accept a
+ # single string argument. Hash rules inherit the default transliteration
+ # rules, while Procs do not.
+ #
+ # *Examples*
+ #
+ # Setting a Hash in <locale>.yml:
+ #
+ # i18n:
+ # transliterate:
+ # rule:
+ # ü: "ue"
+ # ö: "oe"
+ #
+ # Setting a Hash using Ruby:
+ #
+ # store_translations(:de, :i18n => {
+ # :transliterate => {
+ # :rule => {
+ # "ü" => "ue",
+ # "ö" => "oe"
+ # }
+ # }
+ # )
+ #
+ # Setting a Proc:
+ #
+ # translit = lambda {|string| MyTransliterator.transliterate(string) }
+ # store_translations(:xx, :i18n => {:transliterate => {:rule => translit})
+ #
+ # Transliterating strings:
+ #
+ # I18n.locale = :en
+ # I18n.transliterate("Jürgen") # => "Jurgen"
+ # I18n.locale = :de
+ # I18n.transliterate("Jürgen") # => "Juergen"
+ # I18n.transliterate("Jürgen", :locale => :en) # => "Jurgen"
+ # I18n.transliterate("Jürgen", :locale => :de) # => "Juergen"
+ def transliterate(*args)
+ options = args.pop if args.last.is_a?(Hash)
+ key = args.shift
+ locale = options && options.delete(:locale) || config.locale
+ raises = options && options.delete(:raise)
+ replacement = options && options.delete(:replacement)
+ config.backend.transliterate(locale, key, replacement)
+ rescue I18n::ArgumentError => exception
+ raise exception if raises
+ handle_exception(exception, locale, key, options)
+ end
+
+ # Localizes certain objects, such as dates and numbers to local formatting.
+ def localize(object, options = {})
+ locale = options.delete(:locale) || config.locale
+ format = options.delete(:format) || :default
+ config.backend.localize(locale, object, format, options)
+ end
+ alias :l :localize
+
+ # Executes block with given I18n.locale set.
+ def with_locale(tmp_locale = nil)
+ if tmp_locale
+ current_locale = self.locale
+ self.locale = tmp_locale
+ end
+ yield
+ ensure
+ self.locale = current_locale if tmp_locale
+ 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_keys(locale, key, scope, separator = nil)
+ separator ||= I18n.default_separator
+
+ keys = []
+ keys.concat normalize_key(locale, separator)
+ keys.concat normalize_key(scope, separator)
+ keys.concat normalize_key(key, separator)
+ keys
+ end
+
+ # making these private until Ruby 1.9.2 can send to protected methods again
+ # see http://redmine.ruby-lang.org/repositories/revision/ruby-19?rev=24280
+ private
+
+ # 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
+
+ # Any exceptions thrown in translate will be sent to the @@exception_handler
+ # which can be a Symbol, a Proc or any other Object.
+ #
+ # If exception_handler is a Symbol then it will simply be sent to I18n as
+ # a method call. A Proc will simply be called. In any other case the
+ # method #call will be called on the exception_handler object.
+ #
+ # Examples:
+ #
+ # I18n.exception_handler = :default_exception_handler # this is the default
+ # I18n.default_exception_handler(exception, locale, key, options) # will be called like this
+ #
+ # I18n.exception_handler = lambda { |*args| ... } # a lambda
+ # I18n.exception_handler.call(exception, locale, key, options) # will be called like this
+ #
+ # I18n.exception_handler = I18nExceptionHandler.new # an object
+ # I18n.exception_handler.call(exception, locale, key, options) # will be called like this
+ def handle_exception(exception, locale, key, options)
+ case config.exception_handler
+ when Symbol
+ send(config.exception_handler, exception, locale, key, options)
+ else
+ config.exception_handler.call(exception, locale, key, options)
+ end
+ end
+
+ # Deprecated. Will raise a warning in future versions and then finally be
+ # removed. Use I18n.normalize_keys instead.
+ def normalize_translation_keys(locale, key, scope, separator = nil)
+ normalize_keys(locale, key, scope, separator)
+ end
+
+ def normalize_key(key, separator)
+ normalized_key_cache[separator][key] ||=
+ case key
+ when Array
+ key.map { |k| normalize_key(k, separator) }.flatten
+ else
+ keys = key.to_s.split(separator)
+ keys.delete('')
+ keys.map!{ |k| k.to_sym }
+ keys
+ end
+ end
+
+ def normalized_key_cache
+ @normalized_key_cache ||= Hash.new { |h,k| h[k] = {} }
+ end
+ end
+end
--- /dev/null
+module I18n
+ module Backend
+ autoload :ActiveRecord, 'i18n/backend/active_record'
+ autoload :Base, 'i18n/backend/base'
+ autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
+ autoload :Cache, 'i18n/backend/cache'
+ autoload :Cascade, 'i18n/backend/cascade'
+ autoload :Chain, 'i18n/backend/chain'
+ autoload :Cldr, 'i18n/backend/cldr'
+ autoload :Fallbacks, 'i18n/backend/fallbacks'
+ autoload :Flatten, 'i18n/backend/flatten'
+ autoload :Gettext, 'i18n/backend/gettext'
+ autoload :KeyValue, 'i18n/backend/key_value'
+ autoload :Memoize, 'i18n/backend/memoize'
+ autoload :Metadata, 'i18n/backend/metadata'
+ autoload :Pluralization, 'i18n/backend/pluralization'
+ autoload :Simple, 'i18n/backend/simple'
+ autoload :Transliterator, 'i18n/backend/transliterator'
+ end
+end
--- /dev/null
+require 'i18n/backend/base'
+require 'i18n/backend/active_record/translation'
+
+module I18n
+ module Backend
+ class ActiveRecord
+ autoload :Missing, 'i18n/backend/active_record/missing'
+ autoload :StoreProcs, 'i18n/backend/active_record/store_procs'
+ autoload :Translation, 'i18n/backend/active_record/translation'
+
+ module Implementation
+ include Base, Flatten
+
+ def available_locales
+ begin
+ Translation.available_locales
+ rescue ::ActiveRecord::StatementInvalid
+ []
+ end
+ end
+
+ def store_translations(locale, data, options = {})
+ escape = options.fetch(:escape, true)
+ flatten_translations(locale, data, escape, false).each do |key, value|
+ Translation.locale(locale).lookup(expand_keys(key)).delete_all
+ Translation.create(:locale => locale.to_s, :key => key.to_s, :value => value)
+ end
+ end
+
+ protected
+
+ def lookup(locale, key, scope = [], options = {})
+ key = normalize_flat_keys(locale, key, scope, options[:separator])
+ result = Translation.locale(locale).lookup(key).all
+
+ if result.empty?
+ nil
+ elsif result.first.key == key
+ result.first.value
+ else
+ chop_range = (key.size + FLATTEN_SEPARATOR.size)..-1
+ result = result.inject({}) do |hash, r|
+ hash[r.key.slice(chop_range)] = r.value
+ hash
+ end
+ result.deep_symbolize_keys
+ end
+ end
+
+ # For a key :'foo.bar.baz' return ['foo', 'foo.bar', 'foo.bar.baz']
+ def expand_keys(key)
+ key.to_s.split(FLATTEN_SEPARATOR).inject([]) do |keys, key|
+ keys << [keys.last, key].compact.join(FLATTEN_SEPARATOR)
+ end
+ end
+ end
+
+ include Implementation
+ end
+ end
+end
--- /dev/null
+# This extension stores translation stub records for missing translations to
+# the database.
+#
+# This is useful if you have a web based translation tool. It will populate
+# the database with untranslated keys as the application is being used. A
+# translator can then go through these and add missing translations.
+#
+# Example usage:
+#
+# I18n::Backend::Chain.send(:include, I18n::Backend::ActiveRecord::Missing)
+# I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n::Backend::Simple.new)
+#
+# Stub records for pluralizations will also be created for each key defined
+# in i18n.plural.keys.
+#
+# For example:
+#
+# # en.yml
+# en:
+# i18n:
+# plural:
+# keys: [:zero, :one, :other]
+#
+# # pl.yml
+# pl:
+# i18n:
+# plural:
+# keys: [:zero, :one, :few, :other]
+#
+# It will also persist interpolation keys in Translation#interpolations so
+# translators will be able to review and use them.
+module I18n
+ module Backend
+ class ActiveRecord
+ module Missing
+ include Flatten
+
+ def store_default_translations(locale, key, options = {})
+ count, scope, default, separator = options.values_at(:count, :scope, :default, :separator)
+ separator ||= I18n.default_separator
+ key = normalize_flat_keys(locale, key, scope, separator)
+
+ unless ActiveRecord::Translation.locale(locale).lookup(key).exists?
+ interpolations = options.keys - Base::RESERVED_KEYS
+ keys = count ? I18n.t('i18n.plural.keys', :locale => locale).map { |k| [key, k].join(FLATTEN_SEPARATOR) } : [key]
+ keys.each { |key| store_default_translation(locale, key, interpolations) }
+ end
+ end
+
+ def store_default_translation(locale, key, interpolations)
+ translation = ActiveRecord::Translation.new :locale => locale.to_s, :key => key
+ translation.interpolations = interpolations
+ translation.save
+ end
+
+ def translate(locale, key, options = {})
+ super
+ rescue I18n::MissingTranslationData => e
+ self.store_default_translations(locale, key, options)
+ raise e
+ end
+ end
+ end
+ end
+end
--- /dev/null
+# This module is intended to be mixed into the ActiveRecord backend to allow
+# storing Ruby Procs as translation values in the database.
+#
+# I18n.backend = I18n::Backend::ActiveRecord.new
+# I18n::Backend::ActiveRecord::Translation.send(:include, I18n::Backend::ActiveRecord::StoreProcs)
+#
+# The StoreProcs module requires the ParseTree and ruby2ruby gems and therefor
+# was extracted from the original backend.
+#
+# ParseTree is not compatible with Ruby 1.9.
+
+begin
+ require 'ruby2ruby'
+ require 'parse_tree'
+ require 'parse_tree_extensions'
+rescue LoadError => e
+ puts "can't use StoreProcs because: #{e.message}"
+end
+
+module I18n
+ module Backend
+ class ActiveRecord
+ module StoreProcs
+ def value=(v)
+ case v
+ when Proc
+ write_attribute(:value, v.to_ruby)
+ write_attribute(:is_proc, true)
+ else
+ write_attribute(:value, v)
+ end
+ end
+
+ Translation.send(:include, self) if method(:to_s).respond_to?(:to_ruby)
+ end
+ end
+ end
+end
\ No newline at end of file
--- /dev/null
+require 'active_record'
+
+module I18n
+ module Backend
+ # ActiveRecord model used to store actual translations to the database.
+ #
+ # This model expects a table like the following to be already set up in
+ # your the database:
+ #
+ # create_table :translations do |t|
+ # t.string :locale
+ # t.string :key
+ # t.text :value
+ # t.text :interpolations
+ # t.boolean :is_proc, :default => false
+ # end
+ #
+ # This model supports to named scopes :locale and :lookup. The :locale
+ # scope simply adds a condition for a given locale:
+ #
+ # I18n::Backend::ActiveRecord::Translation.locale(:en).all
+ # # => all translation records that belong to the :en locale
+ #
+ # The :lookup scope adds a condition for looking up all translations
+ # that either start with the given keys (joined by an optionally given
+ # separator or I18n.default_separator) or that exactly have this key.
+ #
+ # # with translations present for :"foo.bar" and :"foo.baz"
+ # I18n::Backend::ActiveRecord::Translation.lookup(:foo)
+ # # => an array with both translation records :"foo.bar" and :"foo.baz"
+ #
+ # I18n::Backend::ActiveRecord::Translation.lookup([:foo, :bar])
+ # I18n::Backend::ActiveRecord::Translation.lookup(:"foo.bar")
+ # # => an array with the translation record :"foo.bar"
+ #
+ # When the StoreProcs module was mixed into this model then Procs will
+ # be stored to the database as Ruby code and evaluated when :value is
+ # called.
+ #
+ # Translation = I18n::Backend::ActiveRecord::Translation
+ # Translation.create \
+ # :locale => 'en'
+ # :key => 'foo'
+ # :value => lambda { |key, options| 'FOO' }
+ # Translation.find_by_locale_and_key('en', 'foo').value
+ # # => 'FOO'
+ class ActiveRecord
+ class Translation < ::ActiveRecord::Base
+ TRUTHY_CHAR = "\001"
+ FALSY_CHAR = "\002"
+
+ set_table_name 'translations'
+ attr_protected :is_proc, :interpolations
+
+ serialize :value
+ serialize :interpolations, Array
+
+ class << self
+ def locale(locale)
+ scoped(:conditions => { :locale => locale.to_s })
+ end
+
+ def lookup(keys, *separator)
+ column_name = connection.quote_column_name('key')
+ keys = Array(keys).map! { |key| key.to_s }
+
+ unless separator.empty?
+ warn "[DEPRECATION] Giving a separator to Translation.lookup is deprecated. " <<
+ "You can change the internal separator by overwriting FLATTEN_SEPARATOR."
+ end
+
+ namespace = "#{keys.last}#{I18n::Backend::Flatten::FLATTEN_SEPARATOR}%"
+ scoped(:conditions => ["#{column_name} IN (?) OR #{column_name} LIKE ?", keys, namespace])
+ end
+
+ def available_locales
+ Translation.find(:all, :select => 'DISTINCT locale').map { |t| t.locale.to_sym }
+ end
+ end
+
+ def interpolates?(key)
+ self.interpolations.include?(key) if self.interpolations
+ end
+
+ def value
+ value = read_attribute(:value)
+ if is_proc
+ Kernel.eval(value)
+ elsif value == FALSY_CHAR
+ false
+ elsif value == TRUTHY_CHAR
+ true
+ else
+ value
+ end
+ end
+
+ def value=(value)
+ if value === false
+ value = FALSY_CHAR
+ elsif value === true
+ value = TRUTHY_CHAR
+ end
+
+ write_attribute(:value, value)
+ end
+ end
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+
+require 'yaml'
+require 'i18n/core_ext/hash'
+
+module I18n
+ module Backend
+ module Base
+ include I18n::Backend::Transliterator
+
+ RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object, :fallback]
+ RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
+ DEPRECATED_INTERPOLATION_SYNTAX_PATTERN = /(\\)?\{\{([^\}]+)\}\}/
+
+ # 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 = I18n.load_path.flatten if filenames.empty?
+ filenames.each { |filename| load_file(filename) }
+ end
+
+ # This method receives a locale, a data hash and options for storing translations.
+ # Should be implemented
+ def store_translations(locale, data, options = {})
+ raise NotImplementedError
+ end
+
+ def translate(locale, key, options = {})
+ raise InvalidLocale.new(locale) unless locale
+ entry = key && lookup(locale, key, options[:scope], options)
+
+ if options.empty?
+ entry = resolve(locale, key, entry, options)
+ else
+ count, default = options.values_at(:count, :default)
+ values = options.except(*RESERVED_KEYS)
+ entry = entry.nil? && default ?
+ default(locale, key, default, options) : resolve(locale, key, entry, options)
+ end
+
+ raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
+ entry = entry.dup if entry.is_a?(String)
+
+ entry = pluralize(locale, entry, count) if count
+ entry = interpolate(locale, entry, values) if values
+ entry
+ end
+
+ # Acts the same as +strftime+, but uses a localized version of the
+ # format 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, options = {})
+ raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
+
+ if Symbol === format
+ key = format
+ type = object.respond_to?(:sec) ? 'time' : 'date'
+ options = options.merge(:raise => true, :object => object, :locale => locale)
+ format = I18n.t(:"#{type}.formats.#{key}", options)
+ end
+
+ # format = resolve(locale, object, format, options)
+ format = format.to_s.gsub(/%[aAbBp]/) do |match|
+ case match
+ when '%a' then I18n.t(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
+ when '%A' then I18n.t(:"date.day_names", :locale => locale, :format => format)[object.wday]
+ when '%b' then I18n.t(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
+ when '%B' then I18n.t(:"date.month_names", :locale => locale, :format => format)[object.mon]
+ when '%p' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format) if object.respond_to? :hour
+ end
+ end
+
+ object.strftime(format)
+ end
+
+ # Returns an array of locales for which translations are available
+ # ignoring the reserved translation meta data key :i18n.
+ def available_locales
+ raise NotImplementedError
+ end
+
+ def reload!
+ @skip_syntax_deprecation = false
+ end
+
+ protected
+
+ # The method which actually looks up for the translation in the store.
+ def lookup(locale, key, scope = [], options = {})
+ raise NotImplementedError
+ end
+
+ # Evaluates defaults.
+ # If given subject is an Array, it walks the array and returns the
+ # first translation that can be resolved. Otherwise it tries to resolve
+ # the translation directly.
+ def default(locale, object, subject, options = {})
+ options = options.dup.reject { |key, value| key == :default }
+ case subject
+ when Array
+ subject.each do |item|
+ result = resolve(locale, object, item, options) and return result
+ end and nil
+ else
+ resolve(locale, object, subject, options)
+ end
+ end
+
+ # Resolves a translation.
+ # If the given subject is a Symbol, it will be translated with the
+ # given options. If it is a Proc then it will be evaluated. All other
+ # subjects will be returned directly.
+ def resolve(locale, object, subject, options = {})
+ return subject if options[:resolve] == false
+ case subject
+ when Symbol
+ I18n.translate(subject, options.merge(:locale => locale, :raise => true))
+ when Proc
+ date_or_time = options.delete(:object) || object
+ resolve(locale, object, subject.call(date_or_time, options))
+ else
+ subject
+ 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) && count
+
+ 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 unless string.is_a?(::String) && !values.empty?
+
+ string = string.gsub(DEPRECATED_INTERPOLATION_SYNTAX_PATTERN) do
+ escaped, key = $1, $2.to_sym
+ if escaped
+ "{{#{key}}}"
+ else
+ warn_syntax_deprecation!(locale, string)
+ "%{#{key}}"
+ end
+ end
+
+ values.each do |key, value|
+ value = value.call(values) if interpolate_lambda?(value, string, key)
+ value = value.to_s unless value.is_a?(::String)
+ values[key] = value
+ end
+
+ string % values
+ rescue KeyError => e
+ if string =~ RESERVED_KEYS_PATTERN
+ raise ReservedInterpolationKey.new($1.to_sym, string)
+ else
+ raise MissingInterpolationArgument.new(values, string)
+ end
+ end
+
+ # returns true when the given value responds to :call and the key is
+ # an interpolation placeholder in the given string
+ def interpolate_lambda?(object, string, key)
+ object.respond_to?(:call) && string =~ /%\{#{key}\}|%\<#{key}>.*?\d*\.?\d*[bBdiouxXeEfgGcps]\}/
+ 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}", true)
+ data = send(:"load_#{type}", filename)
+ raise InvalidLocaleData.new(filename) unless data.is_a?(Hash)
+ data.each { |locale, d| store_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_file(filename)
+ end
+
+ def warn_syntax_deprecation!(locale, string) #:nodoc:
+ return if @skip_syntax_deprecation
+ warn "The {{key}} interpolation syntax in I18n messages is deprecated. Please use %{key} instead.\n#{locale} - #{string}\n"
+ @skip_syntax_deprecation = true
+ end
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+
+# This module allows you to easily cache all responses from the backend - thus
+# speeding up the I18n aspects of your application quite a bit.
+#
+# To enable caching you can simply include the Cache module to the Simple
+# backend - or whatever other backend you are using:
+#
+# I18n::Backend::Simple.send(:include, I18n::Backend::Cache)
+#
+# You will also need to set a cache store implementation that you want to use:
+#
+# I18n.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
+#
+# You can use any cache implementation you want that provides the same API as
+# ActiveSupport::Cache (only the methods #fetch and #write are being used).
+#
+# The cache_key implementation assumes that you only pass values to
+# I18n.translate that return a valid key from #hash (see
+# http://www.ruby-doc.org/core/classes/Object.html#M000337).
+#
+# If you use a lambda as a default value in your translation like this:
+#
+# I18n.t(:"date.order", :default => lambda {[:month, :day, :year]})
+#
+# Then you will always have a cache miss, because each time this method
+# is called the lambda will have a different hash value. If you know
+# the result of the lambda is a constant as in the example above, then
+# to cache this you can make the lambda a constant, like this:
+#
+# DEFAULT_DATE_ORDER = lambda {[:month, :day, :year]}
+# ...
+# I18n.t(:"date.order", :default => DEFAULT_DATE_ORDER)
+#
+# If the lambda may result in different values for each call then consider
+# also using the Memoize backend.
+#
+module I18n
+ class << self
+ @@cache_store = nil
+ @@cache_namespace = nil
+
+ def cache_store
+ @@cache_store
+ end
+
+ def cache_store=(store)
+ @@cache_store = store
+ end
+
+ def cache_namespace
+ @@cache_namespace
+ end
+
+ def cache_namespace=(namespace)
+ @@cache_namespace = namespace
+ end
+
+ def perform_caching?
+ !cache_store.nil?
+ end
+ end
+
+ module Backend
+ # TODO Should the cache be cleared if new translations are stored?
+ module Cache
+ def translate(locale, key, options = {})
+ I18n.perform_caching? ? fetch(cache_key(locale, key, options)) { super } : super
+ end
+
+ protected
+
+ def fetch(cache_key, &block)
+ result = fetch_storing_missing_translation_exception(cache_key, &block)
+ raise result if result.is_a?(Exception)
+ result = result.dup if result.frozen? rescue result
+ result
+ end
+
+ def fetch_storing_missing_translation_exception(cache_key, &block)
+ fetch_ignoring_procs(cache_key, &block)
+ rescue MissingTranslationData => exception
+ I18n.cache_store.write(cache_key, exception)
+ exception
+ end
+
+ def fetch_ignoring_procs(cache_key, &block)
+ I18n.cache_store.read(cache_key) || yield.tap do |result|
+ I18n.cache_store.write(cache_key, result) unless result.is_a?(Proc)
+ end
+ end
+
+ def cache_key(locale, key, options)
+ # This assumes that only simple, native Ruby values are passed to I18n.translate.
+ "i18n/#{I18n.cache_namespace}/#{locale}/#{key.hash}/#{USE_INSPECT_HASH ? options.inspect.hash : options.hash}"
+ end
+
+ private
+ # In Ruby < 1.9 the following is true: { :foo => 1, :bar => 2 }.hash == { :foo => 2, :bar => 1 }.hash
+ # Therefore we must use the hash of the inspect string instead to avoid cache key colisions.
+ USE_INSPECT_HASH = RUBY_VERSION <= "1.9"
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+
+# EXPERIMENTAL
+#
+# The Cascade module adds the ability to do cascading lookups to backends that
+# are compatible to the Simple backend.
+#
+# By cascading lookups we mean that for any key that can not be found the
+# Cascade module strips one segment off the scope part of the key and then
+# tries to look up the key in that scope.
+#
+# E.g. when a lookup for the key :"foo.bar.baz" does not yield a result then
+# the segment :bar will be stripped off the scope part :"foo.bar" and the new
+# scope :foo will be used to look up the key :baz. If that does not succeed
+# then the remaining scope segment :foo will be omitted, too, and again the
+# key :baz will be looked up (now with no scope).
+#
+# To enable a cascading lookup one passes the :cascade option:
+#
+# I18n.t(:'foo.bar.baz', :cascade => true)
+#
+# This will return the first translation found for :"foo.bar.baz", :"foo.baz"
+# or :baz in this order.
+#
+# The cascading lookup takes precedence over resolving any given defaults.
+# I.e. defaults will kick in after the cascading lookups haven't succeeded.
+#
+# This behavior is useful for libraries like ActiveRecord validations where
+# the library wants to give users a bunch of more or less fine-grained options
+# of scopes for a particular key.
+#
+# Thanks to Clemens Kofler for the initial idea and implementation! See
+# http://github.com/clemens/i18n-cascading-backend
+
+module I18n
+ module Backend
+ module Cascade
+ def lookup(locale, key, scope = [], options = {})
+ return super unless cascade = options[:cascade]
+
+ separator = options[:separator] || I18n.default_separator
+ skip_root = cascade.has_key?(:skip_root) ? cascade[:skip_root] : true
+ step = cascade[:step]
+
+ keys = I18n.normalize_keys(nil, key, nil, separator)
+ offset = options[:cascade][:offset] || keys.length
+ scope = I18n.normalize_keys(nil, nil, scope, separator) + keys
+ key = scope.slice!(-offset, offset).join(separator)
+
+ begin
+ result = super
+ return result unless result.nil?
+ end while !scope.empty? && scope.slice!(-step, step) && (!scope.empty? || !skip_root)
+ end
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+
+module I18n
+ module Backend
+ # Backend that chains multiple other backends and checks each of them when
+ # a translation needs to be looked up. This is useful when you want to use
+ # standard translations with a Simple backend but store custom application
+ # translations in a database or other backends.
+ #
+ # To use the Chain backend instantiate it and set it to the I18n module.
+ # You can add chained backends through the initializer or backends
+ # accessor:
+ #
+ # # preserves the existing Simple backend set to I18n.backend
+ # I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)
+ #
+ # The implementation assumes that all backends added to the Chain implement
+ # a lookup method with the same API as Simple backend does.
+ class Chain
+ module Implementation
+ include Base
+
+ attr_accessor :backends
+
+ def initialize(*backends)
+ self.backends = backends
+ end
+
+ def reload!
+ backends.each { |backend| backend.reload! }
+ end
+
+ def store_translations(locale, data, options = {})
+ backends.first.store_translations(locale, data, options = {})
+ end
+
+ def available_locales
+ backends.map { |backend| backend.available_locales }.flatten.uniq
+ end
+
+ def translate(locale, key, default_options = {})
+ namespace = nil
+ options = default_options.except(:default)
+
+ backends.each do |backend|
+ begin
+ options = default_options if backend == backends.last
+ translation = backend.translate(locale, key, options)
+ if namespace_lookup?(translation, options)
+ namespace ||= {}
+ namespace.merge!(translation)
+ elsif !translation.nil?
+ return translation
+ end
+ rescue MissingTranslationData
+ end
+ end
+
+ return namespace if namespace
+ raise(I18n::MissingTranslationData.new(locale, key, options))
+ end
+
+ def localize(locale, object, format = :default, options = {})
+ backends.each do |backend|
+ begin
+ result = backend.localize(locale, object, format, options) and return result
+ rescue MissingTranslationData
+ end
+ end
+ raise(I18n::MissingTranslationData.new(locale, format, options))
+ end
+
+ protected
+ def namespace_lookup?(result, options)
+ result.is_a?(Hash) && !options.has_key?(:count)
+ end
+ end
+
+ include Implementation
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+require 'cldr'
+
+module I18n
+ module Backend
+ module Cldr
+ include ::Cldr::Format
+
+ def localize(locale, object, format = :default, options = {})
+ options[:as] ||= detect_type(object, options)
+ send(:"format_#{options[:as]}", locale, object, format, options)
+ end
+
+ def format_decimal(locale, object, format = :default, options = {})
+ formatter(locale, :decimal, format).apply(object, options)
+ end
+
+ def format_integer(locale, object, format = :default, options = {})
+ format_object(number, options.merge(:precision => 0))
+ end
+
+ def format_currency(locale, object, format = :default, options = {})
+ options.merge!(:currency => lookup_currency(locale, options[:currency], object)) if options[:currency].is_a?(Symbol)
+ formatter(locale, :currency, format).apply(object, options)
+ end
+
+ def format_percent(locale, object, format = :default, options = {})
+ formatter(locale, :percent, format).apply(object, options)
+ end
+
+ def format_date(locale, object, format = :default, options = {})
+ formatter(locale, :date, format).apply(object, options)
+ end
+
+ def format_time(locale, object, format = :default, options = {})
+ formatter(locale, :time, format).apply(object, options)
+ end
+
+ def format_datetime(locale, object, format = :default, options = {})
+ key = :"calendars.gregorian.formats.datetime.#{format}.pattern"
+ date = I18n.l(object, :format => options[:date_format] || format, :locale => locale, :as => :date)
+ time = I18n.l(object, :format => options[:time_format] || format, :locale => locale, :as => :time)
+ I18n.t(key, :date => date, :time => time, :locale => locale, :raise => true)
+ end
+
+ protected
+
+ def detect_type(object, options)
+ options.has_key?(:currency) ? :currency : case object
+ when ::Numeric
+ :decimal
+ when ::Date, ::DateTime, ::Time
+ object.class.name.downcase.to_sym
+ else
+ raise_unspecified_format_type!
+ end
+ end
+
+ def formatter(locale, type, format)
+ (@formatters ||= {})[:"#{locale}.#{type}.#{format}"] ||= begin
+ format = lookup_format(locale, type, format)
+ data = lookup_format_data(locale, type)
+ ::Cldr::Format.const_get(type.to_s.camelize).new(format, data)
+ end
+ end
+
+ def lookup_format(locale, type, format)
+ key = case type
+ when :date, :time, :datetime
+ :"calendars.gregorian.formats.#{type}.#{format}.pattern"
+ else
+ :"numbers.formats.#{type}.patterns.#{format || :default}"
+ end
+ I18n.t(key, :locale => locale, :raise => true)
+ end
+
+ def lookup_format_data(locale, type)
+ key = case type
+ when :date, :time, :datetime
+ :'calendars.gregorian'
+ else
+ :'numbers.symbols'
+ end
+ I18n.t(key, :locale => locale, :raise => true)
+ end
+
+ def lookup_currency(locale, currency, count)
+ I18n.t(:"currencies.#{currency}", :locale => locale, :count => count)
+ end
+
+ def raise_unspecified_format_type!
+ raise ArgumentError.new("You have to specify a format type, e.g. :as => :number.")
+ end
+
+ def raise_unspecified_currency!
+ raise ArgumentError.new("You have to specify a currency, e.g. :currency => 'EUR'.")
+ end
+ end
+ end
+end
\ No newline at end of file
--- /dev/null
+# encoding: utf-8
+
+# I18n locale fallbacks are useful when you want your application to use
+# translations from other locales when translations for the current locale are
+# missing. E.g. you might want to use :en translations when translations in
+# your applications main locale :de are missing.
+#
+# To enable locale fallbacks you can simply include the Fallbacks module to
+# the Simple backend - or whatever other backend you are using:
+#
+# I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
+module I18n
+ @@fallbacks = nil
+
+ class << self
+ # Returns the current fallbacks implementation. Defaults to +I18n::Locale::Fallbacks+.
+ def fallbacks
+ @@fallbacks ||= I18n::Locale::Fallbacks.new
+ end
+
+ # Sets the current fallbacks implementation. Use this to set a different fallbacks implementation.
+ def fallbacks=(fallbacks)
+ @@fallbacks = fallbacks
+ end
+ end
+
+ module Backend
+ module Fallbacks
+ # Overwrites the Base backend translate method so that it will try each
+ # locale given by I18n.fallbacks for the given locale. E.g. for the
+ # locale :"de-DE" it might try the locales :"de-DE", :de and :en
+ # (depends on the fallbacks implementation) until it finds a result with
+ # the given options. If it does not find any result for any of the
+ # locales it will then raise a MissingTranslationData exception as
+ # usual.
+ #
+ # The default option takes precedence over fallback locales
+ # only when it's a Symbol. When the default contains a String or a Proc
+ # it is evaluated last after all the fallback locales have been tried.
+ def translate(locale, key, options = {})
+ return super if options[:fallback]
+ default = extract_string_or_lambda_default!(options) if options[:default]
+
+ options[:fallback] = true
+ I18n.fallbacks[locale].each do |fallback|
+ begin
+ result = super(fallback, key, options)
+ return result unless result.nil?
+ rescue I18n::MissingTranslationData
+ end
+ end
+ options.delete(:fallback)
+
+ return super(locale, nil, options.merge(:default => default)) if default
+ raise(I18n::MissingTranslationData.new(locale, key, options))
+ end
+
+ def extract_string_or_lambda_default!(options)
+ defaults = Array(options[:default])
+ if index = find_first_string_or_lambda_default(defaults)
+ options[:default] = defaults[0, index]
+ defaults[index]
+ end
+ end
+
+ def find_first_string_or_lambda_default(defaults)
+ defaults.each_with_index { |default, ix| return ix if String === default || Proc === default }
+ nil
+ end
+ end
+ end
+end
--- /dev/null
+module I18n
+ module Backend
+ # This module contains several helpers to assist flattening translations.
+ # You may want to flatten translations for:
+ #
+ # 1) speed up lookups, as in the Memoize backend;
+ # 2) In case you want to store translations in a data store, as in ActiveRecord backend;
+ #
+ # You can check both backends above for some examples.
+ # This module also keeps all links in a hash so they can be properly resolved when flattened.
+ module Flatten
+ SEPARATOR_ESCAPE_CHAR = "\001"
+ FLATTEN_SEPARATOR = "."
+
+ # normalize_keys the flatten way. This method is significantly faster
+ # and creates way less objects than the one at I18n.normalize_keys.
+ # It also handles escaping the translation keys.
+ def self.normalize_flat_keys(locale, key, scope, separator)
+ keys = [scope, key].flatten.compact
+ separator ||= I18n.default_separator
+
+ if separator != FLATTEN_SEPARATOR
+ keys.map! do |k|
+ k.to_s.tr("#{FLATTEN_SEPARATOR}#{separator}",
+ "#{SEPARATOR_ESCAPE_CHAR}#{FLATTEN_SEPARATOR}")
+ end
+ end
+
+ keys.join(".")
+ end
+
+ # Receives a string and escape the default separator.
+ def self.escape_default_separator(key) #:nodoc:
+ key.to_s.tr(FLATTEN_SEPARATOR, SEPARATOR_ESCAPE_CHAR)
+ end
+
+ # Shortcut to I18n::Backend::Flatten.normalize_flat_keys
+ # and then resolve_links.
+ def normalize_flat_keys(locale, key, scope, separator)
+ key = I18n::Backend::Flatten.normalize_flat_keys(locale, key, scope, separator)
+ resolve_link(locale, key)
+ end
+
+ # Store flattened links.
+ def links
+ @links ||= Hash.new { |h,k| h[k] = {} }
+ end
+
+ # Flatten keys for nested Hashes by chaining up keys:
+ #
+ # >> { "a" => { "b" => { "c" => "d", "e" => "f" }, "g" => "h" }, "i" => "j"}.wind
+ # => { "a.b.c" => "d", "a.b.e" => "f", "a.g" => "h", "i" => "j" }
+ #
+ def flatten_keys(hash, escape, prev_key=nil, &block)
+ hash.each_pair do |key, value|
+ key = escape_default_separator(key) if escape
+ curr_key = [prev_key, key].compact.join(FLATTEN_SEPARATOR).to_sym
+ yield curr_key, value
+ flatten_keys(value, escape, curr_key, &block) if value.is_a?(Hash)
+ end
+ end
+
+ # Receives a hash of translations (where the key is a locale and
+ # the value is another hash) and return a hash with all
+ # translations flattened.
+ #
+ # Nested hashes are included in the flattened hash just if subtree
+ # is true and Symbols are automatically stored as links.
+ def flatten_translations(locale, data, escape, subtree)
+ hash = {}
+ flatten_keys(data, escape) do |key, value|
+ if value.is_a?(Hash)
+ hash[key] = value if subtree
+ else
+ store_link(locale, key, value) if value.is_a?(Symbol)
+ hash[key] = value
+ end
+ end
+ hash
+ end
+
+ protected
+
+ def store_link(locale, key, link)
+ links[locale.to_sym][key.to_s] = link.to_s
+ end
+
+ def resolve_link(locale, key)
+ key, locale = key.to_s, locale.to_sym
+ links = self.links[locale]
+
+ if links.key?(key)
+ links[key]
+ elsif link = find_link(locale, key)
+ store_link(locale, key, key.gsub(*link))
+ else
+ key
+ end
+ end
+
+ def find_link(locale, key) #:nodoc:
+ links[locale].each do |from, to|
+ return [from, to] if key[0, from.length] == from
+ end && nil
+ end
+
+ def escape_default_separator(key) #:nodoc:
+ I18n::Backend::Flatten.escape_default_separator(key)
+ end
+
+ end
+ end
+end
\ No newline at end of file
--- /dev/null
+# encoding: utf-8
+
+require 'i18n/gettext'
+require 'i18n/gettext/po_parser'
+
+# Experimental support for using Gettext po files to store translations.
+#
+# To use this you can simply include the module to the Simple backend - or
+# whatever other backend you are using.
+#
+# I18n::Backend::Simple.send(:include, I18n::Backend::Gettext)
+#
+# Now you should be able to include your Gettext translation (*.po) files to
+# the I18n.load_path so they're loaded to the backend and you can use them as
+# usual:
+#
+# I18n.load_path += Dir["path/to/locales/*.po"]
+#
+# Following the Gettext convention this implementation expects that your
+# translation files are named by their locales. E.g. the file en.po would
+# contain the translations for the English locale.
+module I18n
+ module Backend
+ module Gettext
+ class PoData < Hash
+ def set_comment(msgid_or_sym, comment)
+ # ignore
+ end
+ end
+
+ protected
+ def load_po(filename)
+ locale = ::File.basename(filename, '.po').to_sym
+ data = normalize(locale, parse(filename))
+ { locale => data }
+ end
+
+ def parse(filename)
+ GetText::PoParser.new.parse(::File.read(filename), PoData.new)
+ end
+
+ def normalize(locale, data)
+ data.inject({}) do |result, (key, value)|
+ unless key.nil? || key.empty?
+ key, value = normalize_pluralization(locale, key, value) if key.index("\000")
+
+ parts = key.split('|').reverse
+ normalized = parts.inject({}) do |normalized, part|
+ normalized = { part => normalized.empty? ? value : normalized }
+ end
+
+ result.deep_merge!(normalized)
+ end
+ result
+ end
+ end
+
+ def normalize_pluralization(locale, key, value)
+ # FIXME po_parser includes \000 chars that can not be turned into Symbols
+ key = key.gsub("\000", I18n::Gettext::PLURAL_SEPARATOR).split(I18n::Gettext::PLURAL_SEPARATOR).first
+
+ keys = I18n::Gettext.plural_keys(locale)
+ values = value.split("\000")
+ raise "invalid number of plurals: #{values.size}, keys: #{keys.inspect}" if values.size != keys.size
+
+ result = {}
+ values.each_with_index { |value, ix| result[keys[ix]] = value }
+ [key, result]
+ end
+
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+
+# The InterpolationCompiler module contains optimizations that can tremendously
+# speed up the interpolation process on the Simple backend.
+#
+# It works by defining a pre-compiled method on stored translation Strings that
+# already bring all the knowledge about contained interpolation variables etc.
+# so that the actual recurring interpolation will be very fast.
+#
+# To enable pre-compiled interpolations you can simply include the
+# InterpolationCompiler module to the Simple backend:
+#
+# I18n::Backend::Simple.send(:include, I18n::Backend::InterpolationCompiler)
+#
+# Note that InterpolationCompiler does not yield meaningful results and consequently
+# should not be used with Ruby 1.9 (YARV) but improves performance everywhere else
+# (jRuby, Rubinius and 1.8.7).
+module I18n
+ module Backend
+ module InterpolationCompiler
+ module Compiler
+ extend self
+
+ TOKENIZER = /(%%\{[^\}]+\}|%\{[^\}]+\})/
+ INTERPOLATION_SYNTAX_PATTERN = /(%)?(%\{([^\}]+)\})/
+
+ def compile_if_an_interpolation(string)
+ if interpolated_str?(string)
+ string.instance_eval <<-RUBY_EVAL, __FILE__, __LINE__
+ def i18n_interpolate(v = {})
+ "#{compiled_interpolation_body(string)}"
+ end
+ RUBY_EVAL
+ end
+
+ string
+ end
+
+ def interpolated_str?(str)
+ str.kind_of?(::String) && str =~ INTERPOLATION_SYNTAX_PATTERN
+ end
+
+ protected
+ # tokenize("foo %{bar} baz %%{buz}") # => ["foo ", "%{bar}", " baz ", "%%{buz}"]
+ def tokenize(str)
+ str.split(TOKENIZER)
+ end
+
+ def compiled_interpolation_body(str)
+ tokenize(str).map do |token|
+ (matchdata = token.match(INTERPOLATION_SYNTAX_PATTERN)) ? handle_interpolation_token(token, matchdata) : escape_plain_str(token)
+ end.join
+ end
+
+ def handle_interpolation_token(interpolation, matchdata)
+ escaped, pattern, key = matchdata.values_at(1, 2, 3)
+ escaped ? pattern : compile_interpolation_token(key.to_sym)
+ end
+
+ def compile_interpolation_token(key)
+ "\#{#{interpolate_or_raise_missing(key)}}"
+ end
+
+ def interpolate_or_raise_missing(key)
+ escaped_key = escape_key_sym(key)
+ Base::RESERVED_KEYS.include?(key) ? reserved_key(escaped_key) : interpolate_key(escaped_key)
+ end
+
+ def interpolate_key(key)
+ [direct_key(key), nil_key(key), missing_key(key)].join('||')
+ end
+
+ def direct_key(key)
+ "((t = v[#{key}]) && t.respond_to?(:call) ? t.call : t)"
+ end
+
+ def nil_key(key)
+ "(v.has_key?(#{key}) && '')"
+ end
+
+ def missing_key(key)
+ "raise(MissingInterpolationArgument.new(#{key}, self))"
+ end
+
+ def reserved_key(key)
+ "raise(ReservedInterpolationKey.new(#{key}, self))"
+ end
+
+ def escape_plain_str(str)
+ str.gsub(/"|\\|#/) {|x| "\\#{x}"}
+ end
+
+ def escape_key_sym(key)
+ # rely on Ruby to do all the hard work :)
+ key.to_sym.inspect
+ end
+ end
+
+ def interpolate(locale, string, values)
+ if string.respond_to?(:i18n_interpolate)
+ string.i18n_interpolate(values)
+ elsif values
+ super
+ else
+ string
+ end
+ end
+
+ def store_translations(locale, data, options = {})
+ compile_all_strings_in(data)
+ super
+ end
+
+ protected
+ def compile_all_strings_in(data)
+ data.each_value do |value|
+ Compiler.compile_if_an_interpolation(value)
+ compile_all_strings_in(value) if value.kind_of?(Hash)
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
--- /dev/null
+# encoding: utf-8
+
+require 'i18n/backend/base'
+require 'active_support/json'
+
+module I18n
+ module Backend
+ # This is a basic backend for key value stores. It receives on
+ # initialization the store, which should respond to three methods:
+ #
+ # * store#[](key) - Used to get a value
+ # * store#[]=(key, value) - Used to set a value
+ # * store#keys - Used to get all keys
+ #
+ # Since these stores only supports string, all values are converted
+ # to JSON before being stored, allowing it to also store booleans,
+ # hashes and arrays. However, this store does not support Procs.
+ #
+ # As the ActiveRecord backend, Symbols are just supported when loading
+ # translations from the filesystem or through explicit store translations.
+ #
+ # Also, avoid calling I18n.available_locales since it's a somehow
+ # expensive operation in most stores.
+ #
+ # == Example
+ #
+ # To setup I18n to use TokyoCabinet in memory is quite straightforward:
+ #
+ # require 'rufus/tokyo/cabinet' # gem install rufus-tokyo
+ # I18n.backend = I18n::Backend::KeyValue.new(Rufus::Tokyo::Cabinet.new('*'))
+ #
+ # == Performance
+ #
+ # You may make this backend even faster by including the Memoize module.
+ # However, notice that you should properly clear the cache if you change
+ # values directly in the key-store.
+ #
+ # == Subtrees
+ #
+ # In most backends, you are allowed to retrieve part of a translation tree:
+ #
+ # I18n.backend.store_translations :en, :foo => { :bar => :baz }
+ # I18n.t "foo" #=> { :bar => :baz }
+ #
+ # This backend supports this feature by default, but it slows down the storage
+ # of new data considerably and makes hard to delete entries. That said, you are
+ # allowed to disable the storage of subtrees on initialization:
+ #
+ # I18n::Backend::KeyValue.new(@store, false)
+ #
+ # This is useful if you are using a KeyValue backend chained to a Simple backend.
+ class KeyValue
+ module Implementation
+ attr_accessor :store
+
+ include Base, Flatten
+
+ def initialize(store, subtrees=true)
+ @store, @subtrees = store, subtrees
+ end
+
+ def store_translations(locale, data, options = {})
+ escape = options.fetch(:escape, true)
+ flatten_translations(locale, data, escape, @subtrees).each do |key, value|
+ key = "#{locale}.#{key}"
+
+ case value
+ when Hash
+ if @subtrees && (old_value = @store[key])
+ old_value = ActiveSupport::JSON.decode(old_value)
+ value = old_value.deep_symbolize_keys.deep_merge!(value) if old_value.is_a?(Hash)
+ end
+ when Proc
+ raise "Key-value stores cannot handle procs"
+ end
+
+ @store[key] = ActiveSupport::JSON.encode(value) unless value.is_a?(Symbol)
+ end
+ end
+
+ def available_locales
+ locales = @store.keys.map { |k| k =~ /\./; $` }
+ locales.uniq!
+ locales.compact!
+ locales.map! { |k| k.to_sym }
+ locales
+ end
+
+ protected
+
+ def lookup(locale, key, scope = [], options = {})
+ key = normalize_flat_keys(locale, key, scope, options[:separator])
+ value = @store["#{locale}.#{key}"]
+ value = ActiveSupport::JSON.decode(value) if value
+ value.is_a?(Hash) ? value.deep_symbolize_keys : value
+ end
+ end
+
+ include Implementation
+ end
+ end
+end
\ No newline at end of file
--- /dev/null
+# encoding: utf-8
+#
+# Memoize module simply memoizes the values returned by lookup using
+# a flat hash and can tremendously speed up the lookup process in a backend.
+#
+# To enable it you can simply include the Memoize module to your backend:
+#
+# I18n::Backend::Simple.send(:include, I18n::Backend::Memoize)
+#
+# Notice that it's the responsibility of the backend to define whenever the
+# cache should be cleaned.
+module I18n
+ module Backend
+ module Memoize
+ def available_locales
+ @memoized_locales ||= super
+ end
+
+ def store_translations(locale, data, options = {})
+ reset_memoizations!(locale)
+ super
+ end
+
+ def reload!
+ reset_memoizations!
+ super
+ end
+
+ protected
+
+ def lookup(locale, key, scope = nil, options = {})
+ flat_key = I18n::Backend::Flatten.normalize_flat_keys(locale,
+ key, scope, options[:separator]).to_sym
+ flat_hash = memoized_lookup[locale.to_sym]
+ flat_hash.key?(flat_key) ? flat_hash[flat_key] : (flat_hash[flat_key] = super)
+ end
+
+ def memoized_lookup
+ @memoized_lookup ||= Hash.new { |h, k| h[k] = {} }
+ end
+
+ def reset_memoizations!(locale=nil)
+ @memoized_locales = nil
+ (locale ? memoized_lookup[locale.to_sym] : memoized_lookup).clear
+ end
+ end
+ end
+end
\ No newline at end of file
--- /dev/null
+# I18n translation metadata is useful when you want to access information
+# about how a translation was looked up, pluralized or interpolated in
+# your application.
+#
+# msg = I18n.t(:message, :default => 'Hi!', :scope => :foo)
+# msg.translation_metadata
+# # => { :key => :message, :scope => :foo, :default => 'Hi!' }
+#
+# If a :count option was passed to #translate it will be set to the metadata.
+# Likewise, if any interpolation variables were passed they will also be set.
+#
+# To enable translation metadata you can simply include the Metadata module
+# into the Simple backend class - or whatever other backend you are using:
+#
+# I18n::Backend::Simple.send(:include, I18n::Backend::Metadata)
+#
+module I18n
+ module Backend
+ module Metadata
+ class << self
+ def included(base)
+ Object.class_eval do
+ def translation_metadata
+ @translation_metadata ||= {}
+ end
+
+ def translation_metadata=(translation_metadata)
+ @translation_metadata = translation_metadata
+ end
+ end unless Object.method_defined?(:translation_metadata)
+ end
+ end
+
+ def translate(locale, key, options = {})
+ metadata = {
+ :locale => locale,
+ :key => key,
+ :scope => options[:scope],
+ :default => options[:default],
+ :separator => options[:separator],
+ :values => options.reject { |name, value| Base::RESERVED_KEYS.include?(name) }
+ }
+ with_metadata(metadata) { super }
+ end
+
+ def interpolate(locale, entry, values = {})
+ metadata = entry.translation_metadata.merge(:original => entry)
+ with_metadata(metadata) { super }
+ end
+
+ def pluralize(locale, entry, count)
+ with_metadata(:count => count) { super }
+ end
+
+ protected
+
+ def with_metadata(metadata, &block)
+ result = yield
+ result.translation_metadata = result.translation_metadata.merge(metadata) if result
+ result
+ end
+
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+
+# I18n locale fallbacks are useful when you want your application to use
+# translations from other locales when translations for the current locale are
+# missing. E.g. you might want to use :en translations when translations in
+# your applications main locale :de are missing.
+#
+# To enable locale specific pluralizations you can simply include the
+# Pluralization module to the Simple backend - or whatever other backend you
+# are using.
+#
+# I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
+#
+# You also need to make sure to provide pluralization algorithms to the
+# backend, i.e. include them to your I18n.load_path accordingly.
+module I18n
+ module Backend
+ module Pluralization
+ # Overwrites the Base backend translate method so that it will check the
+ # translation meta data space (:i18n) for a locale specific pluralization
+ # rule and use it to pluralize the given entry. I.e. the library expects
+ # pluralization rules to be stored at I18n.t(:'i18n.plural.rule')
+ #
+ # Pluralization rules are expected to respond to #call(entry, count) and
+ # return a pluralization key. Valid keys depend on the translation data
+ # hash (entry) but it is generally recommended to follow CLDR's style,
+ # i.e., return one of the keys :zero, :one, :few, :many, :other.
+ #
+ # The :zero key is always picked directly when count equals 0 AND the
+ # translation data has the key :zero. This way translators are free to
+ # either pick a special :zero translation even for languages where the
+ # pluralizer does not return a :zero key.
+ def pluralize(locale, entry, count)
+ return entry unless entry.is_a?(Hash) and count
+
+ pluralizer = pluralizer(locale)
+ if pluralizer.respond_to?(:call)
+ key = count == 0 && entry.has_key?(:zero) ? :zero : pluralizer.call(count)
+ raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
+ entry[key]
+ else
+ super
+ end
+ end
+
+ protected
+
+ def pluralizers
+ @pluralizers ||= {}
+ end
+
+ def pluralizer(locale)
+ pluralizers[locale] ||= I18n.t(:'i18n.plural.rule', :locale => locale, :resolve => false)
+ end
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+
+module I18n
+ module Backend
+ # A simple backend that reads translations from YAML files and stores them in
+ # an in-memory hash. Relies on the Base backend.
+ #
+ # The implementation is provided by a Implementation module allowing to easily
+ # extend Simple backend's behavior by including modules. E.g.:
+ #
+ # module I18n::Backend::Pluralization
+ # def pluralize(*args)
+ # # extended pluralization logic
+ # super
+ # end
+ # end
+ #
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
+ class Simple
+ module Implementation
+ include Base
+
+ def initialized?
+ @initialized ||= false
+ 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, options = {})
+ locale = locale.to_sym
+ translations[locale] ||= {}
+ data = data.deep_symbolize_keys
+ translations[locale].deep_merge!(data)
+ end
+
+ # Get available locales from the translations hash
+ def available_locales
+ init_translations unless initialized?
+ translations.inject([]) do |locales, (locale, data)|
+ locales << locale unless (data.keys - [:i18n]).empty?
+ locales
+ end
+ end
+
+ # Clean up translations hash and set initialized to false on reload!
+ def reload!
+ @initialized = false
+ @translations = nil
+ super
+ end
+
+ protected
+
+ def init_translations
+ load_translations
+ @initialized = true
+ end
+
+ 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 = [], options = {})
+ init_translations unless initialized?
+ keys = I18n.normalize_keys(locale, key, scope, options[:separator])
+
+ keys.inject(translations) do |result, _key|
+ _key = _key.to_sym
+ return nil unless result.is_a?(Hash) && result.has_key?(_key)
+ result = result[_key]
+ result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
+ result
+ end
+ end
+ end
+
+ include Implementation
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+module I18n
+ module Backend
+ module Transliterator
+ DEFAULT_REPLACEMENT_CHAR = "?"
+
+ # Given a locale and a UTF-8 string, return the locale's ASCII
+ # approximation for the string.
+ def transliterate(locale, string, replacement = nil)
+ @transliterators ||= {}
+ @transliterators[locale] ||= Transliterator.get I18n.t(:'i18n.transliterate.rule',
+ :locale => locale, :resolve => false, :default => {})
+ @transliterators[locale].transliterate(string, replacement)
+ end
+
+ # Get a transliterator instance.
+ def self.get(rule = nil)
+ if !rule || rule.kind_of?(Hash)
+ HashTransliterator.new(rule)
+ elsif rule.kind_of? Proc
+ ProcTransliterator.new(rule)
+ else
+ raise I18n::ArgumentError, "Transliteration rule must be a proc or a hash."
+ end
+ end
+
+ # A transliterator which accepts a Proc as its transliteration rule.
+ class ProcTransliterator
+ def initialize(rule)
+ @rule = rule
+ end
+
+ def transliterate(string, replacement = nil)
+ @rule.call(string)
+ end
+ end
+
+ # A transliterator which accepts a Hash of characters as its translation
+ # rule.
+ class HashTransliterator
+ DEFAULT_APPROXIMATIONS = {
+ "À"=>"A", "Á"=>"A", "Â"=>"A", "Ã"=>"A", "Ä"=>"A", "Å"=>"A", "Æ"=>"AE",
+ "Ç"=>"C", "È"=>"E", "É"=>"E", "Ê"=>"E", "Ë"=>"E", "Ì"=>"I", "Í"=>"I",
+ "Î"=>"I", "Ï"=>"I", "Ð"=>"D", "Ñ"=>"N", "Ò"=>"O", "Ó"=>"O", "Ô"=>"O",
+ "Õ"=>"O", "Ö"=>"O", "×"=>"x", "Ø"=>"O", "Ù"=>"U", "Ú"=>"U", "Û"=>"U",
+ "Ü"=>"U", "Ý"=>"Y", "Þ"=>"Th", "ß"=>"ss", "à"=>"a", "á"=>"a", "â"=>"a",
+ "ã"=>"a", "ä"=>"a", "å"=>"a", "æ"=>"ae", "ç"=>"c", "è"=>"e", "é"=>"e",
+ "ê"=>"e", "ë"=>"e", "ì"=>"i", "í"=>"i", "î"=>"i", "ï"=>"i", "ð"=>"d",
+ "ñ"=>"n", "ò"=>"o", "ó"=>"o", "ô"=>"o", "õ"=>"o", "ö"=>"o", "ø"=>"o",
+ "ù"=>"u", "ú"=>"u", "û"=>"u", "ü"=>"u", "ý"=>"y", "þ"=>"th", "ÿ"=>"y",
+ "Ā"=>"A", "ā"=>"a", "Ă"=>"A", "ă"=>"a", "Ą"=>"A", "ą"=>"a", "Ć"=>"C",
+ "ć"=>"c", "Ĉ"=>"C", "ĉ"=>"c", "Ċ"=>"C", "ċ"=>"c", "Č"=>"C", "č"=>"c",
+ "Ď"=>"D", "ď"=>"d", "Đ"=>"D", "đ"=>"d", "Ē"=>"E", "ē"=>"e", "Ĕ"=>"E",
+ "ĕ"=>"e", "Ė"=>"E", "ė"=>"e", "Ę"=>"E", "ę"=>"e", "Ě"=>"E", "ě"=>"e",
+ "Ĝ"=>"G", "ĝ"=>"g", "Ğ"=>"G", "ğ"=>"g", "Ġ"=>"G", "ġ"=>"g", "Ģ"=>"G",
+ "ģ"=>"g", "Ĥ"=>"H", "ĥ"=>"h", "Ħ"=>"H", "ħ"=>"h", "Ĩ"=>"I", "ĩ"=>"i",
+ "Ī"=>"I", "ī"=>"i", "Ĭ"=>"I", "ĭ"=>"i", "Į"=>"I", "į"=>"i", "İ"=>"I",
+ "ı"=>"i", "IJ"=>"IJ", "ij"=>"ij", "Ĵ"=>"J", "ĵ"=>"j", "Ķ"=>"K", "ķ"=>"k",
+ "ĸ"=>"k", "Ĺ"=>"L", "ĺ"=>"l", "Ļ"=>"L", "ļ"=>"l", "Ľ"=>"L", "ľ"=>"l",
+ "Ŀ"=>"L", "ŀ"=>"l", "Ł"=>"L", "ł"=>"l", "Ń"=>"N", "ń"=>"n", "Ņ"=>"N",
+ "ņ"=>"n", "Ň"=>"N", "ň"=>"n", "ʼn"=>"'n", "Ŋ"=>"NG", "ŋ"=>"ng",
+ "Ō"=>"O", "ō"=>"o", "Ŏ"=>"O", "ŏ"=>"o", "Ő"=>"O", "ő"=>"o", "Œ"=>"OE",
+ "œ"=>"oe", "Ŕ"=>"R", "ŕ"=>"r", "Ŗ"=>"R", "ŗ"=>"r", "Ř"=>"R", "ř"=>"r",
+ "Ś"=>"S", "ś"=>"s", "Ŝ"=>"S", "ŝ"=>"s", "Ş"=>"S", "ş"=>"s", "Š"=>"S",
+ "š"=>"s", "Ţ"=>"T", "ţ"=>"t", "Ť"=>"T", "ť"=>"t", "Ŧ"=>"T", "ŧ"=>"t",
+ "Ũ"=>"U", "ũ"=>"u", "Ū"=>"U", "ū"=>"u", "Ŭ"=>"U", "ŭ"=>"u", "Ů"=>"U",
+ "ů"=>"u", "Ű"=>"U", "ű"=>"u", "Ų"=>"U", "ų"=>"u", "Ŵ"=>"W", "ŵ"=>"w",
+ "Ŷ"=>"Y", "ŷ"=>"y", "Ÿ"=>"Y", "Ź"=>"Z", "ź"=>"z", "Ż"=>"Z", "ż"=>"z",
+ "Ž"=>"Z", "ž"=>"z"
+ }
+
+ def initialize(rule = nil)
+ @rule = rule
+ add DEFAULT_APPROXIMATIONS
+ add rule if rule
+ end
+
+ def transliterate(string, replacement = nil)
+ string.gsub(/[^\x00-\x7f]/u) do |char|
+ approximations[char] || replacement || DEFAULT_REPLACEMENT_CHAR
+ end
+ end
+
+ private
+
+ def approximations
+ @approximations ||= {}
+ end
+
+ # Add transliteration rules to the approximations hash.
+ def add(hash)
+ hash.keys.each {|key| hash[key.to_s] = hash.delete(key).to_s}
+ approximations.merge! hash
+ end
+ end
+ end
+ end
+end
--- /dev/null
+module I18n
+ class Config
+ # The only configuration value that is not global and scoped to thread is :locale.
+ # It defaults to the default_locale.
+ def locale
+ @locale ||= default_locale
+ end
+
+ # Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
+ def locale=(locale)
+ @locale = locale.to_sym rescue nil
+ end
+
+ # 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'
+ def default_locale
+ @@default_locale ||= :en
+ end
+
+ # Sets the current default locale. Used to set a custom default locale.
+ def default_locale=(locale)
+ @@default_locale = locale.to_sym rescue nil
+ end
+
+ # Returns an array of locales for which translations are available.
+ # Unless you explicitely set these through I18n.available_locales=
+ # the call will be delegated to the backend.
+ def available_locales
+ @@available_locales ||= nil
+ @@available_locales || backend.available_locales
+ end
+
+ # Sets the available locales.
+ def available_locales=(locales)
+ @@available_locales = Array(locales).map {|locale| locale.to_sym}
+ @@available_locales = nil if @@available_locales.empty?
+ end
+
+ # Returns the current default scope separator. Defaults to '.'
+ def default_separator
+ @@default_separator ||= '.'
+ end
+
+ # Sets the current default scope separator.
+ def default_separator=(separator)
+ @@default_separator = separator
+ end
+
+ # Return the current exception handler. Defaults to :default_exception_handler.
+ def exception_handler
+ @@exception_handler ||= :default_exception_handler
+ end
+
+ # Sets the exception handler.
+ def exception_handler=(exception_handler)
+ @@exception_handler = exception_handler
+ end
+
+ # Allow clients to register paths providing translation data sources. 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. So for the SimpleBackend clients may
+ # register translation files like this:
+ # I18n.load_path << 'path/to/locale/en.yml'
+ def load_path
+ @@load_path ||= []
+ end
+
+ # Sets the load path instance. Custom implementations are expected to
+ # behave like a Ruby Array.
+ def load_path=(load_path)
+ @@load_path = load_path
+ end
+ end
+end
\ No newline at end of file
--- /dev/null
+class Hash
+ def slice(*keep_keys)
+ h = {}
+ keep_keys.each { |key| h[key] = fetch(key) }
+ h
+ end unless Hash.method_defined?(:slice)
+
+ def except(*less_keys)
+ slice(*keys - less_keys)
+ end unless Hash.method_defined?(:except)
+
+ def deep_symbolize_keys
+ inject({}) { |result, (key, value)|
+ value = value.deep_symbolize_keys if value.is_a?(Hash)
+ result[(key.to_sym rescue key) || key] = value
+ result
+ }
+ end unless Hash.method_defined?(:deep_symbolize_keys)
+
+ # deep_merge_hash! by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
+ MERGER = proc do |key, v1, v2|
+ Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2
+ end
+
+ def deep_merge!(data)
+ merge!(data, &MERGER)
+ end unless Hash.method_defined?(:deep_merge!)
+end
+
--- /dev/null
+# encoding: utf-8
+
+=begin
+ heavily based on Masao Mutoh's gettext String interpolation extension
+ http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb
+ Copyright (C) 2005-2009 Masao Mutoh
+ You may redistribute it and/or modify it under the same license terms as Ruby.
+=end
+
+begin
+ raise ArgumentError if ("a %{x}" % {:x=>'b'}) != 'a b'
+rescue ArgumentError
+ # KeyError is raised by String#% when the string contains a named placeholder
+ # that is not contained in the given arguments hash. Ruby 1.9 includes and
+ # raises this exception natively. We define it to mimic Ruby 1.9's behaviour
+ # in Ruby 1.8.x
+ class KeyError < IndexError
+ def initialize(message = nil)
+ super(message || "key not found")
+ end
+ end unless defined?(KeyError)
+
+ # Extension for String class. This feature is included in Ruby 1.9 or later but not occur TypeError.
+ #
+ # String#% method which accept "named argument". The translator can know
+ # the meaning of the msgids using "named argument" instead of %s/%d style.
+ class String
+ # For older ruby versions, such as ruby-1.8.5
+ alias :bytesize :size unless instance_methods.find {|m| m.to_s == 'bytesize'}
+ alias :interpolate_without_ruby_19_syntax :% # :nodoc:
+
+ INTERPOLATION_PATTERN = Regexp.union(
+ /%\{(\w+)\}/, # matches placeholders like "%{foo}"
+ /%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
+ )
+
+ INTERPOLATION_PATTERN_WITH_ESCAPE = Regexp.union(
+ /%%/,
+ INTERPOLATION_PATTERN
+ )
+
+ # % uses self (i.e. the String) as a format specification and returns the
+ # result of applying it to the given arguments. In other words it interpolates
+ # the given arguments to the string according to the formats the string
+ # defines.
+ #
+ # There are three ways to use it:
+ #
+ # * Using a single argument or Array of arguments.
+ #
+ # This is the default behaviour of the String class. See Kernel#sprintf for
+ # more details about the format string.
+ #
+ # Example:
+ #
+ # "%d %s" % [1, "message"]
+ # # => "1 message"
+ #
+ # * Using a Hash as an argument and unformatted, named placeholders.
+ #
+ # When you pass a Hash as an argument and specify placeholders with %{foo}
+ # it will interpret the hash values as named arguments.
+ #
+ # Example:
+ #
+ # "%{firstname}, %{lastname}" % {:firstname => "Masao", :lastname => "Mutoh"}
+ # # => "Masao Mutoh"
+ #
+ # * Using a Hash as an argument and formatted, named placeholders.
+ #
+ # When you pass a Hash as an argument and specify placeholders with %<foo>d
+ # it will interpret the hash values as named arguments and format the value
+ # according to the formatting instruction appended to the closing >.
+ #
+ # Example:
+ #
+ # "%<integer>d, %<float>.1f" % { :integer => 10, :float => 43.4 }
+ # # => "10, 43.3"
+ def %(args)
+ if args.kind_of?(Hash)
+ dup.gsub(INTERPOLATION_PATTERN_WITH_ESCAPE) do |match|
+ if match == '%%'
+ '%'
+ else
+ key = ($1 || $2).to_sym
+ raise KeyError unless args.has_key?(key)
+ $3 ? sprintf("%#{$3}", args[key]) : args[key]
+ end
+ end
+ elsif self =~ INTERPOLATION_PATTERN
+ raise ArgumentError.new('one hash required')
+ else
+ result = gsub(/%([{<])/, '%%\1')
+ result.send :'interpolate_without_ruby_19_syntax', args
+ end
+ end
+ end
+end
\ No newline at end of file
--- /dev/null
+# encoding: utf-8
+
+class KeyError < IndexError
+ def initialize(message = nil)
+ super(message || "key not found")
+ end
+end unless defined?(KeyError)
+
+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 InvalidLocaleData < ArgumentError
+ attr_reader :filename
+ def initialize(filename)
+ @filename = filename
+ super "can not load translations from #{filename}, expected it to return a hash, but does not"
+ end
+ end
+
+ class MissingTranslationData < ArgumentError
+ attr_reader :locale, :key, :options
+ def initialize(locale, key, opts = nil)
+ @key, @locale, @options = key, locale, opts.dup || {}
+ options.each { |k, v| options[k] = v.inspect if v.is_a?(Proc) }
+
+ keys = I18n.normalize_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 :values, :string
+ def initialize(values, string)
+ @values, @string = values, string
+ super "missing interpolation argument in #{string.inspect} (#{values.inspect} given)"
+ 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
--- /dev/null
+# encoding: utf-8
+
+module I18n
+ module Gettext
+ PLURAL_SEPARATOR = "\001"
+ CONTEXT_SEPARATOR = "\004"
+
+ autoload :Helpers, 'i18n/gettext/helpers'
+
+ @@plural_keys = { :en => [:one, :other] }
+
+ class << self
+ # returns an array of plural keys for the given locale so that we can
+ # convert from gettext's integer-index based style
+ # TODO move this information to the pluralization module
+ def plural_keys(locale)
+ @@plural_keys[locale] || @@plural_keys[:en]
+ end
+
+ def extract_scope(msgid, separator)
+ scope = msgid.to_s.split(separator)
+ msgid = scope.pop
+ [scope, msgid]
+ end
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+require 'i18n/gettext'
+
+module I18n
+ module Gettext
+ # Implements classical Gettext style accessors. To use this include the
+ # module to the global namespace or wherever you want to use it.
+ #
+ # include I18n::Gettext::Helpers
+ module Helpers
+ def gettext(msgid, options = {})
+ I18n.t(msgid, { :default => msgid, :separator => '|' }.merge(options))
+ end
+ alias _ gettext
+
+ def sgettext(msgid, separator = '|')
+ scope, msgid = I18n::Gettext.extract_scope(msgid, separator)
+ I18n.t(msgid, :scope => scope, :default => msgid, :separator => separator)
+ end
+ alias s_ sgettext
+
+ def pgettext(msgctxt, msgid)
+ separator = I18n::Gettext::CONTEXT_SEPARATOR
+ sgettext([msgctxt, msgid].join(separator), separator)
+ end
+ alias p_ pgettext
+
+ def ngettext(msgid, msgid_plural, n = 1)
+ nsgettext(msgid, msgid_plural, n)
+ end
+ alias n_ ngettext
+
+ # Method signatures:
+ # nsgettext('Fruits|apple', 'apples', 2)
+ # nsgettext(['Fruits|apple', 'apples'], 2)
+ def nsgettext(msgid, msgid_plural, n = 1, separator = '|')
+ if msgid.is_a?(Array)
+ msgid, msgid_plural, n, separator = msgid[0], msgid[1], msgid_plural, n
+ separator = '|' unless separator.is_a?(::String)
+ end
+
+ scope, msgid = I18n::Gettext.extract_scope(msgid, separator)
+ default = { :one => msgid, :other => msgid_plural }
+ I18n.t(msgid, :default => default, :count => n, :scope => scope, :separator => separator)
+ end
+ alias ns_ nsgettext
+
+ # Method signatures:
+ # npgettext('Fruits', 'apple', 'apples', 2)
+ # npgettext('Fruits', ['apple', 'apples'], 2)
+ def npgettext(msgctxt, msgid, msgid_plural, n = 1)
+ separator = I18n::Gettext::CONTEXT_SEPARATOR
+
+ if msgid.is_a?(Array)
+ msgid_plural, msgid, n = msgid[1], [msgctxt, msgid[0]].join(separator), msgid_plural
+ else
+ msgid = [msgctxt, msgid].join(separator)
+ end
+
+ nsgettext(msgid, msgid_plural, n, separator)
+ end
+ alias np_ npgettext
+ end
+ end
+end
--- /dev/null
+=begin
+ poparser.rb - Generate a .mo
+
+ Copyright (C) 2003-2009 Masao Mutoh <mutoh at highway.ne.jp>
+
+ You may redistribute it and/or modify it under the same
+ license terms as Ruby.
+=end
+
+#MODIFIED
+# removed include GetText etc
+# added stub translation method _(x)
+require 'racc/parser'
+
+module GetText
+
+ class PoParser < Racc::Parser
+
+ def _(x)
+ x
+ end
+
+module_eval <<'..end src/poparser.ry modeval..id7a99570e05', 'src/poparser.ry', 108
+ def unescape(orig)
+ ret = orig.gsub(/\\n/, "\n")
+ ret.gsub!(/\\t/, "\t")
+ ret.gsub!(/\\r/, "\r")
+ ret.gsub!(/\\"/, "\"")
+ ret
+ end
+
+ def parse(str, data, ignore_fuzzy = true)
+ @comments = []
+ @data = data
+ @fuzzy = false
+ @msgctxt = ""
+ $ignore_fuzzy = ignore_fuzzy
+
+ str.strip!
+ @q = []
+ until str.empty? do
+ case str
+ when /\A\s+/
+ str = $'
+ when /\Amsgctxt/
+ @q.push [:MSGCTXT, $&]
+ str = $'
+ when /\Amsgid_plural/
+ @q.push [:MSGID_PLURAL, $&]
+ str = $'
+ when /\Amsgid/
+ @q.push [:MSGID, $&]
+ str = $'
+ when /\Amsgstr/
+ @q.push [:MSGSTR, $&]
+ str = $'
+ when /\A\[(\d+)\]/
+ @q.push [:PLURAL_NUM, $1]
+ str = $'
+ when /\A\#~(.*)/
+ $stderr.print _("Warning: obsolete msgid exists.\n")
+ $stderr.print " #{$&}\n"
+ @q.push [:COMMENT, $&]
+ str = $'
+ when /\A\#(.*)/
+ @q.push [:COMMENT, $&]
+ str = $'
+ when /\A\"(.*)\"/
+ @q.push [:STRING, $1]
+ str = $'
+ else
+ #c = str[0,1]
+ #@q.push [:STRING, c]
+ str = str[1..-1]
+ end
+ end
+ @q.push [false, '$end']
+ if $DEBUG
+ @q.each do |a,b|
+ puts "[#{a}, #{b}]"
+ end
+ end
+ @yydebug = true if $DEBUG
+ do_parse
+
+ if @comments.size > 0
+ @data.set_comment(:last, @comments.join("\n"))
+ end
+ @data
+ end
+
+ def next_token
+ @q.shift
+ end
+
+ def on_message(msgid, msgstr)
+ if msgstr.size > 0
+ @data[msgid] = msgstr
+ @data.set_comment(msgid, @comments.join("\n"))
+ end
+ @comments.clear
+ @msgctxt = ""
+ end
+
+ def on_comment(comment)
+ @fuzzy = true if (/fuzzy/ =~ comment)
+ @comments << comment
+ end
+
+
+..end src/poparser.ry modeval..id7a99570e05
+
+##### racc 1.4.5 generates ###
+
+racc_reduce_table = [
+ 0, 0, :racc_error,
+ 0, 10, :_reduce_none,
+ 2, 10, :_reduce_none,
+ 2, 10, :_reduce_none,
+ 2, 10, :_reduce_none,
+ 2, 12, :_reduce_5,
+ 1, 13, :_reduce_none,
+ 1, 13, :_reduce_none,
+ 4, 15, :_reduce_8,
+ 5, 16, :_reduce_9,
+ 2, 17, :_reduce_10,
+ 1, 17, :_reduce_none,
+ 3, 18, :_reduce_12,
+ 1, 11, :_reduce_13,
+ 2, 14, :_reduce_14,
+ 1, 14, :_reduce_15 ]
+
+racc_reduce_n = 16
+
+racc_shift_n = 26
+
+racc_action_table = [
+ 3, 13, 5, 7, 9, 15, 16, 17, 20, 17,
+ 13, 17, 13, 13, 11, 17, 23, 20, 13, 17 ]
+
+racc_action_check = [
+ 1, 16, 1, 1, 1, 12, 12, 12, 18, 18,
+ 7, 14, 15, 9, 3, 19, 20, 21, 23, 25 ]
+
+racc_action_pointer = [
+ nil, 0, nil, 14, nil, nil, nil, 3, nil, 6,
+ nil, nil, 0, nil, 4, 5, -6, nil, 2, 8,
+ 8, 11, nil, 11, nil, 12 ]
+
+racc_action_default = [
+ -1, -16, -2, -16, -3, -13, -4, -16, -6, -16,
+ -7, 26, -16, -15, -5, -16, -16, -14, -16, -8,
+ -16, -9, -11, -16, -10, -12 ]
+
+racc_goto_table = [
+ 12, 22, 14, 4, 24, 6, 2, 8, 18, 19,
+ 10, 21, 1, nil, nil, nil, 25 ]
+
+racc_goto_check = [
+ 5, 9, 5, 3, 9, 4, 2, 6, 5, 5,
+ 7, 8, 1, nil, nil, nil, 5 ]
+
+racc_goto_pointer = [
+ nil, 12, 5, 2, 4, -7, 6, 9, -7, -17 ]
+
+racc_goto_default = [
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil ]
+
+racc_token_table = {
+ false => 0,
+ Object.new => 1,
+ :COMMENT => 2,
+ :MSGID => 3,
+ :MSGCTXT => 4,
+ :MSGID_PLURAL => 5,
+ :MSGSTR => 6,
+ :STRING => 7,
+ :PLURAL_NUM => 8 }
+
+racc_use_result_var = true
+
+racc_nt_base = 9
+
+Racc_arg = [
+ racc_action_table,
+ racc_action_check,
+ racc_action_default,
+ racc_action_pointer,
+ racc_goto_table,
+ racc_goto_check,
+ racc_goto_default,
+ racc_goto_pointer,
+ racc_nt_base,
+ racc_reduce_table,
+ racc_token_table,
+ racc_shift_n,
+ racc_reduce_n,
+ racc_use_result_var ]
+
+Racc_token_to_s_table = [
+'$end',
+'error',
+'COMMENT',
+'MSGID',
+'MSGCTXT',
+'MSGID_PLURAL',
+'MSGSTR',
+'STRING',
+'PLURAL_NUM',
+'$start',
+'msgfmt',
+'comment',
+'msgctxt',
+'message',
+'string_list',
+'single_message',
+'plural_message',
+'msgstr_plural',
+'msgstr_plural_line']
+
+Racc_debug_parser = true
+
+##### racc system variables end #####
+
+ # reduce 0 omitted
+
+ # reduce 1 omitted
+
+ # reduce 2 omitted
+
+ # reduce 3 omitted
+
+ # reduce 4 omitted
+
+module_eval <<'.,.,', 'src/poparser.ry', 25
+ def _reduce_5( val, _values, result )
+ @msgctxt = unescape(val[1]) + "\004"
+ result
+ end
+.,.,
+
+ # reduce 6 omitted
+
+ # reduce 7 omitted
+
+module_eval <<'.,.,', 'src/poparser.ry', 48
+ def _reduce_8( val, _values, result )
+ if @fuzzy and $ignore_fuzzy
+ if val[1] != ""
+ $stderr.print _("Warning: fuzzy message was ignored.\n")
+ $stderr.print " msgid '#{val[1]}'\n"
+ else
+ on_message('', unescape(val[3]))
+ end
+ @fuzzy = false
+ else
+ on_message(@msgctxt + unescape(val[1]), unescape(val[3]))
+ end
+ result = ""
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'src/poparser.ry', 65
+ def _reduce_9( val, _values, result )
+ if @fuzzy and $ignore_fuzzy
+ if val[1] != ""
+ $stderr.print _("Warning: fuzzy message was ignored.\n")
+ $stderr.print "msgid = '#{val[1]}\n"
+ else
+ on_message('', unescape(val[3]))
+ end
+ @fuzzy = false
+ else
+ on_message(@msgctxt + unescape(val[1]) + "\000" + unescape(val[3]), unescape(val[4]))
+ end
+ result = ""
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'src/poparser.ry', 76
+ def _reduce_10( val, _values, result )
+ if val[0].size > 0
+ result = val[0] + "\000" + val[1]
+ else
+ result = ""
+ end
+ result
+ end
+.,.,
+
+ # reduce 11 omitted
+
+module_eval <<'.,.,', 'src/poparser.ry', 84
+ def _reduce_12( val, _values, result )
+ result = val[2]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'src/poparser.ry', 91
+ def _reduce_13( val, _values, result )
+ on_comment(val[0])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'src/poparser.ry', 99
+ def _reduce_14( val, _values, result )
+ result = val.delete_if{|item| item == ""}.join
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'src/poparser.ry', 103
+ def _reduce_15( val, _values, result )
+ result = val[0]
+ result
+ end
+.,.,
+
+ def _reduce_none( val, _values, result )
+ result
+ end
+
+ end # class PoParser
+
+end # module GetText
--- /dev/null
+module I18n
+ module Locale
+ autoload :Fallbacks, 'i18n/locale/fallbacks'
+ autoload :Tag, 'i18n/locale/tag'
+ end
+end
--- /dev/null
+# encoding: utf-8
+
+# Locale Fallbacks
+#
+# Extends the I18n module to hold a fallbacks instance which is set to an
+# instance of I18n::Locale::Fallbacks by default but can be swapped with a
+# different implementation.
+#
+# Locale fallbacks will compute a number of fallback locales for a given locale.
+# For example:
+#
+# <pre><code>
+# I18n.fallbacks[:"es-MX"] # => [:"es-MX", :es, :en] </code></pre>
+#
+# Locale fallbacks always fall back to
+#
+# * all parent locales of a given locale (e.g. :es for :"es-MX") first,
+# * the current default locales and all of their parents second
+#
+# The default locales are set to [I18n.default_locale] by default but can be
+# set to something else.
+#
+# One can additionally add any number of additional fallback locales manually.
+# These will be added before the default locales to the fallback chain. For
+# example:
+#
+# # using the default locale as default fallback locale
+#
+# I18n.default_locale = :"en-US"
+# I18n.fallbacks = I18n::Fallbacks.new(:"de-AT" => :"de-DE")
+# I18n.fallbacks[:"de-AT"] # => [:"de-AT", :"de-DE", :de, :"en-US", :en]
+#
+# # using a custom locale as default fallback locale
+#
+# I18n.fallbacks = I18n::Fallbacks.new(:"en-GB", :"de-AT" => :de, :"de-CH" => :de)
+# I18n.fallbacks[:"de-AT"] # => [:"de-AT", :de, :"en-GB", :en]
+# I18n.fallbacks[:"de-CH"] # => [:"de-CH", :de, :"en-GB", :en]
+#
+# # mapping fallbacks to an existing instance
+#
+# # people speaking Catalan also speak Spanish as spoken in Spain
+# fallbacks = I18n.fallbacks
+# fallbacks.map(:ca => :"es-ES")
+# fallbacks[:ca] # => [:ca, :"es-ES", :es, :"en-US", :en]
+#
+# # people speaking Arabian as spoken in Palestine also speak Hebrew as spoken in Israel
+# fallbacks.map(:"ar-PS" => :"he-IL")
+# fallbacks[:"ar-PS"] # => [:"ar-PS", :ar, :"he-IL", :he, :"en-US", :en]
+# fallbacks[:"ar-EG"] # => [:"ar-EG", :ar, :"en-US", :en]
+#
+# # people speaking Sami as spoken in Finnland also speak Swedish and Finnish as spoken in Finnland
+# fallbacks.map(:sms => [:"se-FI", :"fi-FI"])
+# fallbacks[:sms] # => [:sms, :"se-FI", :se, :"fi-FI", :fi, :"en-US", :en]
+
+module I18n
+ module Locale
+ class Fallbacks < Hash
+ def initialize(*mappings)
+ @map = {}
+ map(mappings.pop) if mappings.last.is_a?(Hash)
+ self.defaults = mappings.empty? ? [I18n.default_locale.to_sym] : mappings
+ end
+
+ def defaults=(defaults)
+ @defaults = defaults.map { |default| compute(default, false) }.flatten
+ end
+ attr_reader :defaults
+
+ def [](locale)
+ raise InvalidLocale.new(locale) if locale.nil?
+ locale = locale.to_sym
+ super || store(locale, compute(locale))
+ end
+
+ def map(mappings)
+ mappings.each do |from, to|
+ from, to = from.to_sym, Array(to)
+ to.each do |to|
+ @map[from] ||= []
+ @map[from] << to.to_sym
+ end
+ end
+ end
+
+ protected
+
+ def compute(tags, include_defaults = true)
+ result = Array(tags).collect do |tag|
+ tags = I18n::Locale::Tag.tag(tag).self_and_parents.map! { |t| t.to_sym }
+ tags.each { |tag| tags += compute(@map[tag]) if @map[tag] }
+ tags
+ end.flatten
+ result.push(*defaults) if include_defaults
+ result.uniq
+ end
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+
+module I18n
+ module Locale
+ module Tag
+ autoload :Parents, 'i18n/locale/tag/parents'
+ autoload :Rfc4646, 'i18n/locale/tag/rfc4646'
+ autoload :Simple, 'i18n/locale/tag/simple'
+
+ class << self
+ # Returns the current locale tag implementation. Defaults to +I18n::Locale::Tag::Simple+.
+ def implementation
+ @@implementation ||= Simple
+ end
+
+ # Sets the current locale tag implementation. Use this to set a different locale tag implementation.
+ def implementation=(implementation)
+ @@implementation = implementation
+ end
+
+ # Factory method for locale tags. Delegates to the current locale tag implementation.
+ def tag(tag)
+ implementation.tag(tag)
+ end
+ end
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+
+module I18n
+ module Locale
+ module Tag
+ module Parents
+ def parent
+ @parent ||= begin
+ segs = to_a.compact
+ segs.length > 1 ? self.class.tag(*segs[0..(segs.length-2)].join('-')) : nil
+ end
+ end
+
+ def self_and_parents
+ @self_and_parents ||= [self] + parents
+ end
+
+ def parents
+ @parents ||= ([parent] + (parent ? parent.parents : [])).compact
+ end
+ end
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+
+# RFC 4646/47 compliant Locale tag implementation that parses locale tags to
+# subtags such as language, script, region, variant etc.
+#
+# For more information see by http://en.wikipedia.org/wiki/IETF_language_tag
+#
+# Rfc4646::Parser does not implement grandfathered tags.
+
+module I18n
+ module Locale
+ module Tag
+ RFC4646_SUBTAGS = [ :language, :script, :region, :variant, :extension, :privateuse, :grandfathered ]
+ RFC4646_FORMATS = { :language => :downcase, :script => :capitalize, :region => :upcase, :variant => :downcase }
+
+ class Rfc4646 < Struct.new(*RFC4646_SUBTAGS)
+ class << self
+ # Parses the given tag and returns a Tag instance if it is valid.
+ # Returns false if the given tag is not valid according to RFC 4646.
+ def tag(tag)
+ matches = parser.match(tag)
+ new(*matches) if matches
+ end
+
+ def parser
+ @@parser ||= Rfc4646::Parser
+ end
+
+ def parser=(parser)
+ @@parser = parser
+ end
+ end
+
+ include Parents
+
+ RFC4646_FORMATS.each do |name, format|
+ define_method(name) { self[name].send(format) unless self[name].nil? }
+ end
+
+ def to_sym
+ to_s.to_sym
+ end
+
+ def to_s
+ @tag ||= to_a.compact.join("-")
+ end
+
+ def to_a
+ members.collect { |attr| self.send(attr) }
+ end
+
+ module Parser
+ PATTERN = %r{\A(?:
+ ([a-z]{2,3}(?:(?:-[a-z]{3}){0,3})?|[a-z]{4}|[a-z]{5,8}) # language
+ (?:-([a-z]{4}))? # script
+ (?:-([a-z]{2}|\d{3}))? # region
+ (?:-([0-9a-z]{5,8}|\d[0-9a-z]{3}))* # variant
+ (?:-([0-9a-wyz](?:-[0-9a-z]{2,8})+))* # extension
+ (?:-(x(?:-[0-9a-z]{1,8})+))?| # privateuse subtag
+ (x(?:-[0-9a-z]{1,8})+)| # privateuse tag
+ /* ([a-z]{1,3}(?:-[0-9a-z]{2,8}){1,2}) */ # grandfathered
+ )\z}xi
+
+ class << self
+ def match(tag)
+ c = PATTERN.match(tag.to_s).captures
+ c[0..4] << (c[5].nil? ? c[6] : c[5]) << c[7] # TODO c[7] is grandfathered, throw a NotImplemented exception here?
+ rescue
+ false
+ end
+ end
+ end
+ end
+ end
+ end
+end
--- /dev/null
+# encoding: utf-8
+
+# Simple Locale tag implementation that computes subtags by simply splitting
+# the locale tag at '-' occurences.
+module I18n
+ module Locale
+ module Tag
+ class Simple
+ class << self
+ def tag(tag)
+ new(tag)
+ end
+ end
+
+ include Parents
+
+ attr_reader :tag
+
+ def initialize(*tag)
+ @tag = tag.join('-').to_sym
+ end
+
+ def subtags
+ @subtags = tag.to_s.split('-').map { |subtag| subtag.to_s }
+ end
+
+ def to_sym
+ tag
+ end
+
+ def to_s
+ tag.to_s
+ end
+
+ def to_a
+ subtags
+ end
+ end
+ end
+ end
+end
--- /dev/null
+module I18n
+ VERSION = "0.4.2"
+end