tml 4.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +243 -0
- data/Rakefile +9 -0
- data/lib/tml.rb +56 -0
- data/lib/tml/api/client.rb +206 -0
- data/lib/tml/api/post_office.rb +71 -0
- data/lib/tml/application.rb +254 -0
- data/lib/tml/base.rb +116 -0
- data/lib/tml/cache.rb +143 -0
- data/lib/tml/cache_adapters/file.rb +89 -0
- data/lib/tml/cache_adapters/memcache.rb +104 -0
- data/lib/tml/cache_adapters/memory.rb +85 -0
- data/lib/tml/cache_adapters/redis.rb +108 -0
- data/lib/tml/config.rb +410 -0
- data/lib/tml/decorators/base.rb +52 -0
- data/lib/tml/decorators/default.rb +43 -0
- data/lib/tml/decorators/html.rb +102 -0
- data/lib/tml/exception.rb +35 -0
- data/lib/tml/ext/array.rb +86 -0
- data/lib/tml/ext/date.rb +99 -0
- data/lib/tml/ext/fixnum.rb +47 -0
- data/lib/tml/ext/hash.rb +99 -0
- data/lib/tml/ext/string.rb +56 -0
- data/lib/tml/ext/time.rb +89 -0
- data/lib/tml/generators/cache/base.rb +117 -0
- data/lib/tml/generators/cache/file.rb +159 -0
- data/lib/tml/language.rb +175 -0
- data/lib/tml/language_case.rb +105 -0
- data/lib/tml/language_case_rule.rb +76 -0
- data/lib/tml/language_context.rb +117 -0
- data/lib/tml/language_context_rule.rb +56 -0
- data/lib/tml/languages/en.json +1363 -0
- data/lib/tml/logger.rb +109 -0
- data/lib/tml/rules_engine/evaluator.rb +162 -0
- data/lib/tml/rules_engine/parser.rb +65 -0
- data/lib/tml/session.rb +199 -0
- data/lib/tml/source.rb +106 -0
- data/lib/tml/tokenizers/data.rb +96 -0
- data/lib/tml/tokenizers/decoration.rb +204 -0
- data/lib/tml/tokenizers/dom.rb +346 -0
- data/lib/tml/tokens/data.rb +403 -0
- data/lib/tml/tokens/method.rb +61 -0
- data/lib/tml/tokens/transform.rb +223 -0
- data/lib/tml/translation.rb +67 -0
- data/lib/tml/translation_key.rb +178 -0
- data/lib/tml/translator.rb +47 -0
- data/lib/tml/utils.rb +130 -0
- data/lib/tml/version.rb +34 -0
- metadata +121 -0
@@ -0,0 +1,223 @@
|
|
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
|
+
# Transform Token Form
|
36
|
+
#
|
37
|
+
# {count:number || one: message, many: messages}
|
38
|
+
# {count:number || one: сообщение, few: сообщения, many: сообщений, other: много сообщений} in other case the number is not displayed#
|
39
|
+
#
|
40
|
+
# {count | message} - will not include {count}, resulting in "messages" with implied {count}
|
41
|
+
# {count | message, messages}
|
42
|
+
#
|
43
|
+
# {count:number | message, messages}
|
44
|
+
#
|
45
|
+
# {user:gender | he, she, he/she}
|
46
|
+
#
|
47
|
+
# {user:gender | male: he, female: she, other: he/she}
|
48
|
+
#
|
49
|
+
# {now:date | did, does, will do}
|
50
|
+
# {users:list | all male, all female, mixed genders}
|
51
|
+
#
|
52
|
+
# {count || message, messages} - will include count: "5 messages"
|
53
|
+
#
|
54
|
+
#######################################################################
|
55
|
+
|
56
|
+
class Tml::Tokens::Transform < Tml::Tokens::Data
|
57
|
+
attr_reader :pipe_separator, :piped_params
|
58
|
+
|
59
|
+
def self.expression
|
60
|
+
/(\{[^_:|][\w]*(:[\w]+)*(::[\w]+)*\s*\|\|?[^{^}]+\})/
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_elements
|
64
|
+
name_without_parens = @full_name[1..-2]
|
65
|
+
name_without_pipes = name_without_parens.split('|').first.strip
|
66
|
+
name_without_case_keys = name_without_pipes.split('::').first.strip
|
67
|
+
|
68
|
+
@short_name = name_without_pipes.split(':').first.strip
|
69
|
+
@case_keys = name_without_pipes.scan(/(::\w+)/).flatten.uniq.collect{|c| c.gsub('::', '')}
|
70
|
+
@context_keys = name_without_case_keys.scan(/(:\w+)/).flatten.uniq.collect{|c| c.gsub(':', '')}
|
71
|
+
|
72
|
+
@pipe_separator = (full_name.index("||") ? "||" : "|")
|
73
|
+
@piped_params = name_without_parens.split(pipe_separator).last.split(",").collect{|param| param.strip}
|
74
|
+
end
|
75
|
+
|
76
|
+
def displayed_in_translation?
|
77
|
+
pipe_separator == "||"
|
78
|
+
end
|
79
|
+
|
80
|
+
def implied?
|
81
|
+
not displayed_in_translation?
|
82
|
+
end
|
83
|
+
|
84
|
+
def prepare_label_for_suggestion(label, index, language)
|
85
|
+
context = context_for_language(language)
|
86
|
+
values = generate_value_map(piped_params, context)
|
87
|
+
|
88
|
+
label.gsub(full_name, values[context.default_rule] || values.values.first)
|
89
|
+
end
|
90
|
+
|
91
|
+
# token: {count|| one: message, many: messages}
|
92
|
+
# results in: {"one": "message", "many": "messages"}
|
93
|
+
#
|
94
|
+
# token: {count|| message}
|
95
|
+
# transform: [{"one": "{$0}", "other": "{$0::plural}"}, {"one": "{$0}", "other": "{$1}"}]
|
96
|
+
# results in: {"one": "message", "other": "messages"}
|
97
|
+
#
|
98
|
+
# token: {count|| message, messages}
|
99
|
+
# transform: [{"one": "{$0}", "other": "{$0::plural}"}, {"one": "{$0}", "other": "{$1}"}]
|
100
|
+
# results in: {"one": "message", "other": "messages"}
|
101
|
+
#
|
102
|
+
# token: {user| Dorogoi, Dorogaya}
|
103
|
+
# transform: ["unsupported", {"male": "{$0}", "female": "{$1}", "other": "{$0}/{$1}"}]
|
104
|
+
# results in: {"male": "Dorogoi", "female": "Dorogaya", "other": "Dorogoi/Dorogaya"}
|
105
|
+
#
|
106
|
+
# token: {actors:|| likes, like}
|
107
|
+
# transform: ["unsupported", {"one": "{$0}", "other": "{$1}"}]
|
108
|
+
# results in: {"one": "likes", "other": "like"}
|
109
|
+
def generate_value_map(params, context)
|
110
|
+
values = {}
|
111
|
+
|
112
|
+
if params.first.index(':')
|
113
|
+
params.each do |p|
|
114
|
+
nv = p.split(':')
|
115
|
+
values[nv.first.strip] = nv.last.strip
|
116
|
+
end
|
117
|
+
return values
|
118
|
+
end
|
119
|
+
|
120
|
+
unless context.token_mapping
|
121
|
+
error("The token context #{context.keyword} does not support transformation for unnamed params: #{full_name}")
|
122
|
+
return nil
|
123
|
+
end
|
124
|
+
|
125
|
+
token_mapping = context.token_mapping
|
126
|
+
|
127
|
+
# "unsupported"
|
128
|
+
if token_mapping.is_a?(String)
|
129
|
+
error("The token mapping #{token_mapping} does not support #{params.size} params: #{full_name}")
|
130
|
+
return nil
|
131
|
+
end
|
132
|
+
|
133
|
+
# ["unsupported", "unsupported", {}]
|
134
|
+
if token_mapping.is_a?(Array)
|
135
|
+
if params.size > token_mapping.size
|
136
|
+
error("The token mapping #{token_mapping} does not support #{params.size} params: #{full_name}")
|
137
|
+
return nil
|
138
|
+
end
|
139
|
+
token_mapping = token_mapping[params.size-1]
|
140
|
+
if token_mapping.is_a?(String)
|
141
|
+
error("The token mapping #{token_mapping} does not support #{params.size} params: #{full_name}")
|
142
|
+
return nil
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# {}
|
147
|
+
token_mapping.each do |key, value|
|
148
|
+
values[key] = value
|
149
|
+
value.scan(/({\$\d(::\w+)*})/).each do |matches|
|
150
|
+
token = matches.first
|
151
|
+
parts = token[1..-2].split('::')
|
152
|
+
index = parts.first.gsub('$', '').to_i
|
153
|
+
|
154
|
+
if params.size < index
|
155
|
+
error("The index inside #{context.token_mapping} is out of bound: #{full_name}")
|
156
|
+
return nil
|
157
|
+
end
|
158
|
+
|
159
|
+
# apply settings cases
|
160
|
+
value = params[index]
|
161
|
+
if language_cases_enabled?
|
162
|
+
parts[1..-1].each do |case_key|
|
163
|
+
lcase = context.language.case_by_keyword(case_key)
|
164
|
+
unless lcase
|
165
|
+
error("Language case #{case_key} for context #{context.keyword} is not defined: #{full_name}")
|
166
|
+
return nil
|
167
|
+
end
|
168
|
+
value = lcase.apply(value)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
values[key] = values[key].gsub(token, value)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
values
|
176
|
+
end
|
177
|
+
|
178
|
+
def substitute(label, context, language, options = {})
|
179
|
+
object = self.class.token_object(context, key)
|
180
|
+
|
181
|
+
unless object
|
182
|
+
return error("Missing value for a token \"#{key}\" in \"#{label}\"", false)
|
183
|
+
end
|
184
|
+
|
185
|
+
if piped_params.empty?
|
186
|
+
return error("Piped params may not be empty for token \"#{key}\" in \"#{label}\"", false)
|
187
|
+
end
|
188
|
+
|
189
|
+
language_context = context_for_language(language)
|
190
|
+
|
191
|
+
unless language_context
|
192
|
+
return error("Unknown context for a token: #{full_name} in #{language.locale}", false)
|
193
|
+
end
|
194
|
+
|
195
|
+
piped_values = generate_value_map(piped_params, language_context)
|
196
|
+
|
197
|
+
unless piped_values
|
198
|
+
return error("Failed to generate value map for: #{full_name} in #{language.locale}", false)
|
199
|
+
end
|
200
|
+
|
201
|
+
rule = language_context.find_matching_rule(object)
|
202
|
+
return label unless rule
|
203
|
+
|
204
|
+
value = piped_values[rule.keyword]
|
205
|
+
if value.nil? and language_context.fallback_rule
|
206
|
+
value = piped_values[language_context.fallback_rule.keyword]
|
207
|
+
end
|
208
|
+
|
209
|
+
return label unless value
|
210
|
+
|
211
|
+
substitution_value = []
|
212
|
+
if displayed_in_translation?
|
213
|
+
substitution_value << token_value(Tml::Utils.hash_value(context, key), language, options)
|
214
|
+
substitution_value << ' '
|
215
|
+
else
|
216
|
+
value = value.gsub("##{short_name}#", token_value(Tml::Utils.hash_value(context, key), language, options))
|
217
|
+
end
|
218
|
+
substitution_value << value
|
219
|
+
|
220
|
+
label.gsub(full_name, substitution_value.join(''))
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
@@ -0,0 +1,67 @@
|
|
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
|
+
class Tml::Translation < Tml::Base
|
34
|
+
belongs_to :translation_key, :language
|
35
|
+
attributes :locale, :label, :context, :precedence
|
36
|
+
|
37
|
+
def has_context_rules?
|
38
|
+
context and context.any?
|
39
|
+
end
|
40
|
+
|
41
|
+
# checks if the translation is valid for the given tokens
|
42
|
+
#{
|
43
|
+
# "count" => {"number":"one"},
|
44
|
+
# "user" => {"gender":"male"}
|
45
|
+
#}
|
46
|
+
def matches_rules?(token_values)
|
47
|
+
return true unless has_context_rules?
|
48
|
+
|
49
|
+
context.each do |token_name, rules|
|
50
|
+
token_object = Tml::Tokens::Data.token_object(token_values, token_name)
|
51
|
+
return false unless token_object
|
52
|
+
|
53
|
+
rules.each do |context_key, rule_key|
|
54
|
+
next if rule_key == 'other'
|
55
|
+
|
56
|
+
context = language.context_by_keyword(context_key)
|
57
|
+
return false unless context
|
58
|
+
|
59
|
+
rule = context.find_matching_rule(token_object)
|
60
|
+
return false if rule.nil? or rule.keyword != rule_key
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,178 @@
|
|
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
|
+
require 'digest/md5'
|
34
|
+
|
35
|
+
class Tml::TranslationKey < Tml::Base
|
36
|
+
belongs_to :application, :language
|
37
|
+
attributes :id, :key, :label, :description, :locale, :level, :locked
|
38
|
+
has_many :translations # hashed by language
|
39
|
+
|
40
|
+
def initialize(attrs = {})
|
41
|
+
super
|
42
|
+
|
43
|
+
self.attributes[:key] ||= self.class.generate_key(label, description)
|
44
|
+
self.attributes[:locale] ||= Tml.session.block_options[:locale] || (application ? application.default_locale : Tml.config.default_locale)
|
45
|
+
self.attributes[:language] ||= application ? application.language(locale) : Tml.config.default_language
|
46
|
+
self.attributes[:translations] = {}
|
47
|
+
|
48
|
+
if hash_value(attrs, :translations)
|
49
|
+
hash_value(attrs, :translations).each do |locale, translations|
|
50
|
+
language = application.language(locale)
|
51
|
+
|
52
|
+
self.attributes[:translations][locale] ||= []
|
53
|
+
|
54
|
+
translations.each do |translation_hash|
|
55
|
+
translation = Tml::Translation.new(translation_hash.merge(:translation_key => self, :locale => language.locale))
|
56
|
+
self.attributes[:translations][locale] << translation
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.generate_key(label, desc = '')
|
63
|
+
"#{Digest::MD5.hexdigest("#{label};;;#{desc}")}~"[0..-2].to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
def has_translations_for_language?(language)
|
67
|
+
translations and translations[language.locale] and translations[language.locale].any?
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_translations(locale, translations)
|
71
|
+
translations.each do |translation|
|
72
|
+
translation.locale ||= locale
|
73
|
+
translation.translation_key = self
|
74
|
+
translation.language = self.application.language(translation.locale)
|
75
|
+
end
|
76
|
+
self.translations[locale] = translations
|
77
|
+
end
|
78
|
+
|
79
|
+
# switches to a new application
|
80
|
+
def set_application(app)
|
81
|
+
self.application = app
|
82
|
+
translations.values.each do |locale_translations|
|
83
|
+
locale_translations.each do |translation|
|
84
|
+
translation.translation_key = self
|
85
|
+
translation.language = self.application.language(translation.locale)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
###############################################################
|
92
|
+
## Translation Methods
|
93
|
+
###############################################################
|
94
|
+
|
95
|
+
def translations_for_language(language)
|
96
|
+
return [] unless self.translations
|
97
|
+
self.translations[language.locale] || []
|
98
|
+
end
|
99
|
+
|
100
|
+
def find_first_valid_translation(language, token_values)
|
101
|
+
translations = translations_for_language(language)
|
102
|
+
|
103
|
+
translations.sort! { |x,y| x.precedence <=> y.precedence }
|
104
|
+
|
105
|
+
translations.each do |translation|
|
106
|
+
return translation if translation.matches_rules?(token_values)
|
107
|
+
end
|
108
|
+
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
|
112
|
+
def translate(language, token_values = {}, options = {})
|
113
|
+
if Tml.config.disabled? or language.locale == self.locale
|
114
|
+
return substitute_tokens(label, token_values, language, options.merge(:fallback => false))
|
115
|
+
end
|
116
|
+
|
117
|
+
translation = find_first_valid_translation(language, token_values)
|
118
|
+
decorator = Tml::Decorators::Base.decorator
|
119
|
+
|
120
|
+
if translation
|
121
|
+
translated_label = substitute_tokens(translation.label, token_values, translation.language, options)
|
122
|
+
return decorator.decorate(translated_label, translation.language, language, self, options)
|
123
|
+
end
|
124
|
+
|
125
|
+
translated_label = substitute_tokens(label, token_values, self.language, options)
|
126
|
+
decorator.decorate(translated_label, self.language, language, self, options)
|
127
|
+
end
|
128
|
+
|
129
|
+
###############################################################
|
130
|
+
## Token Substitution Methods
|
131
|
+
###############################################################
|
132
|
+
|
133
|
+
# Returns an array of decoration tokens from the translation key
|
134
|
+
def decoration_tokens
|
135
|
+
@decoration_tokens ||= begin
|
136
|
+
dt = Tml::Tokenizers::Decoration.new(label)
|
137
|
+
dt.parse
|
138
|
+
dt.tokens
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns an array of data tokens from the translation key
|
143
|
+
def data_tokens
|
144
|
+
@data_tokens ||= begin
|
145
|
+
dt = Tml::Tokenizers::Data.new(label)
|
146
|
+
dt.tokens
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def data_tokens_names_map
|
151
|
+
@data_tokens_names_map ||= begin
|
152
|
+
map = {}
|
153
|
+
data_tokens.each do |token|
|
154
|
+
map[token.name] = token
|
155
|
+
end
|
156
|
+
map
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# if the translations engine is disabled
|
161
|
+
def self.substitute_tokens(label, token_values, language, options = {})
|
162
|
+
return label.to_s if options[:skip_substitution]
|
163
|
+
Tml::TranslationKey.new(:label => label.to_s).substitute_tokens(label.to_s, token_values, language, options)
|
164
|
+
end
|
165
|
+
|
166
|
+
def substitute_tokens(translated_label, token_values, language, options = {})
|
167
|
+
if Tml::Tokenizers::Data.required?(translated_label)
|
168
|
+
translated_label = Tml::Tokenizers::Data.new(translated_label, token_values, :allowed_tokens => data_tokens_names_map).substitute(language, options)
|
169
|
+
end
|
170
|
+
|
171
|
+
if Tml::Tokenizers::Decoration.required?(translated_label)
|
172
|
+
translated_label = Tml::Tokenizers::Decoration.new(translated_label, token_values, :allowed_tokens => decoration_tokens).substitute
|
173
|
+
end
|
174
|
+
|
175
|
+
translated_label
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|