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,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