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.
@@ -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