tr8n_core 4.0.1

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