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,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The Cascade module adds the ability to do cascading lookups to backends that
|
4
|
+
# are compatible to the Simple backend.
|
5
|
+
#
|
6
|
+
# By cascading lookups we mean that for any key that can not be found the
|
7
|
+
# Cascade module strips one segment off the scope part of the key and then
|
8
|
+
# tries to look up the key in that scope.
|
9
|
+
#
|
10
|
+
# E.g. when a lookup for the key :"foo.bar.baz" does not yield a result then
|
11
|
+
# the segment :bar will be stripped off the scope part :"foo.bar" and the new
|
12
|
+
# scope :foo will be used to look up the key :baz. If that does not succeed
|
13
|
+
# then the remaining scope segment :foo will be omitted, too, and again the
|
14
|
+
# key :baz will be looked up (now with no scope).
|
15
|
+
#
|
16
|
+
# To enable a cascading lookup one passes the :cascade option:
|
17
|
+
#
|
18
|
+
# I18n.t(:'foo.bar.baz', :cascade => true)
|
19
|
+
#
|
20
|
+
# This will return the first translation found for :"foo.bar.baz", :"foo.baz"
|
21
|
+
# or :baz in this order.
|
22
|
+
#
|
23
|
+
# The cascading lookup takes precedence over resolving any given defaults.
|
24
|
+
# I.e. defaults will kick in after the cascading lookups haven't succeeded.
|
25
|
+
#
|
26
|
+
# This behavior is useful for libraries like ActiveRecord validations where
|
27
|
+
# the library wants to give users a bunch of more or less fine-grained options
|
28
|
+
# of scopes for a particular key.
|
29
|
+
#
|
30
|
+
# Thanks to Clemens Kofler for the initial idea and implementation! See
|
31
|
+
# http://github.com/clemens/i18n-cascading-backend
|
32
|
+
|
33
|
+
module I18n
|
34
|
+
module Backend
|
35
|
+
module Cascade
|
36
|
+
def lookup(locale, key, scope = [], options = EMPTY_HASH)
|
37
|
+
return super unless cascade = options[:cascade]
|
38
|
+
|
39
|
+
cascade = { :step => 1 } unless cascade.is_a?(Hash)
|
40
|
+
step = cascade[:step] || 1
|
41
|
+
offset = cascade[:offset] || 1
|
42
|
+
separator = options[:separator] || I18n.default_separator
|
43
|
+
skip_root = cascade.has_key?(:skip_root) ? cascade[:skip_root] : true
|
44
|
+
|
45
|
+
scope = I18n.normalize_keys(nil, key, scope, separator)
|
46
|
+
key = (scope.slice!(-offset, offset) || []).join(separator)
|
47
|
+
|
48
|
+
begin
|
49
|
+
result = super
|
50
|
+
return result unless result.nil?
|
51
|
+
scope = scope.dup
|
52
|
+
end while (!scope.empty? || !skip_root) && scope.slice!(-step, step)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18n
|
4
|
+
module Backend
|
5
|
+
# Backend that chains multiple other backends and checks each of them when
|
6
|
+
# a translation needs to be looked up. This is useful when you want to use
|
7
|
+
# standard translations with a Simple backend but store custom application
|
8
|
+
# translations in a database or other backends.
|
9
|
+
#
|
10
|
+
# To use the Chain backend instantiate it and set it to the I18n module.
|
11
|
+
# You can add chained backends through the initializer or backends
|
12
|
+
# accessor:
|
13
|
+
#
|
14
|
+
# # preserves the existing Simple backend set to I18n.backend
|
15
|
+
# I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)
|
16
|
+
#
|
17
|
+
# The implementation assumes that all backends added to the Chain implement
|
18
|
+
# a lookup method with the same API as Simple backend does.
|
19
|
+
class Chain
|
20
|
+
using I18n::HashRefinements
|
21
|
+
|
22
|
+
module Implementation
|
23
|
+
include Base
|
24
|
+
|
25
|
+
attr_accessor :backends
|
26
|
+
|
27
|
+
def initialize(*backends)
|
28
|
+
self.backends = backends
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialized?
|
32
|
+
backends.all? do |backend|
|
33
|
+
backend.instance_eval do
|
34
|
+
return false unless initialized?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def reload!
|
41
|
+
backends.each { |backend| backend.reload! }
|
42
|
+
end
|
43
|
+
|
44
|
+
def eager_load!
|
45
|
+
backends.each { |backend| backend.eager_load! }
|
46
|
+
end
|
47
|
+
|
48
|
+
def store_translations(locale, data, options = EMPTY_HASH)
|
49
|
+
backends.first.store_translations(locale, data, options)
|
50
|
+
end
|
51
|
+
|
52
|
+
def available_locales
|
53
|
+
backends.map { |backend| backend.available_locales }.flatten.uniq
|
54
|
+
end
|
55
|
+
|
56
|
+
def translate(locale, key, default_options = EMPTY_HASH)
|
57
|
+
namespace = nil
|
58
|
+
options = default_options.except(:default)
|
59
|
+
|
60
|
+
backends.each do |backend|
|
61
|
+
catch(:exception) do
|
62
|
+
options = default_options if backend == backends.last
|
63
|
+
translation = backend.translate(locale, key, options)
|
64
|
+
if namespace_lookup?(translation, options)
|
65
|
+
namespace = _deep_merge(translation, namespace || {})
|
66
|
+
elsif !translation.nil? || (options.key?(:default) && options[:default].nil?)
|
67
|
+
return translation
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
return namespace if namespace
|
73
|
+
throw(:exception, I18n::MissingTranslation.new(locale, key, options))
|
74
|
+
end
|
75
|
+
|
76
|
+
def exists?(locale, key)
|
77
|
+
backends.any? do |backend|
|
78
|
+
backend.exists?(locale, key)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def localize(locale, object, format = :default, options = EMPTY_HASH)
|
83
|
+
backends.each do |backend|
|
84
|
+
catch(:exception) do
|
85
|
+
result = backend.localize(locale, object, format, options) and return result
|
86
|
+
end
|
87
|
+
end
|
88
|
+
throw(:exception, I18n::MissingTranslation.new(locale, format, options))
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
def init_translations
|
93
|
+
backends.each do |backend|
|
94
|
+
backend.send(:init_translations)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def translations
|
99
|
+
backends.first.instance_eval do
|
100
|
+
init_translations unless initialized?
|
101
|
+
translations
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def namespace_lookup?(result, options)
|
106
|
+
result.is_a?(Hash) && !options.has_key?(:count)
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
# This is approximately what gets used in ActiveSupport.
|
111
|
+
# However since we are not guaranteed to run in an ActiveSupport context
|
112
|
+
# it is wise to have our own copy. We underscore it
|
113
|
+
# to not pollute the namespace of the including class.
|
114
|
+
def _deep_merge(hash, other_hash)
|
115
|
+
copy = hash.dup
|
116
|
+
other_hash.each_pair do |k,v|
|
117
|
+
value_from_other = hash[k]
|
118
|
+
copy[k] = value_from_other.is_a?(Hash) && v.is_a?(Hash) ? _deep_merge(value_from_other, v) : v
|
119
|
+
end
|
120
|
+
copy
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
include Implementation
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# I18n locale fallbacks are useful when you want your application to use
|
4
|
+
# translations from other locales when translations for the current locale are
|
5
|
+
# missing. E.g. you might want to use :en translations when translations in
|
6
|
+
# your applications main locale :de are missing.
|
7
|
+
#
|
8
|
+
# To enable locale fallbacks you can simply include the Fallbacks module to
|
9
|
+
# the Simple backend - or whatever other backend you are using:
|
10
|
+
#
|
11
|
+
# I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
|
12
|
+
module I18n
|
13
|
+
@@fallbacks = nil
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# Returns the current fallbacks implementation. Defaults to +I18n::Locale::Fallbacks+.
|
17
|
+
def fallbacks
|
18
|
+
@@fallbacks ||= I18n::Locale::Fallbacks.new
|
19
|
+
end
|
20
|
+
|
21
|
+
# Sets the current fallbacks implementation. Use this to set a different fallbacks implementation.
|
22
|
+
def fallbacks=(fallbacks)
|
23
|
+
@@fallbacks = fallbacks
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module Backend
|
28
|
+
module Fallbacks
|
29
|
+
# Overwrites the Base backend translate method so that it will try each
|
30
|
+
# locale given by I18n.fallbacks for the given locale. E.g. for the
|
31
|
+
# locale :"de-DE" it might try the locales :"de-DE", :de and :en
|
32
|
+
# (depends on the fallbacks implementation) until it finds a result with
|
33
|
+
# the given options. If it does not find any result for any of the
|
34
|
+
# locales it will then throw MissingTranslation as usual.
|
35
|
+
#
|
36
|
+
# The default option takes precedence over fallback locales only when
|
37
|
+
# it's a Symbol. When the default contains a String, Proc or Hash
|
38
|
+
# it is evaluated last after all the fallback locales have been tried.
|
39
|
+
def translate(locale, key, options = EMPTY_HASH)
|
40
|
+
return super unless options.fetch(:fallback, true)
|
41
|
+
return super if options[:fallback_in_progress]
|
42
|
+
default = extract_non_symbol_default!(options) if options[:default]
|
43
|
+
|
44
|
+
fallback_options = options.merge(:fallback_in_progress => true)
|
45
|
+
I18n.fallbacks[locale].each do |fallback|
|
46
|
+
begin
|
47
|
+
catch(:exception) do
|
48
|
+
result = super(fallback, key, fallback_options)
|
49
|
+
return result unless result.nil?
|
50
|
+
end
|
51
|
+
rescue I18n::InvalidLocale
|
52
|
+
# we do nothing when the locale is invalid, as this is a fallback anyways.
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
return if options.key?(:default) && options[:default].nil?
|
57
|
+
|
58
|
+
return super(locale, nil, options.merge(:default => default)) if default
|
59
|
+
throw(:exception, I18n::MissingTranslation.new(locale, key, options))
|
60
|
+
end
|
61
|
+
|
62
|
+
def extract_non_symbol_default!(options)
|
63
|
+
defaults = [options[:default]].flatten
|
64
|
+
first_non_symbol_default = defaults.detect{|default| !default.is_a?(Symbol)}
|
65
|
+
if first_non_symbol_default
|
66
|
+
options[:default] = defaults[0, defaults.index(first_non_symbol_default)]
|
67
|
+
end
|
68
|
+
return first_non_symbol_default
|
69
|
+
end
|
70
|
+
|
71
|
+
def exists?(locale, key)
|
72
|
+
I18n.fallbacks[locale].each do |fallback|
|
73
|
+
begin
|
74
|
+
return true if super(fallback, key)
|
75
|
+
rescue I18n::InvalidLocale
|
76
|
+
# we do nothing when the locale is invalid, as this is a fallback anyways.
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18n
|
4
|
+
module Backend
|
5
|
+
# This module contains several helpers to assist flattening translations.
|
6
|
+
# You may want to flatten translations for:
|
7
|
+
#
|
8
|
+
# 1) speed up lookups, as in the Memoize backend;
|
9
|
+
# 2) In case you want to store translations in a data store, as in ActiveRecord backend;
|
10
|
+
#
|
11
|
+
# You can check both backends above for some examples.
|
12
|
+
# This module also keeps all links in a hash so they can be properly resolved when flattened.
|
13
|
+
module Flatten
|
14
|
+
SEPARATOR_ESCAPE_CHAR = "\001"
|
15
|
+
FLATTEN_SEPARATOR = "."
|
16
|
+
|
17
|
+
# normalize_keys the flatten way. This method is significantly faster
|
18
|
+
# and creates way less objects than the one at I18n.normalize_keys.
|
19
|
+
# It also handles escaping the translation keys.
|
20
|
+
def self.normalize_flat_keys(locale, key, scope, separator)
|
21
|
+
keys = [scope, key].flatten.compact
|
22
|
+
separator ||= I18n.default_separator
|
23
|
+
|
24
|
+
if separator != FLATTEN_SEPARATOR
|
25
|
+
keys.map! do |k|
|
26
|
+
k.to_s.tr("#{FLATTEN_SEPARATOR}#{separator}",
|
27
|
+
"#{SEPARATOR_ESCAPE_CHAR}#{FLATTEN_SEPARATOR}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
keys.join(".")
|
32
|
+
end
|
33
|
+
|
34
|
+
# Receives a string and escape the default separator.
|
35
|
+
def self.escape_default_separator(key) #:nodoc:
|
36
|
+
key.to_s.tr(FLATTEN_SEPARATOR, SEPARATOR_ESCAPE_CHAR)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Shortcut to I18n::Backend::Flatten.normalize_flat_keys
|
40
|
+
# and then resolve_links.
|
41
|
+
def normalize_flat_keys(locale, key, scope, separator)
|
42
|
+
key = I18n::Backend::Flatten.normalize_flat_keys(locale, key, scope, separator)
|
43
|
+
resolve_link(locale, key)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Store flattened links.
|
47
|
+
def links
|
48
|
+
@links ||= I18n.new_double_nested_cache
|
49
|
+
end
|
50
|
+
|
51
|
+
# Flatten keys for nested Hashes by chaining up keys:
|
52
|
+
#
|
53
|
+
# >> { "a" => { "b" => { "c" => "d", "e" => "f" }, "g" => "h" }, "i" => "j"}.wind
|
54
|
+
# => { "a.b.c" => "d", "a.b.e" => "f", "a.g" => "h", "i" => "j" }
|
55
|
+
#
|
56
|
+
def flatten_keys(hash, escape, prev_key=nil, &block)
|
57
|
+
hash.each_pair do |key, value|
|
58
|
+
key = escape_default_separator(key) if escape
|
59
|
+
curr_key = [prev_key, key].compact.join(FLATTEN_SEPARATOR).to_sym
|
60
|
+
yield curr_key, value
|
61
|
+
flatten_keys(value, escape, curr_key, &block) if value.is_a?(Hash)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Receives a hash of translations (where the key is a locale and
|
66
|
+
# the value is another hash) and return a hash with all
|
67
|
+
# translations flattened.
|
68
|
+
#
|
69
|
+
# Nested hashes are included in the flattened hash just if subtree
|
70
|
+
# is true and Symbols are automatically stored as links.
|
71
|
+
def flatten_translations(locale, data, escape, subtree)
|
72
|
+
hash = {}
|
73
|
+
flatten_keys(data, escape) do |key, value|
|
74
|
+
if value.is_a?(Hash)
|
75
|
+
hash[key] = value if subtree
|
76
|
+
else
|
77
|
+
store_link(locale, key, value) if value.is_a?(Symbol)
|
78
|
+
hash[key] = value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
hash
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
def store_link(locale, key, link)
|
87
|
+
links[locale.to_sym][key.to_s] = link.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
def resolve_link(locale, key)
|
91
|
+
key, locale = key.to_s, locale.to_sym
|
92
|
+
links = self.links[locale]
|
93
|
+
|
94
|
+
if links.key?(key)
|
95
|
+
links[key]
|
96
|
+
elsif link = find_link(locale, key)
|
97
|
+
store_link(locale, key, key.gsub(*link))
|
98
|
+
else
|
99
|
+
key
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def find_link(locale, key) #:nodoc:
|
104
|
+
links[locale].each_pair do |from, to|
|
105
|
+
return [from, to] if key[0, from.length] == from
|
106
|
+
end && nil
|
107
|
+
end
|
108
|
+
|
109
|
+
def escape_default_separator(key) #:nodoc:
|
110
|
+
I18n::Backend::Flatten.escape_default_separator(key)
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'i18n/gettext'
|
4
|
+
require 'i18n/gettext/po_parser'
|
5
|
+
|
6
|
+
module I18n
|
7
|
+
module Backend
|
8
|
+
# Experimental support for using Gettext po files to store translations.
|
9
|
+
#
|
10
|
+
# To use this you can simply include the module to the Simple backend - or
|
11
|
+
# whatever other backend you are using.
|
12
|
+
#
|
13
|
+
# I18n::Backend::Simple.include(I18n::Backend::Gettext)
|
14
|
+
#
|
15
|
+
# Now you should be able to include your Gettext translation (*.po) files to
|
16
|
+
# the +I18n.load_path+ so they're loaded to the backend and you can use them as
|
17
|
+
# usual:
|
18
|
+
#
|
19
|
+
# I18n.load_path += Dir["path/to/locales/*.po"]
|
20
|
+
#
|
21
|
+
# Following the Gettext convention this implementation expects that your
|
22
|
+
# translation files are named by their locales. E.g. the file en.po would
|
23
|
+
# contain the translations for the English locale.
|
24
|
+
#
|
25
|
+
# To translate text <b>you must use</b> one of the translate methods provided by
|
26
|
+
# I18n::Gettext::Helpers.
|
27
|
+
#
|
28
|
+
# include I18n::Gettext::Helpers
|
29
|
+
# puts _("some string")
|
30
|
+
#
|
31
|
+
# Without it strings containing periods (".") will not be translated.
|
32
|
+
|
33
|
+
module Gettext
|
34
|
+
using I18n::HashRefinements
|
35
|
+
|
36
|
+
class PoData < Hash
|
37
|
+
def set_comment(msgid_or_sym, comment)
|
38
|
+
# ignore
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
def load_po(filename)
|
44
|
+
locale = ::File.basename(filename, '.po').to_sym
|
45
|
+
data = normalize(locale, parse(filename))
|
46
|
+
{ locale => data }
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse(filename)
|
50
|
+
GetText::PoParser.new.parse(::File.read(filename), PoData.new)
|
51
|
+
end
|
52
|
+
|
53
|
+
def normalize(locale, data)
|
54
|
+
data.inject({}) do |result, (key, value)|
|
55
|
+
unless key.nil? || key.empty?
|
56
|
+
key = key.gsub(I18n::Gettext::CONTEXT_SEPARATOR, '|')
|
57
|
+
key, value = normalize_pluralization(locale, key, value) if key.index("\000")
|
58
|
+
|
59
|
+
parts = key.split('|').reverse
|
60
|
+
normalized = parts.inject({}) do |_normalized, part|
|
61
|
+
{ part => _normalized.empty? ? value : _normalized }
|
62
|
+
end
|
63
|
+
|
64
|
+
result.deep_merge!(normalized)
|
65
|
+
end
|
66
|
+
result
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def normalize_pluralization(locale, key, value)
|
71
|
+
# FIXME po_parser includes \000 chars that can not be turned into Symbols
|
72
|
+
key = key.gsub("\000", I18n::Gettext::PLURAL_SEPARATOR).split(I18n::Gettext::PLURAL_SEPARATOR).first
|
73
|
+
|
74
|
+
keys = I18n::Gettext.plural_keys(locale)
|
75
|
+
values = value.split("\000")
|
76
|
+
raise "invalid number of plurals: #{values.size}, keys: #{keys.inspect} on #{locale} locale for msgid #{key.inspect} with values #{values.inspect}" if values.size != keys.size
|
77
|
+
|
78
|
+
result = {}
|
79
|
+
values.each_with_index { |_value, ix| result[keys[ix]] = _value }
|
80
|
+
[key, result]
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|