i18n-inflector 2.1.0 → 2.2.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.
@@ -22,6 +22,9 @@ module I18n
22
22
  # Usually you don't have to know what's here to use it.
23
23
  module Inflector
24
24
 
25
+ # Shortcut to configuration module.
26
+ InflectorCfg = I18n::Inflector::Config
27
+
25
28
  # This accessor allows to reach API methods of the
26
29
  # inflector object associated with this class.
27
30
  def inflector
@@ -47,28 +50,61 @@ module I18n
47
50
  # @param [Symbol] locale locale
48
51
  # @param [Symbol,String] key translation key
49
52
  # @param [Hash] options a set of options to pass to the translation routines.
50
- # @note Inflector requires at least one of the +options+ to have a value that
51
- # corresponds with token present in a pattern (or its alias). The name of that
52
- # particular option should be the same as the name of a kind of tokens from a pattern.
53
- # All +options+ along with a +string+ and +locale+ are passed to
53
+ # @note The given +options+ along with a translated string and the given
54
+ # +locale+ are passed to
54
55
  # {I18n::Backend::Simple#translate I18n::Backend::Simple#translate}
55
- # and the result is processed by {I18n::Inflector::API#interpolate}
56
+ # and then the result is processed by {I18n::Inflector::API#interpolate}
56
57
  # @return [String] the translated string with interpolated patterns
57
58
  def translate(locale, key, options = {})
58
- orig_options = options.dup
59
+ inflector_try_init
60
+
61
+ # take care about cache-awareness
62
+ cached = options.has_key?(:inflector_cache_aware) ?
63
+ options[:inflector_cache_aware] : @inflector.options.cache_aware
64
+
65
+ if cached
66
+ interpolate_options = options
67
+ @inflector.options.prepare_options!(options)
68
+ else
69
+ interpolate_options = options.dup
70
+ @inflector.options.clean_for_translate!(options)
71
+ end
72
+
73
+ # translate string using original translate
59
74
  translated_string = super
60
75
 
61
- return translated_string if (locale.nil? || locale.to_s.empty?)
76
+ # generate a pattern from key-based inflection object
77
+ if (translated_string.is_a?(Hash) && key.to_s[0..0] == InflectorCfg::Markers::PATTERN)
78
+ translated_string = @inflector.key_to_pattern(translated_string)
79
+ end
62
80
 
63
- unless @inflector.inflected_locale?(locale)
64
- return translated_string.gsub(I18n::Inflector::PATTERN,'')
81
+ # return immediatelly if something is wrong with locale (preventive)
82
+ # return if locale is not inflected - return string cleaned from pattern
83
+ if (locale.nil? || !@inflector.inflected_locale?(locale))
84
+ return translated_string.gsub(InflectorCfg::PATTERN_REGEXP) do
85
+ InflectorCfg::Escapes::PATTERN[$1] ? $& : ''
86
+ end
65
87
  end
66
88
 
67
- unless translated_string.include?(I18n::Inflector::FAST_MATCHER)
89
+ # no pattern in a string - return string as is
90
+ unless translated_string.include?(InflectorCfg::Markers::PATTERN)
68
91
  return translated_string
69
92
  end
70
93
 
71
- @inflector.interpolate(translated_string, locale, orig_options)
94
+ # interpolate string
95
+ begin
96
+
97
+ @inflector.options.prepare_options!(interpolate_options) unless cached
98
+ @inflector.interpolate(translated_string, locale, interpolate_options)
99
+
100
+ # complete the exception by adding translation key
101
+ rescue I18n::InflectionException => e
102
+
103
+ e.key = key
104
+ raise
105
+
106
+ end
107
+
72
108
  end
73
109
 
74
110
  # Stores translations in memory.
@@ -77,6 +113,7 @@ module I18n
77
113
  # @raise [I18n::InvalidLocale] if the given +locale+ is invalid
78
114
  # @raise [I18n::BadInflectionToken] if a name of some loaded token is invalid
79
115
  # @raise [I18n::BadInflectionAlias] if a loaded alias points to a token that does not exists
116
+ # @raise [I18n::BadInflectionKind] if a loaded kind identifier is invalid
80
117
  # @raise [I18n::DuplicatedInflectionToken] if a token has already appeard in loaded configuration
81
118
  # @note If inflections are changed it will regenerate proper internal
82
119
  # structures.
@@ -115,6 +152,7 @@ module I18n
115
152
  # @note It calls {I18n::Backend::Simple#init_translations I18n::Backend::Simple#init_translations}
116
153
  # @raise [I18n::BadInflectionToken] if a name of some loaded token is invalid
117
154
  # @raise [I18n::BadInflectionAlias] if a loaded alias points to a token that does not exists
155
+ # @raise [I18n::BadInflectionKind] if a loaded kind identifier is invalid
118
156
  # @raise [I18n::DuplicatedInflectionToken] if a token has already appeard in loaded configuration
119
157
  # @return [Boolean] +true+ if everything went fine
120
158
  def init_translations
@@ -171,7 +209,7 @@ module I18n
171
209
  kind_subtree = inflections_tree[kind]
172
210
  value = kind_subtree[token].to_s
173
211
 
174
- if value[0..0] != I18n::Inflector::ALIAS_MARKER
212
+ if value[0..0] != InflectorCfg::Markers::ALIAS
175
213
  if kind_subtree.has_key?(token)
176
214
  return token
177
215
  else
@@ -181,9 +219,10 @@ module I18n
181
219
  orig_token = token
182
220
  token = value[1..-1]
183
221
 
184
- if (token.nil? || token.to_s.empty?)
222
+ if InflectorCfg::Reserved::Tokens.invalid?(token, :DB)
185
223
  raise I18n::BadInflectionToken.new(locale, token, kind)
186
224
  end
225
+
187
226
  token = token.to_sym
188
227
  if kind_subtree[token].nil?
189
228
  raise BadInflectionAlias.new(locale, orig_token, kind, token)
@@ -198,7 +237,8 @@ module I18n
198
237
  # to resolve kinds assigned to inflection tokens and aliases, including defaults.
199
238
  # @return [I18n::Inflector::InflectionData,nil] the database containing inflections tokens
200
239
  # or +nil+ if something went wrong
201
- # @raise [I18n::BadInflectionToken] if a name of some loaded token is invalid
240
+ # @raise [I18n::BadInflectionToken] if a token identifier is invalid
241
+ # @raise [I18n::BadInflectionKind] if a kind identifier is invalid
202
242
  # @raise [I18n::BadInflectionAlias] if a loaded alias points to a token that does not exists
203
243
  # @raise [I18n::DuplicatedInflectionToken] if a token has already appeard in loaded configuration
204
244
  # @overload load_inflection_tokens(locale)
@@ -223,23 +263,32 @@ module I18n
223
263
 
224
264
  return nil if (idb.nil? || idb_strict.nil?)
225
265
 
226
- inflections = prepare_inflections(inflections_tree, idb, idb_strict)
266
+ inflections = prepare_inflections(locale, inflections_tree, idb, idb_strict)
227
267
 
228
268
  inflections.each do |orig_kind, kind, strict_kind, subdb, tokens|
269
+
270
+ # validate token's kind
271
+ if (kind.to_s.empty? || InflectorCfg::Reserved::Kinds.invalid?(orig_kind, :DB))
272
+ raise I18n::BadInflectionKind.new(locale, orig_kind)
273
+ end
274
+
229
275
  tokens.each_pair do |token, description|
230
276
 
231
277
  # test for duplicate
232
278
  if subdb.has_token?(token, strict_kind)
233
- raise I18n::DuplicatedInflectionToken.new(subdb.get_kind(token, strict_kind), orig_kind, token)
279
+ raise I18n::DuplicatedInflectionToken.new(locale, token, orig_kind,
280
+ subdb.get_kind(token, strict_kind))
234
281
  end
235
282
 
236
283
  # validate token's name
237
- raise I18n::BadInflectionToken.new(locale, token, orig_kind) if (token.nil? || token.to_s.empty?)
284
+ if InflectorCfg::Reserved::Tokens.invalid?(token, :DB)
285
+ raise I18n::BadInflectionToken.new(locale, token, orig_kind)
286
+ end
238
287
 
239
288
  # validate token's description
240
289
  if description.nil?
241
290
  raise I18n::BadInflectionToken.new(locale, token, orig_kind, description)
242
- elsif description[0..0] == I18n::Inflector::ALIAS_MARKER
291
+ elsif description[0..0] == InflectorCfg::Markers::ALIAS
243
292
  next
244
293
  end
245
294
 
@@ -254,7 +303,7 @@ module I18n
254
303
  inflections.each do |orig_kind, kind, strict_kind, subdb, tokens|
255
304
  tokens.each_pair do |token, description|
256
305
  next if token == :default
257
- next if description[0..0] != I18n::Inflector::ALIAS_MARKER
306
+ next if description[0..0] != InflectorCfg::Markers::ALIAS
258
307
  real_token = shorten_inflection_alias(token, orig_kind, locale, inflections_tree)
259
308
  subdb.add_alias(token, real_token, kind) unless real_token.nil?
260
309
  end
@@ -264,11 +313,11 @@ module I18n
264
313
  inflections.each do |orig_kind, kind, strict_kind, subdb, tokens|
265
314
  next unless tokens.has_key?(:default)
266
315
  if subdb.has_default_token?(kind)
267
- raise I18n::DuplicatedInflectionToken.new(orig_kind, nil, :default)
316
+ raise I18n::DuplicatedInflectionToken.new(locale, :default, kind, orig_kind)
268
317
  end
269
318
  orig_target = tokens[:default]
270
319
  target = orig_target.to_s
271
- target = target[1..-1] if target[0..0] == I18n::Inflector::ALIAS_MARKER
320
+ target = target[1..-1] if target[0..0] == InflectorCfg::Markers::ALIAS
272
321
  if target.empty?
273
322
  raise I18n::BadInflectionToken.new(locale, token, orig_kind, orig_target)
274
323
  end
@@ -283,13 +332,19 @@ module I18n
283
332
  end
284
333
 
285
334
  # @private
286
- def prepare_inflections(inflections, idb, idb_strict)
335
+ def prepare_inflections(locale, inflections, idb, idb_strict)
336
+ unless inflections.respond_to?(:has_key?)
337
+ raise I18n::BadInflectionKind.new(locale, :INFLECTIONS_ROOT)
338
+ end
287
339
  I18n::Inflector::LazyHashEnumerator.new(inflections).ary_map do |kind, tokens|
288
340
  next if (tokens.nil? || tokens.empty?)
341
+ unless tokens.respond_to?(:has_key?)
342
+ raise I18n::BadInflectionKind.new(locale, kind)
343
+ end
289
344
  subdb = idb
290
345
  strict_kind = nil
291
346
  orig_kind = kind
292
- if kind.to_s[0..0] == I18n::Inflector::NAMED_MARKER
347
+ if kind.to_s[0..0] == InflectorCfg::Markers::PATTERN
293
348
  kind = kind.to_s[1..-1]
294
349
  next if kind.empty?
295
350
  kind = kind.to_sym
@@ -0,0 +1,297 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Author:: Paweł Wilk (mailto:pw@gnu.org)
4
+ # Copyright:: (c) 2011 by Paweł Wilk
5
+ # License:: This program is licensed under the terms of {file:LGPL GNU Lesser General Public License} or {file:COPYING Ruby License}.
6
+ #
7
+ # This file contains configuration of I18n::Inflector module.
8
+
9
+ module I18n
10
+
11
+ module Inflector
12
+
13
+ # This module contains submodules and module
14
+ # methods for handling global configuration
15
+ # of the engine.
16
+ module Config
17
+
18
+ # @private
19
+ def get_i18n_reserved_keys
20
+ return I18n::RESERVED_KEYS if defined?(I18n::RESERVED_KEYS)
21
+ return I18n::Backend::Base::RESERVED_KEYS if defined?(I18n::Backend::Base::RESERVED_KEYS)
22
+ return I18n::Backend::Simple::RESERVED_KEYS if defined?(I18n::Backend::Simple::RESERVED_KEYS)
23
+ return RESERVED_KEYS if defined?(RESERVED_KEYS)
24
+ []
25
+ end
26
+ module_function :get_i18n_reserved_keys
27
+
28
+ # @private
29
+ def all_consts(obj, f=String)
30
+ obj.constants.map do |c|
31
+ v = obj.const_get(c)
32
+ (v.is_a?(f) && c != 'ALL') ? v : nil
33
+ end.compact.uniq
34
+ end
35
+ module_function :all_consts
36
+
37
+ # @private
38
+ def gen_regexp(ary)
39
+ ::Regexp.new '[' + ary.join + ']'
40
+ end
41
+ module_function :gen_regexp
42
+
43
+ # Prefix that makes option a controlling option.
44
+ OPTION_PREFIX = InflectionOptions::OPTION_PREFIX
45
+
46
+ # Regexp matching a prefix that makes option
47
+ # a controlling option.
48
+ OPTION_PREFIX_REGEXP = Regexp.new '^' + OPTION_PREFIX
49
+
50
+ # This module contains keys that have special
51
+ # meaning.
52
+ module Keys
53
+
54
+ # A Symbol that is used to mark default token
55
+ # in configuration and in options.
56
+ DEFAULT_TOKEN = :default
57
+
58
+ # All keys
59
+ ALL = HSet.new Config.all_consts(self, Symbol)
60
+ end
61
+
62
+ # This module contains characters that are markers
63
+ # giving the shape for a pattern and its elements.
64
+ module Markers
65
+
66
+ # A character that is used to mark pattern
67
+ # and a strict kind.
68
+ PATTERN = '@'
69
+
70
+ # A character that is used to open a pattern.
71
+ PATTERN_BEGIN = '{'
72
+
73
+ # A character that ends a pattern.
74
+ PATTERN_END = '}'
75
+
76
+ # A character that indicates an alias.
77
+ ALIAS = '@'
78
+
79
+ # A character used to mark token value as loud.
80
+ LOUD_VALUE = '~'
81
+
82
+ # All markers.
83
+ ALL = Config.all_consts(self)
84
+
85
+ end # module Markers
86
+
87
+ module Escapes
88
+
89
+ # A general esape symbol.
90
+ ESCAPE = '\\'
91
+
92
+ # A regular expression that catches escape symbols.
93
+ ESCAPE_R = /\\([^\\])/
94
+
95
+ # A list of escape symbols that cause a pattern to be escaped.
96
+ PATTERN = HSet[Markers::PATTERN, Escapes::ESCAPE]
97
+
98
+ end # module Escapes
99
+
100
+ # This module contains constants that define
101
+ # operators in patterns.
102
+ module Operators
103
+
104
+ # This module contains constants that define
105
+ # operators in patterns that handle token
106
+ # groups or tokens.
107
+ module Tokens
108
+
109
+ # A character used to mark patterns as complex
110
+ # and to separate token groups assigned to different
111
+ # strict kinds.
112
+ AND = '+'
113
+
114
+ # A character that is used to separate tokens
115
+ # or token groups within a pattern.
116
+ OR = '|'
117
+
118
+ # A character used to assign value to a token
119
+ # or a group of tokens.
120
+ ASSIGN = ':'
121
+
122
+ # All token groups operators.
123
+ ALL = Config.all_consts(self)
124
+
125
+ end # module Tokens
126
+
127
+ # This module contains constants that are operators
128
+ # in patterns that handle token groups or tokens.
129
+ module Token
130
+
131
+ # A character used to separate multiple tokens.
132
+ OR = ','
133
+
134
+ # A character used to mark tokens as negative.
135
+ NOT = '!'
136
+
137
+ # All token operators.
138
+ ALL = Config.all_consts(self)
139
+
140
+ end # module Token
141
+
142
+ # All operators.
143
+ ALL = (Tokens::ALL + Token::ALL).uniq
144
+
145
+ end # module Operators
146
+
147
+ # This module contains constants defining
148
+ # reserved characters in tokens and kinds.
149
+ module Reserved
150
+
151
+ # Reserved keys.
152
+ KEYS = HSet.new Config.get_i18n_reserved_keys +
153
+ Config::Keys::ALL.to_a +
154
+ InflectionOptions.known.values
155
+
156
+ # This module contains constants defining
157
+ # reserved characters in token identifiers.
158
+ module Tokens
159
+
160
+ # Reserved characters in token identifiers placed in configuration.
161
+ DB = (Operators::ALL + Markers::ALL - [Markers::LOUD_VALUE]).uniq
162
+
163
+ # Reserved characters in token identifiers passed as options.
164
+ OPTION = DB
165
+
166
+ # Reserved characters in token identifiers placed in patterns.
167
+ PATTERN = OPTION
168
+
169
+ # This module contains constants defining
170
+ # regular expressions for reserved characters
171
+ # in token identifiers.
172
+ module Regexp
173
+
174
+ # Reserved characters in token identifiers placed in configuration.
175
+ DB = Config.gen_regexp Tokens::DB
176
+
177
+ # Reserved characters in token identifiers passed as options.
178
+ OPTION = Config.gen_regexp Tokens::OPTION
179
+
180
+ # Reserved characters in token identifiers placed in patterns.
181
+ PATTERN = Config.gen_regexp Tokens::PATTERN
182
+
183
+ end # module Regexp
184
+
185
+ # This method checks if the given +token+ is invalid,
186
+ # that means it's either +nil+ or empty or it matches
187
+ # the refular expression given as +root+.
188
+ #
189
+ # @api public
190
+ # @param [Symbol,String] token the identifier of a token
191
+ # @param [Regexp] root the regular expression used to test
192
+ # @return [Boolean] +true+ if the given +token+ is
193
+ # invalid, +false+ otherwise
194
+ def invalid?(token, root)
195
+ token = token.to_s
196
+ token.empty? ||
197
+ (root == Regexp::PATTERN && Keys::ALL[token.to_sym]) ||
198
+ Regexp.const_get(root) =~ token
199
+ end
200
+ module_function :invalid?
201
+
202
+ end # module Tokens
203
+
204
+ # This module contains constants defining
205
+ # reserved characters in kind identifiers.
206
+ module Kinds
207
+
208
+ # Reserved characters in kind identifiers placed in configuration.
209
+ DB = (Operators::ALL + Markers::ALL - [Markers::ALIAS, Markers::LOUD_VALUE]).uniq
210
+
211
+ # Reserved characters in kind identifiers passed as option values.
212
+ OPTION = DB
213
+
214
+ # Reserved characters in kind identifiers placed in patterns.
215
+ PATTERN = (Operators::ALL + Markers::ALL - [Markers::LOUD_VALUE]).uniq
216
+
217
+ # This module contains constants defining
218
+ # regular expressions for reserved characters
219
+ # in kind identifiers.
220
+ module Regexp
221
+
222
+ # Reserved characters in kind identifiers placed in configuration.
223
+ DB = Config.gen_regexp Kinds::DB
224
+
225
+ # Reserved characters in kind identifiers passed as option values.
226
+ OPTION = Config.gen_regexp Kinds::OPTION
227
+
228
+ # Reserved characters in kind identifiers placed in patterns.
229
+ PATTERN = Config.gen_regexp Kinds::PATTERN
230
+
231
+ end # module Regexp
232
+
233
+ # This method checks if the given +kind+ is invalid,
234
+ # that means it's either +nil+ or empty or it matches
235
+ # the refular expression given as +root+.
236
+ #
237
+ # @api public
238
+ # @param [Symbol,String] kind the identifier of a kind
239
+ # @param [Regexp] root the regular expression used to test
240
+ # @return [Boolean] +true+ if the given +kind+ is
241
+ # invalid, +false+ otherwise
242
+ def invalid?(kind, root)
243
+ kind = kind.to_s
244
+ kind.empty? ||
245
+ (root != Regexp::OPTION &&
246
+ (KEYS[kind.to_sym] || OPTION_PREFIX_REGEXP =~ kind)) ||
247
+ Regexp.const_get(root) =~ kind
248
+ end
249
+ module_function :invalid?
250
+
251
+ end # module Kinds
252
+
253
+ end # module Reserved
254
+
255
+ # A regular expression that catches patterns.
256
+ PATTERN_REGEXP = Regexp.new '(.?)' + Markers::PATTERN +
257
+ '([^\\' + Markers::PATTERN_BEGIN + ']*)\\' + Markers::PATTERN_BEGIN +
258
+ '([^\\' + Markers::PATTERN_END + ']+)\\' + Markers::PATTERN_END +
259
+ '((?:\\'+ Markers::PATTERN_BEGIN + '([^\\' + Markers::PATTERN_BEGIN +
260
+ ']+)\\' + Markers::PATTERN_END + ')*)'
261
+
262
+ # A regular expression that extracts additional patterns attached.
263
+ MULTI_REGEXP = Regexp.new '\\' + Markers::PATTERN_BEGIN +
264
+ '([^\\' + Markers::PATTERN_END + ']+)\\' +
265
+ Markers::PATTERN_END
266
+
267
+ # A regular expression that catches token groups or single tokens.
268
+ TOKENS_REGEXP = Regexp.new '(?:' +
269
+ '([^' + Operators::Tokens::ASSIGN + '\\' + Operators::Tokens::OR + ']+)' +
270
+ Operators::Tokens::ASSIGN + '+' +
271
+ '([^\\' + Operators::Tokens::OR + ']+)\1?)' +
272
+ '|([^' + Operators::Tokens::ASSIGN + '\\' + Operators::Tokens::OR + ']+)'
273
+
274
+ end # module Config
275
+
276
+ # @private
277
+ PATTERN_MARKER = Config::Markers::PATTERN
278
+ # @private
279
+ NAMED_MARKER = Config::Markers::PATTERN
280
+ # @private
281
+ ALIAS_MARKER = Config::Markers::ALIAS
282
+ # @private
283
+ ESCAPE = Config::Escapes::ESCAPE
284
+ # @private
285
+ ESCAPE_R = Config::Escapes::ESCAPE_R
286
+ # @private
287
+ ESCAPES = Config::Escapes::PATTERN
288
+ # @private
289
+ PATTERN = Config::PATTERN_REGEXP
290
+ # @private
291
+ TOKENS = Config::TOKENS_REGEXP
292
+ # @private
293
+ INFLECTOR_RESERVED_KEYS = Config::Reserved::KEYS
294
+
295
+ end # module Inflector
296
+
297
+ end # module I18n