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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +0 -0
  3. data/README.md +127 -0
  4. data/lib/i18n/backend/base.rb +189 -111
  5. data/lib/i18n/backend/cache.rb +58 -22
  6. data/lib/i18n/backend/cache_file.rb +36 -0
  7. data/lib/i18n/backend/cascade.rb +9 -10
  8. data/lib/i18n/backend/chain.rb +95 -42
  9. data/lib/i18n/backend/fallbacks.rb +68 -22
  10. data/lib/i18n/backend/flatten.rb +13 -8
  11. data/lib/i18n/backend/gettext.rb +33 -25
  12. data/lib/i18n/backend/interpolation_compiler.rb +12 -14
  13. data/lib/i18n/backend/key_value.rb +112 -10
  14. data/lib/i18n/backend/lazy_loadable.rb +184 -0
  15. data/lib/i18n/backend/memoize.rb +13 -7
  16. data/lib/i18n/backend/metadata.rb +12 -6
  17. data/lib/i18n/backend/pluralization.rb +64 -25
  18. data/lib/i18n/backend/simple.rb +44 -18
  19. data/lib/i18n/backend/transliterator.rb +43 -33
  20. data/lib/i18n/backend.rb +5 -3
  21. data/lib/i18n/config.rb +91 -10
  22. data/lib/i18n/exceptions.rb +118 -22
  23. data/lib/i18n/gettext/helpers.rb +14 -4
  24. data/lib/i18n/gettext/po_parser.rb +7 -7
  25. data/lib/i18n/gettext.rb +6 -5
  26. data/lib/i18n/interpolate/ruby.rb +53 -0
  27. data/lib/i18n/locale/fallbacks.rb +33 -26
  28. data/lib/i18n/locale/tag/parents.rb +8 -8
  29. data/lib/i18n/locale/tag/rfc4646.rb +0 -2
  30. data/lib/i18n/locale/tag/simple.rb +2 -4
  31. data/lib/i18n/locale.rb +2 -0
  32. data/lib/i18n/middleware.rb +17 -0
  33. data/lib/i18n/tests/basics.rb +58 -0
  34. data/lib/i18n/tests/defaults.rb +52 -0
  35. data/lib/i18n/tests/interpolation.rb +167 -0
  36. data/lib/i18n/tests/link.rb +66 -0
  37. data/lib/i18n/tests/localization/date.rb +122 -0
  38. data/lib/i18n/tests/localization/date_time.rb +103 -0
  39. data/lib/i18n/tests/localization/procs.rb +118 -0
  40. data/lib/i18n/tests/localization/time.rb +103 -0
  41. data/lib/i18n/tests/localization.rb +19 -0
  42. data/lib/i18n/tests/lookup.rb +81 -0
  43. data/lib/i18n/tests/pluralization.rb +35 -0
  44. data/lib/i18n/tests/procs.rb +66 -0
  45. data/lib/i18n/tests.rb +14 -0
  46. data/lib/i18n/utils.rb +55 -0
  47. data/lib/i18n/version.rb +3 -1
  48. data/lib/i18n.rb +200 -87
  49. metadata +64 -56
  50. data/CHANGELOG.textile +0 -135
  51. data/README.textile +0 -93
  52. data/lib/i18n/backend/active_record/missing.rb +0 -65
  53. data/lib/i18n/backend/active_record/store_procs.rb +0 -38
  54. data/lib/i18n/backend/active_record/translation.rb +0 -93
  55. data/lib/i18n/backend/active_record.rb +0 -61
  56. data/lib/i18n/backend/cldr.rb +0 -100
  57. data/lib/i18n/core_ext/hash.rb +0 -29
  58. data/lib/i18n/core_ext/string/interpolate.rb +0 -98
@@ -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,55 +45,63 @@ 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", "à"=>"a", "á"=>"a", "â"=>"a",
47
- "ã"=>"a", "ä"=>"a", "å"=>"a", "æ"=>"ae", "ç"=>"c", "è"=>"e", "é"=>"e",
48
- "ê"=>"e", "ë"=>"e", "ì"=>"i", "í"=>"i", "î"=>"i", "ï"=>"i", "ð"=>"d",
49
- "ñ"=>"n", "ò"=>"o", "ó"=>"o", "ô"=>"o", "õ"=>"o", "ö"=>"o", "ø"=>"o",
50
- "ù"=>"u", "ú"=>"u", "û"=>"u", "ü"=>"u", "ý"=>"y", "þ"=>"th", "ÿ"=>"y",
51
- "Ā"=>"A", "ā"=>"a", "Ă"=>"A", "ă"=>"a", "Ą"=>"A", "ą"=>"a", "Ć"=>"C",
52
- "ć"=>"c", "Ĉ"=>"C", "ĉ"=>"c", "Ċ"=>"C", "ċ"=>"c", "Č"=>"C", "č"=>"c",
53
- "Ď"=>"D", "ď"=>"d", "Đ"=>"D", "đ"=>"d", "Ē"=>"E", "ē"=>"e", "Ĕ"=>"E",
54
- "ĕ"=>"e", "Ė"=>"E", "ė"=>"e", "Ę"=>"E", "ę"=>"e", "Ě"=>"E", "ě"=>"e",
55
- "Ĝ"=>"G", "ĝ"=>"g", "Ğ"=>"G", "ğ"=>"g", "Ġ"=>"G", "ġ"=>"g", "Ģ"=>"G",
56
- "ģ"=>"g", "Ĥ"=>"H", "ĥ"=>"h", "Ħ"=>"H", "ħ"=>"h", "Ĩ"=>"I", "ĩ"=>"i",
57
- "Ī"=>"I", "ī"=>"i", "Ĭ"=>"I", "ĭ"=>"i", "Į"=>"I", "į"=>"i", "İ"=>"I",
58
- "ı"=>"i", "IJ"=>"IJ", "ij"=>"ij", "Ĵ"=>"J", "ĵ"=>"j", "Ķ"=>"K", "ķ"=>"k",
59
- "ĸ"=>"k", "Ĺ"=>"L", "ĺ"=>"l", "Ļ"=>"L", "ļ"=>"l", "Ľ"=>"L", "ľ"=>"l",
60
- "Ŀ"=>"L", "ŀ"=>"l", "Ł"=>"L", "ł"=>"l", "Ń"=>"N", "ń"=>"n", "Ņ"=>"N",
61
- "ņ"=>"n", "Ň"=>"N", "ň"=>"n", "ʼn"=>"'n", "Ŋ"=>"NG", "ŋ"=>"ng",
62
- "Ō"=>"O", "ō"=>"o", "Ŏ"=>"O", "ŏ"=>"o", "Ő"=>"O", "ő"=>"o", "Œ"=>"OE",
63
- "œ"=>"oe", "Ŕ"=>"R", "ŕ"=>"r", "Ŗ"=>"R", "ŗ"=>"r", "Ř"=>"R", "ř"=>"r",
64
- "Ś"=>"S", "ś"=>"s", "Ŝ"=>"S", "ŝ"=>"s", "Ş"=>"S", "ş"=>"s", "Š"=>"S",
65
- "š"=>"s", "Ţ"=>"T", "ţ"=>"t", "Ť"=>"T", "ť"=>"t", "Ŧ"=>"T", "ŧ"=>"t",
66
- "Ũ"=>"U", "ũ"=>"u", "Ū"=>"U", "ū"=>"u", "Ŭ"=>"U", "ŭ"=>"u", "Ů"=>"U",
67
- "ů"=>"u", "Ű"=>"U", "ű"=>"u", "Ų"=>"U", "ų"=>"u", "Ŵ"=>"W", "ŵ"=>"w",
68
- "Ŷ"=>"Y", "ŷ"=>"y", "Ÿ"=>"Y", "Ź"=>"Z", "ź"=>"z", "Ż"=>"Z", "ż"=>"z",
69
- "Ž"=>"Z", "ž"=>"z"
70
- }
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
+ }.freeze
71
73
 
72
74
  def initialize(rule = nil)
73
75
  @rule = rule
74
- add DEFAULT_APPROXIMATIONS
76
+ add_default_approximations
75
77
  add rule if rule
76
78
  end
77
79
 
78
80
  def transliterate(string, replacement = nil)
81
+ replacement ||= DEFAULT_REPLACEMENT_CHAR
79
82
  string.gsub(/[^\x00-\x7f]/u) do |char|
80
- approximations[char] || replacement || DEFAULT_REPLACEMENT_CHAR
83
+ approximations[char] || replacement
81
84
  end
82
85
  end
83
86
 
84
87
  private
85
88
 
86
- def approximations
87
- @approximations ||= {}
89
+ def approximations
90
+ @approximations ||= {}
91
+ end
92
+
93
+ def add_default_approximations
94
+ DEFAULT_APPROXIMATIONS.each do |key, value|
95
+ approximations[key] = value
88
96
  end
97
+ end
89
98
 
90
- # Add transliteration rules to the approximations hash.
91
- def add(hash)
92
- hash.keys.each {|key| hash[key.to_s] = hash.delete(key).to_s}
93
- approximations.merge! hash
99
+ # Add transliteration rules to the approximations hash.
100
+ def add(hash)
101
+ hash.each do |key, value|
102
+ approximations[key.to_s] = value.to_s
94
103
  end
104
+ end
95
105
  end
96
106
  end
97
107
  end
data/lib/i18n/backend.rb CHANGED
@@ -1,16 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module I18n
2
4
  module Backend
3
- autoload :ActiveRecord, 'i18n/backend/active_record'
4
5
  autoload :Base, 'i18n/backend/base'
5
- autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
6
6
  autoload :Cache, 'i18n/backend/cache'
7
+ autoload :CacheFile, 'i18n/backend/cache_file'
7
8
  autoload :Cascade, 'i18n/backend/cascade'
8
9
  autoload :Chain, 'i18n/backend/chain'
9
- autoload :Cldr, 'i18n/backend/cldr'
10
10
  autoload :Fallbacks, 'i18n/backend/fallbacks'
11
11
  autoload :Flatten, 'i18n/backend/flatten'
12
12
  autoload :Gettext, 'i18n/backend/gettext'
13
+ autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
13
14
  autoload :KeyValue, 'i18n/backend/key_value'
15
+ autoload :LazyLoadable, 'i18n/backend/lazy_loadable'
14
16
  autoload :Memoize, 'i18n/backend/memoize'
15
17
  autoload :Metadata, 'i18n/backend/metadata'
16
18
  autoload :Pluralization, 'i18n/backend/pluralization'
data/lib/i18n/config.rb CHANGED
@@ -1,14 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
1
5
  module I18n
2
6
  class Config
3
7
  # The only configuration value that is not global and scoped to thread is :locale.
4
8
  # It defaults to the default_locale.
5
9
  def locale
6
- @locale ||= default_locale
10
+ defined?(@locale) && @locale != nil ? @locale : default_locale
7
11
  end
8
12
 
9
13
  # Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
10
14
  def locale=(locale)
11
- @locale = locale.to_sym rescue nil
15
+ I18n.enforce_available_locales!(locale)
16
+ @locale = locale && locale.to_sym
12
17
  end
13
18
 
14
19
  # Returns the current backend. Defaults to +Backend::Simple+.
@@ -28,19 +33,42 @@ module I18n
28
33
 
29
34
  # Sets the current default locale. Used to set a custom default locale.
30
35
  def default_locale=(locale)
31
- @@default_locale = locale.to_sym rescue nil
36
+ I18n.enforce_available_locales!(locale)
37
+ @@default_locale = locale && locale.to_sym
32
38
  end
33
39
 
34
40
  # Returns an array of locales for which translations are available.
35
- # Unless you explicitely set the these through I18n.available_locales=
36
- # the call will be delegated to the backend and memoized on the I18n module.
41
+ # Unless you explicitly set these through I18n.available_locales=
42
+ # the call will be delegated to the backend.
37
43
  def available_locales
38
- @@available_locales ||= backend.available_locales
44
+ @@available_locales ||= nil
45
+ @@available_locales || backend.available_locales
46
+ end
47
+
48
+ # Caches the available locales list as both strings and symbols in a Set, so
49
+ # that we can have faster lookups to do the available locales enforce check.
50
+ def available_locales_set #:nodoc:
51
+ @@available_locales_set ||= available_locales.inject(Set.new) do |set, locale|
52
+ set << locale.to_s << locale.to_sym
53
+ end
39
54
  end
40
55
 
41
56
  # Sets the available locales.
42
57
  def available_locales=(locales)
43
- @@available_locales = locales
58
+ @@available_locales = Array(locales).map { |locale| locale.to_sym }
59
+ @@available_locales = nil if @@available_locales.empty?
60
+ @@available_locales_set = nil
61
+ end
62
+
63
+ # Returns true if the available_locales have been initialized
64
+ def available_locales_initialized?
65
+ ( !!defined?(@@available_locales) && !!@@available_locales )
66
+ end
67
+
68
+ # Clears the available locales set so it can be recomputed again after I18n
69
+ # gets reloaded.
70
+ def clear_available_locales_set #:nodoc:
71
+ @@available_locales_set = nil
44
72
  end
45
73
 
46
74
  # Returns the current default scope separator. Defaults to '.'
@@ -53,9 +81,10 @@ module I18n
53
81
  @@default_separator = separator
54
82
  end
55
83
 
56
- # Return the current exception handler. Defaults to :default_exception_handler.
84
+ # Returns the current exception handler. Defaults to an instance of
85
+ # I18n::ExceptionHandler.
57
86
  def exception_handler
58
- @@exception_handler ||= :default_exception_handler
87
+ @@exception_handler ||= ExceptionHandler.new
59
88
  end
60
89
 
61
90
  # Sets the exception handler.
@@ -63,6 +92,29 @@ module I18n
63
92
  @@exception_handler = exception_handler
64
93
  end
65
94
 
95
+ # Returns the current handler for situations when interpolation argument
96
+ # is missing. MissingInterpolationArgument will be raised by default.
97
+ def missing_interpolation_argument_handler
98
+ @@missing_interpolation_argument_handler ||= lambda do |missing_key, provided_hash, string|
99
+ raise MissingInterpolationArgument.new(missing_key, provided_hash, string)
100
+ end
101
+ end
102
+
103
+ # Sets the missing interpolation argument handler. It can be any
104
+ # object that responds to #call. The arguments that will be passed to #call
105
+ # are the same as for MissingInterpolationArgument initializer. Use +Proc.new+
106
+ # if you don't care about arity.
107
+ #
108
+ # == Example:
109
+ # You can suppress raising an exception and return string instead:
110
+ #
111
+ # I18n.config.missing_interpolation_argument_handler = Proc.new do |key|
112
+ # "#{key} is missing"
113
+ # end
114
+ def missing_interpolation_argument_handler=(exception_handler)
115
+ @@missing_interpolation_argument_handler = exception_handler
116
+ end
117
+
66
118
  # Allow clients to register paths providing translation data sources. The
67
119
  # backend defines acceptable sources.
68
120
  #
@@ -79,6 +131,35 @@ module I18n
79
131
  # behave like a Ruby Array.
80
132
  def load_path=(load_path)
81
133
  @@load_path = load_path
134
+ @@available_locales_set = nil
135
+ backend.reload!
136
+ end
137
+
138
+ # Whether or not to verify if locales are in the list of available locales.
139
+ # Defaults to true.
140
+ @@enforce_available_locales = true
141
+ def enforce_available_locales
142
+ @@enforce_available_locales
143
+ end
144
+
145
+ def enforce_available_locales=(enforce_available_locales)
146
+ @@enforce_available_locales = enforce_available_locales
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
82
163
  end
83
164
  end
84
- end
165
+ end
@@ -1,14 +1,34 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- class KeyError < IndexError
4
- def initialize(message = nil)
5
- super(message || "key not found")
6
- end
7
- end unless defined?(KeyError)
3
+ require 'cgi'
8
4
 
9
5
  module I18n
6
+ class ExceptionHandler
7
+ def call(exception, _locale, _key, _options)
8
+ if exception.is_a?(MissingTranslation)
9
+ exception.message
10
+ else
11
+ raise exception
12
+ end
13
+ end
14
+ end
15
+
10
16
  class ArgumentError < ::ArgumentError; end
11
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
+
12
32
  class InvalidLocale < ArgumentError
13
33
  attr_reader :locale
14
34
  def initialize(locale)
@@ -17,29 +37,71 @@ module I18n
17
37
  end
18
38
  end
19
39
 
20
- class MissingTranslationData < ArgumentError
21
- attr_reader :locale, :key, :options
22
- def initialize(locale, key, opts = nil)
23
- @key, @locale, @options = key, locale, opts || {}
24
- keys = I18n.normalize_keys(locale, key, options[:scope])
25
- keys << 'no key' if keys.size < 2
26
- super "translation missing: #{keys.join(', ')}"
40
+ class InvalidLocaleData < ArgumentError
41
+ attr_reader :filename
42
+ def initialize(filename, exception_message)
43
+ @filename, @exception_message = filename, exception_message
44
+ super "can not load translations from #{filename}: #{exception_message}"
45
+ end
46
+ end
47
+
48
+ class MissingTranslation < ArgumentError
49
+ module Base
50
+ PERMITTED_KEYS = [:scope, :default].freeze
51
+
52
+ attr_reader :locale, :key, :options
53
+
54
+ def initialize(locale, key, options = EMPTY_HASH)
55
+ @key, @locale, @options = key, locale, options.slice(*PERMITTED_KEYS)
56
+ options.each { |k, v| self.options[k] = v.inspect if v.is_a?(Proc) }
57
+ end
58
+
59
+ def keys
60
+ @keys ||= I18n.normalize_keys(locale, key, options[:scope]).tap do |keys|
61
+ keys << 'no key' if keys.size < 2
62
+ end
63
+ end
64
+
65
+ def message
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
72
+ end
73
+
74
+ def normalized_option(key)
75
+ I18n.normalize_keys(locale, key, options[:scope]).join('.')
76
+ end
77
+
78
+ alias :to_s :message
79
+
80
+ def to_exception
81
+ MissingTranslationData.new(locale, key, options)
82
+ end
27
83
  end
84
+
85
+ include Base
86
+ end
87
+
88
+ class MissingTranslationData < ArgumentError
89
+ include MissingTranslation::Base
28
90
  end
29
91
 
30
92
  class InvalidPluralizationData < ArgumentError
31
- attr_reader :entry, :count
32
- def initialize(entry, count)
33
- @entry, @count = entry, count
34
- super "translation data #{entry.inspect} can not be used with :count => #{count}"
93
+ attr_reader :entry, :count, :key
94
+ def initialize(entry, count, key)
95
+ @entry, @count, @key = entry, count, key
96
+ super "translation data #{entry.inspect} can not be used with :count => #{count}. key '#{key}' is missing."
35
97
  end
36
98
  end
37
99
 
38
100
  class MissingInterpolationArgument < ArgumentError
39
- attr_reader :values, :string
40
- def initialize(values, string)
41
- @values, @string = values, string
42
- super "missing interpolation argument in #{string.inspect} (#{values.inspect} given)"
101
+ attr_reader :key, :values, :string
102
+ def initialize(key, values, string)
103
+ @key, @values, @string = key, values, string
104
+ super "missing interpolation argument #{key.inspect} in #{string.inspect} (#{values.inspect} given)"
43
105
  end
44
106
  end
45
107
 
@@ -58,4 +120,38 @@ module I18n
58
120
  super "can not load translations from #{filename}, the file type #{type} is not known"
59
121
  end
60
122
  end
61
- 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
157
+ end
@@ -1,4 +1,5 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  require 'i18n/gettext'
3
4
 
4
5
  module I18n
@@ -6,10 +7,19 @@ module I18n
6
7
  # Implements classical Gettext style accessors. To use this include the
7
8
  # module to the global namespace or wherever you want to use it.
8
9
  #
9
- # include I18n::Helpers::Gettext
10
+ # include I18n::Gettext::Helpers
10
11
  module Helpers
11
- def gettext(msgid, options = {})
12
- I18n.t(msgid, { :default => msgid, :separator => '|' }.merge(options))
12
+ # Makes dynamic translation messages readable for the gettext parser.
13
+ # <tt>_(fruit)</tt> cannot be understood by the gettext parser. To help the parser find all your translations,
14
+ # you can add <tt>fruit = N_("Apple")</tt> which does not translate, but tells the parser: "Apple" needs translation.
15
+ # * msgid: the message id.
16
+ # * Returns: msgid.
17
+ def N_(msgsid)
18
+ msgsid
19
+ end
20
+
21
+ def gettext(msgid, options = EMPTY_HASH)
22
+ I18n.t(msgid, **{:default => msgid, :separator => '|'}.merge(options))
13
23
  end
14
24
  alias _ gettext
15
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,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module I18n
4
4
  module Gettext
@@ -10,11 +10,12 @@ module I18n
10
10
  @@plural_keys = { :en => [:one, :other] }
11
11
 
12
12
  class << self
13
- # returns an array of plural keys for the given locale so that we can
14
- # convert from gettext's integer-index based style
13
+ # returns an array of plural keys for the given locale or the whole hash
14
+ # of locale mappings to plural keys so that we can convert from gettext's
15
+ # integer-index based style
15
16
  # TODO move this information to the pluralization module
16
- def plural_keys(locale)
17
- @@plural_keys[locale] || @@plural_keys[:en]
17
+ def plural_keys(*args)
18
+ args.empty? ? @@plural_keys : @@plural_keys[args.first] || @@plural_keys[:en]
18
19
  end
19
20
 
20
21
  def extract_scope(msgid, separator)
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # heavily based on Masao Mutoh's gettext String interpolation extension
4
+ # http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb
5
+
6
+ module I18n
7
+ DEFAULT_INTERPOLATION_PATTERNS = [
8
+ /%%/,
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
19
+
20
+ class << self
21
+ # Return String or raises MissingInterpolationArgument exception.
22
+ # Missing argument's logic is handled by I18n.config.missing_interpolation_argument_handler.
23
+ def interpolate(string, values)
24
+ raise ReservedInterpolationKey.new($1.to_sym, string) if string =~ I18n.reserved_keys_pattern
25
+ raise ArgumentError.new('Interpolation values must be a Hash.') unless values.kind_of?(Hash)
26
+ interpolate_hash(string, values)
27
+ end
28
+
29
+ def interpolate_hash(string, values)
30
+ pattern = INTERPOLATION_PATTERNS_CACHE[config.interpolation_patterns]
31
+ interpolated = false
32
+
33
+ interpolated_string = string.gsub(pattern) do |match|
34
+ interpolated = true
35
+
36
+ if match == '%%'
37
+ '%'
38
+ else
39
+ key = ($1 || $2 || match.tr("%{}", "")).to_sym
40
+ value = if values.key?(key)
41
+ values[key]
42
+ else
43
+ config.missing_interpolation_argument_handler.call(key, values, string)
44
+ end
45
+ value = value.call(values) if value.respond_to?(:call)
46
+ $3 ? sprintf("%#{$3}", value) : value
47
+ end
48
+ end
49
+
50
+ interpolated ? interpolated_string : string
51
+ end
52
+ end
53
+ end