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
data/lib/tr8n/config.rb
ADDED
@@ -0,0 +1,291 @@
|
|
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
|
+
|
26
|
+
def self.config
|
27
|
+
@config ||= Tr8n::Config.new
|
28
|
+
end
|
29
|
+
|
30
|
+
# config class can be set
|
31
|
+
def self.config=(config)
|
32
|
+
@config = config
|
33
|
+
end
|
34
|
+
|
35
|
+
# Acts as a global singleton that holds all Tr8n configuration
|
36
|
+
# The class can be extended with a different implementation, as long as the interface is supported
|
37
|
+
class Config < Tr8n::Base
|
38
|
+
thread_safe_attributes :application, :current_user, :current_language, :current_translator, :current_source, :current_component
|
39
|
+
thread_safe_attributes :block_options
|
40
|
+
|
41
|
+
def root
|
42
|
+
@root ||= File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
|
43
|
+
end
|
44
|
+
|
45
|
+
def env
|
46
|
+
'defaults'
|
47
|
+
end
|
48
|
+
|
49
|
+
def defaults
|
50
|
+
@defaults ||= Tr8n::Utils.load_yaml("#{root}/config/config.yml", env)
|
51
|
+
end
|
52
|
+
|
53
|
+
def enabled?
|
54
|
+
hash_value(defaults, "enabled")
|
55
|
+
end
|
56
|
+
|
57
|
+
def disabled?
|
58
|
+
not enabled?
|
59
|
+
end
|
60
|
+
|
61
|
+
#########################################################
|
62
|
+
## Application
|
63
|
+
#########################################################
|
64
|
+
|
65
|
+
def host
|
66
|
+
hash_value(defaults, "application.host")
|
67
|
+
end
|
68
|
+
|
69
|
+
def app_key
|
70
|
+
hash_value(defaults, "application.key")
|
71
|
+
end
|
72
|
+
|
73
|
+
def app_secret
|
74
|
+
hash_value(defaults, "application.secret")
|
75
|
+
end
|
76
|
+
|
77
|
+
def init_application
|
78
|
+
Tr8n::Application.init(host, app_key, app_secret)
|
79
|
+
self.current_source = "/tr8n/core"
|
80
|
+
end
|
81
|
+
|
82
|
+
def default_locale
|
83
|
+
return application.default_locale if application
|
84
|
+
hash_value(defaults, "default_locale")
|
85
|
+
end
|
86
|
+
|
87
|
+
def default_language
|
88
|
+
return Tr8n::Language.new(:locale => default_locale, :default => true) if disabled?
|
89
|
+
application.language(default_locale)
|
90
|
+
end
|
91
|
+
|
92
|
+
def default_level
|
93
|
+
return application.default_level if application
|
94
|
+
hash_value(defaults, "default_level")
|
95
|
+
end
|
96
|
+
|
97
|
+
def reset
|
98
|
+
self.application = nil
|
99
|
+
self.current_user = nil
|
100
|
+
self.current_language = nil
|
101
|
+
self.current_translator = nil
|
102
|
+
self.current_source = nil
|
103
|
+
self.current_component = nil
|
104
|
+
self.block_options = nil
|
105
|
+
end
|
106
|
+
|
107
|
+
#########################################################
|
108
|
+
## Logger
|
109
|
+
#########################################################
|
110
|
+
|
111
|
+
def logger_enabled?
|
112
|
+
hash_value(defaults, "logger.enabled")
|
113
|
+
end
|
114
|
+
|
115
|
+
def log_path
|
116
|
+
".#{hash_value(defaults, "logger.path")}"
|
117
|
+
end
|
118
|
+
|
119
|
+
#########################################################
|
120
|
+
## Cache
|
121
|
+
#########################################################
|
122
|
+
|
123
|
+
def cache_enabled?
|
124
|
+
hash_value(defaults, "cache.enabled")
|
125
|
+
end
|
126
|
+
|
127
|
+
def cache_path
|
128
|
+
"#{root}#{hash_value(defaults, "cache.path")}"
|
129
|
+
end
|
130
|
+
|
131
|
+
def cache_adapter
|
132
|
+
hash_value(defaults, "cache.adapter")
|
133
|
+
end
|
134
|
+
|
135
|
+
def cache_version
|
136
|
+
hash_value(defaults, "cache.version")
|
137
|
+
end
|
138
|
+
|
139
|
+
def cache_host
|
140
|
+
hash_value(defaults, "cache.host")
|
141
|
+
end
|
142
|
+
|
143
|
+
def cache_timeout
|
144
|
+
hash_value(defaults, 'cache.timeout')
|
145
|
+
end
|
146
|
+
|
147
|
+
#########################################################
|
148
|
+
## Rules Engine
|
149
|
+
#########################################################
|
150
|
+
|
151
|
+
def context_rules
|
152
|
+
@context_rules ||= {
|
153
|
+
"number" => {
|
154
|
+
"variables" => {
|
155
|
+
}
|
156
|
+
},
|
157
|
+
"gender" => {
|
158
|
+
"variables" => {
|
159
|
+
"@gender" => "gender",
|
160
|
+
}
|
161
|
+
},
|
162
|
+
"genders" => {
|
163
|
+
"variables" => {
|
164
|
+
"@genders" => lambda{|list| list.collect do |u|
|
165
|
+
u.is_a?(Hash) ? (u["gender"] || u[:gender]) : u.gender
|
166
|
+
end
|
167
|
+
},
|
168
|
+
"@size" => lambda{|list| list.size}
|
169
|
+
}
|
170
|
+
},
|
171
|
+
"date" => {
|
172
|
+
"variables" => {
|
173
|
+
}
|
174
|
+
},
|
175
|
+
"time" => {
|
176
|
+
"variables" => {
|
177
|
+
}
|
178
|
+
},
|
179
|
+
"list" => {
|
180
|
+
"variables" => {
|
181
|
+
"@count" => lambda{|list| list.size}
|
182
|
+
}
|
183
|
+
},
|
184
|
+
}
|
185
|
+
end
|
186
|
+
|
187
|
+
def decorator_class
|
188
|
+
Tr8n::Decorators::Default
|
189
|
+
end
|
190
|
+
|
191
|
+
def default_data_tokens
|
192
|
+
@default_data_tokens ||= Tr8n::Utils.load_yaml("#{root}/config/tokens/data.yml")
|
193
|
+
end
|
194
|
+
|
195
|
+
def default_decoration_tokens
|
196
|
+
@default_decoration_tokens ||= Tr8n::Utils.load_yaml("#{root}/config/tokens/decorations.yml")
|
197
|
+
end
|
198
|
+
|
199
|
+
def default_token_value(token_name, type = :data, format = :html)
|
200
|
+
return default_data_tokens[format.to_s][token_name.to_s] if type.to_sym == :data
|
201
|
+
return default_decoration_tokens[format.to_s][token_name.to_s] if type.to_sym == :decoration
|
202
|
+
end
|
203
|
+
|
204
|
+
#########################################################
|
205
|
+
## Block Options
|
206
|
+
#########################################################
|
207
|
+
|
208
|
+
def block_options
|
209
|
+
(Thread.current[:block_options] ||= []).last || {}
|
210
|
+
end
|
211
|
+
|
212
|
+
def with_block_options(opts)
|
213
|
+
Thread.current[:block_options] ||= []
|
214
|
+
Thread.current[:block_options].push(opts)
|
215
|
+
if block_given?
|
216
|
+
ret = yield
|
217
|
+
end
|
218
|
+
Thread.current[:block_options].pop
|
219
|
+
ret
|
220
|
+
end
|
221
|
+
|
222
|
+
def current_source_from_block_options
|
223
|
+
arr = Thread.current[:block_options] || []
|
224
|
+
arr.reverse.each do |opts|
|
225
|
+
return application.source_by_key(opts[:source]) unless opts[:source].blank?
|
226
|
+
end
|
227
|
+
nil
|
228
|
+
end
|
229
|
+
|
230
|
+
def current_component_from_block_options
|
231
|
+
arr = Thread.current[:block_options] || []
|
232
|
+
arr.reverse.each do |opts|
|
233
|
+
return application.component_by_key(opts[:component]) unless opts[:component].blank?
|
234
|
+
end
|
235
|
+
Tr8n.config.current_component
|
236
|
+
end
|
237
|
+
|
238
|
+
#########################################################
|
239
|
+
## Localization
|
240
|
+
#########################################################
|
241
|
+
|
242
|
+
def localization
|
243
|
+
hash_value(defaults, "localization")
|
244
|
+
end
|
245
|
+
|
246
|
+
def strftime_symbol_to_token(symbol)
|
247
|
+
{
|
248
|
+
"%a" => "{short_week_day_name}",
|
249
|
+
"%A" => "{week_day_name}",
|
250
|
+
"%b" => "{short_month_name}",
|
251
|
+
"%B" => "{month_name}",
|
252
|
+
"%p" => "{am_pm}",
|
253
|
+
"%d" => "{days}",
|
254
|
+
"%e" => "{day_of_month}",
|
255
|
+
"%j" => "{year_days}",
|
256
|
+
"%m" => "{months}",
|
257
|
+
"%W" => "{week_num}",
|
258
|
+
"%w" => "{week_days}",
|
259
|
+
"%y" => "{short_years}",
|
260
|
+
"%Y" => "{years}",
|
261
|
+
"%l" => "{trimed_hour}",
|
262
|
+
"%H" => "{full_hours}",
|
263
|
+
"%I" => "{short_hours}",
|
264
|
+
"%M" => "{minutes}",
|
265
|
+
"%S" => "{seconds}",
|
266
|
+
"%s" => "{since_epoch}"
|
267
|
+
}[symbol]
|
268
|
+
end
|
269
|
+
|
270
|
+
def default_day_names
|
271
|
+
hash_value(defaults, "localization.default_day_names")
|
272
|
+
end
|
273
|
+
|
274
|
+
def default_abbr_day_names
|
275
|
+
hash_value(defaults, "localization.default_abbr_day_names")
|
276
|
+
end
|
277
|
+
|
278
|
+
def default_month_names
|
279
|
+
hash_value(defaults, "localization.default_month_names")
|
280
|
+
end
|
281
|
+
|
282
|
+
def default_abbr_month_names
|
283
|
+
hash_value(defaults, "localization.default_abbr_month_names")
|
284
|
+
end
|
285
|
+
|
286
|
+
def default_date_formats
|
287
|
+
hash_value(defaults, "localization.custom_date_formats")
|
288
|
+
end
|
289
|
+
|
290
|
+
end
|
291
|
+
end
|
@@ -0,0 +1,35 @@
|
|
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::Decorators::Base < Tr8n::Base
|
25
|
+
|
26
|
+
def self.decorator
|
27
|
+
# can alternate based on the format in the config
|
28
|
+
Tr8n.config.decorator_class.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def decorate(translated_label, translation_language, target_language, translation_key, options = {})
|
32
|
+
raise Tr8n::Exception.new("Must be implemented by the extending class")
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
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::Decorators::Default < Tr8n::Decorators::Base
|
25
|
+
|
26
|
+
def decorate(translated_label, translation_language, target_language, translation_key, options = {})
|
27
|
+
translated_label
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,63 @@
|
|
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::Decorators::Html < Tr8n::Decorators::Base
|
25
|
+
|
26
|
+
def decorate(translated_label, translation_language, target_language, translation_key, options = {})
|
27
|
+
# skip decoration if instructed so
|
28
|
+
return translated_label if options[:skip_decorations]
|
29
|
+
# if translation key language is the same as target language - skip decorations
|
30
|
+
return translated_label if translation_key.language == target_language
|
31
|
+
return translated_label unless Tr8n.config.current_translator and Tr8n.config.current_translator.inline?
|
32
|
+
return translated_label if translation_key.locked? and not Tr8n.config.current_translator.manager?
|
33
|
+
|
34
|
+
element = 'span'
|
35
|
+
if options[:use_div]
|
36
|
+
element = 'div'
|
37
|
+
end
|
38
|
+
|
39
|
+
if translation_key.id.nil?
|
40
|
+
return "<#{element} class='tr8n_pending'>#{translated_label}</#{element}>"
|
41
|
+
end
|
42
|
+
|
43
|
+
classes = ['tr8n_translatable']
|
44
|
+
|
45
|
+
if translation_key.locked?
|
46
|
+
# must be a manager and enabling locking feature
|
47
|
+
return translated_label unless Tr8n.config.current_translator.feature_enabled?(:show_locked_keys)
|
48
|
+
classes << 'tr8n_locked'
|
49
|
+
elsif translation_language == translation_key.language
|
50
|
+
classes << 'tr8n_not_translated'
|
51
|
+
elsif translation_language == target_language
|
52
|
+
classes << 'tr8n_translated'
|
53
|
+
else
|
54
|
+
classes << 'tr8n_fallback'
|
55
|
+
end
|
56
|
+
|
57
|
+
html = "<#{element} class='#{classes.join(' ')}' data-translation_key_id='#{translation_key.id}'>"
|
58
|
+
html << translated_label
|
59
|
+
html << "</#{element}>"
|
60
|
+
html
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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::Exception < StandardError
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,250 @@
|
|
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::Language < Tr8n::Base
|
25
|
+
belongs_to :application
|
26
|
+
attributes :locale, :name, :english_name, :native_name, :right_to_left, :flag_url
|
27
|
+
has_many :contexts, :cases
|
28
|
+
|
29
|
+
def initialize(attrs = {})
|
30
|
+
super
|
31
|
+
|
32
|
+
self.attributes[:contexts] = {}
|
33
|
+
if hash_value(attrs, :contexts)
|
34
|
+
hash_value(attrs, :contexts).each do |key, context|
|
35
|
+
self.attributes[:contexts][key] = Tr8n::LanguageContext.new(context.merge(:language => self))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
self.attributes[:cases] = {}
|
40
|
+
if hash_value(attrs, :cases)
|
41
|
+
hash_value(attrs, :cases).each do |key, lcase|
|
42
|
+
self.attributes[:cases][key] = Tr8n::LanguageCase.new(lcase.merge(:language => self))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def context_by_keyword(keyword)
|
48
|
+
hash_value(contexts, keyword)
|
49
|
+
end
|
50
|
+
|
51
|
+
def context_by_token_name(token_name)
|
52
|
+
contexts.values.detect{|ctx| ctx.applies_to_token?(token_name)}
|
53
|
+
end
|
54
|
+
|
55
|
+
def language_case_by_keyword(keyword)
|
56
|
+
cases[keyword]
|
57
|
+
end
|
58
|
+
|
59
|
+
def has_definition?
|
60
|
+
contexts.any?
|
61
|
+
end
|
62
|
+
|
63
|
+
def default?
|
64
|
+
return true unless application
|
65
|
+
application.default_locale == locale
|
66
|
+
end
|
67
|
+
|
68
|
+
def dir
|
69
|
+
right_to_left? ? "rtl" : "ltr"
|
70
|
+
end
|
71
|
+
|
72
|
+
def align(dest)
|
73
|
+
return dest unless right_to_left?
|
74
|
+
dest.to_s == 'left' ? 'right' : 'left'
|
75
|
+
end
|
76
|
+
|
77
|
+
def full_name
|
78
|
+
return english_name if english_name == native_name
|
79
|
+
"#{english_name} - #{native_name}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def current_source(options)
|
83
|
+
options[:source] || Tr8n.config.block_options[:source] || Tr8n.config.current_source
|
84
|
+
end
|
85
|
+
|
86
|
+
#######################################################################################################
|
87
|
+
# Translation Methods
|
88
|
+
#
|
89
|
+
# Note - when inline translation mode is enable, cache will not be used and translators will
|
90
|
+
# always hit the live service to get the most recent translations
|
91
|
+
#
|
92
|
+
# Some cache adapters cache by source, others by key. Some are read-only, some are built on the fly.
|
93
|
+
#
|
94
|
+
# There are three ways to call the tr method
|
95
|
+
#
|
96
|
+
# tr(label, description = "", tokens = {}, options = {})
|
97
|
+
# or
|
98
|
+
# tr(label, tokens = {}, options = {})
|
99
|
+
# or
|
100
|
+
# tr(:label => label, :description => "", :tokens => {}, :options => {})
|
101
|
+
########################################################################################################
|
102
|
+
|
103
|
+
def translate(label, description = nil, tokens = {}, options = {})
|
104
|
+
return label if label.tr8n_translated?
|
105
|
+
|
106
|
+
params = Tr8n::Utils.normalize_tr_params(label, description, tokens, options)
|
107
|
+
key_locale = hash_value(params[:options], :locale) || hash_value(Tr8n.config.block_options, :locale) || Tr8n.config.default_locale
|
108
|
+
key_level = hash_value(params[:options], :level) || hash_value(Tr8n.config.block_options, :level) || Tr8n.config.default_level
|
109
|
+
|
110
|
+
temp_key = Tr8n::TranslationKey.new({
|
111
|
+
:application => application,
|
112
|
+
:label => params[:label],
|
113
|
+
:description => params[:description],
|
114
|
+
:locale => key_locale,
|
115
|
+
:level => key_level,
|
116
|
+
:translations => []
|
117
|
+
})
|
118
|
+
|
119
|
+
#Tr8n.logger.info("Translating " + params[:label] + " from: " + key_locale.inspect + " to " + locale.inspect)
|
120
|
+
|
121
|
+
params[:tokens].merge!(:viewing_user => Tr8n.config.current_user)
|
122
|
+
|
123
|
+
if Tr8n.config.disabled? or locale == key_locale
|
124
|
+
return temp_key.substitute_tokens(params[:label], params[:tokens], self, params[:options]).tr8n_translated
|
125
|
+
end
|
126
|
+
|
127
|
+
translation_key = application.translation_key(temp_key.key)
|
128
|
+
if translation_key
|
129
|
+
return translation_key.translate(self, params[:tokens], params[:options])
|
130
|
+
end
|
131
|
+
|
132
|
+
# no cache or translator is using inline mode - use service, otherwise load from cache
|
133
|
+
if not Tr8n.config.cache_enabled? or (Tr8n.config.current_translator and Tr8n.config.current_translator.inline?)
|
134
|
+
return translate_from_service(temp_key, params[:tokens], params[:options]).tr8n_translated
|
135
|
+
end
|
136
|
+
|
137
|
+
translate_from_cache(temp_key, params[:tokens], params[:options]).tr8n_translated
|
138
|
+
rescue Exception => ex
|
139
|
+
Tr8n.logger.error("Failed to translate: #{params[:label]} : #{ex.message}")
|
140
|
+
Tr8n.logger.error(ex.backtrace)
|
141
|
+
params[:label]
|
142
|
+
end
|
143
|
+
alias :tr :translate
|
144
|
+
|
145
|
+
def translate_from_cache(translation_key, tokens, options)
|
146
|
+
# In most scenarios translations should be cached by source
|
147
|
+
if Tr8n.cache.cached_by_source?
|
148
|
+
source_key = current_source(options)
|
149
|
+
cache_key = Tr8n::Source.cache_key(source_key, locale);
|
150
|
+
source = Tr8n.cache.fetch(cache_key)
|
151
|
+
|
152
|
+
# if it came from cache, it will be full of translation keys with translations for the locale
|
153
|
+
if source
|
154
|
+
translation_keys = source.translation_keys
|
155
|
+
elsif Tr8n.cache.read_only?
|
156
|
+
translation_keys = {}
|
157
|
+
else
|
158
|
+
# get the source info from the application
|
159
|
+
source = application.source(source_key)
|
160
|
+
translation_keys = source.fetch_translations_for_language(self, options)
|
161
|
+
Tr8n.cache.store(cache_key, source)
|
162
|
+
end
|
163
|
+
|
164
|
+
if translation_keys[translation_key.key]
|
165
|
+
translation_key = translation_keys[translation_key.key]
|
166
|
+
return translation_key.translate(self, tokens, options)
|
167
|
+
end
|
168
|
+
|
169
|
+
translation_key.translations = {locale => []}
|
170
|
+
application.cache_translation_key(translation_key)
|
171
|
+
return translation_key.translate(self, tokens, options)
|
172
|
+
end
|
173
|
+
|
174
|
+
# CDB allows for caching by key
|
175
|
+
cache_key = Tr8n::TranslationKey.cache_key(translation_key.label, translation_key.description, locale)
|
176
|
+
translations = Tr8n.cache.fetch(cache_key)
|
177
|
+
|
178
|
+
if translations.nil?
|
179
|
+
if Tr8n.cache.read_only?
|
180
|
+
translation_key.translations = {locale => []}
|
181
|
+
application.cache_translation_key(translation_key)
|
182
|
+
return translation_key.translate(self, tokens, options)
|
183
|
+
end
|
184
|
+
|
185
|
+
translation_key = translation_key.fetch_translations(self, options)
|
186
|
+
Tr8n.cache.store(cache_key, translation_key.translations(self))
|
187
|
+
return translation_key.translate(self, tokens, options)
|
188
|
+
end
|
189
|
+
|
190
|
+
unless translations.is_a?(Array)
|
191
|
+
translations = [translations]
|
192
|
+
end
|
193
|
+
|
194
|
+
translation_key.translations = {locale => translations}
|
195
|
+
application.cache_translation_key(translation_key)
|
196
|
+
translation_key.translate(self, tokens, options)
|
197
|
+
end
|
198
|
+
|
199
|
+
def translate_from_service(translation_key, tokens, options)
|
200
|
+
source_key = current_source(options)
|
201
|
+
|
202
|
+
if source_key
|
203
|
+
source = application.source(source_key)
|
204
|
+
source_translation_keys = source.fetch_translations_for_language(self, options)
|
205
|
+
if source_translation_keys[translation_key.key]
|
206
|
+
translation_key = source_translation_keys[translation_key.key]
|
207
|
+
else
|
208
|
+
application.register_missing_key(translation_key, source)
|
209
|
+
end
|
210
|
+
|
211
|
+
return translation_key.translate(self, tokens, options)
|
212
|
+
end
|
213
|
+
|
214
|
+
# all translations are cached in memory as the second level cache
|
215
|
+
memory_cached_translation_key = application.translation_key(translation_key.key)
|
216
|
+
if memory_cached_translation_key
|
217
|
+
translation_key = memory_cached_translation_key
|
218
|
+
else
|
219
|
+
translation_key = translation_key.fetch_translations(self, options)
|
220
|
+
end
|
221
|
+
|
222
|
+
translation_key.translate(self, tokens, options)
|
223
|
+
end
|
224
|
+
|
225
|
+
#######################################################################################################
|
226
|
+
## Cache Methods
|
227
|
+
#######################################################################################################
|
228
|
+
|
229
|
+
def self.cache_prefix
|
230
|
+
'l@'
|
231
|
+
end
|
232
|
+
|
233
|
+
def self.cache_key(locale)
|
234
|
+
"#{cache_prefix}_[#{locale}]"
|
235
|
+
end
|
236
|
+
|
237
|
+
def to_cache_hash
|
238
|
+
hash = to_hash(:locale, :name, :english_name, :native_name, :right_to_left, :flag_url)
|
239
|
+
hash[:contexts] = {}
|
240
|
+
contexts.each do |name, value|
|
241
|
+
hash[:contexts][name] = value.to_cache_hash
|
242
|
+
end
|
243
|
+
hash[:cases] = {}
|
244
|
+
cases.each do |name, value|
|
245
|
+
hash[:cases][name] = value.to_cache_hash
|
246
|
+
end
|
247
|
+
hash
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|