i18n 0.4.0 → 1.14.4

Sign up to get free protection for your applications and to get access to all the features.
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