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.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +69 -0
- data/Rakefile +9 -0
- data/config/config.yml +34 -0
- data/config/tokens/data.yml +45 -0
- data/config/tokens/decorations.yml +37 -0
- data/lib/tr8n/application.rb +320 -0
- data/lib/tr8n/base.rb +123 -0
- data/lib/tr8n/cache.rb +144 -0
- data/lib/tr8n/cache_adapters/cdb.rb +74 -0
- data/lib/tr8n/cache_adapters/file.rb +70 -0
- data/lib/tr8n/cache_adapters/memcache.rb +91 -0
- data/lib/tr8n/cache_adapters/redis.rb +94 -0
- data/lib/tr8n/component.rb +68 -0
- data/lib/tr8n/config.rb +291 -0
- data/lib/tr8n/decorators/base.rb +35 -0
- data/lib/tr8n/decorators/default.rb +30 -0
- data/lib/tr8n/decorators/html.rb +63 -0
- data/lib/tr8n/exception.rb +26 -0
- data/lib/tr8n/language.rb +250 -0
- data/lib/tr8n/language_case.rb +116 -0
- data/lib/tr8n/language_case_rule.rb +85 -0
- data/lib/tr8n/language_context.rb +115 -0
- data/lib/tr8n/language_context_rule.rb +62 -0
- data/lib/tr8n/logger.rb +83 -0
- data/lib/tr8n/rules_engine/evaluator.rb +156 -0
- data/lib/tr8n/rules_engine/parser.rb +83 -0
- data/lib/tr8n/source.rb +95 -0
- data/lib/tr8n/tokens/data.rb +410 -0
- data/lib/tr8n/tokens/data_tokenizer.rb +82 -0
- data/lib/tr8n/tokens/decoration_tokenizer.rb +200 -0
- data/lib/tr8n/tokens/hidden.rb +48 -0
- data/lib/tr8n/tokens/method.rb +52 -0
- data/lib/tr8n/tokens/transform.rb +191 -0
- data/lib/tr8n/translation.rb +104 -0
- data/lib/tr8n/translation_key.rb +205 -0
- data/lib/tr8n/translator.rb +62 -0
- data/lib/tr8n/utils.rb +124 -0
- data/lib/tr8n_core/ext/array.rb +74 -0
- data/lib/tr8n_core/ext/date.rb +63 -0
- data/lib/tr8n_core/ext/fixnum.rb +39 -0
- data/lib/tr8n_core/ext/hash.rb +126 -0
- data/lib/tr8n_core/ext/string.rb +44 -0
- data/lib/tr8n_core/ext/time.rb +71 -0
- data/lib/tr8n_core/generators/cache/base.rb +85 -0
- data/lib/tr8n_core/generators/cache/cdb.rb +27 -0
- data/lib/tr8n_core/generators/cache/file.rb +69 -0
- data/lib/tr8n_core/modules/logger.rb +43 -0
- data/lib/tr8n_core/version.rb +27 -0
- data/lib/tr8n_core.rb +68 -0
- data/spec/application_spec.rb +228 -0
- data/spec/base_spec.rb +19 -0
- data/spec/config_spec.rb +16 -0
- data/spec/decorator_spec.rb +10 -0
- data/spec/decorators/base_spec.rb +14 -0
- data/spec/decorators/default_spec.rb +12 -0
- data/spec/decorators/html_spec.rb +50 -0
- data/spec/fixtures/application.json +112 -0
- data/spec/fixtures/languages/en-US.json +1424 -0
- data/spec/fixtures/languages/es.json +291 -0
- data/spec/fixtures/languages/ru.json +550 -0
- data/spec/fixtures/translations/ru/basic.json +56 -0
- data/spec/fixtures/translations/ru/counters.json +43 -0
- data/spec/fixtures/translations/ru/genders.json +171 -0
- data/spec/fixtures/translations/ru/last_names.txt +200 -0
- data/spec/fixtures/translations/ru/names.json +1 -0
- data/spec/fixtures/translations/ru/names.txt +458 -0
- data/spec/helper.rb +84 -0
- data/spec/language_case_rule_spec.rb +57 -0
- data/spec/language_case_spec.rb +58 -0
- data/spec/language_context_rule_spec.rb +73 -0
- data/spec/language_context_spec.rb +331 -0
- data/spec/language_spec.rb +16 -0
- data/spec/rules_engine/evaluator_spec.rb +148 -0
- data/spec/rules_engine/parser_spec.rb +29 -0
- data/spec/tokens/data_spec.rb +160 -0
- data/spec/tokens/data_tokenizer_spec.rb +29 -0
- data/spec/tokens/decoration_tokenizer_spec.rb +81 -0
- data/spec/tokens/hidden_spec.rb +24 -0
- data/spec/tokens/method_spec.rb +84 -0
- data/spec/tokens/transform_spec.rb +50 -0
- data/spec/translation_key_spec.rb +96 -0
- data/spec/translation_spec.rb +24 -0
- data/spec/utils_spec.rb +64 -0
- 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
|
data/lib/tr8n/source.rb
ADDED
@@ -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
|