i18n 1.12.0 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b92d195deaeca5e93f73cc62be2d2fb4c93d8a9787449e168b493773e5072458
4
- data.tar.gz: f1172c9fac93f493c8a5b16cff4b27154ff917aed48cbc46a4a702363863111d
3
+ metadata.gz: e85066f2cf6bbf170ff946391c6037d0335e5314d0c1e0723e980d5ee7df7af0
4
+ data.tar.gz: 2a4629aa0644c2a6e8cc5d175d4912b5f5cd4abc066dd5ee196270b7a3eadd08
5
5
  SHA512:
6
- metadata.gz: 0174d10f7bac17e29cf7622d3ce69850e51755d53fd3d17d2f672bc9ca3c75823b851f6d04cbd367fea8ff5830c8c755fb9c80433bac9ec3c46590cbe9a874d0
7
- data.tar.gz: ef22ed98cb7f223753a65b9d63d0f40e4b6821c93a41b1b41627e4c7b4e3de6b7bd411d8a6ed3d46dc6554432a776535c0461248e8d1fbfeee05dcda2ab89235
6
+ metadata.gz: '01326189eb7b675594bd63c802ae9470135e9aab222de4df1429c99c585b9b59b172ed62386c03b82fd45c862bbb42484dc89e0289c83970315f9a5fa03ba91c'
7
+ data.tar.gz: 290489ae277759fe65197e65d6486b7cca67605c2c98197a472c91bb2496d9db168dfd2082706b87f6f806051827b8df12cc0037258f3bf10d5f74a9ddb9f575
data/README.md CHANGED
@@ -26,7 +26,7 @@ gem 'i18n'
26
26
  Then configure I18n with some translations, and a default locale:
27
27
 
28
28
  ```ruby
29
- I18n.load_path << Dir[File.expand_path("config/locales") + "/*.yml"]
29
+ I18n.load_path += Dir[File.expand_path("config/locales") + "/*.yml"]
30
30
  I18n.default_locale = :en # (note that `en` is already the default!)
31
31
  ```
32
32
 
@@ -54,7 +54,7 @@ module I18n
54
54
  end
55
55
 
56
56
  deep_interpolation = options[:deep_interpolation]
57
- values = Utils.except(options, *RESERVED_KEYS)
57
+ values = Utils.except(options, *RESERVED_KEYS) unless options.empty?
58
58
  if values
59
59
  entry = if deep_interpolation
60
60
  deep_interpolate(locale, entry, values)
@@ -66,7 +66,7 @@ module I18n
66
66
  end
67
67
 
68
68
  def exists?(locale, key, options = EMPTY_HASH)
69
- lookup(locale, key) != nil
69
+ lookup(locale, key, options[:scope]) != nil
70
70
  end
71
71
 
72
72
  # Acts the same as +strftime+, but uses a localized version of the
@@ -123,7 +123,12 @@ module I18n
123
123
  # first translation that can be resolved. Otherwise it tries to resolve
124
124
  # the translation directly.
125
125
  def default(locale, object, subject, options = EMPTY_HASH)
126
- options = options.reject { |key, value| key == :default }
126
+ if options.size == 1 && options.has_key?(:default)
127
+ options = {}
128
+ else
129
+ options = Utils.except(options, :default)
130
+ end
131
+
127
132
  case subject
128
133
  when Array
129
134
  subject.each do |item|
@@ -166,7 +171,7 @@ module I18n
166
171
  # Other backends can implement more flexible or complex pluralization rules.
167
172
  def pluralize(locale, entry, count)
168
173
  entry = entry.reject { |k, _v| k == :attributes } if entry.is_a?(Hash)
169
- return entry unless entry.is_a?(Hash) && count && entry.values.none? { |v| v.is_a?(Hash) }
174
+ return entry unless entry.is_a?(Hash) && count
170
175
 
171
176
  key = pluralization_key(entry, count)
172
177
  raise InvalidPluralizationData.new(entry, count, key) unless entry.has_key?(key)
@@ -282,8 +287,8 @@ module I18n
282
287
  when '%^b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon].upcase
283
288
  when '%B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon]
284
289
  when '%^B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon].upcase
285
- when '%p' then I18n.t!(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).upcase if object.respond_to? :hour
286
- when '%P' then I18n.t!(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).downcase if object.respond_to? :hour
290
+ when '%p' then I18n.t!(:"time.#{(object.respond_to?(:hour) ? object.hour : 0) < 12 ? :am : :pm}", :locale => locale, :format => format).upcase
291
+ when '%P' then I18n.t!(:"time.#{(object.respond_to?(:hour) ? object.hour : 0) < 12 ? :am : :pm}", :locale => locale, :format => format).downcase
287
292
  end
288
293
  end
289
294
  rescue MissingTranslationData => e
@@ -16,26 +16,57 @@ module I18n
16
16
  module Pluralization
17
17
  # Overwrites the Base backend translate method so that it will check the
18
18
  # translation meta data space (:i18n) for a locale specific pluralization
19
- # rule and use it to pluralize the given entry. I.e. the library expects
19
+ # rule and use it to pluralize the given entry. I.e., the library expects
20
20
  # pluralization rules to be stored at I18n.t(:'i18n.plural.rule')
21
21
  #
22
22
  # Pluralization rules are expected to respond to #call(count) and
23
- # return a pluralization key. Valid keys depend on the translation data
24
- # hash (entry) but it is generally recommended to follow CLDR's style,
25
- # i.e., return one of the keys :zero, :one, :few, :many, :other.
23
+ # return a pluralization key. Valid keys depend on the pluralization
24
+ # rules for the locale, as defined in the CLDR.
25
+ # As of v41, 6 locale-specific plural categories are defined:
26
+ # :few, :many, :one, :other, :two, :zero
26
27
  #
27
- # The :zero key is always picked directly when count equals 0 AND the
28
- # translation data has the key :zero. This way translators are free to
29
- # either pick a special :zero translation even for languages where the
30
- # pluralizer does not return a :zero key.
28
+ # n.b., The :one plural category does not imply the number 1.
29
+ # Instead, :one is a category for any number that behaves like 1 in
30
+ # that locale. For example, in some locales, :one is used for numbers
31
+ # that end in "1" (like 1, 21, 151) but that don't end in
32
+ # 11 (like 11, 111, 10311).
33
+ # Similar notes apply to the :two, and :zero plural categories.
34
+ #
35
+ # If you want to have different strings for the categories of count == 0
36
+ # (e.g. "I don't have any cars") or count == 1 (e.g. "I have a single car")
37
+ # use the explicit `"0"` and `"1"` keys.
38
+ # https://unicode-org.github.io/cldr/ldml/tr35-numbers.html#Explicit_0_1_rules
31
39
  def pluralize(locale, entry, count)
32
40
  return entry unless entry.is_a?(Hash) && count
33
41
 
34
42
  pluralizer = pluralizer(locale)
35
43
  if pluralizer.respond_to?(:call)
36
- key = count == 0 && entry.has_key?(:zero) ? :zero : pluralizer.call(count)
37
- raise InvalidPluralizationData.new(entry, count, key) unless entry.has_key?(key)
38
- entry[key]
44
+ # Deprecation: The use of the `zero` key in this way is incorrect.
45
+ # Users that want a different string for the case of `count == 0` should use the explicit "0" key instead.
46
+ # We keep this incorrect behaviour for now for backwards compatibility until we can remove it.
47
+ # Ref: https://github.com/ruby-i18n/i18n/issues/629
48
+ return entry[:zero] if count == 0 && entry.has_key?(:zero)
49
+
50
+ # "0" and "1" are special cases
51
+ # https://unicode-org.github.io/cldr/ldml/tr35-numbers.html#Explicit_0_1_rules
52
+ if count == 0 || count == 1
53
+ value = entry[symbolic_count(count)]
54
+ return value if value
55
+ end
56
+
57
+ # Lateral Inheritance of "count" attribute (http://www.unicode.org/reports/tr35/#Lateral_Inheritance):
58
+ # > If there is no value for a path, and that path has a [@count="x"] attribute and value, then:
59
+ # > 1. If "x" is numeric, the path falls back to the path with [@count=«the plural rules category for x for that locale»], within that the same locale.
60
+ # > 2. If "x" is anything but "other", it falls back to a path [@count="other"], within that the same locale.
61
+ # > 3. If "x" is "other", it falls back to the path that is completely missing the count item, within that the same locale.
62
+ # Note: We don't yet implement #3 above, since we haven't decided how lateral inheritance attributes should be represented.
63
+ plural_rule_category = pluralizer.call(count)
64
+
65
+ value = if entry.has_key?(plural_rule_category) || entry.has_key?(:other)
66
+ entry[plural_rule_category] || entry[:other]
67
+ else
68
+ raise InvalidPluralizationData.new(entry, count, plural_rule_category)
69
+ end
39
70
  else
40
71
  super
41
72
  end
@@ -43,13 +74,23 @@ module I18n
43
74
 
44
75
  protected
45
76
 
46
- def pluralizers
47
- @pluralizers ||= {}
48
- end
77
+ def pluralizers
78
+ @pluralizers ||= {}
79
+ end
49
80
 
50
- def pluralizer(locale)
51
- pluralizers[locale] ||= I18n.t(:'i18n.plural.rule', :locale => locale, :resolve => false)
52
- end
81
+ def pluralizer(locale)
82
+ pluralizers[locale] ||= I18n.t(:'i18n.plural.rule', :locale => locale, :resolve => false)
83
+ end
84
+
85
+ private
86
+
87
+ # Normalizes categories of 0.0 and 1.0
88
+ # and returns the symbolic version
89
+ def symbolic_count(count)
90
+ count = 0 if count == 0
91
+ count = 1 if count == 1
92
+ count.to_s.to_sym
93
+ end
53
94
  end
54
95
  end
55
96
  end
@@ -21,6 +21,9 @@ module I18n
21
21
  class Simple
22
22
  module Implementation
23
23
  include Base
24
+
25
+ # Mutex to ensure that concurrent translations loading will be thread-safe
26
+ MUTEX = Mutex.new
24
27
 
25
28
  def initialized?
26
29
  @initialized ||= false
@@ -68,7 +71,11 @@ module I18n
68
71
  # call `init_translations`
69
72
  init_translations if do_init && !initialized?
70
73
 
71
- @translations ||= Concurrent::Hash.new { |h, k| h[k] = Concurrent::Hash.new }
74
+ @translations ||= Concurrent::Hash.new do |h, k|
75
+ MUTEX.synchronize do
76
+ h[k] = Concurrent::Hash.new
77
+ end
78
+ end
72
79
  end
73
80
 
74
81
  protected
@@ -94,7 +101,7 @@ module I18n
94
101
  return nil unless result.has_key?(_key)
95
102
  end
96
103
  result = result[_key]
97
- result = resolve_entry(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
104
+ result = resolve_entry(locale, _key, result, Utils.except(options.merge(:scope => nil), :count)) if result.is_a?(Symbol)
98
105
  result
99
106
  end
100
107
  end
@@ -45,30 +45,30 @@ module I18n
45
45
  "Ç"=>"C", "È"=>"E", "É"=>"E", "Ê"=>"E", "Ë"=>"E", "Ì"=>"I", "Í"=>"I",
46
46
  "Î"=>"I", "Ï"=>"I", "Ð"=>"D", "Ñ"=>"N", "Ò"=>"O", "Ó"=>"O", "Ô"=>"O",
47
47
  "Õ"=>"O", "Ö"=>"O", "×"=>"x", "Ø"=>"O", "Ù"=>"U", "Ú"=>"U", "Û"=>"U",
48
- "Ü"=>"U", "Ý"=>"Y", "Þ"=>"Th", "ß"=>"ss", "à"=>"a", "á"=>"a", "â"=>"a",
49
- "ã"=>"a", "ä"=>"a", "å"=>"a", "æ"=>"ae", "ç"=>"c", "è"=>"e", "é"=>"e",
50
- "ê"=>"e", "ë"=>"e", "ì"=>"i", "í"=>"i", "î"=>"i", "ï"=>"i", "ð"=>"d",
51
- "ñ"=>"n", "ò"=>"o", "ó"=>"o", "ô"=>"o", "õ"=>"o", "ö"=>"o", "ø"=>"o",
52
- "ù"=>"u", "ú"=>"u", "û"=>"u", "ü"=>"u", "ý"=>"y", "þ"=>"th", "ÿ"=>"y",
53
- "Ā"=>"A", "ā"=>"a", "Ă"=>"A", "ă"=>"a", "Ą"=>"A", "ą"=>"a", "Ć"=>"C",
54
- "ć"=>"c", "Ĉ"=>"C", "ĉ"=>"c", "Ċ"=>"C", "ċ"=>"c", "Č"=>"C", "č"=>"c",
55
- "Ď"=>"D", "ď"=>"d", "Đ"=>"D", "đ"=>"d", "Ē"=>"E", "ē"=>"e", "Ĕ"=>"E",
56
- "ĕ"=>"e", "Ė"=>"E", "ė"=>"e", "Ę"=>"E", "ę"=>"e", "Ě"=>"E", "ě"=>"e",
57
- "Ĝ"=>"G", "ĝ"=>"g", "Ğ"=>"G", "ğ"=>"g", "Ġ"=>"G", "ġ"=>"g", "Ģ"=>"G",
58
- "ģ"=>"g", "Ĥ"=>"H", "ĥ"=>"h", "Ħ"=>"H", "ħ"=>"h", "Ĩ"=>"I", "ĩ"=>"i",
59
- "Ī"=>"I", "ī"=>"i", "Ĭ"=>"I", "ĭ"=>"i", "Į"=>"I", "į"=>"i", "İ"=>"I",
60
- "ı"=>"i", "IJ"=>"IJ", "ij"=>"ij", "Ĵ"=>"J", "ĵ"=>"j", "Ķ"=>"K", "ķ"=>"k",
61
- "ĸ"=>"k", "Ĺ"=>"L", "ĺ"=>"l", "Ļ"=>"L", "ļ"=>"l", "Ľ"=>"L", "ľ"=>"l",
62
- "Ŀ"=>"L", "ŀ"=>"l", "Ł"=>"L", "ł"=>"l", "Ń"=>"N", "ń"=>"n", "Ņ"=>"N",
63
- "ņ"=>"n", "Ň"=>"N", "ň"=>"n", "ʼn"=>"'n", "Ŋ"=>"NG", "ŋ"=>"ng",
64
- "Ō"=>"O", "ō"=>"o", "Ŏ"=>"O", "ŏ"=>"o", "Ő"=>"O", "ő"=>"o", "Œ"=>"OE",
65
- "œ"=>"oe", "Ŕ"=>"R", "ŕ"=>"r", "Ŗ"=>"R", "ŗ"=>"r", "Ř"=>"R", "ř"=>"r",
66
- "Ś"=>"S", "ś"=>"s", "Ŝ"=>"S", "ŝ"=>"s", "Ş"=>"S", "ş"=>"s", "Š"=>"S",
67
- "š"=>"s", "Ţ"=>"T", "ţ"=>"t", "Ť"=>"T", "ť"=>"t", "Ŧ"=>"T", "ŧ"=>"t",
68
- "Ũ"=>"U", "ũ"=>"u", "Ū"=>"U", "ū"=>"u", "Ŭ"=>"U", "ŭ"=>"u", "Ů"=>"U",
69
- "ů"=>"u", "Ű"=>"U", "ű"=>"u", "Ų"=>"U", "ų"=>"u", "Ŵ"=>"W", "ŵ"=>"w",
70
- "Ŷ"=>"Y", "ŷ"=>"y", "Ÿ"=>"Y", "Ź"=>"Z", "ź"=>"z", "Ż"=>"Z", "ż"=>"z",
71
- "Ž"=>"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"
72
72
  }.freeze
73
73
 
74
74
  def initialize(rule = nil)
@@ -1,3 +1,5 @@
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
 
@@ -10,6 +12,11 @@ module I18n
10
12
  INTERPOLATION_PATTERN = Regexp.union(DEFAULT_INTERPOLATION_PATTERNS)
11
13
  deprecate_constant :INTERPOLATION_PATTERN
12
14
 
15
+ INTERPOLATION_PATTERNS_CACHE = Hash.new do |hash, patterns|
16
+ hash[patterns] = Regexp.union(patterns)
17
+ end
18
+ private_constant :INTERPOLATION_PATTERNS_CACHE
19
+
13
20
  class << self
14
21
  # Return String or raises MissingInterpolationArgument exception.
15
22
  # Missing argument's logic is handled by I18n.config.missing_interpolation_argument_handler.
@@ -20,7 +27,12 @@ module I18n
20
27
  end
21
28
 
22
29
  def interpolate_hash(string, values)
23
- string.gsub(Regexp.union(config.interpolation_patterns)) do |match|
30
+ pattern = INTERPOLATION_PATTERNS_CACHE[config.interpolation_patterns]
31
+ interpolated = false
32
+
33
+ interpolated_string = string.gsub(pattern) do |match|
34
+ interpolated = true
35
+
24
36
  if match == '%%'
25
37
  '%'
26
38
  else
@@ -34,6 +46,8 @@ module I18n
34
46
  $3 ? sprintf("%#{$3}", value) : value
35
47
  end
36
48
  end
49
+
50
+ interpolated ? interpolated_string : string
37
51
  end
38
52
  end
39
53
  end
@@ -34,6 +34,11 @@ module I18n
34
34
  assert_equal 'Sa', I18n.l(@date, :format => '%a', :locale => :de)
35
35
  end
36
36
 
37
+ test "localize Date: given an meridian indicator format it returns the correct meridian indicator" do
38
+ assert_equal 'AM', I18n.l(@date, :format => '%p', :locale => :de)
39
+ assert_equal 'am', I18n.l(@date, :format => '%P', :locale => :de)
40
+ end
41
+
37
42
  test "localize Date: given an abbreviated and uppercased day name format it returns the correct abbreviated day name in upcase" do
38
43
  assert_equal 'sa'.upcase, I18n.l(@date, :format => '%^a', :locale => :de)
39
44
  end
data/lib/i18n/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module I18n
4
- VERSION = "1.12.0"
4
+ VERSION = "1.13.0"
5
5
  end
data/lib/i18n.rb CHANGED
@@ -331,12 +331,11 @@ module I18n
331
331
  # keys are Symbols.
332
332
  def normalize_keys(locale, key, scope, separator = nil)
333
333
  separator ||= I18n.default_separator
334
+ locale = locale.to_sym if locale
334
335
 
335
- [
336
- *normalize_key(locale, separator),
337
- *normalize_key(scope, separator),
338
- *normalize_key(key, separator)
339
- ]
336
+ result = [locale]
337
+ result.concat(normalize_key(scope, separator)) if scope
338
+ result.concat(normalize_key(key, separator))
340
339
  end
341
340
 
342
341
  # Returns true when the passed locale, which can be either a String or a
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.0
4
+ version: 1.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Fuchs
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2022-07-13 00:00:00.000000000 Z
16
+ date: 2023-04-26 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: concurrent-ruby