i18n 0.9.5 → 1.8.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +70 -32
  3. data/lib/i18n.rb +95 -36
  4. data/lib/i18n/backend.rb +3 -0
  5. data/lib/i18n/backend/base.rb +53 -23
  6. data/lib/i18n/backend/cache.rb +10 -11
  7. data/lib/i18n/backend/cache_file.rb +36 -0
  8. data/lib/i18n/backend/cascade.rb +3 -1
  9. data/lib/i18n/backend/chain.rb +38 -5
  10. data/lib/i18n/backend/fallbacks.rb +25 -14
  11. data/lib/i18n/backend/flatten.rb +10 -5
  12. data/lib/i18n/backend/gettext.rb +4 -0
  13. data/lib/i18n/backend/interpolation_compiler.rb +3 -1
  14. data/lib/i18n/backend/key_value.rb +31 -2
  15. data/lib/i18n/backend/memoize.rb +10 -2
  16. data/lib/i18n/backend/metadata.rb +5 -3
  17. data/lib/i18n/backend/pluralization.rb +3 -1
  18. data/lib/i18n/backend/simple.rb +26 -10
  19. data/lib/i18n/backend/transliterator.rb +2 -0
  20. data/lib/i18n/config.rb +20 -2
  21. data/lib/i18n/core_ext/hash.rb +54 -24
  22. data/lib/i18n/exceptions.rb +23 -16
  23. data/lib/i18n/gettext.rb +2 -0
  24. data/lib/i18n/gettext/helpers.rb +4 -2
  25. data/lib/i18n/gettext/po_parser.rb +7 -7
  26. data/lib/i18n/interpolate/ruby.rb +6 -4
  27. data/lib/i18n/locale.rb +2 -0
  28. data/lib/i18n/locale/fallbacks.rb +9 -6
  29. data/lib/i18n/locale/tag/parents.rb +8 -6
  30. data/lib/i18n/locale/tag/simple.rb +1 -1
  31. data/lib/i18n/middleware.rb +2 -0
  32. data/lib/i18n/tests.rb +2 -0
  33. data/lib/i18n/tests/interpolation.rb +9 -4
  34. data/lib/i18n/tests/link.rb +11 -1
  35. data/lib/i18n/tests/localization/date.rb +29 -7
  36. data/lib/i18n/tests/localization/date_time.rb +27 -6
  37. data/lib/i18n/tests/localization/procs.rb +6 -5
  38. data/lib/i18n/tests/localization/time.rb +26 -4
  39. data/lib/i18n/tests/lookup.rb +2 -2
  40. data/lib/i18n/tests/procs.rb +5 -0
  41. data/lib/i18n/version.rb +3 -1
  42. metadata +14 -60
  43. data/gemfiles/Gemfile.rails-3.2.x +0 -10
  44. data/gemfiles/Gemfile.rails-4.0.x +0 -10
  45. data/gemfiles/Gemfile.rails-4.1.x +0 -10
  46. data/gemfiles/Gemfile.rails-4.2.x +0 -10
  47. data/gemfiles/Gemfile.rails-5.0.x +0 -10
  48. data/gemfiles/Gemfile.rails-5.1.x +0 -10
  49. data/gemfiles/Gemfile.rails-master +0 -10
  50. data/lib/i18n/core_ext/kernel/suppress_warnings.rb +0 -8
  51. data/lib/i18n/core_ext/string/interpolate.rb +0 -9
  52. data/test/api/all_features_test.rb +0 -58
  53. data/test/api/cascade_test.rb +0 -28
  54. data/test/api/chain_test.rb +0 -24
  55. data/test/api/fallbacks_test.rb +0 -30
  56. data/test/api/key_value_test.rb +0 -24
  57. data/test/api/memoize_test.rb +0 -56
  58. data/test/api/override_test.rb +0 -42
  59. data/test/api/pluralization_test.rb +0 -30
  60. data/test/api/simple_test.rb +0 -28
  61. data/test/backend/cache_test.rb +0 -109
  62. data/test/backend/cascade_test.rb +0 -86
  63. data/test/backend/chain_test.rb +0 -122
  64. data/test/backend/exceptions_test.rb +0 -36
  65. data/test/backend/fallbacks_test.rb +0 -219
  66. data/test/backend/interpolation_compiler_test.rb +0 -118
  67. data/test/backend/key_value_test.rb +0 -61
  68. data/test/backend/memoize_test.rb +0 -79
  69. data/test/backend/metadata_test.rb +0 -48
  70. data/test/backend/pluralization_test.rb +0 -45
  71. data/test/backend/simple_test.rb +0 -103
  72. data/test/backend/transliterator_test.rb +0 -84
  73. data/test/core_ext/hash_test.rb +0 -36
  74. data/test/gettext/api_test.rb +0 -214
  75. data/test/gettext/backend_test.rb +0 -92
  76. data/test/i18n/exceptions_test.rb +0 -117
  77. data/test/i18n/gettext_plural_keys_test.rb +0 -20
  78. data/test/i18n/interpolate_test.rb +0 -91
  79. data/test/i18n/load_path_test.rb +0 -34
  80. data/test/i18n/middleware_test.rb +0 -24
  81. data/test/i18n_test.rb +0 -462
  82. data/test/locale/fallbacks_test.rb +0 -133
  83. data/test/locale/tag/rfc4646_test.rb +0 -143
  84. data/test/locale/tag/simple_test.rb +0 -32
  85. data/test/run_all.rb +0 -20
  86. data/test/test_data/locales/de.po +0 -82
  87. data/test/test_data/locales/en.rb +0 -3
  88. data/test/test_data/locales/en.yml +0 -3
  89. data/test/test_data/locales/invalid/empty.yml +0 -0
  90. data/test/test_data/locales/invalid/syntax.yml +0 -4
  91. data/test/test_data/locales/plurals.rb +0 -113
  92. data/test/test_helper.rb +0 -61
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # I18n Pluralization are useful when you want your application to
2
4
  # customize pluralization rules.
3
5
  #
@@ -27,7 +29,7 @@ module I18n
27
29
  # either pick a special :zero translation even for languages where the
28
30
  # pluralizer does not return a :zero key.
29
31
  def pluralize(locale, entry, count)
30
- return entry unless entry.is_a?(Hash) and count
32
+ return entry unless entry.is_a?(Hash) && count
31
33
 
32
34
  pluralizer = pluralizer(locale)
33
35
  if pluralizer.respond_to?(:call)
@@ -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
@@ -15,7 +19,7 @@ module I18n
15
19
  #
16
20
  # I18n::Backend::Simple.include(I18n::Backend::Pluralization)
17
21
  class Simple
18
- (class << self; self; end).class_eval { public :include }
22
+ using I18n::HashRefinements
19
23
 
20
24
  module Implementation
21
25
  include Base
@@ -28,7 +32,7 @@ module I18n
28
32
  # This uses a deep merge for the translations hash, so existing
29
33
  # translations will be overwritten by new ones only at the deepest
30
34
  # level of the hash.
31
- def store_translations(locale, data, options = {})
35
+ def store_translations(locale, data, options = EMPTY_HASH)
32
36
  if I18n.enforce_available_locales &&
33
37
  I18n.available_locales_initialized? &&
34
38
  !I18n.available_locales.include?(locale.to_sym) &&
@@ -36,7 +40,7 @@ module I18n
36
40
  return data
37
41
  end
38
42
  locale = locale.to_sym
39
- translations[locale] ||= {}
43
+ translations[locale] ||= Concurrent::Hash.new
40
44
  data = data.deep_symbolize_keys
41
45
  translations[locale].deep_merge!(data)
42
46
  end
@@ -57,6 +61,19 @@ 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 { |h, k| h[k] = Concurrent::Hash.new }
75
+ end
76
+
60
77
  protected
61
78
 
62
79
  def init_translations
@@ -64,22 +81,21 @@ module I18n
64
81
  @initialized = true
65
82
  end
66
83
 
67
- def translations
68
- @translations ||= {}
69
- end
70
-
71
84
  # Looks up a translation from the translations hash. Returns nil if
72
85
  # either key is nil, or locale, scope or key do not exist as a key in the
73
86
  # nested translations hash. Splits keys or scopes containing dots
74
87
  # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
75
88
  # <tt>%w(currency format)</tt>.
76
- def lookup(locale, key, scope = [], options = {})
89
+ def lookup(locale, key, scope = [], options = EMPTY_HASH)
77
90
  init_translations unless initialized?
78
91
  keys = I18n.normalize_keys(locale, key, scope, options[:separator])
79
92
 
80
93
  keys.inject(translations) do |result, _key|
81
- _key = _key.to_sym
82
- return nil unless result.is_a?(Hash) && result.has_key?(_key)
94
+ return nil unless result.is_a?(Hash)
95
+ unless result.has_key?(_key)
96
+ _key = _key.to_s.to_sym
97
+ return nil unless result.has_key?(_key)
98
+ end
83
99
  result = result[_key]
84
100
  result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
85
101
  result
@@ -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
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.
@@ -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 )
@@ -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
@@ -1,29 +1,59 @@
1
- class Hash
2
- def slice(*keep_keys)
3
- h = {}
4
- keep_keys.each { |key| h[key] = fetch(key) if has_key?(key) }
5
- h
6
- end unless Hash.method_defined?(:slice)
1
+ module I18n
2
+ module HashRefinements
3
+ refine Hash do
4
+ using I18n::HashRefinements
5
+ def except(*keys)
6
+ dup.except!(*keys)
7
+ end
7
8
 
8
- def except(*less_keys)
9
- slice(*keys - less_keys)
10
- end unless Hash.method_defined?(:except)
9
+ def except!(*keys)
10
+ keys.each { |key| delete(key) }
11
+ self
12
+ end
11
13
 
12
- def deep_symbolize_keys
13
- inject({}) { |result, (key, value)|
14
- value = value.deep_symbolize_keys if value.is_a?(Hash)
15
- result[(key.to_sym rescue key) || key] = value
16
- result
17
- }
18
- end unless Hash.method_defined?(:deep_symbolize_keys)
14
+ def deep_symbolize_keys
15
+ each_with_object({}) do |(key, value), result|
16
+ result[symbolize_key(key)] = deep_symbolize_keys_in_object(value)
17
+ result
18
+ end
19
+ end
19
20
 
20
- # deep_merge_hash! by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
21
- MERGER = proc do |key, v1, v2|
22
- Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2
21
+ # deep_merge from activesupport 5
22
+ # Copyright (c) 2005-2019 David Heinemeier Hansson
23
+ def deep_merge(other_hash, &block)
24
+ dup.deep_merge!(other_hash, &block)
25
+ end
26
+
27
+ # deep_merge! from activesupport 5
28
+ # Copyright (c) 2005-2019 David Heinemeier Hansson
29
+ def deep_merge!(other_hash, &block)
30
+ merge!(other_hash) do |key, this_val, other_val|
31
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
32
+ this_val.deep_merge(other_val, &block)
33
+ elsif block_given?
34
+ block.call(key, this_val, other_val)
35
+ else
36
+ other_val
37
+ end
38
+ end
39
+ end
40
+
41
+ def symbolize_key(key)
42
+ key.respond_to?(:to_sym) ? key.to_sym : key
43
+ end
44
+
45
+ private
46
+
47
+ def deep_symbolize_keys_in_object(value)
48
+ case value
49
+ when Hash
50
+ value.deep_symbolize_keys
51
+ when Array
52
+ value.map { |e| deep_symbolize_keys_in_object(e) }
53
+ else
54
+ value
55
+ end
56
+ end
57
+ end
23
58
  end
24
-
25
- def deep_merge!(data)
26
- merge!(data, &MERGER)
27
- end unless Hash.method_defined?(:deep_merge!)
28
59
  end
29
-
@@ -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
- include Module.new {
10
- def call(exception, locale, key, options)
11
- case exception
12
- when MissingTranslation
13
- exception.message
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 explictly 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)
@@ -42,7 +49,7 @@ module I18n
42
49
  module Base
43
50
  attr_reader :locale, :key, :options
44
51
 
45
- def initialize(locale, key, options = {})
52
+ def initialize(locale, key, options = EMPTY_HASH)
46
53
  @key, @locale, @options = key, locale, options.dup
47
54
  options.each { |k, v| self.options[k] = v.inspect if v.is_a?(Proc) }
48
55
  end
data/lib/i18n/gettext.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module I18n
2
4
  module Gettext
3
5
  PLURAL_SEPARATOR = "\001"
@@ -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, { :default => msgid, :separator => '|' }.merge(options))
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"
@@ -2,11 +2,13 @@
2
2
  # http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb
3
3
 
4
4
  module I18n
5
- INTERPOLATION_PATTERN = Regexp.union(
5
+ DEFAULT_INTERPOLATION_PATTERNS = [
6
6
  /%%/,
7
- /%\{(\w+)\}/, # matches placeholders like "%{foo}"
7
+ /%\{([\w|]+)\}/, # matches placeholders like "%{foo} or %{foo|word}"
8
8
  /%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
9
- )
9
+ ].freeze
10
+ INTERPOLATION_PATTERN = Regexp.union(DEFAULT_INTERPOLATION_PATTERNS)
11
+ deprecate_constant :INTERPOLATION_PATTERN
10
12
 
11
13
  class << self
12
14
  # Return String or raises MissingInterpolationArgument exception.
@@ -18,7 +20,7 @@ module I18n
18
20
  end
19
21
 
20
22
  def interpolate_hash(string, values)
21
- string.gsub(INTERPOLATION_PATTERN) do |match|
23
+ string.gsub(Regexp.union(config.interpolation_patterns)) do |match|
22
24
  if match == '%%'
23
25
  '%'
24
26
  else
data/lib/i18n/locale.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module I18n
2
4
  module Locale
3
5
  autoload :Fallbacks, 'i18n/locale/fallbacks'
@@ -26,7 +26,7 @@
26
26
  #
27
27
  # I18n.default_locale = :"en-US"
28
28
  # I18n.fallbacks = I18n::Locale::Fallbacks.new(:"de-AT" => :"de-DE")
29
- # I18n.fallbacks[:"de-AT"] # => [:"de-AT", :"de-DE", :de, :"en-US", :en]
29
+ # I18n.fallbacks[:"de-AT"] # => [:"de-AT", :de, :"de-DE"]
30
30
  #
31
31
  # # using a custom locale as default fallback locale
32
32
  #
@@ -56,16 +56,17 @@ module I18n
56
56
  def initialize(*mappings)
57
57
  @map = {}
58
58
  map(mappings.pop) if mappings.last.is_a?(Hash)
59
- self.defaults = mappings.empty? ? [I18n.default_locale.to_sym] : mappings
59
+ self.defaults = mappings.empty? ? [] : mappings
60
60
  end
61
61
 
62
62
  def defaults=(defaults)
63
- @defaults = defaults.map { |default| compute(default, false) }.flatten
63
+ @defaults = defaults.flat_map { |default| compute(default, false) }
64
64
  end
65
65
  attr_reader :defaults
66
66
 
67
67
  def [](locale)
68
68
  raise InvalidLocale.new(locale) if locale.nil?
69
+ raise Disabled.new('fallback#[]') if locale == false
69
70
  locale = locale.to_sym
70
71
  super || store(locale, compute(locale))
71
72
  end
@@ -83,13 +84,15 @@ module I18n
83
84
  protected
84
85
 
85
86
  def compute(tags, include_defaults = true, exclude = [])
86
- result = Array(tags).collect do |tag|
87
+ result = Array(tags).flat_map do |tag|
87
88
  tags = I18n::Locale::Tag.tag(tag).self_and_parents.map! { |t| t.to_sym } - exclude
88
89
  tags.each { |_tag| tags += compute(@map[_tag], false, exclude + tags) if @map[_tag] }
89
90
  tags
90
- end.flatten
91
+ end
91
92
  result.push(*defaults) if include_defaults
92
- result.uniq.compact
93
+ result.uniq!
94
+ result.compact!
95
+ result
93
96
  end
94
97
  end
95
98
  end