i18n 1.0.0 → 1.14.7
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 +4 -4
- data/README.md +75 -32
- data/lib/i18n/backend/base.rb +93 -34
- data/lib/i18n/backend/cache.rb +10 -11
- data/lib/i18n/backend/cache_file.rb +36 -0
- data/lib/i18n/backend/cascade.rb +3 -1
- data/lib/i18n/backend/chain.rb +39 -6
- data/lib/i18n/backend/fallbacks.rb +48 -15
- data/lib/i18n/backend/flatten.rb +10 -5
- data/lib/i18n/backend/gettext.rb +4 -2
- data/lib/i18n/backend/interpolation_compiler.rb +8 -8
- data/lib/i18n/backend/key_value.rb +31 -4
- data/lib/i18n/backend/lazy_loadable.rb +184 -0
- data/lib/i18n/backend/memoize.rb +10 -2
- data/lib/i18n/backend/metadata.rb +5 -3
- data/lib/i18n/backend/pluralization.rb +61 -18
- data/lib/i18n/backend/simple.rb +44 -24
- data/lib/i18n/backend/transliterator.rb +26 -24
- data/lib/i18n/backend.rb +5 -1
- data/lib/i18n/config.rb +22 -4
- data/lib/i18n/exceptions.rb +71 -18
- data/lib/i18n/gettext/helpers.rb +4 -2
- data/lib/i18n/gettext/po_parser.rb +7 -7
- data/lib/i18n/gettext.rb +2 -0
- data/lib/i18n/interpolate/ruby.rb +22 -6
- data/lib/i18n/locale/fallbacks.rb +33 -22
- data/lib/i18n/locale/tag/parents.rb +8 -6
- data/lib/i18n/locale/tag/simple.rb +2 -2
- data/lib/i18n/locale.rb +2 -0
- data/lib/i18n/middleware.rb +2 -0
- data/lib/i18n/tests/basics.rb +5 -7
- data/lib/i18n/tests/defaults.rb +8 -1
- data/lib/i18n/tests/interpolation.rb +34 -7
- data/lib/i18n/tests/link.rb +11 -1
- data/lib/i18n/tests/localization/date.rb +37 -10
- data/lib/i18n/tests/localization/date_time.rb +28 -7
- data/lib/i18n/tests/localization/procs.rb +9 -7
- data/lib/i18n/tests/localization/time.rb +27 -5
- data/lib/i18n/tests/lookup.rb +11 -5
- data/lib/i18n/tests/pluralization.rb +1 -1
- data/lib/i18n/tests/procs.rb +23 -7
- data/lib/i18n/tests.rb +2 -0
- data/lib/i18n/utils.rb +55 -0
- data/lib/i18n/version.rb +3 -1
- data/lib/i18n.rb +179 -58
- metadata +16 -61
- data/gemfiles/Gemfile.rails-3.2.x +0 -10
- data/gemfiles/Gemfile.rails-4.0.x +0 -10
- data/gemfiles/Gemfile.rails-4.1.x +0 -10
- data/gemfiles/Gemfile.rails-4.2.x +0 -10
- data/gemfiles/Gemfile.rails-5.0.x +0 -10
- data/gemfiles/Gemfile.rails-5.1.x +0 -10
- data/gemfiles/Gemfile.rails-master +0 -10
- data/lib/i18n/core_ext/hash.rb +0 -29
- data/lib/i18n/core_ext/kernel/suppress_warnings.rb +0 -8
- data/lib/i18n/core_ext/string/interpolate.rb +0 -9
- data/test/api/all_features_test.rb +0 -58
- data/test/api/cascade_test.rb +0 -28
- data/test/api/chain_test.rb +0 -24
- data/test/api/fallbacks_test.rb +0 -30
- data/test/api/key_value_test.rb +0 -24
- data/test/api/memoize_test.rb +0 -56
- data/test/api/override_test.rb +0 -42
- data/test/api/pluralization_test.rb +0 -30
- data/test/api/simple_test.rb +0 -28
- data/test/backend/cache_test.rb +0 -109
- data/test/backend/cascade_test.rb +0 -86
- data/test/backend/chain_test.rb +0 -122
- data/test/backend/exceptions_test.rb +0 -36
- data/test/backend/fallbacks_test.rb +0 -219
- data/test/backend/interpolation_compiler_test.rb +0 -118
- data/test/backend/key_value_test.rb +0 -61
- data/test/backend/memoize_test.rb +0 -79
- data/test/backend/metadata_test.rb +0 -48
- data/test/backend/pluralization_test.rb +0 -45
- data/test/backend/simple_test.rb +0 -103
- data/test/backend/transliterator_test.rb +0 -84
- data/test/core_ext/hash_test.rb +0 -36
- data/test/gettext/api_test.rb +0 -214
- data/test/gettext/backend_test.rb +0 -92
- data/test/i18n/exceptions_test.rb +0 -117
- data/test/i18n/gettext_plural_keys_test.rb +0 -20
- data/test/i18n/interpolate_test.rb +0 -91
- data/test/i18n/load_path_test.rb +0 -34
- data/test/i18n/middleware_test.rb +0 -24
- data/test/i18n_test.rb +0 -462
- data/test/locale/fallbacks_test.rb +0 -133
- data/test/locale/tag/rfc4646_test.rb +0 -143
- data/test/locale/tag/simple_test.rb +0 -32
- data/test/run_all.rb +0 -20
- data/test/test_data/locales/de.po +0 -82
- data/test/test_data/locales/en.rb +0 -3
- data/test/test_data/locales/en.yml +0 -3
- data/test/test_data/locales/invalid/empty.yml +0 -0
- data/test/test_data/locales/invalid/syntax.yml +0 -4
- data/test/test_data/locales/plurals.rb +0 -113
- data/test/test_helper.rb +0 -61
data/lib/i18n/backend/simple.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'i18n/backend/base'
|
4
|
+
|
1
5
|
module I18n
|
2
6
|
module Backend
|
3
7
|
# A simple backend that reads translations from YAML files and stores them in
|
@@ -6,19 +10,20 @@ module I18n
|
|
6
10
|
# The implementation is provided by a Implementation module allowing to easily
|
7
11
|
# extend Simple backend's behavior by including modules. E.g.:
|
8
12
|
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
13
|
+
# module I18n::Backend::Pluralization
|
14
|
+
# def pluralize(*args)
|
15
|
+
# # extended pluralization logic
|
16
|
+
# super
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# I18n::Backend::Simple.include(I18n::Backend::Pluralization)
|
17
21
|
class Simple
|
18
|
-
(class << self; self; end).class_eval { public :include }
|
19
|
-
|
20
22
|
module Implementation
|
21
23
|
include Base
|
24
|
+
|
25
|
+
# Mutex to ensure that concurrent translations loading will be thread-safe
|
26
|
+
MUTEX = Mutex.new
|
22
27
|
|
23
28
|
def initialized?
|
24
29
|
@initialized ||= false
|
@@ -28,17 +33,16 @@ module I18n
|
|
28
33
|
# This uses a deep merge for the translations hash, so existing
|
29
34
|
# translations will be overwritten by new ones only at the deepest
|
30
35
|
# level of the hash.
|
31
|
-
def store_translations(locale, data, options =
|
36
|
+
def store_translations(locale, data, options = EMPTY_HASH)
|
32
37
|
if I18n.enforce_available_locales &&
|
33
38
|
I18n.available_locales_initialized? &&
|
34
|
-
!I18n.
|
35
|
-
!I18n.available_locales.include?(locale.to_s)
|
39
|
+
!I18n.locale_available?(locale)
|
36
40
|
return data
|
37
41
|
end
|
38
42
|
locale = locale.to_sym
|
39
|
-
translations[locale] ||=
|
40
|
-
data = data.
|
41
|
-
translations[locale]
|
43
|
+
translations[locale] ||= Concurrent::Hash.new
|
44
|
+
data = Utils.deep_symbolize_keys(data) unless options.fetch(:skip_symbolize_keys, false)
|
45
|
+
Utils.deep_merge!(translations[locale], data)
|
42
46
|
end
|
43
47
|
|
44
48
|
# Get available locales from the translations hash
|
@@ -57,6 +61,23 @@ module I18n
|
|
57
61
|
super
|
58
62
|
end
|
59
63
|
|
64
|
+
def eager_load!
|
65
|
+
init_translations unless initialized?
|
66
|
+
super
|
67
|
+
end
|
68
|
+
|
69
|
+
def translations(do_init: false)
|
70
|
+
# To avoid returning empty translations,
|
71
|
+
# call `init_translations`
|
72
|
+
init_translations if do_init && !initialized?
|
73
|
+
|
74
|
+
@translations ||= Concurrent::Hash.new do |h, k|
|
75
|
+
MUTEX.synchronize do
|
76
|
+
h[k] = Concurrent::Hash.new
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
60
81
|
protected
|
61
82
|
|
62
83
|
def init_translations
|
@@ -64,24 +85,23 @@ module I18n
|
|
64
85
|
@initialized = true
|
65
86
|
end
|
66
87
|
|
67
|
-
def translations
|
68
|
-
@translations ||= {}
|
69
|
-
end
|
70
|
-
|
71
88
|
# Looks up a translation from the translations hash. Returns nil if
|
72
89
|
# either key is nil, or locale, scope or key do not exist as a key in the
|
73
90
|
# nested translations hash. Splits keys or scopes containing dots
|
74
91
|
# into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
|
75
92
|
# <tt>%w(currency format)</tt>.
|
76
|
-
def lookup(locale, key, scope = [], options =
|
93
|
+
def lookup(locale, key, scope = [], options = EMPTY_HASH)
|
77
94
|
init_translations unless initialized?
|
78
95
|
keys = I18n.normalize_keys(locale, key, scope, options[:separator])
|
79
96
|
|
80
97
|
keys.inject(translations) do |result, _key|
|
81
|
-
|
82
|
-
|
98
|
+
return nil unless result.is_a?(Hash)
|
99
|
+
unless result.has_key?(_key)
|
100
|
+
_key = _key.to_s.to_sym
|
101
|
+
return nil unless result.has_key?(_key)
|
102
|
+
end
|
83
103
|
result = result[_key]
|
84
|
-
result =
|
104
|
+
result = resolve_entry(locale, _key, result, Utils.except(options.merge(:scope => nil), :count)) if result.is_a?(Symbol)
|
85
105
|
result
|
86
106
|
end
|
87
107
|
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
module I18n
|
3
5
|
module Backend
|
4
6
|
module Transliterator
|
@@ -43,30 +45,30 @@ module I18n
|
|
43
45
|
"Ç"=>"C", "È"=>"E", "É"=>"E", "Ê"=>"E", "Ë"=>"E", "Ì"=>"I", "Í"=>"I",
|
44
46
|
"Î"=>"I", "Ï"=>"I", "Ð"=>"D", "Ñ"=>"N", "Ò"=>"O", "Ó"=>"O", "Ô"=>"O",
|
45
47
|
"Õ"=>"O", "Ö"=>"O", "×"=>"x", "Ø"=>"O", "Ù"=>"U", "Ú"=>"U", "Û"=>"U",
|
46
|
-
"Ü"=>"U", "Ý"=>"Y", "Þ"=>"Th", "ß"=>"ss", "
|
47
|
-
"
|
48
|
-
"
|
49
|
-
"
|
50
|
-
"
|
51
|
-
"
|
52
|
-
"
|
53
|
-
"
|
54
|
-
"
|
55
|
-
"
|
56
|
-
"
|
57
|
-
"
|
58
|
-
"
|
59
|
-
"
|
60
|
-
"
|
61
|
-
"ņ"=>"n", "Ň"=>"N", "ň"=>"n", "ʼn"=>"'n", "Ŋ"=>"NG",
|
62
|
-
"Ō"=>"O", "ō"=>"o", "Ŏ"=>"O", "ŏ"=>"o", "Ő"=>"O", "ő"=>"o",
|
63
|
-
"œ"=>"oe", "Ŕ"=>"R", "ŕ"=>"r", "Ŗ"=>"R", "ŗ"=>"r", "Ř"=>"R",
|
64
|
-
"Ś"=>"S", "ś"=>"s", "Ŝ"=>"S", "ŝ"=>"s", "Ş"=>"S", "ş"=>"s",
|
65
|
-
"š"=>"s", "Ţ"=>"T", "ţ"=>"t", "Ť"=>"T", "ť"=>"t", "Ŧ"=>"T",
|
66
|
-
"Ũ"=>"U", "ũ"=>"u", "Ū"=>"U", "ū"=>"u", "Ŭ"=>"U", "ŭ"=>"u",
|
67
|
-
"ů"=>"u", "Ű"=>"U", "ű"=>"u", "Ų"=>"U", "ų"=>"u", "Ŵ"=>"W",
|
68
|
-
"Ŷ"=>"Y", "ŷ"=>"y", "Ÿ"=>"Y", "Ź"=>"Z", "ź"=>"z", "Ż"=>"Z",
|
69
|
-
"Ž"=>"Z", "ž"=>"z"
|
48
|
+
"Ü"=>"U", "Ý"=>"Y", "Þ"=>"Th", "ß"=>"ss", "ẞ"=>"SS", "à"=>"a",
|
49
|
+
"á"=>"a", "â"=>"a", "ã"=>"a", "ä"=>"a", "å"=>"a", "æ"=>"ae", "ç"=>"c",
|
50
|
+
"è"=>"e", "é"=>"e", "ê"=>"e", "ë"=>"e", "ì"=>"i", "í"=>"i", "î"=>"i",
|
51
|
+
"ï"=>"i", "ð"=>"d", "ñ"=>"n", "ò"=>"o", "ó"=>"o", "ô"=>"o", "õ"=>"o",
|
52
|
+
"ö"=>"o", "ø"=>"o", "ù"=>"u", "ú"=>"u", "û"=>"u", "ü"=>"u", "ý"=>"y",
|
53
|
+
"þ"=>"th", "ÿ"=>"y", "Ā"=>"A", "ā"=>"a", "Ă"=>"A", "ă"=>"a", "Ą"=>"A",
|
54
|
+
"ą"=>"a", "Ć"=>"C", "ć"=>"c", "Ĉ"=>"C", "ĉ"=>"c", "Ċ"=>"C", "ċ"=>"c",
|
55
|
+
"Č"=>"C", "č"=>"c", "Ď"=>"D", "ď"=>"d", "Đ"=>"D", "đ"=>"d", "Ē"=>"E",
|
56
|
+
"ē"=>"e", "Ĕ"=>"E", "ĕ"=>"e", "Ė"=>"E", "ė"=>"e", "Ę"=>"E", "ę"=>"e",
|
57
|
+
"Ě"=>"E", "ě"=>"e", "Ĝ"=>"G", "ĝ"=>"g", "Ğ"=>"G", "ğ"=>"g", "Ġ"=>"G",
|
58
|
+
"ġ"=>"g", "Ģ"=>"G", "ģ"=>"g", "Ĥ"=>"H", "ĥ"=>"h", "Ħ"=>"H", "ħ"=>"h",
|
59
|
+
"Ĩ"=>"I", "ĩ"=>"i", "Ī"=>"I", "ī"=>"i", "Ĭ"=>"I", "ĭ"=>"i", "Į"=>"I",
|
60
|
+
"į"=>"i", "İ"=>"I", "ı"=>"i", "IJ"=>"IJ", "ij"=>"ij", "Ĵ"=>"J", "ĵ"=>"j",
|
61
|
+
"Ķ"=>"K", "ķ"=>"k", "ĸ"=>"k", "Ĺ"=>"L", "ĺ"=>"l", "Ļ"=>"L", "ļ"=>"l",
|
62
|
+
"Ľ"=>"L", "ľ"=>"l", "Ŀ"=>"L", "ŀ"=>"l", "Ł"=>"L", "ł"=>"l", "Ń"=>"N",
|
63
|
+
"ń"=>"n", "Ņ"=>"N", "ņ"=>"n", "Ň"=>"N", "ň"=>"n", "ʼn"=>"'n", "Ŋ"=>"NG",
|
64
|
+
"ŋ"=>"ng", "Ō"=>"O", "ō"=>"o", "Ŏ"=>"O", "ŏ"=>"o", "Ő"=>"O", "ő"=>"o",
|
65
|
+
"Œ"=>"OE", "œ"=>"oe", "Ŕ"=>"R", "ŕ"=>"r", "Ŗ"=>"R", "ŗ"=>"r", "Ř"=>"R",
|
66
|
+
"ř"=>"r", "Ś"=>"S", "ś"=>"s", "Ŝ"=>"S", "ŝ"=>"s", "Ş"=>"S", "ş"=>"s",
|
67
|
+
"Š"=>"S", "š"=>"s", "Ţ"=>"T", "ţ"=>"t", "Ť"=>"T", "ť"=>"t", "Ŧ"=>"T",
|
68
|
+
"ŧ"=>"t", "Ũ"=>"U", "ũ"=>"u", "Ū"=>"U", "ū"=>"u", "Ŭ"=>"U", "ŭ"=>"u",
|
69
|
+
"Ů"=>"U", "ů"=>"u", "Ű"=>"U", "ű"=>"u", "Ų"=>"U", "ų"=>"u", "Ŵ"=>"W",
|
70
|
+
"ŵ"=>"w", "Ŷ"=>"Y", "ŷ"=>"y", "Ÿ"=>"Y", "Ź"=>"Z", "ź"=>"z", "Ż"=>"Z",
|
71
|
+
"ż"=>"z", "Ž"=>"Z", "ž"=>"z"
|
70
72
|
}.freeze
|
71
73
|
|
72
74
|
def initialize(rule = nil)
|
data/lib/i18n/backend.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module I18n
|
2
4
|
module Backend
|
3
5
|
autoload :Base, 'i18n/backend/base'
|
4
|
-
autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
|
5
6
|
autoload :Cache, 'i18n/backend/cache'
|
7
|
+
autoload :CacheFile, 'i18n/backend/cache_file'
|
6
8
|
autoload :Cascade, 'i18n/backend/cascade'
|
7
9
|
autoload :Chain, 'i18n/backend/chain'
|
8
10
|
autoload :Fallbacks, 'i18n/backend/fallbacks'
|
9
11
|
autoload :Flatten, 'i18n/backend/flatten'
|
10
12
|
autoload :Gettext, 'i18n/backend/gettext'
|
13
|
+
autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
|
11
14
|
autoload :KeyValue, 'i18n/backend/key_value'
|
15
|
+
autoload :LazyLoadable, 'i18n/backend/lazy_loadable'
|
12
16
|
autoload :Memoize, 'i18n/backend/memoize'
|
13
17
|
autoload :Metadata, 'i18n/backend/metadata'
|
14
18
|
autoload :Pluralization, 'i18n/backend/pluralization'
|
data/lib/i18n/config.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'set'
|
2
4
|
|
3
5
|
module I18n
|
@@ -5,7 +7,7 @@ module I18n
|
|
5
7
|
# The only configuration value that is not global and scoped to thread is :locale.
|
6
8
|
# It defaults to the default_locale.
|
7
9
|
def locale
|
8
|
-
defined?(@locale) && @locale ? @locale : default_locale
|
10
|
+
defined?(@locale) && @locale != nil ? @locale : default_locale
|
9
11
|
end
|
10
12
|
|
11
13
|
# Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
|
@@ -36,7 +38,7 @@ module I18n
|
|
36
38
|
end
|
37
39
|
|
38
40
|
# Returns an array of locales for which translations are available.
|
39
|
-
# Unless you
|
41
|
+
# Unless you explicitly set these through I18n.available_locales=
|
40
42
|
# the call will be delegated to the backend.
|
41
43
|
def available_locales
|
42
44
|
@@available_locales ||= nil
|
@@ -57,7 +59,7 @@ module I18n
|
|
57
59
|
@@available_locales = nil if @@available_locales.empty?
|
58
60
|
@@available_locales_set = nil
|
59
61
|
end
|
60
|
-
|
62
|
+
|
61
63
|
# Returns true if the available_locales have been initialized
|
62
64
|
def available_locales_initialized?
|
63
65
|
( !!defined?(@@available_locales) && !!@@available_locales )
|
@@ -104,7 +106,7 @@ module I18n
|
|
104
106
|
# if you don't care about arity.
|
105
107
|
#
|
106
108
|
# == Example:
|
107
|
-
# You can
|
109
|
+
# You can suppress raising an exception and return string instead:
|
108
110
|
#
|
109
111
|
# I18n.config.missing_interpolation_argument_handler = Proc.new do |key|
|
110
112
|
# "#{key} is missing"
|
@@ -143,5 +145,21 @@ module I18n
|
|
143
145
|
def enforce_available_locales=(enforce_available_locales)
|
144
146
|
@@enforce_available_locales = enforce_available_locales
|
145
147
|
end
|
148
|
+
|
149
|
+
# Returns the current interpolation patterns. Defaults to
|
150
|
+
# I18n::DEFAULT_INTERPOLATION_PATTERNS.
|
151
|
+
def interpolation_patterns
|
152
|
+
@@interpolation_patterns ||= I18n::DEFAULT_INTERPOLATION_PATTERNS.dup
|
153
|
+
end
|
154
|
+
|
155
|
+
# Sets the current interpolation patterns. Used to set a interpolation
|
156
|
+
# patterns.
|
157
|
+
#
|
158
|
+
# E.g. using {{}} as a placeholder like "{{hello}}, world!":
|
159
|
+
#
|
160
|
+
# I18n.config.interpolation_patterns << /\{\{(\w+)\}\}/
|
161
|
+
def interpolation_patterns=(interpolation_patterns)
|
162
|
+
@@interpolation_patterns = interpolation_patterns
|
163
|
+
end
|
146
164
|
end
|
147
165
|
end
|
data/lib/i18n/exceptions.rb
CHANGED
@@ -1,27 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cgi'
|
2
4
|
|
3
5
|
module I18n
|
4
|
-
# Handles exceptions raised in the backend. All exceptions except for
|
5
|
-
# MissingTranslationData exceptions are re-thrown. When a MissingTranslationData
|
6
|
-
# was caught the handler returns an error message string containing the key/scope.
|
7
|
-
# Note that the exception handler is not called when the option :throw was given.
|
8
6
|
class ExceptionHandler
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
when Exception
|
15
|
-
raise exception
|
16
|
-
else
|
17
|
-
throw :exception, exception
|
18
|
-
end
|
7
|
+
def call(exception, _locale, _key, _options)
|
8
|
+
if exception.is_a?(MissingTranslation)
|
9
|
+
exception.message
|
10
|
+
else
|
11
|
+
raise exception
|
19
12
|
end
|
20
|
-
|
13
|
+
end
|
21
14
|
end
|
22
15
|
|
23
16
|
class ArgumentError < ::ArgumentError; end
|
24
17
|
|
18
|
+
class Disabled < ArgumentError
|
19
|
+
def initialize(method)
|
20
|
+
super(<<~MESSAGE)
|
21
|
+
I18n.#{method} is currently disabled, likely because your application is still in its loading phase.
|
22
|
+
|
23
|
+
This method is meant to display text in the user locale, so calling it before the user locale has
|
24
|
+
been set is likely to display text from the wrong locale to some users.
|
25
|
+
|
26
|
+
If you have a legitimate reason to access i18n data outside of the user flow, you can do so by passing
|
27
|
+
the desired locale explicitly with the `locale` argument, e.g. `I18n.#{method}(..., locale: :en)`
|
28
|
+
MESSAGE
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
25
32
|
class InvalidLocale < ArgumentError
|
26
33
|
attr_reader :locale
|
27
34
|
def initialize(locale)
|
@@ -40,10 +47,12 @@ module I18n
|
|
40
47
|
|
41
48
|
class MissingTranslation < ArgumentError
|
42
49
|
module Base
|
50
|
+
PERMITTED_KEYS = [:scope, :default].freeze
|
51
|
+
|
43
52
|
attr_reader :locale, :key, :options
|
44
53
|
|
45
|
-
def initialize(locale, key, options =
|
46
|
-
@key, @locale, @options = key, locale, options.
|
54
|
+
def initialize(locale, key, options = EMPTY_HASH)
|
55
|
+
@key, @locale, @options = key, locale, options.slice(*PERMITTED_KEYS)
|
47
56
|
options.each { |k, v| self.options[k] = v.inspect if v.is_a?(Proc) }
|
48
57
|
end
|
49
58
|
|
@@ -54,8 +63,18 @@ module I18n
|
|
54
63
|
end
|
55
64
|
|
56
65
|
def message
|
57
|
-
|
66
|
+
if (default = options[:default]).is_a?(Array) && default.any?
|
67
|
+
other_options = ([key, *default]).map { |k| normalized_option(k).prepend('- ') }.join("\n")
|
68
|
+
"Translation missing. Options considered were:\n#{other_options}"
|
69
|
+
else
|
70
|
+
"Translation missing: #{keys.join('.')}"
|
71
|
+
end
|
58
72
|
end
|
73
|
+
|
74
|
+
def normalized_option(key)
|
75
|
+
I18n.normalize_keys(locale, key, options[:scope]).join('.')
|
76
|
+
end
|
77
|
+
|
59
78
|
alias :to_s :message
|
60
79
|
|
61
80
|
def to_exception
|
@@ -101,4 +120,38 @@ module I18n
|
|
101
120
|
super "can not load translations from #{filename}, the file type #{type} is not known"
|
102
121
|
end
|
103
122
|
end
|
123
|
+
|
124
|
+
class UnsupportedMethod < ArgumentError
|
125
|
+
attr_reader :method, :backend_klass, :msg
|
126
|
+
def initialize(method, backend_klass, msg)
|
127
|
+
@method = method
|
128
|
+
@backend_klass = backend_klass
|
129
|
+
@msg = msg
|
130
|
+
super "#{backend_klass} does not support the ##{method} method. #{msg}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class InvalidFilenames < ArgumentError
|
135
|
+
NUMBER_OF_ERRORS_SHOWN = 20
|
136
|
+
def initialize(file_errors)
|
137
|
+
super <<~MSG
|
138
|
+
Found #{file_errors.count} error(s).
|
139
|
+
The first #{[file_errors.count, NUMBER_OF_ERRORS_SHOWN].min} error(s):
|
140
|
+
#{file_errors.map(&:message).first(NUMBER_OF_ERRORS_SHOWN).join("\n")}
|
141
|
+
|
142
|
+
To use the LazyLoadable backend:
|
143
|
+
1. Filenames must start with the locale.
|
144
|
+
2. An underscore must separate the locale with any optional text that follows.
|
145
|
+
3. The file must only contain translation data for the single locale.
|
146
|
+
|
147
|
+
Example:
|
148
|
+
"/config/locales/fr.yml" which contains:
|
149
|
+
```yml
|
150
|
+
fr:
|
151
|
+
dog:
|
152
|
+
chien
|
153
|
+
```
|
154
|
+
MSG
|
155
|
+
end
|
156
|
+
end
|
104
157
|
end
|
data/lib/i18n/gettext/helpers.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'i18n/gettext'
|
2
4
|
|
3
5
|
module I18n
|
@@ -16,8 +18,8 @@ module I18n
|
|
16
18
|
msgsid
|
17
19
|
end
|
18
20
|
|
19
|
-
def gettext(msgid, options =
|
20
|
-
I18n.t(msgid, {
|
21
|
+
def gettext(msgid, options = EMPTY_HASH)
|
22
|
+
I18n.t(msgid, **{:default => msgid, :separator => '|'}.merge(options))
|
21
23
|
end
|
22
24
|
alias _ gettext
|
23
25
|
|
@@ -28,7 +28,7 @@ module_eval <<'..end src/poparser.ry modeval..id7a99570e05', 'src/poparser.ry',
|
|
28
28
|
ret.gsub!(/\\"/, "\"")
|
29
29
|
ret
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
def parse(str, data, ignore_fuzzy = true)
|
33
33
|
@comments = []
|
34
34
|
@data = data
|
@@ -64,7 +64,7 @@ module_eval <<'..end src/poparser.ry modeval..id7a99570e05', 'src/poparser.ry',
|
|
64
64
|
str = $'
|
65
65
|
when /\A\#(.*)/
|
66
66
|
@q.push [:COMMENT, $&]
|
67
|
-
str = $'
|
67
|
+
str = $'
|
68
68
|
when /\A\"(.*)\"/
|
69
69
|
@q.push [:STRING, $1]
|
70
70
|
str = $'
|
@@ -73,7 +73,7 @@ module_eval <<'..end src/poparser.ry modeval..id7a99570e05', 'src/poparser.ry',
|
|
73
73
|
#@q.push [:STRING, c]
|
74
74
|
str = str[1..-1]
|
75
75
|
end
|
76
|
-
end
|
76
|
+
end
|
77
77
|
@q.push [false, '$end']
|
78
78
|
if $DEBUG
|
79
79
|
@q.each do |a,b|
|
@@ -88,7 +88,7 @@ module_eval <<'..end src/poparser.ry modeval..id7a99570e05', 'src/poparser.ry',
|
|
88
88
|
end
|
89
89
|
@data
|
90
90
|
end
|
91
|
-
|
91
|
+
|
92
92
|
def next_token
|
93
93
|
@q.shift
|
94
94
|
end
|
@@ -101,11 +101,11 @@ module_eval <<'..end src/poparser.ry modeval..id7a99570e05', 'src/poparser.ry',
|
|
101
101
|
@comments.clear
|
102
102
|
@msgctxt = ""
|
103
103
|
end
|
104
|
-
|
104
|
+
|
105
105
|
def on_comment(comment)
|
106
106
|
@fuzzy = true if (/fuzzy/ =~ comment)
|
107
107
|
@comments << comment
|
108
|
-
end
|
108
|
+
end
|
109
109
|
|
110
110
|
|
111
111
|
..end src/poparser.ry modeval..id7a99570e05
|
@@ -245,7 +245,7 @@ module_eval <<'.,.,', 'src/poparser.ry', 25
|
|
245
245
|
|
246
246
|
module_eval <<'.,.,', 'src/poparser.ry', 48
|
247
247
|
def _reduce_8( val, _values, result )
|
248
|
-
if @fuzzy and $ignore_fuzzy
|
248
|
+
if @fuzzy and $ignore_fuzzy
|
249
249
|
if val[1] != ""
|
250
250
|
$stderr.print _("Warning: fuzzy message was ignored.\n")
|
251
251
|
$stderr.print " msgid '#{val[1]}'\n"
|
data/lib/i18n/gettext.rb
CHANGED
@@ -1,24 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# heavily based on Masao Mutoh's gettext String interpolation extension
|
2
4
|
# http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb
|
3
5
|
|
4
6
|
module I18n
|
5
|
-
|
7
|
+
DEFAULT_INTERPOLATION_PATTERNS = [
|
6
8
|
/%%/,
|
7
|
-
/%\{(\w+)\}/,
|
8
|
-
/%<(\w+)>(
|
9
|
-
|
9
|
+
/%\{([\w|]+)\}/, # matches placeholders like "%{foo} or %{foo|word}"
|
10
|
+
/%<(\w+)>([^\d]*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
|
11
|
+
].freeze
|
12
|
+
INTERPOLATION_PATTERN = Regexp.union(DEFAULT_INTERPOLATION_PATTERNS)
|
13
|
+
deprecate_constant :INTERPOLATION_PATTERN
|
14
|
+
|
15
|
+
INTERPOLATION_PATTERNS_CACHE = Hash.new do |hash, patterns|
|
16
|
+
hash[patterns] = Regexp.union(patterns)
|
17
|
+
end
|
18
|
+
private_constant :INTERPOLATION_PATTERNS_CACHE
|
10
19
|
|
11
20
|
class << self
|
12
21
|
# Return String or raises MissingInterpolationArgument exception.
|
13
22
|
# Missing argument's logic is handled by I18n.config.missing_interpolation_argument_handler.
|
14
23
|
def interpolate(string, values)
|
15
|
-
raise ReservedInterpolationKey.new($1.to_sym, string) if string =~
|
24
|
+
raise ReservedInterpolationKey.new($1.to_sym, string) if string =~ I18n.reserved_keys_pattern
|
16
25
|
raise ArgumentError.new('Interpolation values must be a Hash.') unless values.kind_of?(Hash)
|
17
26
|
interpolate_hash(string, values)
|
18
27
|
end
|
19
28
|
|
20
29
|
def interpolate_hash(string, values)
|
21
|
-
|
30
|
+
pattern = INTERPOLATION_PATTERNS_CACHE[config.interpolation_patterns]
|
31
|
+
interpolated = false
|
32
|
+
|
33
|
+
interpolated_string = string.gsub(pattern) do |match|
|
34
|
+
interpolated = true
|
35
|
+
|
22
36
|
if match == '%%'
|
23
37
|
'%'
|
24
38
|
else
|
@@ -32,6 +46,8 @@ module I18n
|
|
32
46
|
$3 ? sprintf("%#{$3}", value) : value
|
33
47
|
end
|
34
48
|
end
|
49
|
+
|
50
|
+
interpolated ? interpolated_string : string
|
35
51
|
end
|
36
52
|
end
|
37
53
|
end
|
@@ -15,19 +15,12 @@
|
|
15
15
|
# * all parent locales of a given locale (e.g. :es for :"es-MX") first,
|
16
16
|
# * the current default locales and all of their parents second
|
17
17
|
#
|
18
|
-
# The default locales are set to [
|
19
|
-
# set to something else.
|
18
|
+
# The default locales are set to [] by default but can be set to something else.
|
20
19
|
#
|
21
20
|
# One can additionally add any number of additional fallback locales manually.
|
22
21
|
# These will be added before the default locales to the fallback chain. For
|
23
22
|
# example:
|
24
23
|
#
|
25
|
-
# # using the default locale as default fallback locale
|
26
|
-
#
|
27
|
-
# I18n.default_locale = :"en-US"
|
28
|
-
# I18n.fallbacks = I18n::Locale::Fallbacks.new(:"de-AT" => :"de-DE")
|
29
|
-
# I18n.fallbacks[:"de-AT"] # => [:"de-AT", :"de-DE", :de, :"en-US", :en]
|
30
|
-
#
|
31
24
|
# # using a custom locale as default fallback locale
|
32
25
|
#
|
33
26
|
# I18n.fallbacks = I18n::Locale::Fallbacks.new(:"en-GB", :"de-AT" => :de, :"de-CH" => :de)
|
@@ -46,7 +39,7 @@
|
|
46
39
|
# fallbacks[:"ar-PS"] # => [:"ar-PS", :ar, :"he-IL", :he, :"en-US", :en]
|
47
40
|
# fallbacks[:"ar-EG"] # => [:"ar-EG", :ar, :"en-US", :en]
|
48
41
|
#
|
49
|
-
# # people speaking Sami as spoken in
|
42
|
+
# # people speaking Sami as spoken in Finland also speak Swedish and Finnish as spoken in Finland
|
50
43
|
# fallbacks.map(:sms => [:"se-FI", :"fi-FI"])
|
51
44
|
# fallbacks[:sms] # => [:sms, :"se-FI", :se, :"fi-FI", :fi, :"en-US", :en]
|
52
45
|
|
@@ -56,40 +49,58 @@ module I18n
|
|
56
49
|
def initialize(*mappings)
|
57
50
|
@map = {}
|
58
51
|
map(mappings.pop) if mappings.last.is_a?(Hash)
|
59
|
-
self.defaults = mappings.empty? ? [
|
52
|
+
self.defaults = mappings.empty? ? [] : mappings
|
60
53
|
end
|
61
54
|
|
62
55
|
def defaults=(defaults)
|
63
|
-
@defaults = defaults.
|
56
|
+
@defaults = defaults.flat_map { |default| compute(default, false) }
|
64
57
|
end
|
65
58
|
attr_reader :defaults
|
66
59
|
|
67
60
|
def [](locale)
|
68
61
|
raise InvalidLocale.new(locale) if locale.nil?
|
62
|
+
raise Disabled.new('fallback#[]') if locale == false
|
69
63
|
locale = locale.to_sym
|
70
64
|
super || store(locale, compute(locale))
|
71
65
|
end
|
72
66
|
|
73
|
-
def map(
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
67
|
+
def map(*args, &block)
|
68
|
+
if args.count == 1 && !block_given?
|
69
|
+
mappings = args.first
|
70
|
+
mappings.each do |from, to|
|
71
|
+
from, to = from.to_sym, Array(to)
|
72
|
+
to.each do |_to|
|
73
|
+
@map[from] ||= []
|
74
|
+
@map[from] << _to.to_sym
|
75
|
+
end
|
79
76
|
end
|
77
|
+
else
|
78
|
+
@map.map(*args, &block)
|
80
79
|
end
|
81
80
|
end
|
82
81
|
|
82
|
+
def empty?
|
83
|
+
@map.empty? && @defaults.empty?
|
84
|
+
end
|
85
|
+
|
86
|
+
def inspect
|
87
|
+
"#<#{self.class.name} @map=#{@map.inspect} @defaults=#{@defaults.inspect}>"
|
88
|
+
end
|
89
|
+
|
83
90
|
protected
|
84
91
|
|
85
92
|
def compute(tags, include_defaults = true, exclude = [])
|
86
|
-
result =
|
93
|
+
result = []
|
94
|
+
Array(tags).each do |tag|
|
87
95
|
tags = I18n::Locale::Tag.tag(tag).self_and_parents.map! { |t| t.to_sym } - exclude
|
88
|
-
|
89
|
-
tags
|
90
|
-
end
|
96
|
+
result += tags
|
97
|
+
tags.each { |_tag| result += compute(@map[_tag], false, exclude + result) if @map[_tag] }
|
98
|
+
end
|
99
|
+
|
91
100
|
result.push(*defaults) if include_defaults
|
92
|
-
result.uniq
|
101
|
+
result.uniq!
|
102
|
+
result.compact!
|
103
|
+
result
|
93
104
|
end
|
94
105
|
end
|
95
106
|
end
|