i18n-inflector-3 3.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.
- checksums.yaml +7 -0
- data/ChangeLog +1674 -0
- data/LGPL-LICENSE +169 -0
- data/README.md +259 -0
- data/docs/COPYING +57 -0
- data/docs/EXAMPLES +357 -0
- data/docs/HISTORY +280 -0
- data/docs/LEGAL +10 -0
- data/docs/LGPL +166 -0
- data/docs/TODO +8 -0
- data/docs/USAGE +967 -0
- data/docs/rdoc.css +20 -0
- data/lib/i18n-inflector/api.rb +753 -0
- data/lib/i18n-inflector/api_strict.rb +671 -0
- data/lib/i18n-inflector/backend.rb +352 -0
- data/lib/i18n-inflector/config.rb +289 -0
- data/lib/i18n-inflector/errors.rb +226 -0
- data/lib/i18n-inflector/hset.rb +24 -0
- data/lib/i18n-inflector/inflection_data.rb +357 -0
- data/lib/i18n-inflector/inflection_data_strict.rb +300 -0
- data/lib/i18n-inflector/inflector.rb +38 -0
- data/lib/i18n-inflector/interpolate.rb +546 -0
- data/lib/i18n-inflector/lazy_enum.rb +267 -0
- data/lib/i18n-inflector/long_comments.rb +57 -0
- data/lib/i18n-inflector/options.rb +329 -0
- data/lib/i18n-inflector/version.rb +27 -0
- data/lib/i18n-inflector.rb +19 -0
- metadata +87 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Author:: Paweł Wilk (mailto:pw@gnu.org)
|
|
5
|
+
# Copyright:: (c) 2011,2012,2013 by Paweł Wilk
|
|
6
|
+
# License:: This program is licensed under the terms of {file:docs/LGPL GNU Lesser General Public License} or {file:docs/COPYING Ruby License}.
|
|
7
|
+
#
|
|
8
|
+
# This file contains I18n::Inflector::Interpolate module,
|
|
9
|
+
# which is included in the API.
|
|
10
|
+
|
|
11
|
+
module I18n
|
|
12
|
+
module Inflector
|
|
13
|
+
# This module contains methods for interpolating
|
|
14
|
+
# inflection patterns.
|
|
15
|
+
module Interpolate
|
|
16
|
+
include I18n::Inflector::Config
|
|
17
|
+
|
|
18
|
+
# Interpolates inflection values in the given +string+
|
|
19
|
+
# using kinds given in +options+ and a matching tokens.
|
|
20
|
+
#
|
|
21
|
+
# @param [String] string the translation string
|
|
22
|
+
# containing patterns to interpolate
|
|
23
|
+
# @param [String,Symbol] locale the locale identifier
|
|
24
|
+
# @param [Hash] options the options
|
|
25
|
+
# ComplexPatternMalformed.new
|
|
26
|
+
# @raise {I18n::InvalidInflectionKind}
|
|
27
|
+
# @raise {I18n::InvalidInflectionOption}
|
|
28
|
+
# @raise {I18n::InvalidInflectionToken}
|
|
29
|
+
# @raise {I18n::MisplacedInflectionToken}
|
|
30
|
+
# @option options [Boolean] :inflector_excluded_defaults (false) local switch
|
|
31
|
+
# that overrides global setting (see: {I18n::Inflector::InflectionOptions#excluded_defaults})
|
|
32
|
+
# @option options [Boolean] :inflector_unknown_defaults (true) local switch
|
|
33
|
+
# that overrides global setting (see: {I18n::Inflector::InflectionOptions#unknown_defaults})
|
|
34
|
+
# @option options [Boolean] :inflector_raises (false) local switch
|
|
35
|
+
# that overrides global setting (see: {I18n::Inflector::InflectionOptions#raises})
|
|
36
|
+
# @option options [Boolean] :inflector_aliased_patterns (false) local switch
|
|
37
|
+
# that overrides global setting (see: {I18n::Inflector::InflectionOptions#aliased_patterns})
|
|
38
|
+
# @option options [Boolean] :inflector_cache_aware (false) local switch
|
|
39
|
+
# that overrides global setting (see: {I18n::Inflector::InflectionOptions#cache_aware})
|
|
40
|
+
# @option options [Boolean] :inflector_traverses (true) local switch
|
|
41
|
+
# that overrides global setting (see: {I18n::Inflector::InflectionOptions#traverses})
|
|
42
|
+
# @option options [Boolean] :inflector_interpolate_symbols (false) local switch
|
|
43
|
+
# that overrides global setting (see: {I18n::Inflector::InflectionOptions#interpolate_symbols})
|
|
44
|
+
# @return [String] the string with interpolated patterns
|
|
45
|
+
def interpolate(string, locale, options = {})
|
|
46
|
+
@inflector_opt_cache = nil
|
|
47
|
+
|
|
48
|
+
case string
|
|
49
|
+
|
|
50
|
+
when String
|
|
51
|
+
|
|
52
|
+
if locale.nil? || !inflected_locale?(locale)
|
|
53
|
+
string.gsub(PATTERN_REGEXP) { Escapes::PATTERN[::Regexp.last_match(1)] ? ::Regexp.last_match(0) : ::Regexp.last_match(1) }
|
|
54
|
+
elsif !string.include?(Markers::PATTERN)
|
|
55
|
+
string
|
|
56
|
+
else
|
|
57
|
+
interpolate_core(string, locale, options)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
when Hash
|
|
61
|
+
|
|
62
|
+
if options[:inflector_traverses]
|
|
63
|
+
string.merge(string) { |_k, v| interpolate(v, locale, options) }
|
|
64
|
+
else
|
|
65
|
+
string
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
when Array
|
|
69
|
+
|
|
70
|
+
if options[:inflector_traverses]
|
|
71
|
+
string.map { |v| interpolate(v, locale, options) }
|
|
72
|
+
else
|
|
73
|
+
string
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
when Symbol
|
|
77
|
+
|
|
78
|
+
if options[:inflector_interpolate_symbols]
|
|
79
|
+
r = interpolate(string.to_s, locale, options)
|
|
80
|
+
begin
|
|
81
|
+
r.to_sym
|
|
82
|
+
rescue StandardError
|
|
83
|
+
:' '
|
|
84
|
+
end
|
|
85
|
+
else
|
|
86
|
+
string
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
else
|
|
90
|
+
|
|
91
|
+
string
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# This method creates an inflection pattern
|
|
97
|
+
# by collecting information contained in a key-based
|
|
98
|
+
# inflection data.
|
|
99
|
+
#
|
|
100
|
+
# @param [Hash] key the given key
|
|
101
|
+
# @return [String] the inflection pattern
|
|
102
|
+
def key_to_pattern(key)
|
|
103
|
+
key = key.dup
|
|
104
|
+
pref = key.delete(:@prefix).to_s
|
|
105
|
+
suff = key.delete(:@suffix).to_s
|
|
106
|
+
kind = key.delete(:@kind).to_s
|
|
107
|
+
free = key.delete(:@free)
|
|
108
|
+
free = free.nil? ? '' : "#{Operators::Tokens::OR}#{free}"
|
|
109
|
+
|
|
110
|
+
"#{pref}#{Markers::PATTERN}#{kind}#{Markers::PATTERN_BEGIN}" <<
|
|
111
|
+
(key.map { |k, v| "#{k}#{Operators::Tokens::ASSIGN}#{v}" }
|
|
112
|
+
.join(Operators::Tokens::OR) + free + Markers::PATTERN_END + suff)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
# @private
|
|
118
|
+
def interpolate_core(string, locale, options)
|
|
119
|
+
@inflector_opt_cache ||= options.except(*Reserved::KEYS)
|
|
120
|
+
passed_kinds = @inflector_opt_cache
|
|
121
|
+
|
|
122
|
+
raises = options[:inflector_raises]
|
|
123
|
+
aliased_patterns = options[:inflector_aliased_patterns]
|
|
124
|
+
unknown_defaults = options[:inflector_unknown_defaults]
|
|
125
|
+
excluded_defaults = options[:inflector_excluded_defaults]
|
|
126
|
+
|
|
127
|
+
idb = @idb[locale]
|
|
128
|
+
idb_strict = @idb_strict[locale]
|
|
129
|
+
|
|
130
|
+
string.gsub(PATTERN_REGEXP) do
|
|
131
|
+
pattern_fix = ::Regexp.last_match(1) # character sticked to the left side of a pattern
|
|
132
|
+
strict_kind = ::Regexp.last_match(2) # strict kind(s) if any
|
|
133
|
+
pattern_content = ::Regexp.last_match(3) # content of a pattern
|
|
134
|
+
multipattern = ::Regexp.last_match(4) # another pattern(s) sticked to the right side of a pattern
|
|
135
|
+
ext_pattern = ::Regexp.last_match(0) # the matching string
|
|
136
|
+
|
|
137
|
+
# initialize some defaults
|
|
138
|
+
ext_freetext = ''
|
|
139
|
+
found = nil
|
|
140
|
+
default_value = nil
|
|
141
|
+
tb_raised = nil
|
|
142
|
+
wildcard_value = nil
|
|
143
|
+
|
|
144
|
+
# leave escaped pattern as-is
|
|
145
|
+
unless pattern_fix.empty?
|
|
146
|
+
ext_pattern = ext_pattern[1..]
|
|
147
|
+
next ext_pattern if Escapes::PATTERN[pattern_fix]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# handle multiple patterns
|
|
151
|
+
unless multipattern.empty?
|
|
152
|
+
patterns = []
|
|
153
|
+
patterns << pattern_content
|
|
154
|
+
patterns += multipattern.scan(MULTI_REGEXP).flatten
|
|
155
|
+
next pattern_fix +
|
|
156
|
+
patterns.map do |content|
|
|
157
|
+
interpolate_core(Markers::PATTERN + strict_kind +
|
|
158
|
+
Markers::PATTERN_BEGIN + content +
|
|
159
|
+
Markers::PATTERN_END,
|
|
160
|
+
locale, options)
|
|
161
|
+
end.join
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# set parsed kind if strict kind is given (named pattern is parsed)
|
|
165
|
+
if strict_kind.empty?
|
|
166
|
+
sym_parsed_kind = nil
|
|
167
|
+
strict_kind = nil
|
|
168
|
+
parsed_kind = nil
|
|
169
|
+
default_token = nil
|
|
170
|
+
subdb = idb
|
|
171
|
+
else
|
|
172
|
+
sym_parsed_kind = :"#{Markers::STRICT_KIND}#{strict_kind}"
|
|
173
|
+
|
|
174
|
+
if strict_kind.include?(Operators::Tokens::AND)
|
|
175
|
+
|
|
176
|
+
# Complex markers processing
|
|
177
|
+
begin
|
|
178
|
+
result = interpolate_complex(strict_kind,
|
|
179
|
+
pattern_content,
|
|
180
|
+
locale, options)
|
|
181
|
+
rescue I18n::InflectionPatternException => e
|
|
182
|
+
e.pattern = ext_pattern
|
|
183
|
+
raise
|
|
184
|
+
end
|
|
185
|
+
found = pattern_content = '' # disable further processing
|
|
186
|
+
|
|
187
|
+
else
|
|
188
|
+
|
|
189
|
+
# Strict kinds preparing
|
|
190
|
+
subdb = idb_strict
|
|
191
|
+
|
|
192
|
+
# validate strict kind and set needed variables
|
|
193
|
+
if Reserved::Kinds.invalid?(strict_kind, :PATTERN) ||
|
|
194
|
+
!idb_strict.has_kind?(strict_kind.to_sym)
|
|
195
|
+
raise I18n::InvalidInflectionKind.new(locale, ext_pattern, sym_parsed_kind) if raises
|
|
196
|
+
|
|
197
|
+
# Take a free text for invalid kind and return it
|
|
198
|
+
next '' + pattern_fix + pattern_content.scan(TOKENS_REGEXP).reverse
|
|
199
|
+
.select { |t, _v, f| t.nil? && !f.nil? }
|
|
200
|
+
.map { |_t, _v, f| f.to_s }
|
|
201
|
+
.first.to_s
|
|
202
|
+
else
|
|
203
|
+
strict_kind = strict_kind.to_sym
|
|
204
|
+
parsed_kind = strict_kind
|
|
205
|
+
# inject default token
|
|
206
|
+
default_token = subdb.get_default_token(parsed_kind)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# process pattern content's
|
|
213
|
+
pattern_content.scan(TOKENS_REGEXP) do
|
|
214
|
+
ext_token = ::Regexp.last_match(1).to_s # token(s)
|
|
215
|
+
ext_value = ::Regexp.last_match(2).to_s # value of token(s)
|
|
216
|
+
ext_freetext = ::Regexp.last_match(3).to_s # freetext if any
|
|
217
|
+
ext_tokens = nil
|
|
218
|
+
tokens = Hash.new(false)
|
|
219
|
+
negatives = Hash.new(false)
|
|
220
|
+
kind = nil
|
|
221
|
+
passed_token = nil
|
|
222
|
+
result = nil
|
|
223
|
+
|
|
224
|
+
# TOKEN GROUP PROCESSING
|
|
225
|
+
|
|
226
|
+
# token not found?
|
|
227
|
+
if ext_token.empty?
|
|
228
|
+
# free text not found too? that should never happend.
|
|
229
|
+
raise I18n::InvalidInflectionToken.new(locale, ext_pattern, ext_token) if ext_freetext.empty? && raises
|
|
230
|
+
|
|
231
|
+
next
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# unroll wildcard token
|
|
235
|
+
if ext_token == Operators::Tokens::WILDCARD
|
|
236
|
+
if parsed_kind.nil?
|
|
237
|
+
# wildcard for a regular kind that we do not know yet
|
|
238
|
+
wildcard_value = ext_value
|
|
239
|
+
else
|
|
240
|
+
# wildcard for a known strict or regular kind
|
|
241
|
+
ext_tokens = subdb.each_true_token(parsed_kind).each_key.map(&:to_s)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# split groupped tokens if comma is present and put into fast list
|
|
246
|
+
ext_tokens = ext_token.split(Operators::Token::OR) if ext_tokens.nil?
|
|
247
|
+
|
|
248
|
+
# for each token from group
|
|
249
|
+
ext_tokens.each do |t|
|
|
250
|
+
# token name corrupted
|
|
251
|
+
if t.to_s.empty?
|
|
252
|
+
raise I18n::InvalidInflectionToken.new(locale, ext_pattern, t) if raises
|
|
253
|
+
|
|
254
|
+
next
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# mark negative-matching token and put it on the negatives fast list
|
|
258
|
+
if t[0..0] == Operators::Token::NOT
|
|
259
|
+
t = t[1..]
|
|
260
|
+
negative = true
|
|
261
|
+
else
|
|
262
|
+
negative = false
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# is token name corrupted?
|
|
266
|
+
if Reserved::Tokens.invalid?(t, :PATTERN)
|
|
267
|
+
raise I18n::InvalidInflectionToken.new(locale, ext_pattern, t) if raises
|
|
268
|
+
|
|
269
|
+
next
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
t = t.to_sym
|
|
273
|
+
t = subdb.get_true_token(t, strict_kind) if aliased_patterns
|
|
274
|
+
negatives[t] = true if negative
|
|
275
|
+
|
|
276
|
+
# get a kind for that token
|
|
277
|
+
kind = subdb.get_kind(t, strict_kind)
|
|
278
|
+
|
|
279
|
+
if kind.nil?
|
|
280
|
+
if raises
|
|
281
|
+
# regular pattern and token that has a bad kind
|
|
282
|
+
raise I18n::InvalidInflectionToken.new(locale, ext_pattern, t, sym_parsed_kind) if strict_kind.nil?
|
|
283
|
+
|
|
284
|
+
# named pattern (kind validated before, so the only error is misplaced token)
|
|
285
|
+
raise I18n::MisplacedInflectionToken.new(locale, ext_pattern, t, sym_parsed_kind)
|
|
286
|
+
|
|
287
|
+
end
|
|
288
|
+
next
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# set processed kind after matching first token in a pattern
|
|
292
|
+
if parsed_kind.nil?
|
|
293
|
+
parsed_kind = kind
|
|
294
|
+
sym_parsed_kind = kind.to_sym
|
|
295
|
+
default_token = subdb.get_default_token(parsed_kind)
|
|
296
|
+
elsif parsed_kind != kind
|
|
297
|
+
# tokens of different kinds in one regular (not named) pattern are prohibited
|
|
298
|
+
raise I18n::MisplacedInflectionToken.new(locale, ext_pattern, t, sym_parsed_kind) if raises
|
|
299
|
+
|
|
300
|
+
next
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# use that token
|
|
304
|
+
unless negatives[t]
|
|
305
|
+
tokens[t] = true
|
|
306
|
+
default_value = ext_value if t == default_token
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# self-explanatory
|
|
311
|
+
if tokens.empty? && negatives.empty? && raises
|
|
312
|
+
raise I18n::InvalidInflectionToken.new(locale, ext_pattern, ext_token)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# INFLECTION OPTION PROCESSING
|
|
316
|
+
|
|
317
|
+
# set up expected_kind depending on type of a kind
|
|
318
|
+
if strict_kind.nil?
|
|
319
|
+
expected_kind = parsed_kind
|
|
320
|
+
else
|
|
321
|
+
expected_kind = sym_parsed_kind
|
|
322
|
+
expected_kind = parsed_kind unless passed_kinds.key?(expected_kind)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# get passed token from options or from a default token
|
|
326
|
+
if passed_kinds.key?(expected_kind)
|
|
327
|
+
|
|
328
|
+
passed_token = passed_kinds[expected_kind]
|
|
329
|
+
|
|
330
|
+
if passed_token.is_a?(Method)
|
|
331
|
+
|
|
332
|
+
passed_token = passed_token.call { next expected_kind, locale }
|
|
333
|
+
passed_kinds[expected_kind] = passed_token # cache the result
|
|
334
|
+
|
|
335
|
+
elsif passed_token.is_a?(Proc)
|
|
336
|
+
|
|
337
|
+
passed_token = passed_token.call(expected_kind, locale)
|
|
338
|
+
passed_kinds[expected_kind] = passed_token # cache the result
|
|
339
|
+
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
orig_passed_token = passed_token
|
|
343
|
+
|
|
344
|
+
# validate passed token's name
|
|
345
|
+
if Reserved::Tokens.invalid?(passed_token, :OPTION)
|
|
346
|
+
raise I18n::InvalidInflectionOption.new(locale, ext_pattern, orig_passed_token) if raises
|
|
347
|
+
|
|
348
|
+
passed_token = default_token if unknown_defaults
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
else
|
|
352
|
+
# current inflection option wasn't found
|
|
353
|
+
# but delay this exception because we might use
|
|
354
|
+
# the default token if found somewhere in a pattern
|
|
355
|
+
if raises
|
|
356
|
+
tb_raised = InflectionOptionNotFound.new(locale, ext_pattern, ext_token,
|
|
357
|
+
expected_kind, orig_passed_token)
|
|
358
|
+
end
|
|
359
|
+
passed_token = default_token
|
|
360
|
+
nil
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# explicit default
|
|
364
|
+
passed_token = default_token if passed_token == Keys::DEFAULT_TOKEN
|
|
365
|
+
|
|
366
|
+
# resolve token from options and check if it's known
|
|
367
|
+
unless passed_token.nil?
|
|
368
|
+
passed_token = subdb.get_true_token(passed_token.to_s.to_sym, parsed_kind)
|
|
369
|
+
passed_token = default_token if passed_token.nil? && unknown_defaults
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# handle memorized wildcard waiting for a kind
|
|
373
|
+
if !wildcard_value.nil? && !parsed_kind.nil?
|
|
374
|
+
found = passed_token
|
|
375
|
+
result = wildcard_value
|
|
376
|
+
wildcard_value = nil
|
|
377
|
+
break
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# throw the value if the given option matches one of the tokens from group
|
|
381
|
+
# or negatively matches one of the negated tokens
|
|
382
|
+
case negatives.count
|
|
383
|
+
when 0 then next unless tokens[passed_token]
|
|
384
|
+
when 1 then next if negatives[passed_token]
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# skip further evaluation of the pattern
|
|
388
|
+
# since the right token has been found
|
|
389
|
+
found = passed_token
|
|
390
|
+
result = ext_value
|
|
391
|
+
break
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# RESULTS PROCESSING
|
|
395
|
+
|
|
396
|
+
# handle memorized wildcard token
|
|
397
|
+
# when there was no way to deduce a token or a kind
|
|
398
|
+
# it's just for regular kinds
|
|
399
|
+
unless wildcard_value.nil? || passed_kinds.nil?
|
|
400
|
+
parsed_kind = nil
|
|
401
|
+
found = nil
|
|
402
|
+
passed_kinds.each do |k, ot|
|
|
403
|
+
t = subdb.get_true_token(ot, k)
|
|
404
|
+
if Reserved::Tokens.invalid?(t, :OPTION)
|
|
405
|
+
raise I18n::InvalidInflectionOption.new(locale, ext_pattern, ot) if raises
|
|
406
|
+
|
|
407
|
+
next
|
|
408
|
+
end
|
|
409
|
+
next if t.nil?
|
|
410
|
+
|
|
411
|
+
found = t
|
|
412
|
+
parsed_kind = k
|
|
413
|
+
break
|
|
414
|
+
end
|
|
415
|
+
if parsed_kind.nil? || found.nil?
|
|
416
|
+
found = nil
|
|
417
|
+
parsed_kind = nil
|
|
418
|
+
else
|
|
419
|
+
result = wildcard_value
|
|
420
|
+
wildcard_value = nil
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# if there was no hit for that option
|
|
425
|
+
if result.nil?
|
|
426
|
+
raise tb_raised unless tb_raised.nil?
|
|
427
|
+
|
|
428
|
+
# try to extract default token's value
|
|
429
|
+
|
|
430
|
+
# if there is excluded_defaults switch turned on
|
|
431
|
+
# and a correct token was found in an inflection option but
|
|
432
|
+
# has not been found in a pattern then interpolate
|
|
433
|
+
# the pattern with a value picked for the default
|
|
434
|
+
# token for that kind if a default token was present
|
|
435
|
+
# in a pattern
|
|
436
|
+
if excluded_defaults && !parsed_kind.nil?
|
|
437
|
+
expected_kind = sym_parsed_kind
|
|
438
|
+
expected_kind = parsed_kind unless passed_kinds.key?(expected_kind)
|
|
439
|
+
t = passed_kinds[expected_kind]
|
|
440
|
+
if t.is_a?(Method)
|
|
441
|
+
t = t.call { next expected_kind, locale }
|
|
442
|
+
passed_kinds[expected_kind] = t # cache the result
|
|
443
|
+
elsif t.is_a?(Proc)
|
|
444
|
+
t = t.call(expected_kind, locale)
|
|
445
|
+
passed_kinds[expected_kind] = t # cache the result
|
|
446
|
+
end
|
|
447
|
+
if Reserved::Tokens.invalid?(t, :OPTION) && raises
|
|
448
|
+
raise I18n::InvalidInflectionOption.new(locale, ext_pattern, t)
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
result = subdb.has_token?(t, parsed_kind) ? default_value : nil
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# interpolate loud tokens
|
|
455
|
+
elsif result == Markers::LOUD_VALUE
|
|
456
|
+
|
|
457
|
+
result = subdb.get_description(found, parsed_kind)
|
|
458
|
+
|
|
459
|
+
# interpolate escaped loud tokens or other escaped strings
|
|
460
|
+
elsif result[0..0] == Escapes::ESCAPE
|
|
461
|
+
|
|
462
|
+
result.sub!(Escapes::ESCAPE_R, '\1')
|
|
463
|
+
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
"#{pattern_fix}#{result || ext_freetext}"
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# This is a helper that reduces a complex inflection pattern
|
|
471
|
+
# by producing equivalent of regular patterns of it and
|
|
472
|
+
# by interpolating them using {#interpolate} method.
|
|
473
|
+
#
|
|
474
|
+
# @param [String] complex_kind the complex kind (many kinds separated
|
|
475
|
+
# by the {Operators::Tokens::AND})
|
|
476
|
+
# @param [String] content the content of the processed pattern
|
|
477
|
+
# @param [Symbol] locale the locale to use
|
|
478
|
+
# @param [Hash] options the options
|
|
479
|
+
# @return [String] the interpolated pattern
|
|
480
|
+
def interpolate_complex(complex_kind, content, locale, options)
|
|
481
|
+
result = nil
|
|
482
|
+
free_text = ''
|
|
483
|
+
kinds = complex_kind.split(Operators::Tokens::AND)
|
|
484
|
+
.reject { |k| k.nil? || k.empty? }.each
|
|
485
|
+
|
|
486
|
+
begin
|
|
487
|
+
content.scan(TOKENS_REGEXP) do |tokens, value, free|
|
|
488
|
+
if tokens.nil?
|
|
489
|
+
raise IndexError if free.empty?
|
|
490
|
+
|
|
491
|
+
free_text = free
|
|
492
|
+
next
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
kinds.rewind
|
|
496
|
+
|
|
497
|
+
# process each token from set
|
|
498
|
+
results = tokens.split(Operators::Tokens::AND).map do |token|
|
|
499
|
+
raise IndexError if token.empty?
|
|
500
|
+
|
|
501
|
+
if value == Markers::LOUD_VALUE
|
|
502
|
+
r = interpolate_core(Markers::PATTERN.to_s +
|
|
503
|
+
kinds.next.to_s +
|
|
504
|
+
Markers::PATTERN_BEGIN +
|
|
505
|
+
token.to_s +
|
|
506
|
+
Operators::Tokens::ASSIGN +
|
|
507
|
+
value.to_s +
|
|
508
|
+
Operators::Tokens::OR +
|
|
509
|
+
Markers::PATTERN +
|
|
510
|
+
Markers::PATTERN_END,
|
|
511
|
+
locale, options)
|
|
512
|
+
break if r == Markers::PATTERN # using this marker only as a helper to indicate empty result!
|
|
513
|
+
else
|
|
514
|
+
r = interpolate_core(Markers::PATTERN.to_s +
|
|
515
|
+
kinds.next.to_s +
|
|
516
|
+
Markers::PATTERN_BEGIN +
|
|
517
|
+
token.to_s +
|
|
518
|
+
Operators::Tokens::ASSIGN +
|
|
519
|
+
value.to_s +
|
|
520
|
+
Markers::PATTERN_END,
|
|
521
|
+
locale, options)
|
|
522
|
+
break if r != value # stop with this set, because something is not matching
|
|
523
|
+
end
|
|
524
|
+
r
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# some token didn't matched, try another set
|
|
528
|
+
next if results.nil?
|
|
529
|
+
|
|
530
|
+
# generate result for set or raise error
|
|
531
|
+
raise IndexError unless results.size == kinds.count
|
|
532
|
+
|
|
533
|
+
result = (value == Markers::LOUD_VALUE) ? results.join(' ') : value
|
|
534
|
+
break
|
|
535
|
+
end
|
|
536
|
+
rescue IndexError, StopIteration
|
|
537
|
+
raise I18n::ComplexPatternMalformed.new(locale, content, nil, complex_kind) if options[:inflector_raises]
|
|
538
|
+
|
|
539
|
+
result = nil
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
result || free_text
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
end
|