tml 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +243 -0
  4. data/Rakefile +9 -0
  5. data/lib/tml.rb +56 -0
  6. data/lib/tml/api/client.rb +206 -0
  7. data/lib/tml/api/post_office.rb +71 -0
  8. data/lib/tml/application.rb +254 -0
  9. data/lib/tml/base.rb +116 -0
  10. data/lib/tml/cache.rb +143 -0
  11. data/lib/tml/cache_adapters/file.rb +89 -0
  12. data/lib/tml/cache_adapters/memcache.rb +104 -0
  13. data/lib/tml/cache_adapters/memory.rb +85 -0
  14. data/lib/tml/cache_adapters/redis.rb +108 -0
  15. data/lib/tml/config.rb +410 -0
  16. data/lib/tml/decorators/base.rb +52 -0
  17. data/lib/tml/decorators/default.rb +43 -0
  18. data/lib/tml/decorators/html.rb +102 -0
  19. data/lib/tml/exception.rb +35 -0
  20. data/lib/tml/ext/array.rb +86 -0
  21. data/lib/tml/ext/date.rb +99 -0
  22. data/lib/tml/ext/fixnum.rb +47 -0
  23. data/lib/tml/ext/hash.rb +99 -0
  24. data/lib/tml/ext/string.rb +56 -0
  25. data/lib/tml/ext/time.rb +89 -0
  26. data/lib/tml/generators/cache/base.rb +117 -0
  27. data/lib/tml/generators/cache/file.rb +159 -0
  28. data/lib/tml/language.rb +175 -0
  29. data/lib/tml/language_case.rb +105 -0
  30. data/lib/tml/language_case_rule.rb +76 -0
  31. data/lib/tml/language_context.rb +117 -0
  32. data/lib/tml/language_context_rule.rb +56 -0
  33. data/lib/tml/languages/en.json +1363 -0
  34. data/lib/tml/logger.rb +109 -0
  35. data/lib/tml/rules_engine/evaluator.rb +162 -0
  36. data/lib/tml/rules_engine/parser.rb +65 -0
  37. data/lib/tml/session.rb +199 -0
  38. data/lib/tml/source.rb +106 -0
  39. data/lib/tml/tokenizers/data.rb +96 -0
  40. data/lib/tml/tokenizers/decoration.rb +204 -0
  41. data/lib/tml/tokenizers/dom.rb +346 -0
  42. data/lib/tml/tokens/data.rb +403 -0
  43. data/lib/tml/tokens/method.rb +61 -0
  44. data/lib/tml/tokens/transform.rb +223 -0
  45. data/lib/tml/translation.rb +67 -0
  46. data/lib/tml/translation_key.rb +178 -0
  47. data/lib/tml/translator.rb +47 -0
  48. data/lib/tml/utils.rb +130 -0
  49. data/lib/tml/version.rb +34 -0
  50. metadata +121 -0
@@ -0,0 +1,403 @@
1
+ # encoding: UTF-8
2
+ #--
3
+ # Copyright (c) 2015 Translation Exchange, Inc
4
+ #
5
+ # _______ _ _ _ ______ _
6
+ # |__ __| | | | | (_) | ____| | |
7
+ # | |_ __ __ _ _ __ ___| | __ _| |_ _ ___ _ __ | |__ __ _____| |__ __ _ _ __ __ _ ___
8
+ # | | '__/ _` | '_ \/ __| |/ _` | __| |/ _ \| '_ \| __| \ \/ / __| '_ \ / _` | '_ \ / _` |/ _ \
9
+ # | | | | (_| | | | \__ \ | (_| | |_| | (_) | | | | |____ > < (__| | | | (_| | | | | (_| | __/
10
+ # |_|_| \__,_|_| |_|___/_|\__,_|\__|_|\___/|_| |_|______/_/\_\___|_| |_|\__,_|_| |_|\__, |\___|
11
+ # __/ |
12
+ # |___/
13
+ # Permission is hereby granted, free of charge, to any person obtaining
14
+ # a copy of this software and associated documentation files (the
15
+ # "Software"), to deal in the Software without restriction, including
16
+ # without limitation the rights to use, copy, modify, merge, publish,
17
+ # distribute, sublicense, and/or sell copies of the Software, and to
18
+ # permit persons to whom the Software is furnished to do so, subject to
19
+ # the following conditions:
20
+ #
21
+ # The above copyright notice and this permission notice shall be
22
+ # included in all copies or substantial portions of the Software.
23
+ #
24
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
+ #++
32
+
33
+ module Tml
34
+ module Tokens
35
+ class Data
36
+
37
+ attr_reader :label, :full_name, :short_name, :case_keys, :context_keys
38
+
39
+ def self.expression
40
+ /(\{[^_:][\w]*(:[\w]+)*(::[\w]+)*\})/
41
+ end
42
+
43
+ def self.parse(label, opts = {})
44
+ tokens = []
45
+ label.scan(expression).uniq.each do |token_array|
46
+ tokens << self.new(label, token_array.first)
47
+ end
48
+ tokens
49
+ end
50
+
51
+ def initialize(label, token)
52
+ @label = label
53
+ @full_name = token
54
+ parse_elements
55
+ end
56
+
57
+ def parse_elements
58
+ name_without_parens = @full_name[1..-2]
59
+ name_without_case_keys = name_without_parens.split('::').first.strip
60
+
61
+ @short_name = name_without_parens.split(':').first.strip
62
+ @case_keys = name_without_parens.scan(/(::\w+)/).flatten.uniq.collect{|c| c.gsub('::', '')}
63
+ @context_keys = name_without_case_keys.scan(/(:\w+)/).flatten.uniq.collect{|c| c.gsub(':', '')}
64
+ end
65
+
66
+ def name(opts = {})
67
+ val = short_name
68
+ val = "#{val}:#{context_keys.join(':')}" if opts[:context_keys] and context_keys.any?
69
+ val = "#{val}::#{case_keys.join('::')}" if opts[:case_keys] and case_keys.any?
70
+ val = "{#{val}}" if opts[:parens]
71
+ val
72
+ end
73
+
74
+ def key
75
+ short_name.to_sym
76
+ end
77
+
78
+ # used by the translator submit dialog
79
+ def name_for_case_keys(keys)
80
+ keys = [keys] unless keys.is_a?(Array)
81
+ "#{name}::#{keys.join('::')}"
82
+ end
83
+
84
+ def context_for_language(language)
85
+ if context_keys.any?
86
+ language.context_by_keyword(context_keys.first)
87
+ else
88
+ language.context_by_token_name(short_name)
89
+ end
90
+ end
91
+
92
+ # Utility method for errors
93
+ def error(msg, return_token = true)
94
+ Tml.logger.error(msg)
95
+ return_token ? full_name : label
96
+ end
97
+
98
+ ##############################################################################
99
+ #
100
+ # returns token object from tokens param
101
+ #
102
+ ##############################################################################
103
+
104
+ def self.token_object(token_values, token_name)
105
+ return nil if token_values.nil?
106
+ token_object = Tml::Utils.hash_value(token_values, token_name)
107
+ return token_object.first if token_object.is_a?(Array)
108
+ if token_object.is_a?(Hash)
109
+ object = Tml::Utils.hash_value(token_object, :object)
110
+ return object if object
111
+ end
112
+ token_object
113
+ end
114
+
115
+ ##############################################################################
116
+ #
117
+ # tr("Hello {user_list}!", "", {:user_list => [[user1, user2, user3], :name]}}
118
+ #
119
+ # first element is an array, the rest of the elements are similar to the
120
+ # regular tokens lambda, symbol, string, with parameters that follow
121
+ #
122
+ # if you want to pass options, then make the second parameter an array as well
123
+ #
124
+ # tr("{users} joined the site", {:users => [[user1, user2, user3], :name]})
125
+ #
126
+ # tr("{users} joined the site", {:users => [[user1, user2, user3], lambda{|user| user.name}]})
127
+ #
128
+ # tr("{users} joined the site", {:users => [[user1, user2, user3], {:attribute => :name})
129
+ #
130
+ # tr("{users} joined the site", {:users => [[user1, user2, user3], {:attribute => :name, :value => "<strong>{$0}</strong>"})
131
+ #
132
+ # tr("{users} joined the site", {:users => [[user1, user2, user3], "<strong>{$0}</strong>")
133
+ #
134
+ # tr("{users} joined the site", {:users => [[user1, user2, user3], :name, {
135
+ # :limit => 4,
136
+ # :separator => ', ',
137
+ # :joiner => 'and',
138
+ # :remainder => lambda{|elements| tr("#{count||other}", :count => elements.size)},
139
+ # :expandable => true,
140
+ # :collapsable => true
141
+ # })
142
+ #
143
+ #
144
+ ##############################################################################
145
+ def token_values_from_array(params, language, options)
146
+ list_options = {
147
+ :description => "List joiner",
148
+ :limit => 4,
149
+ :separator => ", ",
150
+ :joiner => 'and',
151
+ :less => '{laquo} less',
152
+ :expandable => true,
153
+ :collapsable => true
154
+ }
155
+
156
+ objects = params[0]
157
+ method = params[1]
158
+ list_options.merge!(params[2]) if params.size > 2
159
+ list_options[:expandable] = false if options[:skip_decorations]
160
+
161
+ values = objects.collect do |obj|
162
+ if method.is_a?(String)
163
+ method.gsub("{$0}", sanitize(obj.to_s, obj, language, options.merge(:safe => false)))
164
+ elsif method.is_a?(Symbol)
165
+ if obj.is_a?(Hash)
166
+ value = Tml::Utils.hash_value(obj, method)
167
+ else
168
+ value = obj.send(method)
169
+ end
170
+ sanitize(value, obj, language, options.merge(:safe => false))
171
+ elsif method.is_a?(Hash)
172
+ attr = Tml::Utils.hash_value(method, :attribute) || Tml::Utils.hash_value(method, :property)
173
+ if obj.is_a?(Hash)
174
+ value = Tml::Utils.hash_value(obj, attr)
175
+ else
176
+ value = obj.send(method)
177
+ end
178
+
179
+ hash_value = Tml::Utils.hash_value(method, :value)
180
+ if hash_value
181
+ hash_value.gsub("{$0}", sanitize(value, obj, language, options.merge(:safe => false)))
182
+ else
183
+ sanitize(value, obj, language, options.merge(:safe => false))
184
+ end
185
+ elsif method.is_a?(Proc)
186
+ sanitize(method.call(obj), obj, language, options.merge(:safe => true))
187
+ end
188
+ end
189
+
190
+ return values.first if objects.size == 1
191
+ return values.join(list_options[:separator]) if list_options[:joiner].nil? || list_options[:joiner] == ""
192
+
193
+ joiner = language.translate(list_options[:joiner], list_options[:description], {}, options)
194
+ if values.size <= list_options[:limit]
195
+ return "#{values[0..-2].join(list_options[:separator])} #{joiner} #{values.last}"
196
+ end
197
+
198
+ display_ary = values[0..(list_options[:limit]-1)]
199
+ remaining_ary = values[list_options[:limit]..-1]
200
+ result = "#{display_ary.join(list_options[:separator])}"
201
+
202
+ unless list_options[:expandable]
203
+ result << " " << joiner << " "
204
+ if list_options[:remainder] and list_options[:remainder].is_a?(Proc)
205
+ result << list_options[:remainder].call(remaining_ary)
206
+ else
207
+ result << language.translate("{count||other}", list_options[:description], {:count => remaining_ary.size}, options)
208
+ end
209
+ return result
210
+ end
211
+
212
+ uniq_id = Tml::TranslationKey.generate_key(label, values.join(","))
213
+ result << "<span id=\"tml_other_link_#{uniq_id}\"> #{joiner} "
214
+
215
+ result << "<a href='#' onClick=\"Tml.Utils.Effects.hide('tml_other_link_#{uniq_id}'); Tml.Utils.Effects.show('tml_other_elements_#{uniq_id}'); return false;\">"
216
+ if list_options[:remainder] and list_options[:remainder].is_a?(Proc)
217
+ result << list_options[:remainder].call(remaining_ary)
218
+ else
219
+ result << language.translate("{count||other}", list_options[:description], {:count => remaining_ary.size}, options)
220
+ end
221
+ result << "</a></span>"
222
+
223
+ result << "<span id=\"tml_other_elements_#{uniq_id}\" style='display:none'>"
224
+ result << list_options[:separator] << " "
225
+ result << remaining_ary[0..-2].join(list_options[:separator])
226
+ result << " #{joiner} "
227
+ result << remaining_ary.last
228
+
229
+ if list_options[:collapsable]
230
+ result << "<a href='#' style='font-size:smaller;white-space:nowrap' onClick=\"Tml.Utils.Effects.show('tml_other_link_#{uniq_id}'); Tml.Utils.Effects.hide('tml_other_elements_#{uniq_id}'); return false;\"> "
231
+ result << language.translate(list_options[:less], list_options[:description], {}, options)
232
+ result << "</a>"
233
+ end
234
+
235
+ result << "</span>"
236
+ end
237
+
238
+ ##############################################################################
239
+ #
240
+ # gets the value based on various evaluation methods
241
+ #
242
+ # examples:
243
+ #
244
+ # tr("Hello {user}", {:user => [current_user, current_user.name]}}
245
+ # tr("Hello {user}", {:user => [current_user, :name]}}
246
+ #
247
+ # tr("Hello {user}", {:user => [{:name => "Michael", :gender => :male}, current_user.name]}}
248
+ # tr("Hello {user}", {:user => [{:name => "Michael", :gender => :male}, :name]}}
249
+ #
250
+ ##############################################################################
251
+
252
+ def token_value_from_array_param(array, language, options)
253
+ # if you provided an array, it better have some values
254
+ if array.size < 2
255
+ return error("Invalid value for array token #{full_name} in #{label}")
256
+ end
257
+
258
+ # if the first value of an array is an array handle it here
259
+ if array[0].is_a?(Array)
260
+ return token_values_from_array(array, language, options)
261
+ end
262
+
263
+ if array[1].is_a?(String)
264
+ return sanitize(array[1], array[0], language, options.merge(:safe => true))
265
+ end
266
+
267
+ if array[0].is_a?(Hash)
268
+ if array[1].is_a?(Symbol)
269
+ return sanitize(Tml::Utils.hash_value(array[0], array[1]), array[0], language, options.merge(:safe => false))
270
+ end
271
+
272
+ return error("Invalid value for array token #{full_name} in #{label}")
273
+ end
274
+
275
+ # if second param is symbol, invoke the method on the object with the remaining values
276
+ if array[1].is_a?(Symbol)
277
+ return sanitize(array[0].send(array[1]), array[0], language, options.merge(:safe => false))
278
+ end
279
+
280
+ error("Invalid value for array token #{full_name} in #{label}")
281
+ end
282
+
283
+ ##############################################################################
284
+ #
285
+ # examples:
286
+ #
287
+ # tr("Hello {user}", {:user => {:value => "Michael", :gender => :male}}}
288
+ #
289
+ # tr("Hello {user}", {:user => {:object => {:gender => :male}, :value => "Michael"}}}
290
+ # tr("Hello {user}", {:user => {:object => {:name => "Michael", :gender => :male}, :property => :name}}}
291
+ # tr("Hello {user}", {:user => {:object => {:name => "Michael", :gender => :male}, :attribute => :name}}}
292
+ #
293
+ # tr("Hello {user}", {:user => {:object => user, :value => "Michael"}}}
294
+ # tr("Hello {user}", {:user => {:object => user, :property => :name}}}
295
+ # tr("Hello {user}", {:user => {:object => user, :attribute => :name}}}
296
+ #
297
+ ##############################################################################
298
+
299
+ def token_value_from_hash_param(hash, language, options)
300
+ value = Tml::Utils.hash_value(hash, :value)
301
+ object = Tml::Utils.hash_value(hash, :object)
302
+
303
+ unless value.nil?
304
+ return sanitize(value, object || hash, language, options.merge(:safe => true))
305
+ end
306
+
307
+ if object.nil?
308
+ return error("Missing value for hash token #{full_name} in #{label}")
309
+ end
310
+
311
+ attr = Tml::Utils.hash_value(hash, :attribute) || Tml::Utils.hash_value(hash, :property)
312
+
313
+ if object.is_a?(Hash)
314
+ unless attr.nil?
315
+ return sanitize(Tml::Utils.hash_value(object, attr), object, language, options.merge(:safe => false))
316
+ end
317
+
318
+ return error("Missing value for hash token #{full_name} in #{label}")
319
+ end
320
+
321
+ sanitize(object.send(attr), object, language, options.merge(:safe => false))
322
+ end
323
+
324
+ # evaluate all possible methods for the token value and return sanitized result
325
+ def token_value(object, language, options = {})
326
+ return token_value_from_array_param(object, language, options) if object.is_a?(Array)
327
+ return token_value_from_hash_param(object, language, options) if object.is_a?(Hash)
328
+ sanitize(object, object, language, options)
329
+ end
330
+
331
+ def sanitize(value, object, language, options)
332
+ value = value.to_s
333
+
334
+ unless Tml.session.block_options[:skip_html_escaping]
335
+ if options[:safe] == false
336
+ value = ERB::Util.html_escape(value)
337
+ end
338
+ end
339
+
340
+ return value unless language_cases_enabled?
341
+ apply_language_cases(value, object, language, options)
342
+ end
343
+
344
+ def language_cases_enabled?
345
+ Tml.session.application and Tml.session.application.feature_enabled?(:language_cases)
346
+ end
347
+
348
+ ##############################################################################
349
+ #
350
+ # chooses the appropriate case for the token value. case is identified with ::
351
+ #
352
+ # examples:
353
+ #
354
+ # tr("Hello {user::nom}", "", :user => current_user)
355
+ # tr("{actor} gave {target::dat} a present", "", :actor => user1, :target => user2)
356
+ # tr("This is {user::pos} toy", "", :user => current_user)
357
+ #
358
+ ##############################################################################
359
+ def apply_case(key, value, object, language, options)
360
+ lcase = language.case_by_keyword(key)
361
+ return value unless lcase
362
+ lcase.apply(value, object, options)
363
+ end
364
+
365
+ def apply_language_cases(value, object, language, options)
366
+ case_keys.each do |key|
367
+ value = apply_case(key, value, object, language, options)
368
+ end
369
+
370
+ value
371
+ end
372
+
373
+ def substitute(label, context, language, options = {})
374
+ # get the object from the values
375
+ object = Tml::Utils.hash_value(context, key)
376
+
377
+ # see if the token is a default html token
378
+ object = Tml.config.default_token_value(key) if object.nil?
379
+
380
+ if object.nil? and not context.key?(key)
381
+ return error("Missing value for #{full_name} in #{label}", false)
382
+ end
383
+
384
+ if object.nil? and not Tml::Config.allow_nil_token_values?
385
+ return error("Token value is nil for #{full_name} in #{label}", false)
386
+ end
387
+
388
+ return label.gsub(full_name, "") if object.nil?
389
+
390
+ value = token_value(object, language, options)
391
+ label.gsub(full_name, value)
392
+ end
393
+
394
+ def sanitized_name
395
+ name(:parens => true)
396
+ end
397
+
398
+ def to_s
399
+ full_name
400
+ end
401
+ end
402
+ end
403
+ end
@@ -0,0 +1,61 @@
1
+ # encoding: UTF-8
2
+ #--
3
+ # Copyright (c) 2015 Translation Exchange, Inc
4
+ #
5
+ # _______ _ _ _ ______ _
6
+ # |__ __| | | | | (_) | ____| | |
7
+ # | |_ __ __ _ _ __ ___| | __ _| |_ _ ___ _ __ | |__ __ _____| |__ __ _ _ __ __ _ ___
8
+ # | | '__/ _` | '_ \/ __| |/ _` | __| |/ _ \| '_ \| __| \ \/ / __| '_ \ / _` | '_ \ / _` |/ _ \
9
+ # | | | | (_| | | | \__ \ | (_| | |_| | (_) | | | | |____ > < (__| | | | (_| | | | | (_| | __/
10
+ # |_|_| \__,_|_| |_|___/_|\__,_|\__|_|\___/|_| |_|______/_/\_\___|_| |_|\__,_|_| |_|\__, |\___|
11
+ # __/ |
12
+ # |___/
13
+ # Permission is hereby granted, free of charge, to any person obtaining
14
+ # a copy of this software and associated documentation files (the
15
+ # "Software"), to deal in the Software without restriction, including
16
+ # without limitation the rights to use, copy, modify, merge, publish,
17
+ # distribute, sublicense, and/or sell copies of the Software, and to
18
+ # permit persons to whom the Software is furnished to do so, subject to
19
+ # the following conditions:
20
+ #
21
+ # The above copyright notice and this permission notice shall be
22
+ # included in all copies or substantial portions of the Software.
23
+ #
24
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
+ #++
32
+
33
+ #######################################################################
34
+ #
35
+ # Method Token Forms
36
+ #
37
+ # {user.name}
38
+ # {user.name:gender}
39
+ #
40
+ #######################################################################
41
+
42
+ class Tml::Tokens::Method < Tml::Tokens::Data
43
+ def self.expression
44
+ /(\{[^_:.][\w]*(\.[\w]+)(:[\w]+)*(::[\w]+)*\})/
45
+ end
46
+
47
+ def object_name
48
+ @object_name ||= short_name.split(".").first
49
+ end
50
+
51
+ def object_method_name
52
+ @object_method_name ||= short_name.split(".").last
53
+ end
54
+
55
+ def substitute(label, context, language, options = {})
56
+ object = Tml::Utils.hash_value(context, object_name)
57
+ raise Tml::Exception.new("Missing value for a token: #{full_name}") unless object
58
+ object_value = sanitize(object.send(object_method_name), object, language, options.merge(:safe => false))
59
+ label.gsub(full_name, object_value)
60
+ end
61
+ end