i18n 1.6.0

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +125 -0
  4. data/lib/i18n.rb +398 -0
  5. data/lib/i18n/backend.rb +21 -0
  6. data/lib/i18n/backend/base.rb +284 -0
  7. data/lib/i18n/backend/cache.rb +113 -0
  8. data/lib/i18n/backend/cache_file.rb +36 -0
  9. data/lib/i18n/backend/cascade.rb +56 -0
  10. data/lib/i18n/backend/chain.rb +127 -0
  11. data/lib/i18n/backend/fallbacks.rb +84 -0
  12. data/lib/i18n/backend/flatten.rb +115 -0
  13. data/lib/i18n/backend/gettext.rb +85 -0
  14. data/lib/i18n/backend/interpolation_compiler.rb +123 -0
  15. data/lib/i18n/backend/key_value.rb +206 -0
  16. data/lib/i18n/backend/memoize.rb +54 -0
  17. data/lib/i18n/backend/metadata.rb +71 -0
  18. data/lib/i18n/backend/pluralization.rb +55 -0
  19. data/lib/i18n/backend/simple.rb +111 -0
  20. data/lib/i18n/backend/transliterator.rb +108 -0
  21. data/lib/i18n/config.rb +165 -0
  22. data/lib/i18n/core_ext/hash.rb +47 -0
  23. data/lib/i18n/exceptions.rb +111 -0
  24. data/lib/i18n/gettext.rb +28 -0
  25. data/lib/i18n/gettext/helpers.rb +75 -0
  26. data/lib/i18n/gettext/po_parser.rb +329 -0
  27. data/lib/i18n/interpolate/ruby.rb +39 -0
  28. data/lib/i18n/locale.rb +8 -0
  29. data/lib/i18n/locale/fallbacks.rb +96 -0
  30. data/lib/i18n/locale/tag.rb +28 -0
  31. data/lib/i18n/locale/tag/parents.rb +22 -0
  32. data/lib/i18n/locale/tag/rfc4646.rb +74 -0
  33. data/lib/i18n/locale/tag/simple.rb +39 -0
  34. data/lib/i18n/middleware.rb +17 -0
  35. data/lib/i18n/tests.rb +14 -0
  36. data/lib/i18n/tests/basics.rb +60 -0
  37. data/lib/i18n/tests/defaults.rb +52 -0
  38. data/lib/i18n/tests/interpolation.rb +159 -0
  39. data/lib/i18n/tests/link.rb +56 -0
  40. data/lib/i18n/tests/localization.rb +19 -0
  41. data/lib/i18n/tests/localization/date.rb +117 -0
  42. data/lib/i18n/tests/localization/date_time.rb +103 -0
  43. data/lib/i18n/tests/localization/procs.rb +116 -0
  44. data/lib/i18n/tests/localization/time.rb +103 -0
  45. data/lib/i18n/tests/lookup.rb +81 -0
  46. data/lib/i18n/tests/pluralization.rb +35 -0
  47. data/lib/i18n/tests/procs.rb +55 -0
  48. data/lib/i18n/version.rb +5 -0
  49. metadata +124 -0
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # I18n Pluralization are useful when you want your application to
4
+ # customize pluralization rules.
5
+ #
6
+ # To enable locale specific pluralizations you can simply include the
7
+ # Pluralization module to the Simple backend - or whatever other backend you
8
+ # are using.
9
+ #
10
+ # I18n::Backend::Simple.include(I18n::Backend::Pluralization)
11
+ #
12
+ # You also need to make sure to provide pluralization algorithms to the
13
+ # backend, i.e. include them to your I18n.load_path accordingly.
14
+ module I18n
15
+ module Backend
16
+ module Pluralization
17
+ # Overwrites the Base backend translate method so that it will check the
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
20
+ # pluralization rules to be stored at I18n.t(:'i18n.plural.rule')
21
+ #
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.
26
+ #
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.
31
+ def pluralize(locale, entry, count)
32
+ return entry unless entry.is_a?(Hash) and count
33
+
34
+ pluralizer = pluralizer(locale)
35
+ 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]
39
+ else
40
+ super
41
+ end
42
+ end
43
+
44
+ protected
45
+
46
+ def pluralizers
47
+ @pluralizers ||= {}
48
+ end
49
+
50
+ def pluralizer(locale)
51
+ pluralizers[locale] ||= I18n.t(:'i18n.plural.rule', :locale => locale, :resolve => false)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/backend/base'
4
+
5
+ module I18n
6
+ module Backend
7
+ # A simple backend that reads translations from YAML files and stores them in
8
+ # an in-memory hash. Relies on the Base backend.
9
+ #
10
+ # The implementation is provided by a Implementation module allowing to easily
11
+ # extend Simple backend's behavior by including modules. E.g.:
12
+ #
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)
21
+ class Simple
22
+ using I18n::HashRefinements
23
+
24
+ (class << self; self; end).class_eval { public :include }
25
+
26
+ module Implementation
27
+ include Base
28
+
29
+ def initialized?
30
+ @initialized ||= false
31
+ end
32
+
33
+ # Stores translations for the given locale in memory.
34
+ # This uses a deep merge for the translations hash, so existing
35
+ # translations will be overwritten by new ones only at the deepest
36
+ # level of the hash.
37
+ def store_translations(locale, data, options = EMPTY_HASH)
38
+ if I18n.enforce_available_locales &&
39
+ I18n.available_locales_initialized? &&
40
+ !I18n.available_locales.include?(locale.to_sym) &&
41
+ !I18n.available_locales.include?(locale.to_s)
42
+ return data
43
+ end
44
+ locale = locale.to_sym
45
+ translations[locale] ||= {}
46
+ data = data.deep_symbolize_keys
47
+ translations[locale].deep_merge!(data)
48
+ end
49
+
50
+ # Get available locales from the translations hash
51
+ def available_locales
52
+ init_translations unless initialized?
53
+ translations.inject([]) do |locales, (locale, data)|
54
+ locales << locale unless data.size <= 1 && (data.empty? || data.has_key?(:i18n))
55
+ locales
56
+ end
57
+ end
58
+
59
+ # Clean up translations hash and set initialized to false on reload!
60
+ def reload!
61
+ @initialized = false
62
+ @translations = nil
63
+ super
64
+ end
65
+
66
+ def eager_load!
67
+ init_translations unless initialized?
68
+ super
69
+ end
70
+
71
+ def translations(do_init: false)
72
+ # To avoid returning empty translations,
73
+ # call `init_translations`
74
+ init_translations if do_init && !initialized?
75
+
76
+ @translations ||= {}
77
+ end
78
+
79
+ protected
80
+
81
+ def init_translations
82
+ load_translations
83
+ @initialized = true
84
+ end
85
+
86
+ # Looks up a translation from the translations hash. Returns nil if
87
+ # either key is nil, or locale, scope or key do not exist as a key in the
88
+ # nested translations hash. Splits keys or scopes containing dots
89
+ # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
90
+ # <tt>%w(currency format)</tt>.
91
+ def lookup(locale, key, scope = [], options = EMPTY_HASH)
92
+ init_translations unless initialized?
93
+ keys = I18n.normalize_keys(locale, key, scope, options[:separator])
94
+
95
+ keys.inject(translations) do |result, _key|
96
+ return nil unless result.is_a?(Hash)
97
+ unless result.has_key?(_key)
98
+ _key = _key.to_s.to_sym
99
+ return nil unless result.has_key?(_key)
100
+ end
101
+ result = result[_key]
102
+ result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
103
+ result
104
+ end
105
+ end
106
+ end
107
+
108
+ include Implementation
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,108 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module I18n
5
+ module Backend
6
+ module Transliterator
7
+ DEFAULT_REPLACEMENT_CHAR = "?"
8
+
9
+ # Given a locale and a UTF-8 string, return the locale's ASCII
10
+ # approximation for the string.
11
+ def transliterate(locale, string, replacement = nil)
12
+ @transliterators ||= {}
13
+ @transliterators[locale] ||= Transliterator.get I18n.t(:'i18n.transliterate.rule',
14
+ :locale => locale, :resolve => false, :default => {})
15
+ @transliterators[locale].transliterate(string, replacement)
16
+ end
17
+
18
+ # Get a transliterator instance.
19
+ def self.get(rule = nil)
20
+ if !rule || rule.kind_of?(Hash)
21
+ HashTransliterator.new(rule)
22
+ elsif rule.kind_of? Proc
23
+ ProcTransliterator.new(rule)
24
+ else
25
+ raise I18n::ArgumentError, "Transliteration rule must be a proc or a hash."
26
+ end
27
+ end
28
+
29
+ # A transliterator which accepts a Proc as its transliteration rule.
30
+ class ProcTransliterator
31
+ def initialize(rule)
32
+ @rule = rule
33
+ end
34
+
35
+ def transliterate(string, replacement = nil)
36
+ @rule.call(string)
37
+ end
38
+ end
39
+
40
+ # A transliterator which accepts a Hash of characters as its translation
41
+ # rule.
42
+ class HashTransliterator
43
+ DEFAULT_APPROXIMATIONS = {
44
+ "À"=>"A", "Á"=>"A", "Â"=>"A", "Ã"=>"A", "Ä"=>"A", "Å"=>"A", "Æ"=>"AE",
45
+ "Ç"=>"C", "È"=>"E", "É"=>"E", "Ê"=>"E", "Ë"=>"E", "Ì"=>"I", "Í"=>"I",
46
+ "Î"=>"I", "Ï"=>"I", "Ð"=>"D", "Ñ"=>"N", "Ò"=>"O", "Ó"=>"O", "Ô"=>"O",
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"
72
+ }.freeze
73
+
74
+ def initialize(rule = nil)
75
+ @rule = rule
76
+ add_default_approximations
77
+ add rule if rule
78
+ end
79
+
80
+ def transliterate(string, replacement = nil)
81
+ replacement ||= DEFAULT_REPLACEMENT_CHAR
82
+ string.gsub(/[^\x00-\x7f]/u) do |char|
83
+ approximations[char] || replacement
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def approximations
90
+ @approximations ||= {}
91
+ end
92
+
93
+ def add_default_approximations
94
+ DEFAULT_APPROXIMATIONS.each do |key, value|
95
+ approximations[key] = value
96
+ end
97
+ end
98
+
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
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module I18n
6
+ class Config
7
+ # The only configuration value that is not global and scoped to thread is :locale.
8
+ # It defaults to the default_locale.
9
+ def locale
10
+ defined?(@locale) && @locale != nil ? @locale : default_locale
11
+ end
12
+
13
+ # Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
14
+ def locale=(locale)
15
+ I18n.enforce_available_locales!(locale)
16
+ @locale = locale && locale.to_sym
17
+ end
18
+
19
+ # Returns the current backend. Defaults to +Backend::Simple+.
20
+ def backend
21
+ @@backend ||= Backend::Simple.new
22
+ end
23
+
24
+ # Sets the current backend. Used to set a custom backend.
25
+ def backend=(backend)
26
+ @@backend = backend
27
+ end
28
+
29
+ # Returns the current default locale. Defaults to :'en'
30
+ def default_locale
31
+ @@default_locale ||= :en
32
+ end
33
+
34
+ # Sets the current default locale. Used to set a custom default locale.
35
+ def default_locale=(locale)
36
+ I18n.enforce_available_locales!(locale)
37
+ @@default_locale = locale && locale.to_sym
38
+ end
39
+
40
+ # Returns an array of locales for which translations are available.
41
+ # Unless you explicitely set these through I18n.available_locales=
42
+ # the call will be delegated to the backend.
43
+ def 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
54
+ end
55
+
56
+ # Sets the available locales.
57
+ def 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
72
+ end
73
+
74
+ # Returns the current default scope separator. Defaults to '.'
75
+ def default_separator
76
+ @@default_separator ||= '.'
77
+ end
78
+
79
+ # Sets the current default scope separator.
80
+ def default_separator=(separator)
81
+ @@default_separator = separator
82
+ end
83
+
84
+ # Returns the current exception handler. Defaults to an instance of
85
+ # I18n::ExceptionHandler.
86
+ def exception_handler
87
+ @@exception_handler ||= ExceptionHandler.new
88
+ end
89
+
90
+ # Sets the exception handler.
91
+ def exception_handler=(exception_handler)
92
+ @@exception_handler = exception_handler
93
+ end
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 supress 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
+
118
+ # Allow clients to register paths providing translation data sources. The
119
+ # backend defines acceptable sources.
120
+ #
121
+ # E.g. the provided SimpleBackend accepts a list of paths to translation
122
+ # files which are either named *.rb and contain plain Ruby Hashes or are
123
+ # named *.yml and contain YAML data. So for the SimpleBackend clients may
124
+ # register translation files like this:
125
+ # I18n.load_path << 'path/to/locale/en.yml'
126
+ def load_path
127
+ @@load_path ||= []
128
+ end
129
+
130
+ # Sets the load path instance. Custom implementations are expected to
131
+ # behave like a Ruby Array.
132
+ def load_path=(load_path)
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
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,47 @@
1
+ module I18n
2
+ module HashRefinements
3
+ refine Hash do
4
+ using I18n::HashRefinements
5
+ def except(*keys)
6
+ dup.except!(*keys)
7
+ end
8
+
9
+ def except!(*keys)
10
+ keys.each { |key| delete(key) }
11
+ self
12
+ end
13
+
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
20
+
21
+ # deep_merge_hash! by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
22
+ def deep_merge!(data)
23
+ merger = lambda do |_key, v1, v2|
24
+ Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2
25
+ end
26
+ merge!(data, &merger)
27
+ end
28
+
29
+ def symbolize_key(key)
30
+ key.respond_to?(:to_sym) ? key.to_sym : key
31
+ end
32
+
33
+ private
34
+
35
+ def deep_symbolize_keys_in_object(value)
36
+ case value
37
+ when Hash
38
+ value.deep_symbolize_keys
39
+ when Array
40
+ value.map { |e| deep_symbolize_keys_in_object(e) }
41
+ else
42
+ value
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end