i18n 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +125 -0
  4. data/lib/i18n.rb +398 -0
  5. data/lib/i18n/backend.rb +21 -0
  6. data/lib/i18n/backend/base.rb +284 -0
  7. data/lib/i18n/backend/cache.rb +113 -0
  8. data/lib/i18n/backend/cache_file.rb +36 -0
  9. data/lib/i18n/backend/cascade.rb +56 -0
  10. data/lib/i18n/backend/chain.rb +127 -0
  11. data/lib/i18n/backend/fallbacks.rb +84 -0
  12. data/lib/i18n/backend/flatten.rb +115 -0
  13. data/lib/i18n/backend/gettext.rb +85 -0
  14. data/lib/i18n/backend/interpolation_compiler.rb +123 -0
  15. data/lib/i18n/backend/key_value.rb +206 -0
  16. data/lib/i18n/backend/memoize.rb +54 -0
  17. data/lib/i18n/backend/metadata.rb +71 -0
  18. data/lib/i18n/backend/pluralization.rb +55 -0
  19. data/lib/i18n/backend/simple.rb +111 -0
  20. data/lib/i18n/backend/transliterator.rb +108 -0
  21. data/lib/i18n/config.rb +165 -0
  22. data/lib/i18n/core_ext/hash.rb +47 -0
  23. data/lib/i18n/exceptions.rb +111 -0
  24. data/lib/i18n/gettext.rb +28 -0
  25. data/lib/i18n/gettext/helpers.rb +75 -0
  26. data/lib/i18n/gettext/po_parser.rb +329 -0
  27. data/lib/i18n/interpolate/ruby.rb +39 -0
  28. data/lib/i18n/locale.rb +8 -0
  29. data/lib/i18n/locale/fallbacks.rb +96 -0
  30. data/lib/i18n/locale/tag.rb +28 -0
  31. data/lib/i18n/locale/tag/parents.rb +22 -0
  32. data/lib/i18n/locale/tag/rfc4646.rb +74 -0
  33. data/lib/i18n/locale/tag/simple.rb +39 -0
  34. data/lib/i18n/middleware.rb +17 -0
  35. data/lib/i18n/tests.rb +14 -0
  36. data/lib/i18n/tests/basics.rb +60 -0
  37. data/lib/i18n/tests/defaults.rb +52 -0
  38. data/lib/i18n/tests/interpolation.rb +159 -0
  39. data/lib/i18n/tests/link.rb +56 -0
  40. data/lib/i18n/tests/localization.rb +19 -0
  41. data/lib/i18n/tests/localization/date.rb +117 -0
  42. data/lib/i18n/tests/localization/date_time.rb +103 -0
  43. data/lib/i18n/tests/localization/procs.rb +116 -0
  44. data/lib/i18n/tests/localization/time.rb +103 -0
  45. data/lib/i18n/tests/lookup.rb +81 -0
  46. data/lib/i18n/tests/pluralization.rb +35 -0
  47. data/lib/i18n/tests/procs.rb +55 -0
  48. data/lib/i18n/version.rb +5 -0
  49. metadata +124 -0
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The InterpolationCompiler module contains optimizations that can tremendously
4
+ # speed up the interpolation process on the Simple backend.
5
+ #
6
+ # It works by defining a pre-compiled method on stored translation Strings that
7
+ # already bring all the knowledge about contained interpolation variables etc.
8
+ # so that the actual recurring interpolation will be very fast.
9
+ #
10
+ # To enable pre-compiled interpolations you can simply include the
11
+ # InterpolationCompiler module to the Simple backend:
12
+ #
13
+ # I18n::Backend::Simple.include(I18n::Backend::InterpolationCompiler)
14
+ #
15
+ # Note that InterpolationCompiler does not yield meaningful results and consequently
16
+ # should not be used with Ruby 1.9 (YARV) but improves performance everywhere else
17
+ # (jRuby, Rubinius).
18
+ module I18n
19
+ module Backend
20
+ module InterpolationCompiler
21
+ module Compiler
22
+ extend self
23
+
24
+ TOKENIZER = /(%%\{[^\}]+\}|%\{[^\}]+\})/
25
+ INTERPOLATION_SYNTAX_PATTERN = /(%)?(%\{([^\}]+)\})/
26
+
27
+ def compile_if_an_interpolation(string)
28
+ if interpolated_str?(string)
29
+ string.instance_eval <<-RUBY_EVAL, __FILE__, __LINE__
30
+ def i18n_interpolate(v = {})
31
+ "#{compiled_interpolation_body(string)}"
32
+ end
33
+ RUBY_EVAL
34
+ end
35
+
36
+ string
37
+ end
38
+
39
+ def interpolated_str?(str)
40
+ str.kind_of?(::String) && str =~ INTERPOLATION_SYNTAX_PATTERN
41
+ end
42
+
43
+ protected
44
+ # tokenize("foo %{bar} baz %%{buz}") # => ["foo ", "%{bar}", " baz ", "%%{buz}"]
45
+ def tokenize(str)
46
+ str.split(TOKENIZER)
47
+ end
48
+
49
+ def compiled_interpolation_body(str)
50
+ tokenize(str).map do |token|
51
+ (matchdata = token.match(INTERPOLATION_SYNTAX_PATTERN)) ? handle_interpolation_token(token, matchdata) : escape_plain_str(token)
52
+ end.join
53
+ end
54
+
55
+ def handle_interpolation_token(interpolation, matchdata)
56
+ escaped, pattern, key = matchdata.values_at(1, 2, 3)
57
+ escaped ? pattern : compile_interpolation_token(key.to_sym)
58
+ end
59
+
60
+ def compile_interpolation_token(key)
61
+ "\#{#{interpolate_or_raise_missing(key)}}"
62
+ end
63
+
64
+ def interpolate_or_raise_missing(key)
65
+ escaped_key = escape_key_sym(key)
66
+ RESERVED_KEYS.include?(key) ? reserved_key(escaped_key) : interpolate_key(escaped_key)
67
+ end
68
+
69
+ def interpolate_key(key)
70
+ [direct_key(key), nil_key(key), missing_key(key)].join('||')
71
+ end
72
+
73
+ def direct_key(key)
74
+ "((t = v[#{key}]) && t.respond_to?(:call) ? t.call : t)"
75
+ end
76
+
77
+ def nil_key(key)
78
+ "(v.has_key?(#{key}) && '')"
79
+ end
80
+
81
+ def missing_key(key)
82
+ "I18n.config.missing_interpolation_argument_handler.call(#{key}, v, self)"
83
+ end
84
+
85
+ def reserved_key(key)
86
+ "raise(ReservedInterpolationKey.new(#{key}, self))"
87
+ end
88
+
89
+ def escape_plain_str(str)
90
+ str.gsub(/"|\\|#/) {|x| "\\#{x}"}
91
+ end
92
+
93
+ def escape_key_sym(key)
94
+ # rely on Ruby to do all the hard work :)
95
+ key.to_sym.inspect
96
+ end
97
+ end
98
+
99
+ def interpolate(locale, string, values)
100
+ if string.respond_to?(:i18n_interpolate)
101
+ string.i18n_interpolate(values)
102
+ elsif values
103
+ super
104
+ else
105
+ string
106
+ end
107
+ end
108
+
109
+ def store_translations(locale, data, options = EMPTY_HASH)
110
+ compile_all_strings_in(data)
111
+ super
112
+ end
113
+
114
+ protected
115
+ def compile_all_strings_in(data)
116
+ data.each_value do |value|
117
+ Compiler.compile_if_an_interpolation(value)
118
+ compile_all_strings_in(value) if value.kind_of?(Hash)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/backend/base'
4
+
5
+ module I18n
6
+
7
+ begin
8
+ require 'oj'
9
+ class JSON
10
+ class << self
11
+ def encode(value)
12
+ Oj::Rails.encode(value)
13
+ end
14
+ def decode(value)
15
+ Oj.load(value)
16
+ end
17
+ end
18
+ end
19
+ rescue LoadError
20
+ require 'active_support/json'
21
+ JSON = ActiveSupport::JSON
22
+ end
23
+
24
+ module Backend
25
+ # This is a basic backend for key value stores. It receives on
26
+ # initialization the store, which should respond to three methods:
27
+ #
28
+ # * store#[](key) - Used to get a value
29
+ # * store#[]=(key, value) - Used to set a value
30
+ # * store#keys - Used to get all keys
31
+ #
32
+ # Since these stores only supports string, all values are converted
33
+ # to JSON before being stored, allowing it to also store booleans,
34
+ # hashes and arrays. However, this store does not support Procs.
35
+ #
36
+ # As the ActiveRecord backend, Symbols are just supported when loading
37
+ # translations from the filesystem or through explicit store translations.
38
+ #
39
+ # Also, avoid calling I18n.available_locales since it's a somehow
40
+ # expensive operation in most stores.
41
+ #
42
+ # == Example
43
+ #
44
+ # To setup I18n to use TokyoCabinet in memory is quite straightforward:
45
+ #
46
+ # require 'rufus/tokyo/cabinet' # gem install rufus-tokyo
47
+ # I18n.backend = I18n::Backend::KeyValue.new(Rufus::Tokyo::Cabinet.new('*'))
48
+ #
49
+ # == Performance
50
+ #
51
+ # You may make this backend even faster by including the Memoize module.
52
+ # However, notice that you should properly clear the cache if you change
53
+ # values directly in the key-store.
54
+ #
55
+ # == Subtrees
56
+ #
57
+ # In most backends, you are allowed to retrieve part of a translation tree:
58
+ #
59
+ # I18n.backend.store_translations :en, :foo => { :bar => :baz }
60
+ # I18n.t "foo" #=> { :bar => :baz }
61
+ #
62
+ # This backend supports this feature by default, but it slows down the storage
63
+ # of new data considerably and makes hard to delete entries. That said, you are
64
+ # allowed to disable the storage of subtrees on initialization:
65
+ #
66
+ # I18n::Backend::KeyValue.new(@store, false)
67
+ #
68
+ # This is useful if you are using a KeyValue backend chained to a Simple backend.
69
+ class KeyValue
70
+ using I18n::HashRefinements
71
+
72
+ module Implementation
73
+ attr_accessor :store
74
+
75
+ include Base, Flatten
76
+
77
+ def initialize(store, subtrees=true)
78
+ @store, @subtrees = store, subtrees
79
+ end
80
+
81
+ def initialized?
82
+ !@store.nil?
83
+ end
84
+
85
+ def store_translations(locale, data, options = EMPTY_HASH)
86
+ escape = options.fetch(:escape, true)
87
+ flatten_translations(locale, data, escape, @subtrees).each do |key, value|
88
+ key = "#{locale}.#{key}"
89
+
90
+ case value
91
+ when Hash
92
+ if @subtrees && (old_value = @store[key])
93
+ old_value = JSON.decode(old_value)
94
+ value = old_value.deep_symbolize_keys.deep_merge!(value) if old_value.is_a?(Hash)
95
+ end
96
+ when Proc
97
+ raise "Key-value stores cannot handle procs"
98
+ end
99
+
100
+ @store[key] = JSON.encode(value) unless value.is_a?(Symbol)
101
+ end
102
+ end
103
+
104
+ def available_locales
105
+ locales = @store.keys.map { |k| k =~ /\./; $` }
106
+ locales.uniq!
107
+ locales.compact!
108
+ locales.map! { |k| k.to_sym }
109
+ locales
110
+ end
111
+
112
+ protected
113
+
114
+ # Queries the translations from the key-value store and converts
115
+ # them into a hash such as the one returned from loading the
116
+ # haml files
117
+ def translations
118
+ @translations = @store.keys.clone.map do |main_key|
119
+ main_value = JSON.decode(@store[main_key])
120
+ main_key.to_s.split(".").reverse.inject(main_value) do |value, key|
121
+ {key.to_sym => value}
122
+ end
123
+ end.inject{|hash, elem| hash.deep_merge!(elem)}.deep_symbolize_keys
124
+ end
125
+
126
+ def init_translations
127
+ # NO OP
128
+ # This call made also inside Simple Backend and accessed by
129
+ # other plugins like I18n-js and babilu and
130
+ # to use it along with the Chain backend we need to
131
+ # provide a uniform API even for protected methods :S
132
+ end
133
+
134
+ def subtrees?
135
+ @subtrees
136
+ end
137
+
138
+ def lookup(locale, key, scope = [], options = EMPTY_HASH)
139
+ key = normalize_flat_keys(locale, key, scope, options[:separator])
140
+ value = @store["#{locale}.#{key}"]
141
+ value = JSON.decode(value) if value
142
+
143
+ if value.is_a?(Hash)
144
+ value.deep_symbolize_keys
145
+ elsif !value.nil?
146
+ value
147
+ elsif !@subtrees
148
+ SubtreeProxy.new("#{locale}.#{key}", @store)
149
+ end
150
+ end
151
+
152
+ def pluralize(locale, entry, count)
153
+ if subtrees?
154
+ super
155
+ else
156
+ return entry unless entry.is_a?(Hash)
157
+ key = pluralization_key(entry, count)
158
+ entry[key]
159
+ end
160
+ end
161
+ end
162
+
163
+ class SubtreeProxy
164
+ def initialize(master_key, store)
165
+ @master_key = master_key
166
+ @store = store
167
+ @subtree = nil
168
+ end
169
+
170
+ def has_key?(key)
171
+ @subtree && @subtree.has_key?(key) || self[key]
172
+ end
173
+
174
+ def [](key)
175
+ unless @subtree && value = @subtree[key]
176
+ value = @store["#{@master_key}.#{key}"]
177
+ if value
178
+ value = JSON.decode(value)
179
+ (@subtree ||= {})[key] = value
180
+ end
181
+ end
182
+ value
183
+ end
184
+
185
+ def is_a?(klass)
186
+ Hash == klass || super
187
+ end
188
+ alias :kind_of? :is_a?
189
+
190
+ def instance_of?(klass)
191
+ Hash == klass || super
192
+ end
193
+
194
+ def nil?
195
+ @subtree.nil?
196
+ end
197
+
198
+ def inspect
199
+ @subtree.inspect
200
+ end
201
+ end
202
+
203
+ include Implementation
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Memoize module simply memoizes the values returned by lookup using
4
+ # a flat hash and can tremendously speed up the lookup process in a backend.
5
+ #
6
+ # To enable it you can simply include the Memoize module to your backend:
7
+ #
8
+ # I18n::Backend::Simple.include(I18n::Backend::Memoize)
9
+ #
10
+ # Notice that it's the responsibility of the backend to define whenever the
11
+ # cache should be cleaned.
12
+ module I18n
13
+ module Backend
14
+ module Memoize
15
+ def available_locales
16
+ @memoized_locales ||= super
17
+ end
18
+
19
+ def store_translations(locale, data, options = EMPTY_HASH)
20
+ reset_memoizations!(locale)
21
+ super
22
+ end
23
+
24
+ def reload!
25
+ reset_memoizations!
26
+ super
27
+ end
28
+
29
+ def eager_load!
30
+ memoized_lookup
31
+ available_locales
32
+ super
33
+ end
34
+
35
+ protected
36
+
37
+ def lookup(locale, key, scope = nil, options = EMPTY_HASH)
38
+ flat_key = I18n::Backend::Flatten.normalize_flat_keys(locale,
39
+ key, scope, options[:separator]).to_sym
40
+ flat_hash = memoized_lookup[locale.to_sym]
41
+ flat_hash.key?(flat_key) ? flat_hash[flat_key] : (flat_hash[flat_key] = super)
42
+ end
43
+
44
+ def memoized_lookup
45
+ @memoized_lookup ||= I18n.new_double_nested_cache
46
+ end
47
+
48
+ def reset_memoizations!(locale=nil)
49
+ @memoized_locales = nil
50
+ (locale ? memoized_lookup[locale.to_sym] : memoized_lookup).clear
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # I18n translation metadata is useful when you want to access information
4
+ # about how a translation was looked up, pluralized or interpolated in
5
+ # your application.
6
+ #
7
+ # msg = I18n.t(:message, :default => 'Hi!', :scope => :foo)
8
+ # msg.translation_metadata
9
+ # # => { :key => :message, :scope => :foo, :default => 'Hi!' }
10
+ #
11
+ # If a :count option was passed to #translate it will be set to the metadata.
12
+ # Likewise, if any interpolation variables were passed they will also be set.
13
+ #
14
+ # To enable translation metadata you can simply include the Metadata module
15
+ # into the Simple backend class - or whatever other backend you are using:
16
+ #
17
+ # I18n::Backend::Simple.include(I18n::Backend::Metadata)
18
+ #
19
+ module I18n
20
+ module Backend
21
+ module Metadata
22
+ class << self
23
+ def included(base)
24
+ Object.class_eval do
25
+ def translation_metadata
26
+ unless self.frozen?
27
+ @translation_metadata ||= {}
28
+ else
29
+ {}
30
+ end
31
+ end
32
+
33
+ def translation_metadata=(translation_metadata)
34
+ @translation_metadata = translation_metadata unless self.frozen?
35
+ end
36
+ end unless Object.method_defined?(:translation_metadata)
37
+ end
38
+ end
39
+
40
+ def translate(locale, key, options = EMPTY_HASH)
41
+ metadata = {
42
+ :locale => locale,
43
+ :key => key,
44
+ :scope => options[:scope],
45
+ :default => options[:default],
46
+ :separator => options[:separator],
47
+ :values => options.reject { |name, _value| RESERVED_KEYS.include?(name) }
48
+ }
49
+ with_metadata(metadata) { super }
50
+ end
51
+
52
+ def interpolate(locale, entry, values = EMPTY_HASH)
53
+ metadata = entry.translation_metadata.merge(:original => entry)
54
+ with_metadata(metadata) { super }
55
+ end
56
+
57
+ def pluralize(locale, entry, count)
58
+ with_metadata(:count => count) { super }
59
+ end
60
+
61
+ protected
62
+
63
+ def with_metadata(metadata, &block)
64
+ result = yield
65
+ result.translation_metadata = result.translation_metadata.merge(metadata) if result
66
+ result
67
+ end
68
+
69
+ end
70
+ end
71
+ end