summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/i18n-1.15.2/lib/i18n/backend/key_value.rb
blob: b937e253a893c6756cb54c42871934f18a738d52 (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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# frozen_string_literal: true

require 'i18n/backend/base'

module I18n

  begin
    require 'oj'
    class JSON
      class << self
        def encode(value)
          Oj::Rails.encode(value)
        end
        def decode(value)
          Oj.load(value)
        end
      end
    end
  rescue LoadError
    require 'active_support/json'
    JSON = ActiveSupport::JSON
  end

  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 initialized?
          !@store.nil?
        end

        def store_translations(locale, data, options = EMPTY_HASH)
          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 = JSON.decode(old_value)
                value = Utils.deep_merge!(Utils.deep_symbolize_keys(old_value), value) if old_value.is_a?(Hash)
              end
            when Proc
              raise "Key-value stores cannot handle procs"
            end

            @store[key] = 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

        # Queries the translations from the key-value store and converts
        # them into a hash such as the one returned from loading the
        # haml files
        def translations
          @translations = Utils.deep_symbolize_keys(@store.keys.clone.map do |main_key|
            main_value = JSON.decode(@store[main_key])
            main_key.to_s.split(".").reverse.inject(main_value) do |value, key|
              {key.to_sym => value}
            end
          end.inject{|hash, elem| Utils.deep_merge!(hash, elem)})
        end

        def init_translations
          # NO OP
          # This call made also inside Simple Backend and accessed by
          # other plugins like I18n-js and babilu and
          # to use it along with the Chain backend we need to
          # provide a uniform API even for protected methods :S
        end

        def subtrees?
          @subtrees
        end

        def lookup(locale, key, scope = [], options = EMPTY_HASH)
          key   = normalize_flat_keys(locale, key, scope, options[:separator])
          value = @store["#{locale}.#{key}"]
          value = JSON.decode(value) if value

          if value.is_a?(Hash)
            Utils.deep_symbolize_keys(value)
          elsif !value.nil?
            value
          elsif !@subtrees
            SubtreeProxy.new("#{locale}.#{key}", @store)
          end
        end

        def pluralize(locale, entry, count)
          if subtrees?
            super
          else
            return entry unless entry.is_a?(Hash)
            key = pluralization_key(entry, count)
            entry[key]
          end
        end
      end

      class SubtreeProxy
        def initialize(master_key, store)
          @master_key = master_key
          @store = store
          @subtree = nil
        end

        def has_key?(key)
          @subtree && @subtree.has_key?(key) || self[key]
        end

        def [](key)
          unless @subtree && value = @subtree[key]
            value = @store["#{@master_key}.#{key}"]
            if value
              value = JSON.decode(value)
              (@subtree ||= {})[key] = value
            end
          end
          value
        end

        def is_a?(klass)
          Hash == klass || super
        end
        alias :kind_of? :is_a?

        def instance_of?(klass)
          Hash == klass || super
        end

        def nil?
          @subtree.nil?
        end

        def inspect
          @subtree.inspect
        end
      end

      include Implementation
    end
  end
end