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,399 @@
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 inline documentation data
8
+ # that would make the file with code less readable
9
+ # if placed there.
10
+ #
11
+ #--
12
+ #
13
+ # Copyright (C) 2010 by Paweł Wilk. All Rights Reserved.
14
+ #
15
+ # This program is free software; you can redistribute it and/or modify
16
+ # it under the terms of either: 1) the GNU Lesser General Public License
17
+ # as published by the Free Software Foundation; either version 3 of the
18
+ # License, or (at your option) any later version; or 2) Ruby's License.
19
+ #
20
+ # See the file COPYING for complete licensing information.
21
+ #
22
+ #++
23
+ module I18n
24
+ module Backend
25
+ # Overwrites the Simple backend translate method so that it will interpolate
26
+ # additional inflection tokens present in translations. These tokens may
27
+ # appear in *patterns* which are contained within <tt>@{</tt> and <tt>}</tt>
28
+ # symbols.
29
+ #
30
+ # You can choose different kinds (gender, title, person, time, author, etc.)
31
+ # of tokens to group them in a meaningful, semantical sets. That means you can
32
+ # apply Inflector to do simple inflection by a gender or a person, when some
33
+ # language requires it.
34
+ #
35
+ # To achieve similar functionality lambdas can be used but there might be
36
+ # some areas of appliance that including proc objects in translations
37
+ # is prohibited.
38
+ # If you have a troop of happy translators that shouldn't have the
39
+ # ability to execute any code yet you need some simple inflection
40
+ # then you can make use of this module.
41
+ # == Usage
42
+ # require 'i18n'
43
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Inflector)
44
+ #
45
+ # i18n.translate('welcome')
46
+ # # where welcome maps to: "Dear @{f:Madam|m:Sir}"
47
+ #
48
+ # == Inflection pattern
49
+ # An example inflection pattern contained in a translation record looks like:
50
+ # welcome: "Dear @{f:Madam|m:Sir|n:You|All}"
51
+ #
52
+ # The +f+, +m+ and +n+ are inflection *tokens* and +Madam+, +Sir+, +You+ and
53
+ # +All+ are *values*. Only one value is going to replace the whole
54
+ # pattern. To select which one an additional option is used. That option
55
+ # must be passed to translate method.
56
+ #
57
+ # == Configuration
58
+ # To recognize tokens present in patterns this module uses keys grouped
59
+ # in the scope called +inflections+ for a given locale. For instance
60
+ # (YAML format):
61
+ # en:
62
+ # i18n:
63
+ # inflections:
64
+ # gender:
65
+ # f: "female"
66
+ # m: "male"
67
+ # n: "neuter"
68
+ # woman: @f
69
+ # man: @m
70
+ # default: n
71
+ #
72
+ # Elements in the example above are:
73
+ # * +en+: language
74
+ # * +i18n+: configuration scope
75
+ # * +inflections+: inflections configuration scope
76
+ # * +gender+: kind scope
77
+ # * +f+, +m+, +n+: inflection tokens
78
+ # * <tt>"male"</tt>, <tt>"female"</tt>, <tt>"neuter"</tt>: tokens' descriptions
79
+ # * +woman+, +man+: inflection aliases
80
+ # * <tt>@f</tt>, <tt>@m</tt>: pointers to real tokens
81
+ # * +default+: default token for a kind +gender+
82
+ #
83
+ # === Kind
84
+ # Note the fourth scope selector in the example above (+gender+). It's called
85
+ # the *kind* and contains *tokens*. We have the kind
86
+ # +gender+ to which the inflection tokens +f+, +m+ and +n+ are
87
+ # assigned.
88
+ #
89
+ # You cannot assign the same token to more than one kind.
90
+ # Trying to do that will raise DuplicatedInflectionToken exception.
91
+ # This is required in order to keep patterns simple and tokens interpolation
92
+ # fast.
93
+ #
94
+ # Kind is also used to instruct I18n.translate method which
95
+ # token it should pick. This will be explained later.
96
+ #
97
+ # === Aliases
98
+ # Aliases are special tokens that point to other tokens. They cannot
99
+ # be used in inflection patterns but they are fully recognized values
100
+ # of options while evaluating kinds.
101
+ #
102
+ # Aliases might be helpful in multilingual applications that are using
103
+ # a fixed set of values passed through options to describe some properties
104
+ # of messages, e.g. +masculine+ and +feminine+ for a grammatical gender.
105
+ # Translators may then use their own tokens (like +f+ and +m+ for English)
106
+ # to produce pretty and intuitive patterns.
107
+ #
108
+ # For example: if some application uses database with gender assigned
109
+ # to a user which may be +male+, +female+ or +none+, then a translator
110
+ # for some language may find it useful to map impersonal token (<tt>none</tt>)
111
+ # to the +neuter+ token, since in translations for his language the
112
+ # neuter gender is in use.
113
+ #
114
+ # Here is the example of such situation:
115
+ #
116
+ # en:
117
+ # i18n:
118
+ # inflections:
119
+ # gender:
120
+ # male: "male"
121
+ # female: "female"
122
+ # none: "impersonal form"
123
+ # default: none
124
+ #
125
+ # pl:
126
+ # i18n:
127
+ # inflections:
128
+ # gender:
129
+ # k: "female"
130
+ # m: "male"
131
+ # n: "neuter"
132
+ # male: @k
133
+ # female: @m
134
+ # none: @n
135
+ # default: none
136
+ #
137
+ # In the case above Polish translator decided to use neuter
138
+ # instead of impersonal form when +none+ token will be passed
139
+ # through the option +:gender+ to the translate method. He
140
+ # also decided that he will use +k+, +m+ or +n+ in patterns,
141
+ # because the names are short and correspond to gender names in
142
+ # Polish language.
143
+ #
144
+ # Aliases may point to other aliases. While loading inflections they
145
+ # will be internally shortened and they will always point to real tokens,
146
+ # not other aliases.
147
+ #
148
+ # === Default token
149
+ # There is special token called the +default+, which points
150
+ # to a token that should be used if translation routine cannot deduce
151
+ # which one it should use because a proper option was not given.
152
+ #
153
+ # Default tokens may point to aliases and may use aliases' syntax, e.g.:
154
+ # default: @man
155
+ #
156
+ # === Descriptions
157
+ # The values of keys in the example (+female+, +male+ and +neuter+)
158
+ # are *descriptions* which are not used by interpolation routines
159
+ # but might be helpful (e.g. in UI). For obvious reasons you cannot
160
+ # describe aliases.
161
+ #
162
+ # == Tokens
163
+ # The token is an element of a pattern. A pattern may have many tokens
164
+ # of the same kind separated by vertical bars. Each token used in a
165
+ # pattern should end with colon sign. After this colon a value should
166
+ # appear (or an empty string). This value is to be picked by interpolation
167
+ # routine and will replace whole pattern, when it matches the value of an
168
+ # option passed to I18n.translate method. A name of that
169
+ # option should be the same as a *kind* of tokens used within a pattern.
170
+ # The first token in a pattern determines the kind of all tokens used
171
+ # in that pattern.
172
+ # ==== Examples
173
+ # # welcome is "Dear @{f:Madam|m:Sir|n:You|All}"
174
+ #
175
+ # I18n.translate('welcome', :gender => :m)
176
+ # # => "Dear Sir"
177
+ #
178
+ # I18n.translate('welcome', :gender => :unknown)
179
+ # # => "Dear All"
180
+ #
181
+ # I18n.translate('welcome')
182
+ # # => "Dear You"
183
+ #
184
+ # In the second example the <b>fallback value</b> +All+ was interpolated
185
+ # because the routine had been unable to find the token called +:unknown+.
186
+ # That differs from the latest example, in which there was no option given,
187
+ # so the default token for a kind had been applied (in this case +n+).
188
+ #
189
+ # == Local fallbacks (free text)
190
+ # The fallback value will be used when any of the given tokens from
191
+ # pattern cannot be interpolated.
192
+ #
193
+ # Be aware that enabling extended error reporting makes it unable
194
+ # to use fallback values in most cases. Local fallbacks will then be
195
+ # applied only when a given option contains a proper value for some
196
+ # kind but it's just not present in a pattern, for example:
197
+ #
198
+ # I18n.locale = :en
199
+ # I18n.backend.store_translations 'en', 'welcome' => 'Dear @{n:You|All}'
200
+ # I18n.backend.store_translations 'en', :i18n => { :inflections => {
201
+ # :gender => { :n => 'neuter', :o => 'other' }}}
202
+ #
203
+ # I18n.translate('welcome', :gender => :o, :inflector_raises => true)
204
+ # # => "Dear All"
205
+ #
206
+ # # since the token :o was configured but not used in the pattern
207
+ #
208
+ # == Unknown and empty tokens in options
209
+ # If an option containing token is not present at all then the interpolation
210
+ # routine will try the default token for a processed kind if the default
211
+ # token is present in a pattern. The same thing will happend if the option
212
+ # is present but its value is unknown, empty or +nil+.
213
+ # If the default token is not present in a pattern or is not defined in
214
+ # a configuration data then the processed pattern will result in an empty
215
+ # string or in a local fallback value if there is a free text placed
216
+ # in a pattern.
217
+ #
218
+ # You can change this default behavior and force inflector
219
+ # not to use a default token when a value of an option for a kind is unknown,
220
+ # empty or +nil+ but only when it's not present.
221
+ # To do that you should set option +:inflector_unknown_defaults+ to
222
+ # +false+ and pass it to I18n.translate method. Other way is to set this
223
+ # globally by using the method called inflector_unknown_defaults.
224
+ # See inflector_unknown_defaults method for examples showing how the
225
+ # translation results are changing when that switch is applied.
226
+ #
227
+ # == Mixing inflection and standard interpolation patterns
228
+ # The Inflector module allows you to include standard <tt>%{}</tt>
229
+ # patterns inside of inflection patterns. The value of a standard
230
+ # interpolation variable will be evaluated and interpolated before
231
+ # processing an inflection pattern. For example:
232
+ # I18nstore_translations(:xx, 'hi' => 'Dear @{f:Lady|m:%{test}}!')
233
+ #
234
+ # I18n.t('hi', :gender => :m, :locale => :xx, :test => "Dude")
235
+ # # => Dear Dude!
236
+ #
237
+ # == Errors
238
+ # By default the module will silently ignore any interpolation errors.
239
+ # You can turn off this default behavior by passing +:inflector_raises+ option.
240
+ # For instance:
241
+ #
242
+ # I18n.locale = :en
243
+ # I18n.backend.store_translations 'en', 'welcome' => 'Dear @{m:Sir|f:Madam|Fallback}'
244
+ # I18n.backend.store_translations 'en', :i18n => { :inflections => {
245
+ # :gender => {
246
+ # :f => 'female',
247
+ # :m => 'male'
248
+ # }}}
249
+ #
250
+ # I18n.translate('welcome', :inflector_raises => true)
251
+ # # => I18n::InvalidOptionForKind: option :gender required
252
+ # by the pattern "@{m:Sir|f:Madam|Fallback}" was not found
253
+ #
254
+ # Here are the exceptions that may be raised when option +:inflector_raises+
255
+ # is set to +true+:
256
+ #
257
+ # * I18n::InvalidOptionForKind
258
+ # * I18n::InvalidInflectionToken
259
+ # * I18n::MisplacedInflectionToken
260
+ #
261
+ # There are also exceptions that are raised regardless of :+inflector_raises+
262
+ # presence or value.
263
+ # These are usually caused by critical errors encountered during processing
264
+ # inflection data. Here is the list:
265
+ #
266
+ # * I18n::InvalidLocale
267
+ # * I18n::DuplicatedInflectionToken
268
+ # * I18n::BadInflectionToken
269
+ # * I18n::BadInflectionAlias
270
+ #
271
+ module Inflector
272
+ # When this switch is set to +true+ then inflector falls back to the default
273
+ # token for a kind if an option passed to the translate method that describes
274
+ # a kind is unknown or +nil+. Note that the value for a default token will be
275
+ # interpolated only when this token is present in pattern. This switch
276
+ # is by default set to +true+.
277
+ #
278
+ # Local option +:inflector_unknown_defaults+ passed to translation method
279
+ # overrides this setting.
280
+ #
281
+ # === Short name
282
+ # <tt>I18n::Inflector.unknown_defaults</tt>
283
+ #
284
+ # == Examples
285
+ #
286
+ # I18n.locale = :en
287
+ # I18n.backend.store_translations 'en', :i18n => { :inflections => {
288
+ # :gender => {
289
+ # :n => 'neuter',
290
+ # :o => 'other',
291
+ # :default => 'n' }}}
292
+ #
293
+ # I18n.backend.store_translations 'en', 'welcome' => 'Dear @{n:You|o:Other}'
294
+ # I18n.backend.store_translations 'en', 'welcome_free' => 'Dear @{n:You|o:Other|Free}'
295
+ #
296
+ # === Example 1
297
+ #
298
+ # # :gender option is not present,
299
+ # # unknown tokens in options are falling back to default
300
+ #
301
+ # I18n.t('welcome')
302
+ # # => "Dear You"
303
+ #
304
+ # # :gender option is not present,
305
+ # # unknown tokens from options are not falling back to default
306
+ #
307
+ # I18n.t('welcome', :inflector_unknown_defaults => false)
308
+ # # => "Dear You"
309
+ #
310
+ # # :gender option is not present, free text is present,
311
+ # # unknown tokens from options are not falling back to default
312
+ #
313
+ # I18n.t('welcome_free', :inflector_unknown_defaults => false)
314
+ # # => "Dear You"
315
+ #
316
+ # === Example 2
317
+ #
318
+ # # :gender option is nil,
319
+ # # unknown tokens from options are falling back to default token for a kind
320
+ #
321
+ # I18n.t('welcome', :gender => nil)
322
+ # # => "Dear You"
323
+ #
324
+ # # :gender option is nil
325
+ # # unknown tokens from options are not falling back to default token for a kind
326
+ #
327
+ # I18n.t('welcome', :gender => nil, :inflector_unknown_defaults => false)
328
+ # # => "Dear "
329
+ #
330
+ # # :gender option is nil, free text is present
331
+ # # unknown tokens from options are not falling back to default token for a kind
332
+ #
333
+ # I18n.t('welcome_free', :gender => nil, :inflector_unknown_defaults => false)
334
+ # # => "Dear Free"
335
+ #
336
+ # === Example 3
337
+ #
338
+ # # :gender option is unknown,
339
+ # # unknown tokens from options are falling back to default token for a kind
340
+ #
341
+ # I18n.t('welcome', :gender => :unknown_blabla)
342
+ # # => "Dear You"
343
+ #
344
+ # # :gender option is unknown,
345
+ # # unknown tokens from options are not falling back to default token for a kind
346
+ #
347
+ # I18n.t('welcome', :gender => :unknown_blabla, :inflector_unknown_defaults => false)
348
+ # # => "Dear "
349
+ #
350
+ # # :gender option is unknown, free text is present
351
+ # # unknown tokens from options are not falling back to default token for a kind
352
+ #
353
+ # I18n.t('welcome_free', :gender => :unknown_blabla, :inflector_unknown_defaults => false)
354
+ # # => "Dear Free"
355
+ attr_writer :inflector_unknown_defaults
356
+
357
+ # When this switch is set to +true+ then inflector falls back and uses the default
358
+ # token for a kind if an option passed to the translate method matches some token
359
+ # for that kind but that particular token is not included in a processed
360
+ # pattern. This switch is by default set to +false+.
361
+ #
362
+ # Local option +:inflector_excluded_defaults+ passed to translation method
363
+ # overrides this setting.
364
+ #
365
+ # === Short name
366
+ # <tt>I18n::Inflector.excluded_defaults</tt>
367
+ #
368
+ # == Example
369
+ #
370
+ # I18n.locale = :en
371
+ # I18n.backend.store_translations 'en', :i18n => { :inflections => {
372
+ # :gender => {
373
+ # :n => 'neuter',
374
+ # :m => 'male',
375
+ # :o => 'other',
376
+ # :default => 'n' }}}
377
+ #
378
+ # I18n.backend.store_translations 'en', 'welcome' => 'Dear @{n:You|m:Sir}'
379
+ #
380
+ # p I18n.t('welcome', :gender => :o)
381
+ # # => "Dear "
382
+ #
383
+ # p I18n.t('welcome', :gender => :o, :inflector_excluded_defaults => true)
384
+ # # => "Dear You"
385
+ attr_writer :inflector_excluded_defaults
386
+
387
+ # This is a switch that enables extended error reporting. When it's enabled then
388
+ # errors will be raised when unknown or empty token is present in pattern or in options.
389
+ # This switch is by default set to +false+.
390
+ #
391
+ # Local option +:inflector_raises+ passed to translation method overrides this setting.
392
+ #
393
+ # === Short name
394
+ # <tt>I18n::Inflector.inflector_raises</tt>
395
+ attr_writer :inflector_raises
396
+
397
+ end
398
+ end
399
+ end
@@ -0,0 +1,62 @@
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::Inflector module,
8
+ # which adds wrappers (module functions) for methods
9
+ # in I18n::Backend::Inflector module in order to
10
+ # access common methods under friendly names.
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
25
+ module Inflector
26
+
27
+ def raises?(*args); I18n.backend.inflector_raises?(*args) end
28
+ def unknown_defaults?(*args); I18n.backend.inflector_unknown_defaults?(*args) end
29
+ def excluded_defaults?(*args); I18n.backend.inflector_excluded_defaults?(*args) end
30
+ def raises(*args); I18n.backend.inflector_raises(*args) end
31
+ def unknown_defaults(*args); I18n.backend.inflector_unknown_defaults(*args) end
32
+ def excluded_defaults(*args); I18n.backend.inflector_excluded_defaults(*args) end
33
+ def reload!; I18n.backend.reload! end
34
+ def default_token(*args); I18n.backend.inflection_default_token(*args) end
35
+ def is_alias?(*args); I18n.backend.inflection_is_alias?(*args) end
36
+ def tokens(*args); I18n.backend.inflection_tokens(*args) end
37
+ def raw_tokens(*args); I18n.backend.inflection_raw_tokens(*args) end
38
+ def true_tokens(*args); I18n.backend.inflection_true_tokens(*args) end
39
+ def aliases(*args); I18n.backend.inflection_aliases(*args) end
40
+ def kinds(*args); I18n.backend.available_inflection_kinds(*args) end
41
+ def locales(*args); I18n.backend.inflected_locales(*args) end
42
+ def description(*args); I18n.backend.inflection_token_description(*args) end
43
+
44
+ module_function :raises?
45
+ module_function :unknown_defaults?
46
+ module_function :excluded_defaults?
47
+ module_function :raises
48
+ module_function :unknown_defaults
49
+ module_function :excluded_defaults
50
+ module_function :reload!
51
+ module_function :default_token
52
+ module_function :is_alias?
53
+ module_function :tokens
54
+ module_function :raw_tokens
55
+ module_function :true_tokens
56
+ module_function :aliases
57
+ module_function :kinds
58
+ module_function :locales
59
+ module_function :description
60
+
61
+ end
62
+ end
@@ -0,0 +1,5 @@
1
+ require 'i18n'
2
+ require 'i18n-inflector/inflector.rb'
3
+ require 'i18n-inflector/shortcuts.rb'
4
+
5
+ I18n::Backend::Simple.send(:include, I18n::Backend::Inflector)
data/test/all.rb ADDED
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ dir = File.dirname(__FILE__)
4
+ $LOAD_PATH.unshift(dir)
5
+
6
+ Dir["#{dir}/**/*_test.rb"].sort.each do |file|
7
+ require file.sub(/^#{dir}\/(.*)\.rb$/, '\1')
8
+ end