i18n-inflector 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +23 -0
- data/Gemfile +10 -0
- data/LGPL-LICENSE +169 -0
- data/Manifest.txt +20 -0
- data/README.textile +27 -0
- data/Rakefile +100 -0
- data/docs/COPYING +61 -0
- data/docs/HISTORY +6 -0
- data/docs/LEGAL +11 -0
- data/docs/LGPL-LICENSE +169 -0
- data/docs/README +101 -0
- data/docs/rdoc.css +20 -0
- data/lib/i18n-inflector/inflector.rb +652 -0
- data/lib/i18n-inflector/long_comments.rb +399 -0
- data/lib/i18n-inflector/shortcuts.rb +62 -0
- data/lib/i18n-inflector.rb +5 -0
- data/test/all.rb +8 -0
- data/test/backend/inflection_test.rb +249 -0
- data/test/run_all.rb +21 -0
- data/test/test_helper.rb +32 -0
- data.tar.gz.sig +0 -0
- metadata +156 -0
- metadata.gz.sig +2 -0
@@ -0,0 +1,652 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Author:: Paweł Wilk (mailto:pw@gnu.org)
|
4
|
+
# Copyright:: (c) 2010 by Paweł Wilk
|
5
|
+
# License:: This program is licensed under the terms of {GNU Lesser General Public License}[link:docs/LGPL-LICENSE.html] or {Ruby License}[link:docs/COPYING.html].
|
6
|
+
#
|
7
|
+
# This file contains I18n::Backend::Inflector module,
|
8
|
+
# which extends I18n::Backend::Simple by adding the ability
|
9
|
+
# to interpolate patterns containing inflection tokens
|
10
|
+
# defined in translation data.
|
11
|
+
#
|
12
|
+
#--
|
13
|
+
#
|
14
|
+
# Copyright (C) 2010 by Paweł Wilk. All Rights Reserved.
|
15
|
+
#
|
16
|
+
# This program is free software; you can redistribute it and/or modify
|
17
|
+
# it under the terms of either: 1) the GNU Lesser General Public License
|
18
|
+
# as published by the Free Software Foundation; either version 3 of the
|
19
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
20
|
+
#
|
21
|
+
# See the file COPYING for complete licensing information.
|
22
|
+
#
|
23
|
+
#++
|
24
|
+
module I18n # :nodoc:
|
25
|
+
module Backend # :nodoc:
|
26
|
+
module Inflector
|
27
|
+
|
28
|
+
EMAIL = 'pw@gnu.org' # :nodoc:
|
29
|
+
VERSION = '1.0.0' # :nodoc:
|
30
|
+
NAME = 'i18n-inflector' # :nodoc:
|
31
|
+
|
32
|
+
# Contains <tt>@{</tt> string that is used to quickly fallback
|
33
|
+
# to standard translate method if it's not found.
|
34
|
+
FAST_MATCHER = '@{'
|
35
|
+
|
36
|
+
# Contains a regular expression that catches patterns.
|
37
|
+
PATTERN = /([^@\\])@\{([^\}]+)\}/
|
38
|
+
|
39
|
+
# Contains a regular expression that catches tokens.
|
40
|
+
TOKENS = /(?:([^\:\|]+):+([^\|]+)\1?)|([^:\|]+)/
|
41
|
+
|
42
|
+
# Contains a symbol that indicates an alias.
|
43
|
+
ALIAS_MARKER = '@'
|
44
|
+
|
45
|
+
attr_writer :inflector_unknown_defaults
|
46
|
+
attr_writer :inflector_excluded_defaults
|
47
|
+
attr_writer :inflector_raises
|
48
|
+
|
49
|
+
# Returns a switch that enables extended error reporting.
|
50
|
+
#
|
51
|
+
# If the option is given then it returns the value of that option instead.
|
52
|
+
# === Short name
|
53
|
+
# <tt>I18n::Inflector.raises?</tt>
|
54
|
+
def inflector_raises?(option=nil)
|
55
|
+
option.nil? ? @inflector_raises : option
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns a switch that enables falling back to default token for a kind when
|
59
|
+
# value passed in options was unknown or empty.
|
60
|
+
#
|
61
|
+
# If the option is given then it returns the value of that option instead.
|
62
|
+
# === Short name
|
63
|
+
# <tt>I18n::Inflector.unknown_defaults?</tt>
|
64
|
+
def inflector_unknown_defaults?(option=nil)
|
65
|
+
option.nil? ? @inflector_unknown_defaults : option
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns a switch that enables falling back to default token for a kind when
|
69
|
+
# value passed in options was unknown or empty.
|
70
|
+
#
|
71
|
+
# If the option is given then it returns the value of that option instead.
|
72
|
+
# === Short name
|
73
|
+
# <tt>I18n::Inflector.excluded_defaults?</tt>
|
74
|
+
def inflector_excluded_defaults?(option=nil)
|
75
|
+
option.nil? ? @inflector_excluded_defaults : option
|
76
|
+
end
|
77
|
+
|
78
|
+
# Cleans up inflection_tokens hash.
|
79
|
+
# === Short name
|
80
|
+
# <tt>I18n::Inflector.reload!</tt>
|
81
|
+
def reload!
|
82
|
+
@inflection_tokens = nil
|
83
|
+
@inflection_aliases = nil
|
84
|
+
@inflection_defaults = nil
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
# Sets up some configuration defaults.
|
89
|
+
def initialize
|
90
|
+
@inflector_excluded_defaults = false
|
91
|
+
@inflector_unknown_defaults = true
|
92
|
+
@inflector_raises = false
|
93
|
+
super
|
94
|
+
end
|
95
|
+
|
96
|
+
# Translates given key taking care of inflections.
|
97
|
+
def translate(locale, key, options = {})
|
98
|
+
translated_string = super
|
99
|
+
return translated_string if locale.to_s.empty?
|
100
|
+
|
101
|
+
unless translated_string.include?(I18n::Backend::Inflector::FAST_MATCHER)
|
102
|
+
return translated_string
|
103
|
+
end
|
104
|
+
|
105
|
+
inflection_tokens = @inflection_tokens[locale]
|
106
|
+
if (inflection_tokens.nil? || inflection_tokens.empty?)
|
107
|
+
return clear_inflection_patterns(translated_string)
|
108
|
+
end
|
109
|
+
|
110
|
+
interpolate_inflections(translated_string, locale, options.dup)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns a default token for a given kind or +nil+.
|
114
|
+
# It may raise I18n::InvalidLocale if a given +locale+ is invalid.
|
115
|
+
# === Short name
|
116
|
+
# <tt>I18n::Inflector.default_token</tt>
|
117
|
+
def inflection_default_token(kind, locale=nil)
|
118
|
+
locale = inflector_prep_locale(locale)
|
119
|
+
return nil if kind.to_s.empty?
|
120
|
+
init_translations unless initialized?
|
121
|
+
inflections = @inflection_defaults[locale]
|
122
|
+
return nil if inflections.nil?
|
123
|
+
inflections[kind.to_sym]
|
124
|
+
end
|
125
|
+
|
126
|
+
# Tells if token is an alias.
|
127
|
+
# It may raise I18n::InvalidLocale if a given +locale+ is invalid.
|
128
|
+
# === Short name
|
129
|
+
# <tt>I18n::Inflector.is_alias?</tt>
|
130
|
+
def inflection_is_alias?(token, locale=nil)
|
131
|
+
return false if token.to_s.empty?
|
132
|
+
locale = inflector_prep_locale(locale)
|
133
|
+
init_translations unless initialized?
|
134
|
+
aliases = @inflection_aliases[locale]
|
135
|
+
return false if aliases.nil?
|
136
|
+
aliases.has_key?(token.to_sym)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns a Hash containing available inflection tokens (token => description)
|
140
|
+
# for a given +kind+ and +locale+ including aliases.
|
141
|
+
#
|
142
|
+
# If locale is not set then I18n.locale is used.
|
143
|
+
# If +kind+ is not given or +nil+ then it returns all available tokens for all kinds.
|
144
|
+
# It may raise I18n::InvalidLocale if a given +locale+ is invalid.
|
145
|
+
#
|
146
|
+
# Note that you cannot deduce where aliases are pointing to since the information
|
147
|
+
# about a target is replaced by a description here. To get targets use the
|
148
|
+
# inflection_raw_tokens method. To just list aliases and their targets use
|
149
|
+
# the inflection_aliases method.
|
150
|
+
# === Short name
|
151
|
+
# <tt>I18n::Inflector.tokens</tt>
|
152
|
+
def inflection_tokens(kind=nil, locale=nil)
|
153
|
+
locale = inflector_prep_locale(locale)
|
154
|
+
true_tokens = inflection_true_tokens(kind, locale)
|
155
|
+
aliases = @inflection_aliases[locale]
|
156
|
+
return true_tokens if aliases.nil?
|
157
|
+
aliases = aliases.reject{|k,v| v[:kind]!=kind} unless kind.nil?
|
158
|
+
aliases = aliases.merge(aliases){|k,v| v[:description]}
|
159
|
+
true_tokens.merge(aliases)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns a Hash containing available inflection tokens for a given +kind+ and
|
163
|
+
# +locale+ including aliases. The values of the result may vary, depending what
|
164
|
+
# they are describing. If the token is an alias the value is type of Symbol
|
165
|
+
# that contains a name of a real token. BTW, an alias is always shortened and it will
|
166
|
+
# never point to other alias, always to a real token. If the token is a real
|
167
|
+
# token then the value contains a String with description.
|
168
|
+
#
|
169
|
+
# If locale is not set then I18n.locale is used.
|
170
|
+
# It may raise I18n::InvalidLocale if a given +locale+ is invalid.
|
171
|
+
# If +kind+ is not given or +nil+ then it returns all available tokens for all kinds.
|
172
|
+
# === Short name
|
173
|
+
# <tt>I18n::Inflector.raw_tokens</tt>
|
174
|
+
def inflection_raw_tokens(kind=nil, locale=nil)
|
175
|
+
true_tokens = inflection_true_tokens(kind, locale)
|
176
|
+
aliases = inflection_aliases(kind, locale)
|
177
|
+
true_tokens.merge(aliases)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns a Hash containing available inflection tokens (token => description)
|
181
|
+
# for a given +kind+ and +locale+. It does not incude aliases, which means
|
182
|
+
# that the returned token can be used in patterns.
|
183
|
+
#
|
184
|
+
# If locale is not set then I18n.locale is used.
|
185
|
+
# It may raise I18n::InvalidLocale if a given +locale+ is invalid.
|
186
|
+
#
|
187
|
+
# # If +kind+ is not given or +nil+ then it returns all
|
188
|
+
# true tokens for all kinds.
|
189
|
+
# === Short name
|
190
|
+
# <tt>I18n::Inflector.true_tokens</tt>
|
191
|
+
def inflection_true_tokens(kind=nil, locale=nil)
|
192
|
+
locale = inflector_prep_locale(locale)
|
193
|
+
init_translations unless initialized?
|
194
|
+
inflections = @inflection_tokens[locale]
|
195
|
+
return {} if inflections.nil?
|
196
|
+
inflections = inflections.reject{|k,v| v[:kind]!=kind} unless kind.nil?
|
197
|
+
inflections.merge(inflections){|k,v| v[:description]}
|
198
|
+
end
|
199
|
+
|
200
|
+
# Returns a Hash (alias => target) containing available inflection
|
201
|
+
# aliases for a given +kind+ and +locale+.
|
202
|
+
#
|
203
|
+
# If locale is not set then I18n.locale is used.
|
204
|
+
# It may raise I18n::InvalidLocale if a given +locale+ is invalid.
|
205
|
+
# If +kind+ is not given or +nil+ then it returns all available aliases for all kinds.
|
206
|
+
# === Short name
|
207
|
+
# <tt>I18n::Inflector.aliases</tt>
|
208
|
+
def inflection_aliases(kind=nil, locale=nil)
|
209
|
+
locale = inflector_prep_locale(locale)
|
210
|
+
init_translations unless initialized?
|
211
|
+
aliases = @inflection_aliases[locale]
|
212
|
+
return {} if aliases.nil?
|
213
|
+
aliases = aliases.reject{|k,v| v[:kind]!=kind} unless kind.nil?
|
214
|
+
aliases.merge(aliases){|k,v| v[:target]}
|
215
|
+
end
|
216
|
+
|
217
|
+
# Returns an array of Symbols containing known kinds of inflections
|
218
|
+
# for a given +locale+.
|
219
|
+
#
|
220
|
+
# If locale is not set then I18n.locale is used.
|
221
|
+
# It may raise I18n::InvalidLocale if a given +locale+ is invalid.
|
222
|
+
# === Short name
|
223
|
+
# <tt>I18n::Inflector.kinds</tt>
|
224
|
+
def available_inflection_kinds(locale=nil)
|
225
|
+
locale = inflector_prep_locale(locale)
|
226
|
+
subtree = inflection_subtree(locale)
|
227
|
+
return [] if subtree.nil?
|
228
|
+
subtree.keys
|
229
|
+
end
|
230
|
+
|
231
|
+
# Returns an array of locales that are inflected. If +kind+ is given it returns
|
232
|
+
# only those locales that are inflected and support inflection by this kind.
|
233
|
+
# === Short name
|
234
|
+
# <tt>I18n::Inflector.locales</tt>
|
235
|
+
def inflected_locales(kind=nil)
|
236
|
+
init_translations unless initialized?
|
237
|
+
inflected_locales = (@inflection_tokens.keys || [])
|
238
|
+
return inflected_locales if kind.to_s.empty?
|
239
|
+
kind = kind.to_sym
|
240
|
+
inflected_locales.select do |loc|
|
241
|
+
kinds = inflection_subtree(loc)
|
242
|
+
kinds.respond_to?(:has_key?) && kinds.has_key?(kind)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Stores translations for the given locale in memory.
|
247
|
+
# If inflections are changed it will regenerate proper internal
|
248
|
+
# structures.
|
249
|
+
#
|
250
|
+
# It may raise I18n::InvalidLocale if a given +locale+ is invalid.
|
251
|
+
def store_translations(locale, data, options = {})
|
252
|
+
r = super
|
253
|
+
locale = inflector_prep_locale(locale)
|
254
|
+
inflector_try_init
|
255
|
+
if data.respond_to?(:has_key?)
|
256
|
+
subdata = (data[:i18n] || data['i18n'])
|
257
|
+
unless subdata.nil?
|
258
|
+
subdata = (subdata[:inflections] || subdata['inflections'])
|
259
|
+
unless subdata.nil?
|
260
|
+
@inflection_tokens.delete(locale)
|
261
|
+
@inflection_aliases.delete(locale)
|
262
|
+
@inflection_defaults.delete(locale)
|
263
|
+
load_inflection_tokens(locale)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
r
|
268
|
+
end
|
269
|
+
|
270
|
+
# Returns the description of the given inflection token. If the token is
|
271
|
+
# an alias it returns the description of the true token that
|
272
|
+
# it points to.
|
273
|
+
#
|
274
|
+
# It returns +nil+ when something goes wrong.
|
275
|
+
# It may raise I18n::InvalidLocale if a given +locale+ is invalid.
|
276
|
+
# === Short name
|
277
|
+
# <tt>I18n::Inflector.description</tt>
|
278
|
+
def inflection_token_description(token, locale=nil)
|
279
|
+
locale = inflector_prep_locale(locale)
|
280
|
+
return nil if token.to_s.empty?
|
281
|
+
init_translations unless initialized?
|
282
|
+
inflections = @inflection_tokens[locale]
|
283
|
+
aliases = @inflection_aliases[locale]
|
284
|
+
return nil if (inflections.nil? || aliases.nil?)
|
285
|
+
token = token.to_sym
|
286
|
+
match = ( inflections[token] || aliases[token] )
|
287
|
+
return nil if match.nil?
|
288
|
+
match[:description]
|
289
|
+
end
|
290
|
+
|
291
|
+
protected
|
292
|
+
|
293
|
+
# Processes locale given as parameter. Returns given locale if it's present
|
294
|
+
# or default locale or +nil+.
|
295
|
+
#
|
296
|
+
# It may raise I18n::InvalidLocale if a given +locale+ is invalid.
|
297
|
+
def inflector_prep_locale(locale=nil)
|
298
|
+
locale ||= I18n.locale
|
299
|
+
raise I18n::InvalidLocale.new(locale) if locale.to_s.empty?
|
300
|
+
locale.to_sym
|
301
|
+
end
|
302
|
+
|
303
|
+
# Interpolates inflection values into a given string
|
304
|
+
# using kinds given in options and a matching tokens.
|
305
|
+
def interpolate_inflections(string, locale, options = {})
|
306
|
+
used_kinds = options.except(*RESERVED_KEYS)
|
307
|
+
raises = inflector_raises? options.delete(:inflector_raises)
|
308
|
+
unknown_defaults = inflector_unknown_defaults? options.delete(:inflector_unknown_defaults)
|
309
|
+
excluded_defaults = inflector_excluded_defaults? options.delete(:inflector_excluded_defaults)
|
310
|
+
inflections = @inflection_tokens[locale]
|
311
|
+
defaults = @inflection_defaults[locale]
|
312
|
+
aliases = @inflection_aliases[locale]
|
313
|
+
|
314
|
+
|
315
|
+
string.gsub(I18n::Backend::Inflector::PATTERN) do
|
316
|
+
pattern_fix = $1
|
317
|
+
ext_pattern = $&
|
318
|
+
parsed_kind = nil
|
319
|
+
ext_value = nil
|
320
|
+
ext_freetext = ''
|
321
|
+
found = false
|
322
|
+
parsed_default_v= nil
|
323
|
+
ext_pattern = ext_pattern[1..-1] unless pattern_fix.nil?
|
324
|
+
|
325
|
+
$2.scan(I18n::Backend::Inflector::TOKENS) do
|
326
|
+
ext_token = $1.to_s
|
327
|
+
ext_value = $2.to_s
|
328
|
+
ext_freetext = $3.to_s
|
329
|
+
kind = nil
|
330
|
+
option = nil
|
331
|
+
|
332
|
+
# token not found?
|
333
|
+
if ext_token.empty?
|
334
|
+
# free text not found too? that should never happend.
|
335
|
+
if ext_freetext.empty?
|
336
|
+
raise I18n::InvalidInflectionToken.new(ext_pattern, ext_token) if raises
|
337
|
+
end
|
338
|
+
next
|
339
|
+
end
|
340
|
+
|
341
|
+
# set token and get current kind for it
|
342
|
+
token = ext_token.to_sym
|
343
|
+
t_entry = inflections[token]
|
344
|
+
|
345
|
+
# kind not found for a given token?
|
346
|
+
if t_entry.nil?
|
347
|
+
raise I18n::InvalidInflectionToken.new(ext_pattern, ext_token) if raises
|
348
|
+
next
|
349
|
+
end
|
350
|
+
|
351
|
+
# set kind
|
352
|
+
kind = t_entry[:kind]
|
353
|
+
|
354
|
+
# set processed kind after matching first token in pattern
|
355
|
+
if parsed_kind.nil?
|
356
|
+
parsed_kind = kind
|
357
|
+
elsif parsed_kind != kind
|
358
|
+
raise I18n::MisplacedInflectionToken.new(ext_pattern, token, parsed_kind) if raises
|
359
|
+
next
|
360
|
+
end
|
361
|
+
|
362
|
+
# memorize default option for further processing
|
363
|
+
default_token = defaults[kind]
|
364
|
+
|
365
|
+
# fetch the kind's option or fetch default if an option does not exists
|
366
|
+
option = options.has_key?(kind) ? options[kind] : default_token
|
367
|
+
|
368
|
+
if option.to_s.empty?
|
369
|
+
# if option is given but is unknown, empty or nil
|
370
|
+
# then use default option for a kind if unknown_defaults is switched on
|
371
|
+
option = unknown_defaults ? default_token : nil
|
372
|
+
else
|
373
|
+
# validate option and if it's unknown try in aliases
|
374
|
+
option = option.to_sym
|
375
|
+
unless inflections.has_key?(option)
|
376
|
+
option = aliases[option]
|
377
|
+
if option.nil?
|
378
|
+
# if still nothing then fall back to default value
|
379
|
+
# for a kind in unknown_defaults switch is on
|
380
|
+
option = unknown_defaults ? default_token : nil
|
381
|
+
else
|
382
|
+
option = option[:target]
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
# if the option is still unknown
|
388
|
+
if option.nil?
|
389
|
+
raise I18n::InvalidOptionForKind.new(ext_pattern, kind, ext_token, option) if raises
|
390
|
+
next
|
391
|
+
end
|
392
|
+
|
393
|
+
# memorize default token's value for further processing
|
394
|
+
# outside this block if excluded_defaults switch is on
|
395
|
+
parsed_default_v = ext_value if (excluded_defaults && token == default_token)
|
396
|
+
|
397
|
+
# throw the value if a given option matches the token
|
398
|
+
next unless option == token
|
399
|
+
|
400
|
+
# skip further evaluation of the pattern
|
401
|
+
# since the right token has been found
|
402
|
+
found = true
|
403
|
+
break
|
404
|
+
|
405
|
+
end # single token:value processing
|
406
|
+
|
407
|
+
result = nil
|
408
|
+
|
409
|
+
# return value of a token that matches option's value
|
410
|
+
# given for a kind or try to return a free text
|
411
|
+
# if it's present
|
412
|
+
if found
|
413
|
+
result = ext_value
|
414
|
+
elsif (excluded_defaults && !parsed_kind.nil?)
|
415
|
+
# if there is excluded_defaults switch turned on
|
416
|
+
# and a correct token was found in options but
|
417
|
+
# has not been found in a pattern then interpolate
|
418
|
+
# the pattern with a value picked for the default
|
419
|
+
# token for that kind if a default token was present
|
420
|
+
# in a pattern
|
421
|
+
kind = nil
|
422
|
+
token = options[parsed_kind]
|
423
|
+
kind = inflections[token] unless token.nil?
|
424
|
+
result = parsed_default_v unless (kind.nil? || kind[:kind].nil?)
|
425
|
+
end
|
426
|
+
|
427
|
+
pattern_fix + (result || ext_freetext)
|
428
|
+
|
429
|
+
end # single pattern processing
|
430
|
+
|
431
|
+
end
|
432
|
+
|
433
|
+
# Initializes inflection_tokens hash.
|
434
|
+
def inflector_try_init
|
435
|
+
@inflection_tokens ||= {}
|
436
|
+
@inflection_aliases ||= {}
|
437
|
+
@inflection_defaults ||= {}
|
438
|
+
end
|
439
|
+
|
440
|
+
def init_translations
|
441
|
+
r = super
|
442
|
+
inflector_try_init
|
443
|
+
available_locales.each{ |locale| load_inflection_tokens(locale) }
|
444
|
+
r
|
445
|
+
end
|
446
|
+
|
447
|
+
# Returns the translation with any inflection patterns removed.
|
448
|
+
def clear_inflection_patterns(translated_string)
|
449
|
+
translated_string.gsub(I18n::Backend::Inflector::PATTERN,'')
|
450
|
+
end
|
451
|
+
|
452
|
+
# Returns part of the translation data that
|
453
|
+
# reflects inflections for a given locale.
|
454
|
+
def inflection_subtree(locale)
|
455
|
+
lookup(locale, :"i18n.inflections", [], :fallback => true, :raise => :false)
|
456
|
+
end
|
457
|
+
|
458
|
+
# Resolves an alias for a token if token contains an alias.
|
459
|
+
# Takes care of aliasing loops.
|
460
|
+
def shorten_inflection_alias(token, kind, locale, count=0)
|
461
|
+
count += 1
|
462
|
+
return nil if count > 64
|
463
|
+
|
464
|
+
inflections = inflection_subtree(locale)
|
465
|
+
return nil if (inflections.nil? || inflections.empty?)
|
466
|
+
|
467
|
+
kind_subtree = inflections[kind]
|
468
|
+
value = kind_subtree[token].to_s
|
469
|
+
|
470
|
+
if value.slice(0,1) != I18n::Backend::Inflector::ALIAS_MARKER
|
471
|
+
if kind_subtree.has_key?(token)
|
472
|
+
return token
|
473
|
+
else
|
474
|
+
# that should never happend but who knows
|
475
|
+
raise I18n::BadInflectionToken.new(locale, token, kind)
|
476
|
+
end
|
477
|
+
else
|
478
|
+
orig_token = token
|
479
|
+
token = value[1..-1]
|
480
|
+
if token.to_s.empty?
|
481
|
+
raise I18n::BadInflectionToken.new(locale, token, kind)
|
482
|
+
end
|
483
|
+
token = token.to_sym
|
484
|
+
if kind_subtree[token].nil?
|
485
|
+
raise BadInflectionAlias.new(locale, orig_token, kind, token)
|
486
|
+
else
|
487
|
+
shorten_inflection_alias(token, kind, locale, count)
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
end
|
492
|
+
|
493
|
+
# Uses the inflections siubtree and creates many-to-one mapping
|
494
|
+
# to resolve genders assigned to inflection tokens.
|
495
|
+
def load_inflection_tokens(locale=nil)
|
496
|
+
return @inflection_tokens[locale] if @inflection_tokens.has_key?(locale)
|
497
|
+
inflections = inflection_subtree(locale)
|
498
|
+
return nil if (inflections.nil? || inflections.empty?)
|
499
|
+
ivars = @inflection_tokens[locale] = {}
|
500
|
+
aliases = @inflection_aliases[locale] = {}
|
501
|
+
defaults = @inflection_defaults[locale] = {}
|
502
|
+
|
503
|
+
inflections.each_pair do |kind, tokens|
|
504
|
+
tokens.each_pair do |token, description|
|
505
|
+
|
506
|
+
# test for duplicate
|
507
|
+
if ivars.has_key?(token)
|
508
|
+
raise I18n::DuplicatedInflectionToken.new(ivars[token], kind, token)
|
509
|
+
end
|
510
|
+
|
511
|
+
# validate token's name
|
512
|
+
if token.nil?
|
513
|
+
raise I18n::BadInflectionToken.new(locale, token, kind)
|
514
|
+
end
|
515
|
+
|
516
|
+
# validate token's description
|
517
|
+
if description.nil?
|
518
|
+
raise I18n::BadInflectionToken.new(locale, token, kind, description)
|
519
|
+
end
|
520
|
+
|
521
|
+
# handle default token for a kind
|
522
|
+
if token == :default
|
523
|
+
if defaults.has_key?(kind) # should never happend unless someone is messing with @translations
|
524
|
+
raise I18n::DuplicatedInflectionToken.new(kind, nil, token)
|
525
|
+
end
|
526
|
+
defaults[kind] = description.to_sym
|
527
|
+
next
|
528
|
+
end
|
529
|
+
|
530
|
+
# handle alias
|
531
|
+
if description.slice(0,1) == I18n::Backend::Inflector::ALIAS_MARKER
|
532
|
+
real_token = shorten_inflection_alias(token, kind, locale)
|
533
|
+
unless real_token.nil?
|
534
|
+
real_token = real_token.to_sym
|
535
|
+
aliases[token] = {}
|
536
|
+
aliases[token][:kind] = kind
|
537
|
+
aliases[token][:target] = real_token
|
538
|
+
aliases[token][:description] = inflections[kind][real_token].to_s
|
539
|
+
end
|
540
|
+
next
|
541
|
+
end
|
542
|
+
|
543
|
+
ivars[token] = {}
|
544
|
+
ivars[token][:kind] = kind.to_sym
|
545
|
+
ivars[token][:description] = description.to_s
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
# validate defaults
|
550
|
+
defaults.each_pair do |kind, pointer|
|
551
|
+
unless ivars.has_key?(pointer)
|
552
|
+
# default may be an alias
|
553
|
+
target = aliases[pointer]
|
554
|
+
target = target[:target] unless target.nil?
|
555
|
+
real_token = (target || shorten_inflection_alias(:default, kind, locale))
|
556
|
+
raise I18n::BadInflectionAlias.new(locale, :default, kind, pointer) if real_token.nil?
|
557
|
+
defaults[kind] = real_token.to_sym
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
ivars
|
562
|
+
end
|
563
|
+
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
# This is raised when there is no kind given in options or the kind is +nil+. The kind
|
568
|
+
# is determined by looking at token placed in a pattern.
|
569
|
+
#
|
570
|
+
# When a default token for some kind is defined it will be tried before raising
|
571
|
+
# this exception.
|
572
|
+
#
|
573
|
+
# This exception will also be raised when a required option, describing token selected
|
574
|
+
# for a kind, is empty or doesn't match any acceptable tokens.
|
575
|
+
class InvalidOptionForKind < ArgumentError
|
576
|
+
attr_reader :pattern, :kind, :token, :option
|
577
|
+
def initialize(pattern, kind, token, option)
|
578
|
+
@pattern, @kind, @token, @option = pattern, kind, token, option
|
579
|
+
if option.nil?
|
580
|
+
super "option #{kind.inspect} required by the " +
|
581
|
+
"pattern #{pattern.inspect} was not found"
|
582
|
+
else
|
583
|
+
super "value #{option.inspect} of #{kind.inspect} required by the " +
|
584
|
+
"pattern #{pattern.inspect} does not match any token"
|
585
|
+
end
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
# This is raised when token given in pattern is invalid (empty or has no
|
590
|
+
# kind assigned).
|
591
|
+
class InvalidInflectionToken < ArgumentError
|
592
|
+
attr_reader :pattern, :token
|
593
|
+
def initialize(pattern, token)
|
594
|
+
@pattern, @token = pattern, token
|
595
|
+
super "token #{token.inspect} used in translation " +
|
596
|
+
"pattern #{pattern.inspect} is invalid"
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
# This is raised when an inflection token used in a pattern does not match
|
601
|
+
# an assumed kind determined by reading previous tokens from that pattern.
|
602
|
+
class MisplacedInflectionToken < ArgumentError
|
603
|
+
attr_reader :pattern, :token, :kind
|
604
|
+
def initialize(pattern, token, kind)
|
605
|
+
@pattern, @token, @kind = pattern, token, kind
|
606
|
+
super "inflection token #{token.inspect} from pattern #{pattern.inspect} " +
|
607
|
+
"is not of expected kind #{kind.inspect}"
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
# This is raised when an inflection token of the same name is already defined in
|
612
|
+
# inflections tree of translation data.
|
613
|
+
class DuplicatedInflectionToken < ArgumentError
|
614
|
+
attr_reader :original_kind, :kind, :token
|
615
|
+
def initialize(original_kind, kind, token)
|
616
|
+
@original_kind, @kind, @token = original_kind, kind, token
|
617
|
+
and_cannot = kind.nil? ? "" : "and cannot be used with kind #{kind.inspect}"
|
618
|
+
super "inflection token #{token.inspect} was already assigned " +
|
619
|
+
"to kind #{original_kind}" + and_cannot
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
# This is raised when an alias for an inflection token points to a token that
|
624
|
+
# doesn't exists. It is also raised when default token of some kind points
|
625
|
+
# to a non-existent token.
|
626
|
+
class BadInflectionAlias < ArgumentError
|
627
|
+
attr_reader :locale, :token, :kind, :pointer
|
628
|
+
def initialize(locale, token, kind, pointer)
|
629
|
+
@locale, @token, @kind, @pointer = locale, token, kind, pointer
|
630
|
+
what = token == :default ? "default token" : "alias"
|
631
|
+
super "the #{what} #{token.inspect} of kind #{kind.inspect} " +
|
632
|
+
"for language #{locale.inspect} points to an unknown token #{pointer.inspect}"
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
# This is raised when an inflection token or its description has a bad name. This
|
637
|
+
# includes an empty name or a name containing prohibited characters.
|
638
|
+
class BadInflectionToken < ArgumentError
|
639
|
+
attr_reader :locale, :token, :kind, :description
|
640
|
+
def initialize(locale, token, kind, description=nil)
|
641
|
+
@locale, @token, @kind, @description = locale, token, kind, description
|
642
|
+
if description.nil?
|
643
|
+
super "Inflection token #{token.inspect} of kind #{kind.inspect} "+
|
644
|
+
"for language #{locale.inspect} has a bad name"
|
645
|
+
else
|
646
|
+
super "Inflection token #{token.inspect} of kind #{kind.inspect} "+
|
647
|
+
"for language #{locale.inspect} has a bad description #{description.inspect}"
|
648
|
+
end
|
649
|
+
end
|
650
|
+
end
|
651
|
+
|
652
|
+
end
|