i18n 0.4.0 → 1.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/MIT-LICENSE +0 -0
 - data/README.md +127 -0
 - data/lib/i18n/backend/base.rb +189 -111
 - data/lib/i18n/backend/cache.rb +58 -22
 - data/lib/i18n/backend/cache_file.rb +36 -0
 - data/lib/i18n/backend/cascade.rb +9 -10
 - data/lib/i18n/backend/chain.rb +95 -42
 - data/lib/i18n/backend/fallbacks.rb +68 -22
 - data/lib/i18n/backend/flatten.rb +13 -8
 - data/lib/i18n/backend/gettext.rb +33 -25
 - data/lib/i18n/backend/interpolation_compiler.rb +12 -14
 - data/lib/i18n/backend/key_value.rb +112 -10
 - data/lib/i18n/backend/lazy_loadable.rb +184 -0
 - data/lib/i18n/backend/memoize.rb +13 -7
 - data/lib/i18n/backend/metadata.rb +12 -6
 - data/lib/i18n/backend/pluralization.rb +64 -25
 - data/lib/i18n/backend/simple.rb +44 -18
 - data/lib/i18n/backend/transliterator.rb +43 -33
 - data/lib/i18n/backend.rb +5 -3
 - data/lib/i18n/config.rb +91 -10
 - data/lib/i18n/exceptions.rb +118 -22
 - data/lib/i18n/gettext/helpers.rb +14 -4
 - data/lib/i18n/gettext/po_parser.rb +7 -7
 - data/lib/i18n/gettext.rb +6 -5
 - data/lib/i18n/interpolate/ruby.rb +53 -0
 - data/lib/i18n/locale/fallbacks.rb +33 -26
 - data/lib/i18n/locale/tag/parents.rb +8 -8
 - data/lib/i18n/locale/tag/rfc4646.rb +0 -2
 - data/lib/i18n/locale/tag/simple.rb +2 -4
 - data/lib/i18n/locale.rb +2 -0
 - data/lib/i18n/middleware.rb +17 -0
 - data/lib/i18n/tests/basics.rb +58 -0
 - data/lib/i18n/tests/defaults.rb +52 -0
 - data/lib/i18n/tests/interpolation.rb +167 -0
 - data/lib/i18n/tests/link.rb +66 -0
 - data/lib/i18n/tests/localization/date.rb +122 -0
 - data/lib/i18n/tests/localization/date_time.rb +103 -0
 - data/lib/i18n/tests/localization/procs.rb +118 -0
 - data/lib/i18n/tests/localization/time.rb +103 -0
 - data/lib/i18n/tests/localization.rb +19 -0
 - data/lib/i18n/tests/lookup.rb +81 -0
 - data/lib/i18n/tests/pluralization.rb +35 -0
 - data/lib/i18n/tests/procs.rb +66 -0
 - data/lib/i18n/tests.rb +14 -0
 - data/lib/i18n/utils.rb +55 -0
 - data/lib/i18n/version.rb +3 -1
 - data/lib/i18n.rb +200 -87
 - metadata +64 -56
 - data/CHANGELOG.textile +0 -135
 - data/README.textile +0 -93
 - data/lib/i18n/backend/active_record/missing.rb +0 -65
 - data/lib/i18n/backend/active_record/store_procs.rb +0 -38
 - data/lib/i18n/backend/active_record/translation.rb +0 -93
 - data/lib/i18n/backend/active_record.rb +0 -61
 - data/lib/i18n/backend/cldr.rb +0 -100
 - data/lib/i18n/core_ext/hash.rb +0 -29
 - data/lib/i18n/core_ext/string/interpolate.rb +0 -98
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA256:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: ef99abbb2ed698cd9dd77ab484720a5ecf68be96a7064f2f8b432a69758ada07
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 0a4ade5577fe636c03d6ef9ca11f7a26b80a5c58078ee87c2f9dcf9fdd6882de
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 21b666c06e7c8ebdb78088ef2ecd8da866ebd414dff3f3a8535b3e950c12bf0f38695a22acac6da7775e8e245c7a99a0d1d9a6bc409e0ea3d552520a0be26f68
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: c5f81a0bafbf70daa4a2c4374f3906ef31cea1d8f94d5d5e9b980224d1b6bc75f95cdd3e08709f6cae410c34f4285deb375d7e5b06e19f283743221606e23434
         
     | 
    
        data/MIT-LICENSE
    CHANGED
    
    | 
         
            File without changes
         
     | 
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,127 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Ruby I18n
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            [](https://badge.fury.io/rb/i18n)
         
     | 
| 
      
 4 
     | 
    
         
            +
            [](https://github.com/ruby-i18n/i18n/actions?query=workflow%3ARuby)
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            Ruby internationalization and localization (i18n) solution.
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            Currently maintained by @radar.
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            ## Usage
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            ### Rails
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            You will most commonly use this library within a Rails app.
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            We support Rails versions from 6.0 and up.
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            [See the Rails Guide](https://guides.rubyonrails.org/i18n.html) for an example of its usage.
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            ### Ruby (without Rails)
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            We support Ruby versions from 3.0 and up.
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            If you want to use this library without Rails, you can simply add `i18n` to your `Gemfile`:
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 27 
     | 
    
         
            +
            gem 'i18n'
         
     | 
| 
      
 28 
     | 
    
         
            +
            ```
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            Then configure I18n with some translations, and a default locale:
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 33 
     | 
    
         
            +
            I18n.load_path += Dir[File.expand_path("config/locales") + "/*.yml"]
         
     | 
| 
      
 34 
     | 
    
         
            +
            I18n.default_locale = :en # (note that `en` is already the default!)
         
     | 
| 
      
 35 
     | 
    
         
            +
            ```
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            A simple translation file in your project might live at `config/locales/en.yml` and look like:
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            ```yml
         
     | 
| 
      
 40 
     | 
    
         
            +
            en:
         
     | 
| 
      
 41 
     | 
    
         
            +
              test: "This is a test"
         
     | 
| 
      
 42 
     | 
    
         
            +
            ```
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            You can then access this translation by doing:
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 47 
     | 
    
         
            +
            I18n.t(:test)
         
     | 
| 
      
 48 
     | 
    
         
            +
            ```
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            You can switch locales in your project by setting `I18n.locale` to a different value:
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 53 
     | 
    
         
            +
            I18n.locale = :de
         
     | 
| 
      
 54 
     | 
    
         
            +
            I18n.t(:test) # => "Dies ist ein Test"
         
     | 
| 
      
 55 
     | 
    
         
            +
            ```
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
            ## Features
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
            * Translation and localization
         
     | 
| 
      
 60 
     | 
    
         
            +
            * Interpolation of values to translations
         
     | 
| 
      
 61 
     | 
    
         
            +
            * Pluralization (CLDR compatible)
         
     | 
| 
      
 62 
     | 
    
         
            +
            * Customizable transliteration to ASCII
         
     | 
| 
      
 63 
     | 
    
         
            +
            * Flexible defaults
         
     | 
| 
      
 64 
     | 
    
         
            +
            * Bulk lookup
         
     | 
| 
      
 65 
     | 
    
         
            +
            * Lambdas as translation data
         
     | 
| 
      
 66 
     | 
    
         
            +
            * Custom key/scope separator
         
     | 
| 
      
 67 
     | 
    
         
            +
            * Custom exception handlers
         
     | 
| 
      
 68 
     | 
    
         
            +
            * Extensible architecture with a swappable backend
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
            ## Pluggable Features
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
            * Cache
         
     | 
| 
      
 73 
     | 
    
         
            +
            * Pluralization: lambda pluralizers stored as translation data
         
     | 
| 
      
 74 
     | 
    
         
            +
            * Locale fallbacks, RFC4647 compliant (optionally: RFC4646 locale validation)
         
     | 
| 
      
 75 
     | 
    
         
            +
            * [Gettext support](https://github.com/ruby-i18n/i18n/wiki/Gettext)
         
     | 
| 
      
 76 
     | 
    
         
            +
            * Translation metadata
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
            ## Alternative Backend
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
            * Chain
         
     | 
| 
      
 81 
     | 
    
         
            +
            * ActiveRecord (optionally: ActiveRecord::Missing and ActiveRecord::StoreProcs)
         
     | 
| 
      
 82 
     | 
    
         
            +
            * KeyValue (uses active_support/json and cannot store procs)
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
            For more information and lots of resources see [the 'Resources' page on the wiki](https://github.com/ruby-i18n/i18n/wiki/Resources).
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
            ## Tests
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
            You can run tests both with
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
            * `rake test` or just `rake`
         
     | 
| 
      
 91 
     | 
    
         
            +
            * run any test file directly, e.g. `ruby -Ilib:test test/api/simple_test.rb`
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
            You can run all tests against all Gemfiles with
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
            * `ruby test/run_all.rb`
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
            The structure of the test suite is a bit unusual as it uses modules to reuse
         
     | 
| 
      
 98 
     | 
    
         
            +
            particular tests in different test cases.
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
            The reason for this is that we need to enforce the I18n API across various
         
     | 
| 
      
 101 
     | 
    
         
            +
            combinations of extensions. E.g. the Simple backend alone needs to support
         
     | 
| 
      
 102 
     | 
    
         
            +
            the same API as any combination of feature and/or optimization modules included
         
     | 
| 
      
 103 
     | 
    
         
            +
            to the Simple backend. We test this by reusing the same API definition (implemented
         
     | 
| 
      
 104 
     | 
    
         
            +
            as test methods) in test cases with different setups.
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
            You can find the test cases that enforce the API in test/api. And you can find
         
     | 
| 
      
 107 
     | 
    
         
            +
            the API definition test methods in test/api/tests.
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
            All other test cases (e.g. as defined in test/backend, test/core_ext) etc.
         
     | 
| 
      
 110 
     | 
    
         
            +
            follow the usual test setup and should be easy to grok.
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
            ## More Documentation
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
            Additional documentation can be found here: https://github.com/ruby-i18n/i18n/wiki
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
            ## Contributors
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
            * @radar
         
     | 
| 
      
 119 
     | 
    
         
            +
            * @carlosantoniodasilva
         
     | 
| 
      
 120 
     | 
    
         
            +
            * @josevalim
         
     | 
| 
      
 121 
     | 
    
         
            +
            * @knapo
         
     | 
| 
      
 122 
     | 
    
         
            +
            * @tigrish
         
     | 
| 
      
 123 
     | 
    
         
            +
            * [and many more](https://github.com/ruby-i18n/i18n/graphs/contributors)
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
            ## License
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
            MIT License. See the included MIT-LICENSE file.
         
     | 
    
        data/lib/i18n/backend/base.rb
    CHANGED
    
    | 
         @@ -1,77 +1,93 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            #  
     | 
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            require 'yaml'
         
     | 
| 
       4 
     | 
    
         
            -
            require ' 
     | 
| 
      
 4 
     | 
    
         
            +
            require 'json'
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
            module I18n
         
     | 
| 
       7 
7 
     | 
    
         
             
              module Backend
         
     | 
| 
       8 
8 
     | 
    
         
             
                module Base
         
     | 
| 
       9 
9 
     | 
    
         
             
                  include I18n::Backend::Transliterator
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                  RESERVED_KEYS = [:scope, :default, :separator, :resolve]
         
     | 
| 
       12 
     | 
    
         
            -
                  RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
         
     | 
| 
       13 
     | 
    
         
            -
                  DEPRECATED_INTERPOLATION_SYNTAX_PATTERN = /(\\)?\{\{([^\}]+)\}\}/
         
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
11 
     | 
    
         
             
                  # Accepts a list of paths to translation files. Loads translations from
         
     | 
| 
       16 
     | 
    
         
            -
                  # plain Ruby (*.rb)  
     | 
| 
      
 12 
     | 
    
         
            +
                  # plain Ruby (*.rb), YAML files (*.yml), or JSON files (*.json). See #load_rb, #load_yml, and #load_json
         
     | 
| 
       17 
13 
     | 
    
         
             
                  # for details.
         
     | 
| 
       18 
14 
     | 
    
         
             
                  def load_translations(*filenames)
         
     | 
| 
       19 
     | 
    
         
            -
                    filenames = I18n.load_path 
     | 
| 
       20 
     | 
    
         
            -
                    filenames.each  
     | 
| 
      
 15 
     | 
    
         
            +
                    filenames = I18n.load_path if filenames.empty?
         
     | 
| 
      
 16 
     | 
    
         
            +
                    filenames.flatten.each do |filename|
         
     | 
| 
      
 17 
     | 
    
         
            +
                      loaded_translations = load_file(filename)
         
     | 
| 
      
 18 
     | 
    
         
            +
                      yield filename, loaded_translations if block_given?
         
     | 
| 
      
 19 
     | 
    
         
            +
                    end
         
     | 
| 
       21 
20 
     | 
    
         
             
                  end
         
     | 
| 
       22 
21 
     | 
    
         | 
| 
       23 
22 
     | 
    
         
             
                  # This method receives a locale, a data hash and options for storing translations.
         
     | 
| 
       24 
23 
     | 
    
         
             
                  # Should be implemented
         
     | 
| 
       25 
     | 
    
         
            -
                  def store_translations(locale, data, options =  
     | 
| 
      
 24 
     | 
    
         
            +
                  def store_translations(locale, data, options = EMPTY_HASH)
         
     | 
| 
       26 
25 
     | 
    
         
             
                    raise NotImplementedError
         
     | 
| 
       27 
26 
     | 
    
         
             
                  end
         
     | 
| 
       28 
27 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
                  def translate(locale, key, options =  
     | 
| 
      
 28 
     | 
    
         
            +
                  def translate(locale, key, options = EMPTY_HASH)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    raise I18n::ArgumentError if (key.is_a?(String) || key.is_a?(Symbol)) && key.empty?
         
     | 
| 
       30 
30 
     | 
    
         
             
                    raise InvalidLocale.new(locale) unless locale
         
     | 
| 
       31 
     | 
    
         
            -
                    return key. 
     | 
| 
      
 31 
     | 
    
         
            +
                    return nil if key.nil? && !options.key?(:default)
         
     | 
| 
       32 
32 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
                    entry =  
     | 
| 
      
 33 
     | 
    
         
            +
                    entry = lookup(locale, key, options[:scope], options) unless key.nil?
         
     | 
| 
       34 
34 
     | 
    
         | 
| 
       35 
     | 
    
         
            -
                    if options. 
     | 
| 
       36 
     | 
    
         
            -
                      entry =  
     | 
| 
      
 35 
     | 
    
         
            +
                    if entry.nil? && options.key?(:default)
         
     | 
| 
      
 36 
     | 
    
         
            +
                      entry = default(locale, key, options[:default], options)
         
     | 
| 
       37 
37 
     | 
    
         
             
                    else
         
     | 
| 
       38 
     | 
    
         
            -
                       
     | 
| 
       39 
     | 
    
         
            -
                      values = options.except(*RESERVED_KEYS)
         
     | 
| 
       40 
     | 
    
         
            -
                      entry = entry.nil? && default ?
         
     | 
| 
       41 
     | 
    
         
            -
                        default(locale, key, default, options) : resolve(locale, key, entry, options)
         
     | 
| 
      
 38 
     | 
    
         
            +
                      entry = resolve_entry(locale, key, entry, options)
         
     | 
| 
       42 
39 
     | 
    
         
             
                    end
         
     | 
| 
       43 
40 
     | 
    
         | 
| 
       44 
     | 
    
         
            -
                     
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
      
 41 
     | 
    
         
            +
                    count = options[:count]
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    if entry.nil? && (subtrees? || !count)
         
     | 
| 
      
 44 
     | 
    
         
            +
                      if (options.key?(:default) && !options[:default].nil?) || !options.key?(:default)
         
     | 
| 
      
 45 
     | 
    
         
            +
                        throw(:exception, I18n::MissingTranslation.new(locale, key, options))
         
     | 
| 
      
 46 
     | 
    
         
            +
                      end
         
     | 
| 
      
 47 
     | 
    
         
            +
                    end
         
     | 
| 
       46 
48 
     | 
    
         | 
| 
      
 49 
     | 
    
         
            +
                    entry = entry.dup if entry.is_a?(String)
         
     | 
| 
       47 
50 
     | 
    
         
             
                    entry = pluralize(locale, entry, count) if count
         
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                    if entry.nil? && !subtrees?
         
     | 
| 
      
 53 
     | 
    
         
            +
                      throw(:exception, I18n::MissingTranslation.new(locale, key, options))
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                    deep_interpolation = options[:deep_interpolation]
         
     | 
| 
      
 57 
     | 
    
         
            +
                    values = Utils.except(options, *RESERVED_KEYS) unless options.empty?
         
     | 
| 
      
 58 
     | 
    
         
            +
                    if values && !values.empty?
         
     | 
| 
      
 59 
     | 
    
         
            +
                      entry = if deep_interpolation
         
     | 
| 
      
 60 
     | 
    
         
            +
                        deep_interpolate(locale, entry, values)
         
     | 
| 
      
 61 
     | 
    
         
            +
                      else
         
     | 
| 
      
 62 
     | 
    
         
            +
                        interpolate(locale, entry, values)
         
     | 
| 
      
 63 
     | 
    
         
            +
                      end
         
     | 
| 
      
 64 
     | 
    
         
            +
                    elsif entry.is_a?(String) && entry =~ I18n.reserved_keys_pattern
         
     | 
| 
      
 65 
     | 
    
         
            +
                      raise ReservedInterpolationKey.new($1.to_sym, entry)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    end
         
     | 
| 
       49 
67 
     | 
    
         
             
                    entry
         
     | 
| 
       50 
68 
     | 
    
         
             
                  end
         
     | 
| 
       51 
69 
     | 
    
         | 
| 
      
 70 
     | 
    
         
            +
                  def exists?(locale, key, options = EMPTY_HASH)
         
     | 
| 
      
 71 
     | 
    
         
            +
                    lookup(locale, key, options[:scope]) != nil
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
       52 
74 
     | 
    
         
             
                  # Acts the same as +strftime+, but uses a localized version of the
         
     | 
| 
       53 
75 
     | 
    
         
             
                  # format string. Takes a key from the date/time formats translations as
         
     | 
| 
       54 
76 
     | 
    
         
             
                  # a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
         
     | 
| 
       55 
     | 
    
         
            -
                  def localize(locale, object, format = :default, options =  
     | 
| 
      
 77 
     | 
    
         
            +
                  def localize(locale, object, format = :default, options = EMPTY_HASH)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    if object.nil? && options.include?(:default)
         
     | 
| 
      
 79 
     | 
    
         
            +
                      return options[:default]
         
     | 
| 
      
 80 
     | 
    
         
            +
                    end
         
     | 
| 
       56 
81 
     | 
    
         
             
                    raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
         
     | 
| 
       57 
82 
     | 
    
         | 
| 
       58 
83 
     | 
    
         
             
                    if Symbol === format
         
     | 
| 
       59 
     | 
    
         
            -
                      key 
     | 
| 
      
 84 
     | 
    
         
            +
                      key  = format
         
     | 
| 
       60 
85 
     | 
    
         
             
                      type = object.respond_to?(:sec) ? 'time' : 'date'
         
     | 
| 
       61 
     | 
    
         
            -
                       
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
       64 
     | 
    
         
            -
                    # format = resolve(locale, object, format, options)
         
     | 
| 
       65 
     | 
    
         
            -
                    format = format.to_s.gsub(/%[aAbBp]/) do |match|
         
     | 
| 
       66 
     | 
    
         
            -
                      case match
         
     | 
| 
       67 
     | 
    
         
            -
                      when '%a' then I18n.t(:"date.abbr_day_names",                  :locale => locale, :format => format)[object.wday]
         
     | 
| 
       68 
     | 
    
         
            -
                      when '%A' then I18n.t(:"date.day_names",                       :locale => locale, :format => format)[object.wday]
         
     | 
| 
       69 
     | 
    
         
            -
                      when '%b' then I18n.t(:"date.abbr_month_names",                :locale => locale, :format => format)[object.mon]
         
     | 
| 
       70 
     | 
    
         
            -
                      when '%B' then I18n.t(:"date.month_names",                     :locale => locale, :format => format)[object.mon]
         
     | 
| 
       71 
     | 
    
         
            -
                      when '%p' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format) if object.respond_to? :hour
         
     | 
| 
       72 
     | 
    
         
            -
                      end
         
     | 
| 
      
 86 
     | 
    
         
            +
                      options = options.merge(:raise => true, :object => object, :locale => locale)
         
     | 
| 
      
 87 
     | 
    
         
            +
                      format  = I18n.t(:"#{type}.formats.#{key}", **options)
         
     | 
| 
       73 
88 
     | 
    
         
             
                    end
         
     | 
| 
       74 
89 
     | 
    
         | 
| 
      
 90 
     | 
    
         
            +
                    format = translate_localization_format(locale, object, format, options)
         
     | 
| 
       75 
91 
     | 
    
         
             
                    object.strftime(format)
         
     | 
| 
       76 
92 
     | 
    
         
             
                  end
         
     | 
| 
       77 
93 
     | 
    
         | 
| 
         @@ -82,26 +98,44 @@ module I18n 
     | 
|
| 
       82 
98 
     | 
    
         
             
                  end
         
     | 
| 
       83 
99 
     | 
    
         | 
| 
       84 
100 
     | 
    
         
             
                  def reload!
         
     | 
| 
       85 
     | 
    
         
            -
                     
     | 
| 
      
 101 
     | 
    
         
            +
                    eager_load! if eager_loaded?
         
     | 
| 
      
 102 
     | 
    
         
            +
                  end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                  def eager_load!
         
     | 
| 
      
 105 
     | 
    
         
            +
                    @eager_loaded = true
         
     | 
| 
       86 
106 
     | 
    
         
             
                  end
         
     | 
| 
       87 
107 
     | 
    
         | 
| 
       88 
108 
     | 
    
         
             
                  protected
         
     | 
| 
       89 
109 
     | 
    
         | 
| 
      
 110 
     | 
    
         
            +
                    def eager_loaded?
         
     | 
| 
      
 111 
     | 
    
         
            +
                      @eager_loaded ||= false
         
     | 
| 
      
 112 
     | 
    
         
            +
                    end
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
       90 
114 
     | 
    
         
             
                    # The method which actually looks up for the translation in the store.
         
     | 
| 
       91 
     | 
    
         
            -
                    def lookup(locale, key, scope = [], options =  
     | 
| 
      
 115 
     | 
    
         
            +
                    def lookup(locale, key, scope = [], options = EMPTY_HASH)
         
     | 
| 
       92 
116 
     | 
    
         
             
                      raise NotImplementedError
         
     | 
| 
       93 
117 
     | 
    
         
             
                    end
         
     | 
| 
       94 
118 
     | 
    
         | 
| 
      
 119 
     | 
    
         
            +
                    def subtrees?
         
     | 
| 
      
 120 
     | 
    
         
            +
                      true
         
     | 
| 
      
 121 
     | 
    
         
            +
                    end
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
       95 
123 
     | 
    
         
             
                    # Evaluates defaults.
         
     | 
| 
       96 
124 
     | 
    
         
             
                    # If given subject is an Array, it walks the array and returns the
         
     | 
| 
       97 
125 
     | 
    
         
             
                    # first translation that can be resolved. Otherwise it tries to resolve
         
     | 
| 
       98 
126 
     | 
    
         
             
                    # the translation directly.
         
     | 
| 
       99 
     | 
    
         
            -
                    def default(locale, object, subject, options =  
     | 
| 
       100 
     | 
    
         
            -
                       
     | 
| 
      
 127 
     | 
    
         
            +
                    def default(locale, object, subject, options = EMPTY_HASH)
         
     | 
| 
      
 128 
     | 
    
         
            +
                      if options.size == 1 && options.has_key?(:default)
         
     | 
| 
      
 129 
     | 
    
         
            +
                        options = {}
         
     | 
| 
      
 130 
     | 
    
         
            +
                      else
         
     | 
| 
      
 131 
     | 
    
         
            +
                        options = Utils.except(options, :default)
         
     | 
| 
      
 132 
     | 
    
         
            +
                      end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
       101 
134 
     | 
    
         
             
                      case subject
         
     | 
| 
       102 
135 
     | 
    
         
             
                      when Array
         
     | 
| 
       103 
136 
     | 
    
         
             
                        subject.each do |item|
         
     | 
| 
       104 
     | 
    
         
            -
                          result = resolve(locale, object, item, options) 
     | 
| 
      
 137 
     | 
    
         
            +
                          result = resolve(locale, object, item, options)
         
     | 
| 
      
 138 
     | 
    
         
            +
                          return result unless result.nil?
         
     | 
| 
       105 
139 
     | 
    
         
             
                        end and nil
         
     | 
| 
       106 
140 
     | 
    
         
             
                      else
         
     | 
| 
       107 
141 
     | 
    
         
             
                        resolve(locale, object, subject, options)
         
     | 
| 
         @@ -112,116 +146,160 @@ module I18n 
     | 
|
| 
       112 
146 
     | 
    
         
             
                    # If the given subject is a Symbol, it will be translated with the
         
     | 
| 
       113 
147 
     | 
    
         
             
                    # given options. If it is a Proc then it will be evaluated. All other
         
     | 
| 
       114 
148 
     | 
    
         
             
                    # subjects will be returned directly.
         
     | 
| 
       115 
     | 
    
         
            -
                    def resolve(locale, object, subject, options =  
     | 
| 
      
 149 
     | 
    
         
            +
                    def resolve(locale, object, subject, options = EMPTY_HASH)
         
     | 
| 
       116 
150 
     | 
    
         
             
                      return subject if options[:resolve] == false
         
     | 
| 
       117 
     | 
    
         
            -
                       
     | 
| 
       118 
     | 
    
         
            -
             
     | 
| 
       119 
     | 
    
         
            -
                         
     | 
| 
       120 
     | 
    
         
            -
             
     | 
| 
       121 
     | 
    
         
            -
                         
     | 
| 
       122 
     | 
    
         
            -
             
     | 
| 
       123 
     | 
    
         
            -
             
     | 
| 
       124 
     | 
    
         
            -
                         
     | 
| 
      
 151 
     | 
    
         
            +
                      result = catch(:exception) do
         
     | 
| 
      
 152 
     | 
    
         
            +
                        case subject
         
     | 
| 
      
 153 
     | 
    
         
            +
                        when Symbol
         
     | 
| 
      
 154 
     | 
    
         
            +
                          I18n.translate(subject, **options.merge(:locale => locale, :throw => true))
         
     | 
| 
      
 155 
     | 
    
         
            +
                        when Proc
         
     | 
| 
      
 156 
     | 
    
         
            +
                          date_or_time = options.delete(:object) || object
         
     | 
| 
      
 157 
     | 
    
         
            +
                          resolve(locale, object, subject.call(date_or_time, **options))
         
     | 
| 
      
 158 
     | 
    
         
            +
                        else
         
     | 
| 
      
 159 
     | 
    
         
            +
                          subject
         
     | 
| 
      
 160 
     | 
    
         
            +
                        end
         
     | 
| 
       125 
161 
     | 
    
         
             
                      end
         
     | 
| 
       126 
     | 
    
         
            -
             
     | 
| 
       127 
     | 
    
         
            -
                      nil
         
     | 
| 
      
 162 
     | 
    
         
            +
                      result unless result.is_a?(MissingTranslation)
         
     | 
| 
       128 
163 
     | 
    
         
             
                    end
         
     | 
| 
      
 164 
     | 
    
         
            +
                    alias_method :resolve_entry, :resolve
         
     | 
| 
       129 
165 
     | 
    
         | 
| 
       130 
     | 
    
         
            -
                    # Picks a translation from  
     | 
| 
       131 
     | 
    
         
            -
                    # rules 
     | 
| 
       132 
     | 
    
         
            -
                    #  
     | 
| 
       133 
     | 
    
         
            -
                    #  
     | 
| 
      
 166 
     | 
    
         
            +
                    # Picks a translation from a pluralized mnemonic subkey according to English
         
     | 
| 
      
 167 
     | 
    
         
            +
                    # pluralization rules :
         
     | 
| 
      
 168 
     | 
    
         
            +
                    # - It will pick the :one subkey if count is equal to 1.
         
     | 
| 
      
 169 
     | 
    
         
            +
                    # - It will pick the :other subkey otherwise.
         
     | 
| 
      
 170 
     | 
    
         
            +
                    # - It will pick the :zero subkey in the special case where count is
         
     | 
| 
      
 171 
     | 
    
         
            +
                    #   equal to 0 and there is a :zero subkey present. This behaviour is
         
     | 
| 
      
 172 
     | 
    
         
            +
                    #   not standard with regards to the CLDR pluralization rules.
         
     | 
| 
      
 173 
     | 
    
         
            +
                    # Other backends can implement more flexible or complex pluralization rules.
         
     | 
| 
       134 
174 
     | 
    
         
             
                    def pluralize(locale, entry, count)
         
     | 
| 
      
 175 
     | 
    
         
            +
                      entry = entry.reject { |k, _v| k == :attributes } if entry.is_a?(Hash)
         
     | 
| 
       135 
176 
     | 
    
         
             
                      return entry unless entry.is_a?(Hash) && count
         
     | 
| 
       136 
177 
     | 
    
         | 
| 
       137 
     | 
    
         
            -
                      key =  
     | 
| 
       138 
     | 
    
         
            -
                       
     | 
| 
       139 
     | 
    
         
            -
                      raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
         
     | 
| 
      
 178 
     | 
    
         
            +
                      key = pluralization_key(entry, count)
         
     | 
| 
      
 179 
     | 
    
         
            +
                      raise InvalidPluralizationData.new(entry, count, key) unless entry.has_key?(key)
         
     | 
| 
       140 
180 
     | 
    
         
             
                      entry[key]
         
     | 
| 
       141 
181 
     | 
    
         
             
                    end
         
     | 
| 
       142 
182 
     | 
    
         | 
| 
       143 
     | 
    
         
            -
                    # Interpolates values into a given  
     | 
| 
      
 183 
     | 
    
         
            +
                    # Interpolates values into a given subject.
         
     | 
| 
       144 
184 
     | 
    
         
             
                    #
         
     | 
| 
       145 
     | 
    
         
            -
                    #    
     | 
| 
      
 185 
     | 
    
         
            +
                    #   if the given subject is a string then:
         
     | 
| 
      
 186 
     | 
    
         
            +
                    #   method interpolates "file %{file} opened by %%{user}", :file => 'test.txt', :user => 'Mr. X'
         
     | 
| 
       146 
187 
     | 
    
         
             
                    #   # => "file test.txt opened by %{user}"
         
     | 
| 
       147 
188 
     | 
    
         
             
                    #
         
     | 
| 
       148 
     | 
    
         
            -
                    #  
     | 
| 
       149 
     | 
    
         
            -
                    # the  
     | 
| 
       150 
     | 
    
         
            -
                    #  
     | 
| 
       151 
     | 
    
         
            -
                     
     | 
| 
       152 
     | 
    
         
            -
             
     | 
| 
       153 
     | 
    
         
            -
                      
         
     | 
| 
       154 
     | 
    
         
            -
                      preserve_encoding(string) do
         
     | 
| 
       155 
     | 
    
         
            -
                        string = string.gsub(DEPRECATED_INTERPOLATION_SYNTAX_PATTERN) do
         
     | 
| 
       156 
     | 
    
         
            -
                          escaped, key = $1, $2.to_sym
         
     | 
| 
       157 
     | 
    
         
            -
                          if escaped
         
     | 
| 
       158 
     | 
    
         
            -
                            "{{#{key}}}"
         
     | 
| 
       159 
     | 
    
         
            -
                          else
         
     | 
| 
       160 
     | 
    
         
            -
                            warn_syntax_deprecation!
         
     | 
| 
       161 
     | 
    
         
            -
                            "%{#{key}}"
         
     | 
| 
       162 
     | 
    
         
            -
                          end
         
     | 
| 
       163 
     | 
    
         
            -
                        end
         
     | 
| 
      
 189 
     | 
    
         
            +
                    #   if the given subject is an array then:
         
     | 
| 
      
 190 
     | 
    
         
            +
                    #   each element of the array is recursively interpolated (until it finds a string)
         
     | 
| 
      
 191 
     | 
    
         
            +
                    #   method interpolates ["yes, %{user}", ["maybe no, %{user}, "no, %{user}"]], :user => "bartuz"
         
     | 
| 
      
 192 
     | 
    
         
            +
                    #   # => "["yes, bartuz",["maybe no, bartuz", "no, bartuz"]]"
         
     | 
| 
      
 193 
     | 
    
         
            +
                    def interpolate(locale, subject, values = EMPTY_HASH)
         
     | 
| 
      
 194 
     | 
    
         
            +
                      return subject if values.empty?
         
     | 
| 
       164 
195 
     | 
    
         | 
| 
       165 
     | 
    
         
            -
             
     | 
| 
       166 
     | 
    
         
            -
             
     | 
| 
       167 
     | 
    
         
            -
             
     | 
| 
       168 
     | 
    
         
            -
                          values[key] = value
         
     | 
| 
       169 
     | 
    
         
            -
                        end
         
     | 
| 
       170 
     | 
    
         
            -
             
     | 
| 
       171 
     | 
    
         
            -
                        string % values
         
     | 
| 
       172 
     | 
    
         
            -
                      end
         
     | 
| 
       173 
     | 
    
         
            -
                    rescue KeyError => e
         
     | 
| 
       174 
     | 
    
         
            -
                      if string =~ RESERVED_KEYS_PATTERN
         
     | 
| 
       175 
     | 
    
         
            -
                        raise ReservedInterpolationKey.new($1.to_sym, string)
         
     | 
| 
      
 196 
     | 
    
         
            +
                      case subject
         
     | 
| 
      
 197 
     | 
    
         
            +
                      when ::String then I18n.interpolate(subject, values)
         
     | 
| 
      
 198 
     | 
    
         
            +
                      when ::Array then subject.map { |element| interpolate(locale, element, values) }
         
     | 
| 
       176 
199 
     | 
    
         
             
                      else
         
     | 
| 
       177 
     | 
    
         
            -
                         
     | 
| 
      
 200 
     | 
    
         
            +
                        subject
         
     | 
| 
       178 
201 
     | 
    
         
             
                      end
         
     | 
| 
       179 
202 
     | 
    
         
             
                    end
         
     | 
| 
       180 
203 
     | 
    
         | 
| 
       181 
     | 
    
         
            -
                     
     | 
| 
       182 
     | 
    
         
            -
             
     | 
| 
       183 
     | 
    
         
            -
             
     | 
| 
       184 
     | 
    
         
            -
             
     | 
| 
       185 
     | 
    
         
            -
             
     | 
| 
       186 
     | 
    
         
            -
             
     | 
| 
      
 204 
     | 
    
         
            +
                    # Deep interpolation
         
     | 
| 
      
 205 
     | 
    
         
            +
                    #
         
     | 
| 
      
 206 
     | 
    
         
            +
                    #   deep_interpolate { people: { ann: "Ann is %{ann}", john: "John is %{john}" } },
         
     | 
| 
      
 207 
     | 
    
         
            +
                    #                    ann: 'good', john: 'big'
         
     | 
| 
      
 208 
     | 
    
         
            +
                    #   #=> { people: { ann: "Ann is good", john: "John is big" } }
         
     | 
| 
      
 209 
     | 
    
         
            +
                    def deep_interpolate(locale, data, values = EMPTY_HASH)
         
     | 
| 
      
 210 
     | 
    
         
            +
                      return data if values.empty?
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                      case data
         
     | 
| 
      
 213 
     | 
    
         
            +
                      when ::String
         
     | 
| 
      
 214 
     | 
    
         
            +
                        I18n.interpolate(data, values)
         
     | 
| 
      
 215 
     | 
    
         
            +
                      when ::Hash
         
     | 
| 
      
 216 
     | 
    
         
            +
                        data.each_with_object({}) do |(k, v), result|
         
     | 
| 
      
 217 
     | 
    
         
            +
                          result[k] = deep_interpolate(locale, v, values)
         
     | 
| 
      
 218 
     | 
    
         
            +
                        end
         
     | 
| 
      
 219 
     | 
    
         
            +
                      when ::Array
         
     | 
| 
      
 220 
     | 
    
         
            +
                        data.map do |v|
         
     | 
| 
      
 221 
     | 
    
         
            +
                          deep_interpolate(locale, v, values)
         
     | 
| 
      
 222 
     | 
    
         
            +
                        end
         
     | 
| 
       187 
223 
     | 
    
         
             
                      else
         
     | 
| 
       188 
     | 
    
         
            -
                         
     | 
| 
      
 224 
     | 
    
         
            +
                        data
         
     | 
| 
       189 
225 
     | 
    
         
             
                      end
         
     | 
| 
       190 
226 
     | 
    
         
             
                    end
         
     | 
| 
       191 
227 
     | 
    
         | 
| 
       192 
     | 
    
         
            -
                    # returns true when the given value responds to :call and the key is
         
     | 
| 
       193 
     | 
    
         
            -
                    # an interpolation placeholder in the given string
         
     | 
| 
       194 
     | 
    
         
            -
                    def interpolate_lambda?(object, string, key)
         
     | 
| 
       195 
     | 
    
         
            -
                      object.respond_to?(:call) && string =~ /%\{#{key}\}|%\<#{key}>.*?\d*\.?\d*[bBdiouxXeEfgGcps]\}/
         
     | 
| 
       196 
     | 
    
         
            -
                    end
         
     | 
| 
       197 
     | 
    
         
            -
             
     | 
| 
       198 
228 
     | 
    
         
             
                    # Loads a single translations file by delegating to #load_rb or
         
     | 
| 
       199 
229 
     | 
    
         
             
                    # #load_yml depending on the file extension and directly merges the
         
     | 
| 
       200 
230 
     | 
    
         
             
                    # data to the existing translations. Raises I18n::UnknownFileType
         
     | 
| 
       201 
231 
     | 
    
         
             
                    # for all other file extensions.
         
     | 
| 
       202 
232 
     | 
    
         
             
                    def load_file(filename)
         
     | 
| 
       203 
233 
     | 
    
         
             
                      type = File.extname(filename).tr('.', '').downcase
         
     | 
| 
       204 
     | 
    
         
            -
                      raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}")
         
     | 
| 
       205 
     | 
    
         
            -
                      data = send(:"load_#{type}", filename) 
     | 
| 
       206 
     | 
    
         
            -
                      data. 
     | 
| 
      
 234 
     | 
    
         
            +
                      raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)
         
     | 
| 
      
 235 
     | 
    
         
            +
                      data, keys_symbolized = send(:"load_#{type}", filename)
         
     | 
| 
      
 236 
     | 
    
         
            +
                      unless data.is_a?(Hash)
         
     | 
| 
      
 237 
     | 
    
         
            +
                        raise InvalidLocaleData.new(filename, 'expects it to return a hash, but does not')
         
     | 
| 
      
 238 
     | 
    
         
            +
                      end
         
     | 
| 
      
 239 
     | 
    
         
            +
                      data.each { |locale, d| store_translations(locale, d || {}, skip_symbolize_keys: keys_symbolized) }
         
     | 
| 
      
 240 
     | 
    
         
            +
             
     | 
| 
      
 241 
     | 
    
         
            +
                      data
         
     | 
| 
       207 
242 
     | 
    
         
             
                    end
         
     | 
| 
       208 
243 
     | 
    
         | 
| 
       209 
244 
     | 
    
         
             
                    # Loads a plain Ruby translations file. eval'ing the file must yield
         
     | 
| 
       210 
245 
     | 
    
         
             
                    # a Hash containing translation data with locales as toplevel keys.
         
     | 
| 
       211 
246 
     | 
    
         
             
                    def load_rb(filename)
         
     | 
| 
       212 
     | 
    
         
            -
                      eval(IO.read(filename), binding, filename)
         
     | 
| 
      
 247 
     | 
    
         
            +
                      translations = eval(IO.read(filename), binding, filename)
         
     | 
| 
      
 248 
     | 
    
         
            +
                      [translations, false]
         
     | 
| 
       213 
249 
     | 
    
         
             
                    end
         
     | 
| 
       214 
250 
     | 
    
         | 
| 
       215 
251 
     | 
    
         
             
                    # Loads a YAML translations file. The data must have locales as
         
     | 
| 
       216 
252 
     | 
    
         
             
                    # toplevel keys.
         
     | 
| 
       217 
253 
     | 
    
         
             
                    def load_yml(filename)
         
     | 
| 
       218 
     | 
    
         
            -
                       
     | 
| 
      
 254 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 255 
     | 
    
         
            +
                        if YAML.respond_to?(:unsafe_load_file) # Psych 4.0 way
         
     | 
| 
      
 256 
     | 
    
         
            +
                          [YAML.unsafe_load_file(filename, symbolize_names: true, freeze: true), true]
         
     | 
| 
      
 257 
     | 
    
         
            +
                        else
         
     | 
| 
      
 258 
     | 
    
         
            +
                          [YAML.load_file(filename), false]
         
     | 
| 
      
 259 
     | 
    
         
            +
                        end
         
     | 
| 
      
 260 
     | 
    
         
            +
                      rescue TypeError, ScriptError, StandardError => e
         
     | 
| 
      
 261 
     | 
    
         
            +
                        raise InvalidLocaleData.new(filename, e.inspect)
         
     | 
| 
      
 262 
     | 
    
         
            +
                      end
         
     | 
| 
       219 
263 
     | 
    
         
             
                    end
         
     | 
| 
      
 264 
     | 
    
         
            +
                    alias_method :load_yaml, :load_yml
         
     | 
| 
       220 
265 
     | 
    
         | 
| 
       221 
     | 
    
         
            -
                     
     | 
| 
       222 
     | 
    
         
            -
             
     | 
| 
       223 
     | 
    
         
            -
             
     | 
| 
       224 
     | 
    
         
            -
                       
     | 
| 
      
 266 
     | 
    
         
            +
                    # Loads a JSON translations file. The data must have locales as
         
     | 
| 
      
 267 
     | 
    
         
            +
                    # toplevel keys.
         
     | 
| 
      
 268 
     | 
    
         
            +
                    def load_json(filename)
         
     | 
| 
      
 269 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 270 
     | 
    
         
            +
                        # Use #load_file as a proxy for a version of JSON where symbolize_names and freeze are supported.
         
     | 
| 
      
 271 
     | 
    
         
            +
                        if ::JSON.respond_to?(:load_file)
         
     | 
| 
      
 272 
     | 
    
         
            +
                          [::JSON.load_file(filename, symbolize_names: true, freeze: true), true]
         
     | 
| 
      
 273 
     | 
    
         
            +
                        else
         
     | 
| 
      
 274 
     | 
    
         
            +
                          [::JSON.parse(File.read(filename)), false]
         
     | 
| 
      
 275 
     | 
    
         
            +
                        end
         
     | 
| 
      
 276 
     | 
    
         
            +
                      rescue TypeError, StandardError => e
         
     | 
| 
      
 277 
     | 
    
         
            +
                        raise InvalidLocaleData.new(filename, e.inspect)
         
     | 
| 
      
 278 
     | 
    
         
            +
                      end
         
     | 
| 
      
 279 
     | 
    
         
            +
                    end
         
     | 
| 
      
 280 
     | 
    
         
            +
             
     | 
| 
      
 281 
     | 
    
         
            +
                    def translate_localization_format(locale, object, format, options)
         
     | 
| 
      
 282 
     | 
    
         
            +
                      format.to_s.gsub(/%(|\^)[aAbBpP]/) do |match|
         
     | 
| 
      
 283 
     | 
    
         
            +
                        case match
         
     | 
| 
      
 284 
     | 
    
         
            +
                        when '%a' then I18n.t!(:"date.abbr_day_names",                  :locale => locale, :format => format)[object.wday]
         
     | 
| 
      
 285 
     | 
    
         
            +
                        when '%^a' then I18n.t!(:"date.abbr_day_names",                 :locale => locale, :format => format)[object.wday].upcase
         
     | 
| 
      
 286 
     | 
    
         
            +
                        when '%A' then I18n.t!(:"date.day_names",                       :locale => locale, :format => format)[object.wday]
         
     | 
| 
      
 287 
     | 
    
         
            +
                        when '%^A' then I18n.t!(:"date.day_names",                      :locale => locale, :format => format)[object.wday].upcase
         
     | 
| 
      
 288 
     | 
    
         
            +
                        when '%b' then I18n.t!(:"date.abbr_month_names",                :locale => locale, :format => format)[object.mon]
         
     | 
| 
      
 289 
     | 
    
         
            +
                        when '%^b' then I18n.t!(:"date.abbr_month_names",               :locale => locale, :format => format)[object.mon].upcase
         
     | 
| 
      
 290 
     | 
    
         
            +
                        when '%B' then I18n.t!(:"date.month_names",                     :locale => locale, :format => format)[object.mon]
         
     | 
| 
      
 291 
     | 
    
         
            +
                        when '%^B' then I18n.t!(:"date.month_names",                    :locale => locale, :format => format)[object.mon].upcase
         
     | 
| 
      
 292 
     | 
    
         
            +
                        when '%p' then I18n.t!(:"time.#{(object.respond_to?(:hour) ? object.hour : 0) < 12 ? :am : :pm}", :locale => locale, :format => format).upcase
         
     | 
| 
      
 293 
     | 
    
         
            +
                        when '%P' then I18n.t!(:"time.#{(object.respond_to?(:hour) ? object.hour : 0) < 12 ? :am : :pm}", :locale => locale, :format => format).downcase
         
     | 
| 
      
 294 
     | 
    
         
            +
                        end
         
     | 
| 
      
 295 
     | 
    
         
            +
                      end
         
     | 
| 
      
 296 
     | 
    
         
            +
                    rescue MissingTranslationData => e
         
     | 
| 
      
 297 
     | 
    
         
            +
                      e.message
         
     | 
| 
      
 298 
     | 
    
         
            +
                    end
         
     | 
| 
      
 299 
     | 
    
         
            +
             
     | 
| 
      
 300 
     | 
    
         
            +
                    def pluralization_key(entry, count)
         
     | 
| 
      
 301 
     | 
    
         
            +
                      key = :zero if count == 0 && entry.has_key?(:zero)
         
     | 
| 
      
 302 
     | 
    
         
            +
                      key ||= count == 1 ? :one : :other
         
     | 
| 
       225 
303 
     | 
    
         
             
                    end
         
     | 
| 
       226 
304 
     | 
    
         
             
                end
         
     | 
| 
       227 
305 
     | 
    
         
             
              end
         
     | 
    
        data/lib/i18n/backend/cache.rb
    CHANGED
    
    | 
         @@ -1,4 +1,4 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            #  
     | 
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            # This module allows you to easily cache all responses from the backend - thus
         
     | 
| 
       4 
4 
     | 
    
         
             
            # speeding up the I18n aspects of your application quite a bit.
         
     | 
| 
         @@ -6,22 +6,44 @@ 
     | 
|
| 
       6 
6 
     | 
    
         
             
            # To enable caching you can simply include the Cache module to the Simple
         
     | 
| 
       7 
7 
     | 
    
         
             
            # backend - or whatever other backend you are using:
         
     | 
| 
       8 
8 
     | 
    
         
             
            #
         
     | 
| 
       9 
     | 
    
         
            -
            # 
     | 
| 
      
 9 
     | 
    
         
            +
            #   I18n::Backend::Simple.send(:include, I18n::Backend::Cache)
         
     | 
| 
       10 
10 
     | 
    
         
             
            #
         
     | 
| 
       11 
11 
     | 
    
         
             
            # You will also need to set a cache store implementation that you want to use:
         
     | 
| 
       12 
12 
     | 
    
         
             
            #
         
     | 
| 
       13 
     | 
    
         
            -
            # 
     | 
| 
      
 13 
     | 
    
         
            +
            #   I18n.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
         
     | 
| 
       14 
14 
     | 
    
         
             
            #
         
     | 
| 
       15 
15 
     | 
    
         
             
            # You can use any cache implementation you want that provides the same API as
         
     | 
| 
       16 
16 
     | 
    
         
             
            # ActiveSupport::Cache (only the methods #fetch and #write are being used).
         
     | 
| 
       17 
17 
     | 
    
         
             
            #
         
     | 
| 
       18 
     | 
    
         
            -
            # The cache_key implementation assumes  
     | 
| 
       19 
     | 
    
         
            -
            #  
     | 
| 
       20 
     | 
    
         
            -
            #  
     | 
| 
      
 18 
     | 
    
         
            +
            # The cache_key implementation by default assumes you pass values that return
         
     | 
| 
      
 19 
     | 
    
         
            +
            # a valid key from #hash (see
         
     | 
| 
      
 20 
     | 
    
         
            +
            # https://www.ruby-doc.org/core/classes/Object.html#M000337). However, you can
         
     | 
| 
      
 21 
     | 
    
         
            +
            # configure your own digest method via which responds to #hexdigest (see
         
     | 
| 
      
 22 
     | 
    
         
            +
            # https://ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL/Digest.html):
         
     | 
| 
      
 23 
     | 
    
         
            +
            #
         
     | 
| 
      
 24 
     | 
    
         
            +
            #   I18n.cache_key_digest = OpenSSL::Digest::SHA256.new
         
     | 
| 
      
 25 
     | 
    
         
            +
            #
         
     | 
| 
      
 26 
     | 
    
         
            +
            # If you use a lambda as a default value in your translation like this:
         
     | 
| 
      
 27 
     | 
    
         
            +
            #
         
     | 
| 
      
 28 
     | 
    
         
            +
            #   I18n.t(:"date.order", :default => lambda {[:month, :day, :year]})
         
     | 
| 
      
 29 
     | 
    
         
            +
            #
         
     | 
| 
      
 30 
     | 
    
         
            +
            # Then you will always have a cache miss, because each time this method
         
     | 
| 
      
 31 
     | 
    
         
            +
            # is called the lambda will have a different hash value. If you know
         
     | 
| 
      
 32 
     | 
    
         
            +
            # the result of the lambda is a constant as in the example above, then
         
     | 
| 
      
 33 
     | 
    
         
            +
            # to cache this you can make the lambda a constant, like this:
         
     | 
| 
      
 34 
     | 
    
         
            +
            #
         
     | 
| 
      
 35 
     | 
    
         
            +
            #   DEFAULT_DATE_ORDER = lambda {[:month, :day, :year]}
         
     | 
| 
      
 36 
     | 
    
         
            +
            #   ...
         
     | 
| 
      
 37 
     | 
    
         
            +
            #   I18n.t(:"date.order", :default => DEFAULT_DATE_ORDER)
         
     | 
| 
      
 38 
     | 
    
         
            +
            #
         
     | 
| 
      
 39 
     | 
    
         
            +
            # If the lambda may result in different values for each call then consider
         
     | 
| 
      
 40 
     | 
    
         
            +
            # also using the Memoize backend.
         
     | 
| 
      
 41 
     | 
    
         
            +
            #
         
     | 
| 
       21 
42 
     | 
    
         
             
            module I18n
         
     | 
| 
       22 
43 
     | 
    
         
             
              class << self
         
     | 
| 
       23 
44 
     | 
    
         
             
                @@cache_store = nil
         
     | 
| 
       24 
45 
     | 
    
         
             
                @@cache_namespace = nil
         
     | 
| 
      
 46 
     | 
    
         
            +
                @@cache_key_digest = nil
         
     | 
| 
       25 
47 
     | 
    
         | 
| 
       26 
48 
     | 
    
         
             
                def cache_store
         
     | 
| 
       27 
49 
     | 
    
         
             
                  @@cache_store
         
     | 
| 
         @@ -39,6 +61,14 @@ module I18n 
     | 
|
| 
       39 
61 
     | 
    
         
             
                  @@cache_namespace = namespace
         
     | 
| 
       40 
62 
     | 
    
         
             
                end
         
     | 
| 
       41 
63 
     | 
    
         | 
| 
      
 64 
     | 
    
         
            +
                def cache_key_digest
         
     | 
| 
      
 65 
     | 
    
         
            +
                  @@cache_key_digest
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def cache_key_digest=(key_digest)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  @@cache_key_digest = key_digest
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
       42 
72 
     | 
    
         
             
                def perform_caching?
         
     | 
| 
       43 
73 
     | 
    
         
             
                  !cache_store.nil?
         
     | 
| 
       44 
74 
     | 
    
         
             
                end
         
     | 
| 
         @@ -47,31 +77,37 @@ module I18n 
     | 
|
| 
       47 
77 
     | 
    
         
             
              module Backend
         
     | 
| 
       48 
78 
     | 
    
         
             
                # TODO Should the cache be cleared if new translations are stored?
         
     | 
| 
       49 
79 
     | 
    
         
             
                module Cache
         
     | 
| 
       50 
     | 
    
         
            -
                  def translate( 
     | 
| 
       51 
     | 
    
         
            -
                    I18n.perform_caching? ? fetch( 
     | 
| 
      
 80 
     | 
    
         
            +
                  def translate(locale, key, options = EMPTY_HASH)
         
     | 
| 
      
 81 
     | 
    
         
            +
                    I18n.perform_caching? ? fetch(cache_key(locale, key, options)) { super } : super
         
     | 
| 
       52 
82 
     | 
    
         
             
                  end
         
     | 
| 
       53 
83 
     | 
    
         | 
| 
       54 
84 
     | 
    
         
             
                  protected
         
     | 
| 
       55 
85 
     | 
    
         | 
| 
       56 
     | 
    
         
            -
                    def fetch( 
     | 
| 
       57 
     | 
    
         
            -
                      result =  
     | 
| 
       58 
     | 
    
         
            -
                       
     | 
| 
      
 86 
     | 
    
         
            +
                    def fetch(cache_key, &block)
         
     | 
| 
      
 87 
     | 
    
         
            +
                      result = _fetch(cache_key, &block)
         
     | 
| 
      
 88 
     | 
    
         
            +
                      throw(:exception, result) if result.is_a?(MissingTranslation)
         
     | 
| 
       59 
89 
     | 
    
         
             
                      result = result.dup if result.frozen? rescue result
         
     | 
| 
       60 
90 
     | 
    
         
             
                      result
         
     | 
| 
       61 
     | 
    
         
            -
                    rescue MissingTranslationData => exception
         
     | 
| 
       62 
     | 
    
         
            -
                      I18n.cache_store.write(cache_key(*args), exception)
         
     | 
| 
       63 
     | 
    
         
            -
                      raise exception
         
     | 
| 
       64 
91 
     | 
    
         
             
                    end
         
     | 
| 
       65 
92 
     | 
    
         | 
| 
       66 
     | 
    
         
            -
                    def cache_key 
     | 
| 
      
 93 
     | 
    
         
            +
                    def _fetch(cache_key, &block)
         
     | 
| 
      
 94 
     | 
    
         
            +
                      result = I18n.cache_store.read(cache_key)
         
     | 
| 
      
 95 
     | 
    
         
            +
                      return result unless result.nil?
         
     | 
| 
      
 96 
     | 
    
         
            +
                      result = catch(:exception, &block)
         
     | 
| 
      
 97 
     | 
    
         
            +
                      I18n.cache_store.write(cache_key, result) unless result.is_a?(Proc)
         
     | 
| 
      
 98 
     | 
    
         
            +
                      result
         
     | 
| 
      
 99 
     | 
    
         
            +
                    end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                    def cache_key(locale, key, options)
         
     | 
| 
       67 
102 
     | 
    
         
             
                      # This assumes that only simple, native Ruby values are passed to I18n.translate.
         
     | 
| 
       68 
     | 
    
         
            -
                       
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
       73 
     | 
    
         
            -
             
     | 
| 
      
 103 
     | 
    
         
            +
                      "i18n/#{I18n.cache_namespace}/#{locale}/#{digest_item(key)}/#{digest_item(options)}"
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                  private
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                    def digest_item(key)
         
     | 
| 
      
 109 
     | 
    
         
            +
                      I18n.cache_key_digest ? I18n.cache_key_digest.hexdigest(key.to_s) : key.to_s.hash
         
     | 
| 
       74 
110 
     | 
    
         
             
                    end
         
     | 
| 
       75 
111 
     | 
    
         
             
                end
         
     | 
| 
       76 
112 
     | 
    
         
             
              end
         
     | 
| 
       77 
     | 
    
         
            -
            end
         
     | 
| 
      
 113 
     | 
    
         
            +
            end
         
     |