i18n-inflector 1.0.0

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