summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/i18n-1.14.7/lib/i18n/backend/chain.rb
blob: e081a91c2a3f2d34fcace045489abfb9c7ad7a05 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# frozen_string_literal: true

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.
    # 
    # Fallback translations using the :default option are only used by the last backend of a chain.
    class Chain
      module Implementation
        include Base

        attr_accessor :backends

        def initialize(*backends)
          self.backends = backends
        end

        def initialized?
          backends.all? do |backend|
            backend.instance_eval do
              return false unless initialized?
            end
          end
          true
        end

        def reload!
          backends.each { |backend| backend.reload! }
        end

        def eager_load!
          backends.each { |backend| backend.eager_load! }
        end

        def store_translations(locale, data, options = EMPTY_HASH)
          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 = EMPTY_HASH)
          namespace = nil
          options = Utils.except(default_options, :default)

          backends.each do |backend|
            catch(:exception) do
              options = default_options if backend == backends.last
              translation = backend.translate(locale, key, options)
              if namespace_lookup?(translation, options)
                namespace = _deep_merge(translation, namespace || {})
              elsif !translation.nil? || (options.key?(:default) && options[:default].nil?)
                return translation
              end
            end
          end

          return namespace if namespace
          throw(:exception, I18n::MissingTranslation.new(locale, key, options))
        end

        def exists?(locale, key, options = EMPTY_HASH)
          backends.any? do |backend|
            backend.exists?(locale, key, options)
          end
        end

        def localize(locale, object, format = :default, options = EMPTY_HASH)
          backends.each do |backend|
            catch(:exception) do
              result = backend.localize(locale, object, format, options) and return result
            end
          end
          throw(:exception, I18n::MissingTranslation.new(locale, format, options))
        end

        protected
          def init_translations
            backends.each do |backend|
              backend.send(:init_translations)
            end
          end

          def translations
            backends.reverse.each_with_object({}) do |backend, memo|
              partial_translations = backend.instance_eval do
                init_translations unless initialized?
                translations
              end
              Utils.deep_merge!(memo, partial_translations) { |_, a, b| b || a }
            end
          end

          def namespace_lookup?(result, options)
            result.is_a?(Hash) && !options.has_key?(:count)
          end

        private
          # This is approximately what gets used in ActiveSupport.
          # However since we are not guaranteed to run in an ActiveSupport context
          # it is wise to have our own copy. We underscore it
          # to not pollute the namespace of the including class.
          def _deep_merge(hash, other_hash)
            copy = hash.dup
            other_hash.each_pair do |k,v|
              value_from_other = hash[k]
              copy[k] = value_from_other.is_a?(Hash) && v.is_a?(Hash) ? _deep_merge(value_from_other, v) : v
            end
            copy
          end
      end

      include Implementation
    end
  end
end