i18n 1.6.0
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 +20 -0
- data/README.md +125 -0
- data/lib/i18n.rb +398 -0
- data/lib/i18n/backend.rb +21 -0
- data/lib/i18n/backend/base.rb +284 -0
- data/lib/i18n/backend/cache.rb +113 -0
- data/lib/i18n/backend/cache_file.rb +36 -0
- data/lib/i18n/backend/cascade.rb +56 -0
- data/lib/i18n/backend/chain.rb +127 -0
- data/lib/i18n/backend/fallbacks.rb +84 -0
- data/lib/i18n/backend/flatten.rb +115 -0
- data/lib/i18n/backend/gettext.rb +85 -0
- data/lib/i18n/backend/interpolation_compiler.rb +123 -0
- data/lib/i18n/backend/key_value.rb +206 -0
- data/lib/i18n/backend/memoize.rb +54 -0
- data/lib/i18n/backend/metadata.rb +71 -0
- data/lib/i18n/backend/pluralization.rb +55 -0
- data/lib/i18n/backend/simple.rb +111 -0
- data/lib/i18n/backend/transliterator.rb +108 -0
- data/lib/i18n/config.rb +165 -0
- data/lib/i18n/core_ext/hash.rb +47 -0
- data/lib/i18n/exceptions.rb +111 -0
- data/lib/i18n/gettext.rb +28 -0
- data/lib/i18n/gettext/helpers.rb +75 -0
- data/lib/i18n/gettext/po_parser.rb +329 -0
- data/lib/i18n/interpolate/ruby.rb +39 -0
- data/lib/i18n/locale.rb +8 -0
- data/lib/i18n/locale/fallbacks.rb +96 -0
- data/lib/i18n/locale/tag.rb +28 -0
- data/lib/i18n/locale/tag/parents.rb +22 -0
- data/lib/i18n/locale/tag/rfc4646.rb +74 -0
- data/lib/i18n/locale/tag/simple.rb +39 -0
- data/lib/i18n/middleware.rb +17 -0
- data/lib/i18n/tests.rb +14 -0
- data/lib/i18n/tests/basics.rb +60 -0
- data/lib/i18n/tests/defaults.rb +52 -0
- data/lib/i18n/tests/interpolation.rb +159 -0
- data/lib/i18n/tests/link.rb +56 -0
- data/lib/i18n/tests/localization.rb +19 -0
- data/lib/i18n/tests/localization/date.rb +117 -0
- data/lib/i18n/tests/localization/date_time.rb +103 -0
- data/lib/i18n/tests/localization/procs.rb +116 -0
- data/lib/i18n/tests/localization/time.rb +103 -0
- data/lib/i18n/tests/lookup.rb +81 -0
- data/lib/i18n/tests/pluralization.rb +35 -0
- data/lib/i18n/tests/procs.rb +55 -0
- data/lib/i18n/version.rb +5 -0
- 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
|