tr8n_core 4.0.1

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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +69 -0
  4. data/Rakefile +9 -0
  5. data/config/config.yml +34 -0
  6. data/config/tokens/data.yml +45 -0
  7. data/config/tokens/decorations.yml +37 -0
  8. data/lib/tr8n/application.rb +320 -0
  9. data/lib/tr8n/base.rb +123 -0
  10. data/lib/tr8n/cache.rb +144 -0
  11. data/lib/tr8n/cache_adapters/cdb.rb +74 -0
  12. data/lib/tr8n/cache_adapters/file.rb +70 -0
  13. data/lib/tr8n/cache_adapters/memcache.rb +91 -0
  14. data/lib/tr8n/cache_adapters/redis.rb +94 -0
  15. data/lib/tr8n/component.rb +68 -0
  16. data/lib/tr8n/config.rb +291 -0
  17. data/lib/tr8n/decorators/base.rb +35 -0
  18. data/lib/tr8n/decorators/default.rb +30 -0
  19. data/lib/tr8n/decorators/html.rb +63 -0
  20. data/lib/tr8n/exception.rb +26 -0
  21. data/lib/tr8n/language.rb +250 -0
  22. data/lib/tr8n/language_case.rb +116 -0
  23. data/lib/tr8n/language_case_rule.rb +85 -0
  24. data/lib/tr8n/language_context.rb +115 -0
  25. data/lib/tr8n/language_context_rule.rb +62 -0
  26. data/lib/tr8n/logger.rb +83 -0
  27. data/lib/tr8n/rules_engine/evaluator.rb +156 -0
  28. data/lib/tr8n/rules_engine/parser.rb +83 -0
  29. data/lib/tr8n/source.rb +95 -0
  30. data/lib/tr8n/tokens/data.rb +410 -0
  31. data/lib/tr8n/tokens/data_tokenizer.rb +82 -0
  32. data/lib/tr8n/tokens/decoration_tokenizer.rb +200 -0
  33. data/lib/tr8n/tokens/hidden.rb +48 -0
  34. data/lib/tr8n/tokens/method.rb +52 -0
  35. data/lib/tr8n/tokens/transform.rb +191 -0
  36. data/lib/tr8n/translation.rb +104 -0
  37. data/lib/tr8n/translation_key.rb +205 -0
  38. data/lib/tr8n/translator.rb +62 -0
  39. data/lib/tr8n/utils.rb +124 -0
  40. data/lib/tr8n_core/ext/array.rb +74 -0
  41. data/lib/tr8n_core/ext/date.rb +63 -0
  42. data/lib/tr8n_core/ext/fixnum.rb +39 -0
  43. data/lib/tr8n_core/ext/hash.rb +126 -0
  44. data/lib/tr8n_core/ext/string.rb +44 -0
  45. data/lib/tr8n_core/ext/time.rb +71 -0
  46. data/lib/tr8n_core/generators/cache/base.rb +85 -0
  47. data/lib/tr8n_core/generators/cache/cdb.rb +27 -0
  48. data/lib/tr8n_core/generators/cache/file.rb +69 -0
  49. data/lib/tr8n_core/modules/logger.rb +43 -0
  50. data/lib/tr8n_core/version.rb +27 -0
  51. data/lib/tr8n_core.rb +68 -0
  52. data/spec/application_spec.rb +228 -0
  53. data/spec/base_spec.rb +19 -0
  54. data/spec/config_spec.rb +16 -0
  55. data/spec/decorator_spec.rb +10 -0
  56. data/spec/decorators/base_spec.rb +14 -0
  57. data/spec/decorators/default_spec.rb +12 -0
  58. data/spec/decorators/html_spec.rb +50 -0
  59. data/spec/fixtures/application.json +112 -0
  60. data/spec/fixtures/languages/en-US.json +1424 -0
  61. data/spec/fixtures/languages/es.json +291 -0
  62. data/spec/fixtures/languages/ru.json +550 -0
  63. data/spec/fixtures/translations/ru/basic.json +56 -0
  64. data/spec/fixtures/translations/ru/counters.json +43 -0
  65. data/spec/fixtures/translations/ru/genders.json +171 -0
  66. data/spec/fixtures/translations/ru/last_names.txt +200 -0
  67. data/spec/fixtures/translations/ru/names.json +1 -0
  68. data/spec/fixtures/translations/ru/names.txt +458 -0
  69. data/spec/helper.rb +84 -0
  70. data/spec/language_case_rule_spec.rb +57 -0
  71. data/spec/language_case_spec.rb +58 -0
  72. data/spec/language_context_rule_spec.rb +73 -0
  73. data/spec/language_context_spec.rb +331 -0
  74. data/spec/language_spec.rb +16 -0
  75. data/spec/rules_engine/evaluator_spec.rb +148 -0
  76. data/spec/rules_engine/parser_spec.rb +29 -0
  77. data/spec/tokens/data_spec.rb +160 -0
  78. data/spec/tokens/data_tokenizer_spec.rb +29 -0
  79. data/spec/tokens/decoration_tokenizer_spec.rb +81 -0
  80. data/spec/tokens/hidden_spec.rb +24 -0
  81. data/spec/tokens/method_spec.rb +84 -0
  82. data/spec/tokens/transform_spec.rb +50 -0
  83. data/spec/translation_key_spec.rb +96 -0
  84. data/spec/translation_spec.rb +24 -0
  85. data/spec/utils_spec.rb +64 -0
  86. metadata +176 -0
@@ -0,0 +1,83 @@
1
+ #--
2
+ # Copyright (c) 2013 Michael Berkovich, tr8nhub.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module Tr8n
25
+ module RulesEngine
26
+
27
+ class Parser
28
+ attr_reader :tokens, :expression
29
+
30
+ def initialize(expression)
31
+ @expression = expression
32
+ if expression =~ /^\(/
33
+ @tokens = expression.scan(/[()]|\w+|@\w+|[\+\-\!\|\=>&<\*\/%]+|".*?"|'.*?'/)
34
+ end
35
+ end
36
+
37
+ def parse
38
+ return @expression unless tokens
39
+ token = tokens.shift
40
+ return nil if token.nil?
41
+ return parse_list if (token) == '('
42
+ return token[1..-2].to_s if token =~ /^['"].*/
43
+ return token.to_i if token =~ /\d+/
44
+ token.to_s
45
+ end
46
+
47
+ def parse_list
48
+ list = []
49
+ list << parse until tokens.empty? or tokens.first == ')'
50
+ tokens.shift
51
+ list
52
+ end
53
+
54
+ def class_for(token)
55
+ {
56
+ /^[\(]$/ => 'open_paren',
57
+ /^[\)]$/ => 'close_paren',
58
+ /^['|"]/ => 'string',
59
+ /^@/ => 'variable',
60
+ /^[\d|.]+$/ => 'number',
61
+ }.each do |regexp, cls|
62
+ return cls if regexp.match(token)
63
+ end
64
+ 'symbol'
65
+ end
66
+
67
+ def decorate
68
+ html = ["<span class='tr8n_sexp'>"]
69
+ if tokens
70
+ html << tokens.collect do |token|
71
+ "<span class='#{class_for(token)}'>#{token}</span>"
72
+ end.join('')
73
+ else
74
+ html << "<span class='#{class_for(expression)}'>#{expression}</span>"
75
+ end
76
+ html << '</span>'
77
+ html.join('').html_safe
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,95 @@
1
+ #--
2
+ # Copyright (c) 2013 Michael Berkovich, tr8nhub.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ class Tr8n::Source < Tr8n::Base
25
+ belongs_to :application
26
+ attributes :source, :url, :name, :description
27
+ has_many :translation_keys
28
+
29
+ def self.normalize(url)
30
+ return nil if url.blank?
31
+ uri = URI.parse(url)
32
+ path = uri.path
33
+ return "/" if uri.path.blank?
34
+ return path if path == "/"
35
+
36
+ # always must start with /
37
+ path = "/#{path}" if path[0] != "/"
38
+ # should not end with /
39
+ path = path[0..-2] if path[-1] == "/"
40
+ path
41
+ end
42
+
43
+ def initialize(attrs = {})
44
+ super
45
+
46
+ self.translation_keys = nil
47
+ if hash_value(attrs, :translation_keys)
48
+ self.translation_keys = {}
49
+ hash_value(attrs, :translation_keys).each do |tk|
50
+ tkey = Tr8n::TranslationKey.new(tk.merge(:application => application))
51
+ self.translation_keys[tkey.key] = application.cache_translation_key(tkey)
52
+ end
53
+ end
54
+ end
55
+
56
+ def fetch_translations_for_language(language, options = {})
57
+ return translation_keys if translation_keys
58
+
59
+ keys_with_translations = application.get("source/translations",
60
+ {:source => source, :locale => language.locale},
61
+ {:class => Tr8n::TranslationKey, :attributes => {:application => application}})
62
+
63
+ self.attributes[:translation_keys] = {}
64
+
65
+ keys_with_translations.each do |tkey|
66
+ self.attributes[:translation_keys][tkey.key] = application.cache_translation_key(tkey)
67
+ end
68
+
69
+ self.attributes[:translation_keys]
70
+ end
71
+
72
+ #######################################################################################################
73
+ ## Cache Methods
74
+ #######################################################################################################
75
+
76
+ def self.cache_prefix
77
+ 's@'
78
+ end
79
+
80
+ def self.cache_key(source_key, locale)
81
+ "#{cache_prefix}_[#{locale}]_[#{source_key}]"
82
+ end
83
+
84
+ def to_cache_hash
85
+ hash = to_hash(:source, :url, :name, :description)
86
+ if translation_keys and translation_keys.any?
87
+ hash[:translation_keys] = []
88
+ translation_keys.values.each do |tkey|
89
+ hash[:translation_keys] << tkey.to_cache_hash
90
+ end
91
+ end
92
+ hash
93
+ end
94
+
95
+ end
@@ -0,0 +1,410 @@
1
+ #--
2
+ # Copyright (c) 2013 Michael Berkovich, tr8nhub.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ #######################################################################
25
+ #
26
+ # Data Token Forms:
27
+ #
28
+ # {count}
29
+ # {count:number}
30
+ # {user:gender}
31
+ # {today:date}
32
+ # {user_list:list}
33
+ # {long_token_name}
34
+ # {user1}
35
+ # {user1:user}
36
+ # {user1:user::pos}
37
+ #
38
+ # Data tokens can be associated with any rules through the :dependency
39
+ # notation or using the naming convention of the token suffix, defined
40
+ # in the tr8n configuration file
41
+ #
42
+ #######################################################################
43
+
44
+ class Tr8n::Tokens::Data < Tr8n::Base
45
+ attr_reader :label, :full_name, :short_name, :case_keys, :context_keys
46
+
47
+ def self.expression
48
+ /(\{[^_:][\w]*(:[\w]+)*(::[\w]+)*\})/
49
+ end
50
+
51
+ def self.parse(label, opts = {})
52
+ tokens = []
53
+ label.scan(expression).uniq.each do |token_array|
54
+ tokens << self.new(label, token_array.first)
55
+ end
56
+ tokens
57
+ end
58
+
59
+ def initialize(label, token)
60
+ @label = label
61
+ @full_name = token
62
+ parse_elements
63
+ end
64
+
65
+ def parse_elements
66
+ name_without_parens = self.full_name[1..-2]
67
+ name_without_case_keys = name_without_parens.split('::').first.strip
68
+
69
+ @short_name = name_without_parens.split(':').first.strip
70
+ @case_keys = name_without_parens.scan(/(::\w+)/).flatten.uniq.collect{|c| c.gsub('::', '')}
71
+ @context_keys = name_without_case_keys.scan(/(:\w+)/).flatten.uniq.collect{|c| c.gsub(':', '')}
72
+ end
73
+
74
+ def name(opts = {})
75
+ val = short_name
76
+ val = "#{val}:#{context_keys.join(':')}" if opts[:context_keys] and context_keys.any?
77
+ val = "#{val}::#{case_keys.join('::')}" if opts[:case_keys] and case_keys.any?
78
+ val = "{#{val}}" if opts[:parens]
79
+ val
80
+ end
81
+
82
+ def key
83
+ short_name.to_sym
84
+ end
85
+
86
+ # used by the translator submit dialog
87
+ def name_for_case_keys(keys)
88
+ keys = [keys] unless keys.is_a?(Array)
89
+ "#{name}::#{keys.join('::')}"
90
+ end
91
+
92
+ def sanitize(object, value, options, language)
93
+ value = "#{value.to_s}" unless value.is_a?(String)
94
+
95
+ unless Tr8n.config.block_options[:skip_html_escaping]
96
+ if options[:sanitize_values]
97
+ value = ERB::Util.html_escape(value)
98
+ end
99
+ end
100
+
101
+ if Tr8n.config.application and not Tr8n.config.application.feature_enabled?(:language_cases)
102
+ return value
103
+ end
104
+
105
+ case_keys.each do |key|
106
+ value = apply_case(key, value, object, options, language)
107
+ end
108
+
109
+ value
110
+ end
111
+
112
+ def context_for_language(language, opts = {})
113
+ if context_keys.any?
114
+ ctx = language.context_by_keyword(context_keys.first)
115
+ else
116
+ ctx = language.context_by_token_name(short_name)
117
+ end
118
+
119
+ unless opts[:silent]
120
+ raise Tr8n::Exception.new("Unknown context for a token: #{full_name} in #{language.locale}") unless ctx
121
+ end
122
+
123
+ ctx
124
+ end
125
+
126
+ ##############################################################################
127
+ #
128
+ # chooses the appropriate case for the token value. case is identified with ::
129
+ #
130
+ # examples:
131
+ #
132
+ # tr("Hello {user::nom}", "", :user => current_user)
133
+ # tr("{actor} gave {target::dat} a present", "", :actor => user1, :target => user2)
134
+ # tr("This is {user::pos} toy", "", :user => current_user)
135
+ #
136
+ ##############################################################################
137
+ def apply_case(key, value, object, options, language)
138
+ lcase = language.language_case_by_keyword(key)
139
+ return value unless lcase
140
+ lcase.apply(value, object, options)
141
+ end
142
+
143
+ def decoration?
144
+ false
145
+ end
146
+
147
+ ##############################################################################
148
+ #
149
+ # gets the value based on various evaluation methods
150
+ #
151
+ # examples:
152
+ #
153
+ # tr("Hello {user}", "", {:user => [current_user, current_user.name]}}
154
+ # tr("Hello {user}", "", {:user => [current_user, "{$0} {$1}", "param1"]}}
155
+ # tr("Hello {user}", "", {:user => [current_user, :name]}}
156
+ # tr("Hello {user}", "", {:user => [current_user, :method_name, "param1"]}}
157
+ # tr("Hello {user}", "", {:user => [current_user, lambda{|user| user.name}]}}
158
+ # tr("Hello {user}", "", {:user => [current_user, lambda{|user, param1| user.name}, "param1"]}}
159
+ #
160
+ ##############################################################################
161
+ def evaluate_token_method_array(object, method_array, options, language)
162
+ # if single object in the array return string value of the object
163
+ if method_array.size == 1
164
+ return sanitize(object, object.to_s, options, language)
165
+ end
166
+
167
+ # second params identifies the method to be used with the object
168
+ method = method_array[1]
169
+ params = method_array[2..-1]
170
+ params_with_object = [object] + params
171
+
172
+ # if the second param is a string, substitute all of the numeric params,
173
+ # with the original object and all the following params
174
+ if method.is_a?(String)
175
+ parametrized_value = method.clone
176
+ if parametrized_value.index("{$")
177
+ params_with_object.each_with_index do |val, i|
178
+ parametrized_value.gsub!("{$#{i}}", sanitize(object, val, options.merge(:skip_decorations => true), language))
179
+ end
180
+ end
181
+ return sanitize(object, parametrized_value, options, language)
182
+ end
183
+
184
+ # if second param is symbol, invoke the method on the object with the remaining values
185
+ if method.is_a?(Symbol)
186
+ return sanitize(object, object.send(method, *params), options.merge(:sanitize_values => true), language)
187
+ end
188
+
189
+ # if second param is lambda, call lambda with the remaining values
190
+ if method.is_a?(Proc)
191
+ return sanitize(object, method.call(*params_with_object), options, language)
192
+ end
193
+
194
+ if method.is_a?(Hash)
195
+ value = hash_value(method, :value)
196
+ attr = hash_value(method, :attribute)
197
+
198
+ unless attr.nil?
199
+ if object.is_a?(Hash)
200
+ value = hash_value(object, attr)
201
+ else
202
+ value = object.send(attr)
203
+ end
204
+ end
205
+
206
+ if value.nil?
207
+ return raise Tr8n::Exception.new("Hash object is missing a value or attribute key for a token: #{full_name}")
208
+ end
209
+
210
+ return sanitize(object,value, options, language)
211
+ end
212
+
213
+ raise Tr8n::Exception.new("Invalid array second token value: #{full_name} in #{label}")
214
+ end
215
+
216
+ def self.token_object(token_values, token_name)
217
+ return nil if token_values.nil?
218
+ object = token_values[token_name.to_s] || token_values[token_name.to_sym]
219
+
220
+ # for arrays
221
+ return object.first if object.is_a?(Array)
222
+
223
+ # hash maybe nested {:object => {}, :attribute => ""}
224
+ if object.is_a?(Hash)
225
+ sub_object = object[:object] || object['object']
226
+ return sub_object if sub_object
227
+ end
228
+
229
+ object
230
+ end
231
+
232
+ ##############################################################################
233
+ #
234
+ # tr("Hello {user_list}!", "", {:user_list => [[user1, user2, user3], :name]}}
235
+ #
236
+ # first element is an array, the rest of the elements are similar to the
237
+ # regular tokens lambda, symbol, string, with parameters that follow
238
+ #
239
+ # if you want to pass options, then make the second parameter an array as well
240
+ # tr("{user_list} joined the site", "",
241
+ # {:user_list => [[user1, user2, user3],
242
+ # [:name], # this can be any of the value methods
243
+ # { :expandable => true,
244
+ # :to_sentence => true,
245
+ # :limit => 4,
246
+ # :separator => ',',
247
+ # :andor => 'and',
248
+ # :translate_items => false,
249
+ # :minimizable => true
250
+ # }
251
+ # ]
252
+ # ]})
253
+ #
254
+ # acceptable params: expandable,
255
+ # to_sentence,
256
+ # limit,
257
+ # andor,
258
+ # more_label,
259
+ # less_label,
260
+ # separator,
261
+ # translate_items,
262
+ # minimizable
263
+ #
264
+ ##############################################################################
265
+ def token_array_value(token_value, options, language)
266
+ objects = token_value.first
267
+
268
+ objects = objects.collect do |obj|
269
+ if token_value[1].is_a?(Array)
270
+ evaluate_token_method_array(obj, [obj] + token_value[1], options, language)
271
+ else
272
+ evaluate_token_method_array(obj, token_value, options, language)
273
+ end
274
+ end
275
+
276
+ list_options = {
277
+ :translate_items => false,
278
+ :expandable => true,
279
+ :minimizable => true,
280
+ :to_sentence => true,
281
+ :limit => 4,
282
+ :separator => ", ",
283
+ :andor => 'and'
284
+ }
285
+
286
+ if token_value[1].is_a?(Array) and token_value.size == 3
287
+ list_options.merge!(token_value.last)
288
+ end
289
+
290
+ objects = objects.collect{|obj| obj.translate("List element", {}, options)} if list_options[:translate_items]
291
+
292
+ # if there is only one element in the array, use it and get out
293
+ return objects.first if objects.size == 1
294
+
295
+ list_options[:expandable] = false if options[:skip_decorations]
296
+
297
+ return objects.join(list_options[:separator]) unless list_options[:to_sentence]
298
+
299
+ if objects.size <= list_options[:limit]
300
+ return "#{objects[0..-2].join(list_options[:separator])} #{list_options[:andor].translate("", {}, options)} #{objects.last}"
301
+ end
302
+
303
+ display_ary = objects[0..(list_options[:limit]-1)]
304
+ remaining_ary = objects[list_options[:limit]..-1]
305
+ result = "#{display_ary.join(list_options[:separator])}"
306
+
307
+ unless list_options[:expandable]
308
+ result << " " << list_options[:andor].translate("", {}, options) << " "
309
+ result << "{num|| other}".translate("List elements joiner", {:num => remaining_ary.size}, options)
310
+ return result
311
+ end
312
+
313
+ uniq_id = Tr8n::TranslationKey.generate_key(label, objects.join(","))
314
+ result << "<span id=\"tr8n_other_link_#{uniq_id}\">" << " " << list_options[:andor].translate("", {}, options) << " "
315
+ result << "<a href='#' onClick=\"Tr8n.Utils.Effects.hide('tr8n_other_link_#{uniq_id}'); Tr8n.Utils.Effects.show('tr8n_other_elements_#{uniq_id}'); return false;\">"
316
+ result << (list_options[:more_label] ? list_options[:more_label] : "{num|| other}".translate("List elements joiner", {:num => remaining_ary.size}, options))
317
+ result << "</a></span>"
318
+ result << "<span id=\"tr8n_other_elements_#{uniq_id}\" style='display:none'>" << list_options[:separator]
319
+ result << "#{remaining_ary[0..-2].join(list_options[:separator])} #{list_options[:andor].translate("", {}, options)} #{remaining_ary.last}"
320
+
321
+ if list_options[:minimizable]
322
+ result << "<a href='#' style='font-size:smaller;white-space:nowrap' onClick=\"Tr8n.Utils.Effects.show('tr8n_other_link_#{uniq_id}'); Tr8n.Utils.Effects.hide('tr8n_other_elements_#{uniq_id}'); return false;\"> "
323
+ result << (list_options[:less_label] ? list_options[:less_label] : "{laquo} less".translate("List elements joiner", {}, options))
324
+ result << "</a>"
325
+ end
326
+
327
+ result << "</span>"
328
+ end
329
+
330
+ # evaluate all possible methods for the token value and return sanitized result
331
+ def token_value(object, options, language)
332
+ # token is an array
333
+ if object.is_a?(Array)
334
+ # if you provided an array, it better have some values
335
+ if object.empty?
336
+ return raise Tr8n::Exception.new("Invalid array value for a token: #{full_name}")
337
+ end
338
+
339
+ # if the first value of an array is an array handle it here
340
+ if object.first.kind_of?(Enumerable)
341
+ return token_array_value(object, options, language)
342
+ end
343
+
344
+ # if the first item in the array is an object, process it
345
+ return evaluate_token_method_array(object.first, object, options, language)
346
+ end
347
+
348
+ if object.is_a?(Hash)
349
+ # if object is a hash, it must be of a form: {:object => {}, :value => "", :attribute => ""}
350
+ # either value can be passed, or the attribute. attribute will be used first
351
+ if hash_value(object, :object).nil?
352
+ return raise Tr8n::Exception.new("Hash token is missing an object key for a token: #{full_name}")
353
+ end
354
+
355
+ obj = hash_value(object, :object)
356
+ value = hash_value(object, :value)
357
+ attr = hash_value(object, :attribute)
358
+
359
+ unless attr.nil?
360
+ if obj.is_a?(Hash)
361
+ value = hash_value(obj, attr)
362
+ else
363
+ value = obj.send(attr)
364
+ end
365
+ end
366
+
367
+ if value.nil?
368
+ return raise Tr8n::Exception.new("Hash object is missing a value or attribute key for a token: #{full_name}")
369
+ end
370
+
371
+ return sanitize(obj, value.to_s, options, language)
372
+ end
373
+
374
+ # simple token
375
+ sanitize(object, object.to_s, options, language)
376
+ end
377
+
378
+ def allowed_in_translation?
379
+ true
380
+ end
381
+
382
+ def implied?
383
+ false
384
+ end
385
+
386
+ def substitute(label, context, language, options = {})
387
+ # get the object from the values
388
+ object = hash_value(context, key, :whole => true)
389
+
390
+ # see if the token is a default html token
391
+ object = Tr8n.config.default_token_value(key) if object.nil?
392
+
393
+ #if object.nil?
394
+ # raise Tr8n::Exception.new("Missing value for a token: #{full_name}")
395
+ #end
396
+
397
+ object = object.to_s if object.nil?
398
+
399
+ value = token_value(object, options, language)
400
+ label.gsub(full_name, value)
401
+ end
402
+
403
+ def sanitized_name
404
+ name(:parens => true)
405
+ end
406
+
407
+ def to_s
408
+ full_name
409
+ end
410
+ end
@@ -0,0 +1,82 @@
1
+ #--
2
+ # Copyright (c) 2013 Michael Berkovich, tr8nhub.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ #######################################################################
25
+ #
26
+ # Decoration Token Forms:
27
+ #
28
+ # [link: click here]
29
+ # or
30
+ # [link] click here [/link]
31
+ #
32
+ # Decoration Tokens Allow Nesting:
33
+ #
34
+ # [link: {count} {_messages}]
35
+ # [link: {count||message}]
36
+ # [link: {count||person, people}]
37
+ # [link: {user.name}]
38
+ #
39
+ #######################################################################
40
+ module Tr8n
41
+ module Tokens
42
+ class DataTokenizer
43
+
44
+ attr_accessor :text, :context, :tokens, :opts
45
+
46
+ def self.supported_tokens
47
+ [Tr8n::Tokens::Data, Tr8n::Tokens::Hidden, Tr8n::Tokens::Method, Tr8n::Tokens::Transform]
48
+ end
49
+
50
+ def initialize(text, context={}, opts={})
51
+ self.text = text
52
+ self.context = context
53
+ self.opts = opts
54
+ self.tokens = []
55
+ tokenize
56
+ end
57
+
58
+ def tokenize
59
+ self.tokens = []
60
+ self.class.supported_tokens.each do |klass|
61
+ self.tokens << klass.parse(self.text)
62
+ end
63
+ self.tokens.flatten!.uniq!
64
+ end
65
+
66
+ def token_allowed?(token)
67
+ return true unless opts[:allowed_tokens]
68
+ not opts[:allowed_tokens][token.name].nil?
69
+ end
70
+
71
+ def substitute(language, options = {})
72
+ label = self.text
73
+ tokens.each do |token|
74
+ next unless token_allowed?(token)
75
+ label = token.substitute(label, context, language, options)
76
+ end
77
+ label
78
+ end
79
+
80
+ end
81
+ end
82
+ end