i18n-inflector 1.0.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.
- 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
|