i18n 0.4.0 → 1.14.4
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 +0 -0
- data/README.md +127 -0
- data/lib/i18n/backend/base.rb +189 -111
- data/lib/i18n/backend/cache.rb +58 -22
- data/lib/i18n/backend/cache_file.rb +36 -0
- data/lib/i18n/backend/cascade.rb +9 -10
- data/lib/i18n/backend/chain.rb +95 -42
- data/lib/i18n/backend/fallbacks.rb +68 -22
- data/lib/i18n/backend/flatten.rb +13 -8
- data/lib/i18n/backend/gettext.rb +33 -25
- data/lib/i18n/backend/interpolation_compiler.rb +12 -14
- data/lib/i18n/backend/key_value.rb +112 -10
- data/lib/i18n/backend/lazy_loadable.rb +184 -0
- data/lib/i18n/backend/memoize.rb +13 -7
- data/lib/i18n/backend/metadata.rb +12 -6
- data/lib/i18n/backend/pluralization.rb +64 -25
- data/lib/i18n/backend/simple.rb +44 -18
- data/lib/i18n/backend/transliterator.rb +43 -33
- data/lib/i18n/backend.rb +5 -3
- data/lib/i18n/config.rb +91 -10
- data/lib/i18n/exceptions.rb +118 -22
- data/lib/i18n/gettext/helpers.rb +14 -4
- data/lib/i18n/gettext/po_parser.rb +7 -7
- data/lib/i18n/gettext.rb +6 -5
- data/lib/i18n/interpolate/ruby.rb +53 -0
- data/lib/i18n/locale/fallbacks.rb +33 -26
- data/lib/i18n/locale/tag/parents.rb +8 -8
- data/lib/i18n/locale/tag/rfc4646.rb +0 -2
- data/lib/i18n/locale/tag/simple.rb +2 -4
- data/lib/i18n/locale.rb +2 -0
- data/lib/i18n/middleware.rb +17 -0
- data/lib/i18n/tests/basics.rb +58 -0
- data/lib/i18n/tests/defaults.rb +52 -0
- data/lib/i18n/tests/interpolation.rb +167 -0
- data/lib/i18n/tests/link.rb +66 -0
- data/lib/i18n/tests/localization/date.rb +122 -0
- data/lib/i18n/tests/localization/date_time.rb +103 -0
- data/lib/i18n/tests/localization/procs.rb +118 -0
- data/lib/i18n/tests/localization/time.rb +103 -0
- data/lib/i18n/tests/localization.rb +19 -0
- data/lib/i18n/tests/lookup.rb +81 -0
- data/lib/i18n/tests/pluralization.rb +35 -0
- data/lib/i18n/tests/procs.rb +66 -0
- data/lib/i18n/tests.rb +14 -0
- data/lib/i18n/utils.rb +55 -0
- data/lib/i18n/version.rb +3 -1
- data/lib/i18n.rb +200 -87
- metadata +64 -56
- data/CHANGELOG.textile +0 -135
- data/README.textile +0 -93
- data/lib/i18n/backend/active_record/missing.rb +0 -65
- data/lib/i18n/backend/active_record/store_procs.rb +0 -38
- data/lib/i18n/backend/active_record/translation.rb +0 -93
- data/lib/i18n/backend/active_record.rb +0 -61
- data/lib/i18n/backend/cldr.rb +0 -100
- data/lib/i18n/core_ext/hash.rb +0 -29
- data/lib/i18n/core_ext/string/interpolate.rb +0 -98
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module I18n
|
6
|
+
module Backend
|
7
|
+
# Overwrites the Base load_file method to cache loaded file contents.
|
8
|
+
module CacheFile
|
9
|
+
# Optionally provide path_roots array to normalize filename paths,
|
10
|
+
# to make the cached i18n data portable across environments.
|
11
|
+
attr_accessor :path_roots
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
# Track loaded translation files in the `i18n.load_file` scope,
|
16
|
+
# and skip loading the file if its contents are still up-to-date.
|
17
|
+
def load_file(filename)
|
18
|
+
initialized = !respond_to?(:initialized?) || initialized?
|
19
|
+
key = I18n::Backend::Flatten.escape_default_separator(normalized_path(filename))
|
20
|
+
old_mtime, old_digest = initialized && lookup(:i18n, key, :load_file)
|
21
|
+
return if (mtime = File.mtime(filename).to_i) == old_mtime ||
|
22
|
+
(digest = OpenSSL::Digest::SHA256.file(filename).hexdigest) == old_digest
|
23
|
+
super
|
24
|
+
store_translations(:i18n, load_file: { key => [mtime, digest] })
|
25
|
+
end
|
26
|
+
|
27
|
+
# Translate absolute filename to relative path for i18n key.
|
28
|
+
def normalized_path(file)
|
29
|
+
return file unless path_roots
|
30
|
+
path = path_roots.find(&file.method(:start_with?)) ||
|
31
|
+
raise(InvalidLocaleData.new(file, 'outside expected path roots'))
|
32
|
+
file.sub(path, path_roots.index(path).to_s)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/i18n/backend/cascade.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# EXPERIMENTAL
|
4
|
-
#
|
5
3
|
# The Cascade module adds the ability to do cascading lookups to backends that
|
6
4
|
# are compatible to the Simple backend.
|
7
5
|
#
|
@@ -35,22 +33,23 @@
|
|
35
33
|
module I18n
|
36
34
|
module Backend
|
37
35
|
module Cascade
|
38
|
-
def lookup(locale, key, scope = [], options =
|
36
|
+
def lookup(locale, key, scope = [], options = EMPTY_HASH)
|
39
37
|
return super unless cascade = options[:cascade]
|
40
38
|
|
39
|
+
cascade = { :step => 1 } unless cascade.is_a?(Hash)
|
40
|
+
step = cascade[:step] || 1
|
41
|
+
offset = cascade[:offset] || 1
|
41
42
|
separator = options[:separator] || I18n.default_separator
|
42
43
|
skip_root = cascade.has_key?(:skip_root) ? cascade[:skip_root] : true
|
43
|
-
step = cascade[:step]
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
scope = I18n.normalize_keys(nil, nil, scope, separator) + keys
|
48
|
-
key = scope.slice!(-offset, offset).join(separator)
|
45
|
+
scope = I18n.normalize_keys(nil, key, scope, separator)
|
46
|
+
key = (scope.slice!(-offset, offset) || []).join(separator)
|
49
47
|
|
50
48
|
begin
|
51
49
|
result = super
|
52
50
|
return result unless result.nil?
|
53
|
-
|
51
|
+
scope = scope.dup
|
52
|
+
end while (!scope.empty? || !skip_root) && scope.slice!(-step, step)
|
54
53
|
end
|
55
54
|
end
|
56
55
|
end
|
data/lib/i18n/backend/chain.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module I18n
|
4
4
|
module Backend
|
@@ -16,62 +16,115 @@ module I18n
|
|
16
16
|
#
|
17
17
|
# The implementation assumes that all backends added to the Chain implement
|
18
18
|
# a lookup method with the same API as Simple backend does.
|
19
|
+
#
|
20
|
+
# Fallback translations using the :default option are only used by the last backend of a chain.
|
19
21
|
class Chain
|
20
|
-
|
22
|
+
module Implementation
|
23
|
+
include Base
|
21
24
|
|
22
|
-
|
25
|
+
attr_accessor :backends
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
+
def initialize(*backends)
|
28
|
+
self.backends = backends
|
29
|
+
end
|
27
30
|
|
28
|
-
|
29
|
-
|
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
|
31
39
|
|
32
|
-
|
33
|
-
|
34
|
-
|
40
|
+
def reload!
|
41
|
+
backends.each { |backend| backend.reload! }
|
42
|
+
end
|
35
43
|
|
36
|
-
|
37
|
-
|
38
|
-
|
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 = Utils.except(default_options, :default)
|
39
59
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
if namespace_lookup?(translation, options)
|
50
|
-
namespace.update(translation)
|
51
|
-
elsif !translation.nil?
|
52
|
-
return translation
|
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
|
53
69
|
end
|
54
|
-
rescue MissingTranslationData
|
55
70
|
end
|
71
|
+
|
72
|
+
return namespace if namespace
|
73
|
+
throw(:exception, I18n::MissingTranslation.new(locale, key, options))
|
56
74
|
end
|
57
|
-
return namespace unless namespace.empty?
|
58
|
-
raise(I18n::MissingTranslationData.new(locale, key, options))
|
59
|
-
end
|
60
75
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
result = backend.localize(locale, object, format, options) and return result
|
65
|
-
rescue MissingTranslationData
|
76
|
+
def exists?(locale, key, options = EMPTY_HASH)
|
77
|
+
backends.any? do |backend|
|
78
|
+
backend.exists?(locale, key, options)
|
66
79
|
end
|
67
80
|
end
|
68
|
-
raise(I18n::MissingTranslationData.new(locale, format, options))
|
69
|
-
end
|
70
81
|
|
71
|
-
|
72
|
-
|
73
|
-
|
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))
|
74
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.reverse.each_with_object({}) do |backend, memo|
|
100
|
+
partial_translations = backend.instance_eval do
|
101
|
+
init_translations unless initialized?
|
102
|
+
translations
|
103
|
+
end
|
104
|
+
Utils.deep_merge!(memo, partial_translations) { |_, a, b| b || a }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def namespace_lookup?(result, options)
|
109
|
+
result.is_a?(Hash) && !options.has_key?(:count)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
# This is approximately what gets used in ActiveSupport.
|
114
|
+
# However since we are not guaranteed to run in an ActiveSupport context
|
115
|
+
# it is wise to have our own copy. We underscore it
|
116
|
+
# to not pollute the namespace of the including class.
|
117
|
+
def _deep_merge(hash, other_hash)
|
118
|
+
copy = hash.dup
|
119
|
+
other_hash.each_pair do |k,v|
|
120
|
+
value_from_other = hash[k]
|
121
|
+
copy[k] = value_from_other.is_a?(Hash) && v.is_a?(Hash) ? _deep_merge(value_from_other, v) : v
|
122
|
+
end
|
123
|
+
copy
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
include Implementation
|
75
128
|
end
|
76
129
|
end
|
77
130
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# I18n locale fallbacks are useful when you want your application to use
|
4
4
|
# translations from other locales when translations for the current locale are
|
@@ -8,7 +8,7 @@
|
|
8
8
|
# To enable locale fallbacks you can simply include the Fallbacks module to
|
9
9
|
# the Simple backend - or whatever other backend you are using:
|
10
10
|
#
|
11
|
-
# I18n::Backend::Simple.
|
11
|
+
# I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
|
12
12
|
module I18n
|
13
13
|
@@fallbacks = nil
|
14
14
|
|
@@ -16,11 +16,13 @@ module I18n
|
|
16
16
|
# Returns the current fallbacks implementation. Defaults to +I18n::Locale::Fallbacks+.
|
17
17
|
def fallbacks
|
18
18
|
@@fallbacks ||= I18n::Locale::Fallbacks.new
|
19
|
+
Thread.current[:i18n_fallbacks] || @@fallbacks
|
19
20
|
end
|
20
21
|
|
21
22
|
# Sets the current fallbacks implementation. Use this to set a different fallbacks implementation.
|
22
23
|
def fallbacks=(fallbacks)
|
23
|
-
@@fallbacks = fallbacks
|
24
|
+
@@fallbacks = fallbacks.is_a?(Array) ? I18n::Locale::Fallbacks.new(fallbacks) : fallbacks
|
25
|
+
Thread.current[:i18n_fallbacks] = @@fallbacks
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
@@ -31,39 +33,83 @@ module I18n
|
|
31
33
|
# locale :"de-DE" it might try the locales :"de-DE", :de and :en
|
32
34
|
# (depends on the fallbacks implementation) until it finds a result with
|
33
35
|
# the given options. If it does not find any result for any of the
|
34
|
-
# locales it will then
|
35
|
-
# usual.
|
36
|
+
# locales it will then throw MissingTranslation as usual.
|
36
37
|
#
|
37
|
-
# The default option takes precedence over fallback locales
|
38
|
-
#
|
39
|
-
# is evaluated after fallback locales.
|
40
|
-
def translate(locale, key, options =
|
41
|
-
|
38
|
+
# The default option takes precedence over fallback locales only when
|
39
|
+
# it's a Symbol. When the default contains a String, Proc or Hash
|
40
|
+
# it is evaluated last after all the fallback locales have been tried.
|
41
|
+
def translate(locale, key, options = EMPTY_HASH)
|
42
|
+
return super unless options.fetch(:fallback, true)
|
43
|
+
return super if options[:fallback_in_progress]
|
44
|
+
default = extract_non_symbol_default!(options) if options[:default]
|
42
45
|
|
46
|
+
fallback_options = options.merge(:fallback_in_progress => true, fallback_original_locale: locale)
|
43
47
|
I18n.fallbacks[locale].each do |fallback|
|
44
48
|
begin
|
45
|
-
|
46
|
-
|
47
|
-
|
49
|
+
catch(:exception) do
|
50
|
+
result = super(fallback, key, fallback_options)
|
51
|
+
unless result.nil?
|
52
|
+
on_fallback(locale, fallback, key, options) if locale.to_s != fallback.to_s
|
53
|
+
return result
|
54
|
+
end
|
55
|
+
end
|
56
|
+
rescue I18n::InvalidLocale
|
57
|
+
# we do nothing when the locale is invalid, as this is a fallback anyways.
|
48
58
|
end
|
49
59
|
end
|
50
60
|
|
61
|
+
return if options.key?(:default) && options[:default].nil?
|
62
|
+
|
51
63
|
return super(locale, nil, options.merge(:default => default)) if default
|
52
|
-
|
64
|
+
throw(:exception, I18n::MissingTranslation.new(locale, key, options))
|
53
65
|
end
|
54
66
|
|
55
|
-
def
|
56
|
-
|
57
|
-
|
58
|
-
options
|
59
|
-
|
67
|
+
def resolve_entry(locale, object, subject, options = EMPTY_HASH)
|
68
|
+
return subject if options[:resolve] == false
|
69
|
+
result = catch(:exception) do
|
70
|
+
options.delete(:fallback_in_progress) if options.key?(:fallback_in_progress)
|
71
|
+
|
72
|
+
case subject
|
73
|
+
when Symbol
|
74
|
+
I18n.translate(subject, **options.merge(:locale => options[:fallback_original_locale], :throw => true))
|
75
|
+
when Proc
|
76
|
+
date_or_time = options.delete(:object) || object
|
77
|
+
resolve_entry(options[:fallback_original_locale], object, subject.call(date_or_time, **options))
|
78
|
+
else
|
79
|
+
subject
|
80
|
+
end
|
60
81
|
end
|
82
|
+
result unless result.is_a?(MissingTranslation)
|
61
83
|
end
|
62
84
|
|
63
|
-
def
|
64
|
-
defaults
|
65
|
-
|
85
|
+
def extract_non_symbol_default!(options)
|
86
|
+
defaults = [options[:default]].flatten
|
87
|
+
first_non_symbol_default = defaults.detect{|default| !default.is_a?(Symbol)}
|
88
|
+
if first_non_symbol_default
|
89
|
+
options[:default] = defaults[0, defaults.index(first_non_symbol_default)]
|
90
|
+
end
|
91
|
+
return first_non_symbol_default
|
92
|
+
end
|
93
|
+
|
94
|
+
def exists?(locale, key, options = EMPTY_HASH)
|
95
|
+
return super unless options.fetch(:fallback, true)
|
96
|
+
I18n.fallbacks[locale].each do |fallback|
|
97
|
+
begin
|
98
|
+
return true if super(fallback, key, options)
|
99
|
+
rescue I18n::InvalidLocale
|
100
|
+
# we do nothing when the locale is invalid, as this is a fallback anyways.
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
false
|
66
105
|
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
# Overwrite on_fallback to add specified logic when the fallback succeeds.
|
110
|
+
def on_fallback(_original_locale, _fallback_locale, _key, _options)
|
111
|
+
nil
|
112
|
+
end
|
67
113
|
end
|
68
114
|
end
|
69
115
|
end
|
data/lib/i18n/backend/flatten.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module I18n
|
2
4
|
module Backend
|
3
5
|
# This module contains several helpers to assist flattening translations.
|
@@ -16,14 +18,17 @@ module I18n
|
|
16
18
|
# and creates way less objects than the one at I18n.normalize_keys.
|
17
19
|
# It also handles escaping the translation keys.
|
18
20
|
def self.normalize_flat_keys(locale, key, scope, separator)
|
19
|
-
keys = [scope, key]
|
21
|
+
keys = [scope, key]
|
22
|
+
keys.flatten!
|
23
|
+
keys.compact!
|
24
|
+
|
20
25
|
separator ||= I18n.default_separator
|
21
26
|
|
22
27
|
if separator != FLATTEN_SEPARATOR
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
28
|
+
from_str = "#{FLATTEN_SEPARATOR}#{separator}"
|
29
|
+
to_str = "#{SEPARATOR_ESCAPE_CHAR}#{FLATTEN_SEPARATOR}"
|
30
|
+
|
31
|
+
keys.map! { |k| k.to_s.tr from_str, to_str }
|
27
32
|
end
|
28
33
|
|
29
34
|
keys.join(".")
|
@@ -43,7 +48,7 @@ module I18n
|
|
43
48
|
|
44
49
|
# Store flattened links.
|
45
50
|
def links
|
46
|
-
@links ||=
|
51
|
+
@links ||= I18n.new_double_nested_cache
|
47
52
|
end
|
48
53
|
|
49
54
|
# Flatten keys for nested Hashes by chaining up keys:
|
@@ -99,7 +104,7 @@ module I18n
|
|
99
104
|
end
|
100
105
|
|
101
106
|
def find_link(locale, key) #:nodoc:
|
102
|
-
links[locale].
|
107
|
+
links[locale].each_pair do |from, to|
|
103
108
|
return [from, to] if key[0, from.length] == from
|
104
109
|
end && nil
|
105
110
|
end
|
@@ -110,4 +115,4 @@ module I18n
|
|
110
115
|
|
111
116
|
end
|
112
117
|
end
|
113
|
-
end
|
118
|
+
end
|
data/lib/i18n/backend/gettext.rb
CHANGED
@@ -1,26 +1,35 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'i18n/gettext'
|
4
4
|
require 'i18n/gettext/po_parser'
|
5
5
|
|
6
|
-
# Experimental support for using Gettext po files to store translations.
|
7
|
-
#
|
8
|
-
# To use this you can simply include the module to the Simple backend - or
|
9
|
-
# whatever other backend you are using.
|
10
|
-
#
|
11
|
-
# I18n::Backend::Simple.send(:include, I18n::Backend::Gettext)
|
12
|
-
#
|
13
|
-
# Now you should be able to include your Gettext translation (*.po) files to
|
14
|
-
# the I18n.load_path so they're loaded to the backend and you can use them as
|
15
|
-
# usual:
|
16
|
-
#
|
17
|
-
# I18n.load_path += Dir["path/to/locales/*.po"]
|
18
|
-
#
|
19
|
-
# Following the Gettext convention this implementation expects that your
|
20
|
-
# translation files are named by their locales. E.g. the file en.po would
|
21
|
-
# contain the translations for the English locale.
|
22
6
|
module I18n
|
23
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
|
+
|
24
33
|
module Gettext
|
25
34
|
class PoData < Hash
|
26
35
|
def set_comment(msgid_or_sym, comment)
|
@@ -32,7 +41,7 @@ module I18n
|
|
32
41
|
def load_po(filename)
|
33
42
|
locale = ::File.basename(filename, '.po').to_sym
|
34
43
|
data = normalize(locale, parse(filename))
|
35
|
-
{ locale => data }
|
44
|
+
[{ locale => data }, false]
|
36
45
|
end
|
37
46
|
|
38
47
|
def parse(filename)
|
@@ -42,16 +51,15 @@ module I18n
|
|
42
51
|
def normalize(locale, data)
|
43
52
|
data.inject({}) do |result, (key, value)|
|
44
53
|
unless key.nil? || key.empty?
|
54
|
+
key = key.gsub(I18n::Gettext::CONTEXT_SEPARATOR, '|')
|
45
55
|
key, value = normalize_pluralization(locale, key, value) if key.index("\000")
|
46
56
|
|
47
57
|
parts = key.split('|').reverse
|
48
|
-
normalized = parts.inject({}) do |
|
49
|
-
|
58
|
+
normalized = parts.inject({}) do |_normalized, part|
|
59
|
+
{ part => _normalized.empty? ? value : _normalized }
|
50
60
|
end
|
51
61
|
|
52
|
-
|
53
|
-
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
54
|
-
result.merge!(normalized, &merger)
|
62
|
+
Utils.deep_merge!(result, normalized)
|
55
63
|
end
|
56
64
|
result
|
57
65
|
end
|
@@ -63,10 +71,10 @@ module I18n
|
|
63
71
|
|
64
72
|
keys = I18n::Gettext.plural_keys(locale)
|
65
73
|
values = value.split("\000")
|
66
|
-
raise "invalid number of plurals: #{values.size}, keys: #{keys.inspect}" if values.size != keys.size
|
74
|
+
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
|
67
75
|
|
68
76
|
result = {}
|
69
|
-
values.each_with_index { |
|
77
|
+
values.each_with_index { |_value, ix| result[keys[ix]] = _value }
|
70
78
|
[key, result]
|
71
79
|
end
|
72
80
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# The InterpolationCompiler module contains optimizations that can tremendously
|
4
4
|
# speed up the interpolation process on the Simple backend.
|
@@ -10,19 +10,18 @@
|
|
10
10
|
# To enable pre-compiled interpolations you can simply include the
|
11
11
|
# InterpolationCompiler module to the Simple backend:
|
12
12
|
#
|
13
|
-
# I18n::Backend::Simple.
|
13
|
+
# I18n::Backend::Simple.include(I18n::Backend::InterpolationCompiler)
|
14
14
|
#
|
15
15
|
# Note that InterpolationCompiler does not yield meaningful results and consequently
|
16
16
|
# should not be used with Ruby 1.9 (YARV) but improves performance everywhere else
|
17
|
-
# (jRuby, Rubinius
|
17
|
+
# (jRuby, Rubinius).
|
18
18
|
module I18n
|
19
19
|
module Backend
|
20
20
|
module InterpolationCompiler
|
21
21
|
module Compiler
|
22
22
|
extend self
|
23
23
|
|
24
|
-
TOKENIZER
|
25
|
-
INTERPOLATION_SYNTAX_PATTERN = /(%)?(%\{([^\}]+)\})/
|
24
|
+
TOKENIZER = /(%%?\{[^}]+\})/
|
26
25
|
|
27
26
|
def compile_if_an_interpolation(string)
|
28
27
|
if interpolated_str?(string)
|
@@ -37,7 +36,7 @@ module I18n
|
|
37
36
|
end
|
38
37
|
|
39
38
|
def interpolated_str?(str)
|
40
|
-
str.kind_of?(::String) && str =~
|
39
|
+
str.kind_of?(::String) && str =~ TOKENIZER
|
41
40
|
end
|
42
41
|
|
43
42
|
protected
|
@@ -48,13 +47,12 @@ module I18n
|
|
48
47
|
|
49
48
|
def compiled_interpolation_body(str)
|
50
49
|
tokenize(str).map do |token|
|
51
|
-
|
50
|
+
token.match(TOKENIZER) ? handle_interpolation_token(token) : escape_plain_str(token)
|
52
51
|
end.join
|
53
52
|
end
|
54
53
|
|
55
|
-
def handle_interpolation_token(
|
56
|
-
|
57
|
-
escaped ? pattern : compile_interpolation_token(key.to_sym)
|
54
|
+
def handle_interpolation_token(token)
|
55
|
+
token.start_with?('%%') ? token[1..] : compile_interpolation_token(token[2..-2])
|
58
56
|
end
|
59
57
|
|
60
58
|
def compile_interpolation_token(key)
|
@@ -63,7 +61,7 @@ module I18n
|
|
63
61
|
|
64
62
|
def interpolate_or_raise_missing(key)
|
65
63
|
escaped_key = escape_key_sym(key)
|
66
|
-
|
64
|
+
RESERVED_KEYS.include?(key) ? reserved_key(escaped_key) : interpolate_key(escaped_key)
|
67
65
|
end
|
68
66
|
|
69
67
|
def interpolate_key(key)
|
@@ -79,7 +77,7 @@ module I18n
|
|
79
77
|
end
|
80
78
|
|
81
79
|
def missing_key(key)
|
82
|
-
"
|
80
|
+
"I18n.config.missing_interpolation_argument_handler.call(#{key}, v, self)"
|
83
81
|
end
|
84
82
|
|
85
83
|
def reserved_key(key)
|
@@ -106,7 +104,7 @@ module I18n
|
|
106
104
|
end
|
107
105
|
end
|
108
106
|
|
109
|
-
def store_translations(locale, data, options =
|
107
|
+
def store_translations(locale, data, options = EMPTY_HASH)
|
110
108
|
compile_all_strings_in(data)
|
111
109
|
super
|
112
110
|
end
|
@@ -120,4 +118,4 @@ module I18n
|
|
120
118
|
end
|
121
119
|
end
|
122
120
|
end
|
123
|
-
end
|
121
|
+
end
|