i18n 1.6.0

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