tr8n_core 4.0.7 → 4.0.9
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 +4 -4
- data/README.md +3 -3
- data/lib/tr8n/api_client.rb +28 -18
- data/lib/tr8n/application.rb +37 -64
- data/lib/tr8n/cache.rb +8 -41
- data/lib/tr8n/cache_adapters/file.rb +1 -2
- data/lib/tr8n/cache_adapters/memcache.rb +2 -2
- data/lib/tr8n/cache_adapters/redis.rb +2 -2
- data/lib/tr8n/config.rb +48 -13
- data/lib/tr8n/decorators/html.rb +3 -10
- data/lib/tr8n/language.rb +25 -112
- data/lib/tr8n/language_case.rb +1 -1
- data/lib/tr8n/session.rb +2 -9
- data/lib/tr8n/source.rb +12 -44
- data/lib/tr8n/{tokens/data_tokenizer.rb → tokenizers/data.rb} +4 -3
- data/lib/tr8n/{tokens/decoration_tokenizer.rb → tokenizers/decoration.rb} +6 -5
- data/lib/tr8n/tokenizers/dom.rb +346 -0
- data/lib/tr8n/tokens/data.rb +15 -22
- data/lib/tr8n/tokens/transform.rb +24 -12
- data/lib/tr8n/translation_key.rb +6 -29
- data/lib/tr8n/utils.rb +5 -0
- data/lib/tr8n_core/version.rb +1 -1
- data/lib/tr8n_core.rb +7 -26
- metadata +30 -100
- data/lib/tr8n/cache_adapters/cdb.rb +0 -87
- data/lib/tr8n/tokens/hidden.rb +0 -57
- data/spec/application_spec.rb +0 -52
- data/spec/base_spec.rb +0 -19
- data/spec/cache/adapters/file_spec.rb +0 -32
- data/spec/cache/adapters/memcache_spec.rb +0 -15
- data/spec/cache/generators/file_generator_spec.rb +0 -30
- data/spec/config_spec.rb +0 -32
- data/spec/decorator_spec.rb +0 -12
- data/spec/decorators/base_spec.rb +0 -14
- data/spec/decorators/default_spec.rb +0 -12
- data/spec/decorators/html_spec.rb +0 -50
- data/spec/ext/array_spec.rb +0 -12
- data/spec/ext/hash_spec.rb +0 -15
- data/spec/ext/string_spec.rb +0 -10
- data/spec/fixtures/application.json +0 -112
- data/spec/fixtures/languages/en-US.json +0 -1424
- data/spec/fixtures/languages/es.json +0 -291
- data/spec/fixtures/languages/ru.json +0 -582
- data/spec/fixtures/translations/ru/basic.json +0 -56
- data/spec/fixtures/translations/ru/counters.json +0 -43
- data/spec/fixtures/translations/ru/genders.json +0 -171
- data/spec/fixtures/translations/ru/last_names.txt +0 -200
- data/spec/fixtures/translations/ru/names.json +0 -1
- data/spec/fixtures/translations/ru/names.txt +0 -458
- data/spec/language_case_rule_spec.rb +0 -57
- data/spec/language_case_spec.rb +0 -58
- data/spec/language_context_rule_spec.rb +0 -75
- data/spec/language_context_spec.rb +0 -333
- data/spec/language_spec.rb +0 -615
- data/spec/logger_spec.rb +0 -15
- data/spec/rules_engine/evaluator_spec.rb +0 -150
- data/spec/rules_engine/parser_spec.rb +0 -31
- data/spec/source_spec.rb +0 -13
- data/spec/spec_helper.rb +0 -101
- data/spec/tokens/data_spec.rb +0 -114
- data/spec/tokens/data_tokenizer_spec.rb +0 -29
- data/spec/tokens/decoration_tokenizer_spec.rb +0 -81
- data/spec/tokens/hidden_spec.rb +0 -24
- data/spec/tokens/method_spec.rb +0 -84
- data/spec/tokens/transform_spec.rb +0 -50
- data/spec/translation_key_spec.rb +0 -96
- data/spec/translation_spec.rb +0 -24
- data/spec/utils_spec.rb +0 -62
data/lib/tr8n/language.rb
CHANGED
@@ -36,7 +36,7 @@ class Tr8n::Language < Tr8n::Base
|
|
36
36
|
has_many :contexts, :cases
|
37
37
|
|
38
38
|
def fetch
|
39
|
-
update_attributes(application.api_client.get("language", {:locale => locale}))
|
39
|
+
update_attributes(application.api_client.get("language", {:locale => locale}, {:cache_key => "#{locale}/language"}))
|
40
40
|
self
|
41
41
|
end
|
42
42
|
|
@@ -119,133 +119,46 @@ class Tr8n::Language < Tr8n::Base
|
|
119
119
|
|
120
120
|
params = Tr8n::Utils.normalize_tr_params(label, description, tokens, options)
|
121
121
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
:locale => key_locale,
|
130
|
-
:level => key_level,
|
131
|
-
:translations => []
|
122
|
+
translation_key = Tr8n::TranslationKey.new({
|
123
|
+
:application => application,
|
124
|
+
:label => params[:label],
|
125
|
+
:description => params[:description],
|
126
|
+
:locale => hash_value(params[:options], :locale) || hash_value(Tr8n.session.block_options, :locale) || Tr8n.config.default_locale,
|
127
|
+
:level => hash_value(params[:options], :level) || hash_value(Tr8n.session.block_options, :level) || Tr8n.config.default_level,
|
128
|
+
:translations => []
|
132
129
|
})
|
133
130
|
|
134
131
|
#Tr8n.logger.info("Translating " + params[:label] + " from: " + key_locale.inspect + " to " + locale.inspect)
|
135
132
|
|
136
133
|
params[:tokens].merge!(:viewing_user => Tr8n.session.current_user)
|
137
134
|
|
138
|
-
if Tr8n.config.disabled? or self.locale ==
|
139
|
-
return
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
end
|
146
|
-
|
147
|
-
# no cache or translator is using inline mode - use service, otherwise load from cache
|
148
|
-
if Tr8n.config.disabled? or (Tr8n.session.current_translator and Tr8n.session.current_translator.inline?)
|
149
|
-
return translate_from_service(temp_key, params[:tokens], params[:options]).tr8n_translated
|
150
|
-
end
|
151
|
-
|
152
|
-
translate_from_cache(temp_key, params[:tokens], params[:options]).tr8n_translated
|
153
|
-
end
|
154
|
-
alias :tr :translate
|
155
|
-
|
156
|
-
def translate_from_cache(translation_key, tokens, options)
|
157
|
-
# In most scenarios translations should be cached by source
|
158
|
-
if Tr8n.cache.cached_by_source?
|
159
|
-
source_key = current_source(options)
|
160
|
-
cache_key = Tr8n::Source.cache_key(source_key, locale);
|
161
|
-
source = Tr8n.cache.fetch(cache_key)
|
162
|
-
|
163
|
-
# if it came from cache, it will be full of translation keys with translations for the locale
|
164
|
-
if source
|
165
|
-
translation_keys = source.translation_keys || {}
|
166
|
-
elsif Tr8n.cache.read_only?
|
167
|
-
translation_keys = {}
|
168
|
-
else
|
169
|
-
# get the source info from the application
|
170
|
-
source = application.source(source_key)
|
171
|
-
translation_keys = source.fetch_translations_for_language(self, options)
|
172
|
-
Tr8n.cache.store(cache_key, source)
|
173
|
-
end
|
174
|
-
|
175
|
-
if translation_keys[translation_key.key]
|
176
|
-
translation_key = translation_keys[translation_key.key]
|
177
|
-
return translation_key.translate(self, tokens, options)
|
178
|
-
end
|
179
|
-
|
180
|
-
translation_key.translations = {locale => []}
|
181
|
-
application.cache_translation_key(translation_key)
|
182
|
-
return translation_key.translate(self, tokens, options)
|
183
|
-
end
|
184
|
-
|
185
|
-
# CDB allows for caching by key
|
186
|
-
cache_key = Tr8n::TranslationKey.cache_key(translation_key.label, translation_key.description, locale)
|
187
|
-
translations = Tr8n.cache.fetch(cache_key)
|
188
|
-
|
189
|
-
if translations.nil?
|
190
|
-
if Tr8n.cache.read_only?
|
191
|
-
translation_key.translations = {locale => []}
|
192
|
-
application.cache_translation_key(translation_key)
|
193
|
-
return translation_key.translate(self, tokens, options)
|
194
|
-
end
|
195
|
-
|
196
|
-
translation_key = translation_key.fetch_translations(self, options)
|
197
|
-
Tr8n.cache.store(cache_key, translation_key.translations(self))
|
198
|
-
return translation_key.translate(self, tokens, options)
|
135
|
+
if Tr8n.config.disabled? or self.locale == translation_key.locale
|
136
|
+
return translation_key.substitute_tokens(
|
137
|
+
params[:label],
|
138
|
+
params[:tokens],
|
139
|
+
self,
|
140
|
+
params[:options]
|
141
|
+
).tr8n_translated
|
199
142
|
end
|
200
143
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
translation_key.translations = {locale => translations}
|
206
|
-
application.cache_translation_key(translation_key)
|
207
|
-
translation_key.translate(self, tokens, options)
|
208
|
-
end
|
144
|
+
# should this be done only for testing?
|
145
|
+
cached_key = application.translation_key(translation_key.key)
|
146
|
+
return cached_key.translate(self, params[:tokens], params[:options]) if cached_key
|
209
147
|
|
210
|
-
def translate_from_service(translation_key, tokens, options)
|
211
148
|
source_key = current_source(options)
|
149
|
+
source = application.source(source_key, locale)
|
212
150
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
translation_key = source_translation_keys[translation_key.key]
|
151
|
+
cached_key = source ? source.translation_key(translation_key.key) : nil
|
152
|
+
if cached_key
|
153
|
+
translation_key = cached_key
|
217
154
|
else
|
218
|
-
application.register_missing_key(
|
155
|
+
application.register_missing_key(source_key, translation_key)
|
219
156
|
memory_cached_translation_key = application.translation_key(translation_key.key)
|
220
157
|
translation_key = memory_cached_translation_key if memory_cached_translation_key
|
221
158
|
end
|
222
159
|
|
223
|
-
translation_key.translate(self, tokens, options)
|
224
|
-
end
|
225
|
-
|
226
|
-
#######################################################################################################
|
227
|
-
## Cache Methods
|
228
|
-
#######################################################################################################
|
229
|
-
|
230
|
-
def self.cache_prefix
|
231
|
-
'l@'
|
232
|
-
end
|
233
|
-
|
234
|
-
def self.cache_key(locale)
|
235
|
-
"#{cache_prefix}_[#{locale}]"
|
236
|
-
end
|
237
|
-
|
238
|
-
def to_cache_hash
|
239
|
-
hash = to_hash(:locale, :name, :english_name, :native_name, :right_to_left, :flag_url)
|
240
|
-
hash[:contexts] = {}
|
241
|
-
contexts.each do |name, value|
|
242
|
-
hash[:contexts][name] = value.to_cache_hash
|
243
|
-
end
|
244
|
-
hash[:cases] = {}
|
245
|
-
cases.each do |name, value|
|
246
|
-
hash[:cases][name] = value.to_cache_hash
|
247
|
-
end
|
248
|
-
hash
|
160
|
+
translation_key.translate(self, params[:tokens], params[:options]).tr8n_translated
|
249
161
|
end
|
162
|
+
alias :tr :translate
|
250
163
|
|
251
164
|
end
|
data/lib/tr8n/language_case.rb
CHANGED
@@ -64,7 +64,7 @@ class Tr8n::LanguageCase < Tr8n::Base
|
|
64
64
|
html_tokens = value.scan(TR8N_HTML_TAGS_REGEX).uniq
|
65
65
|
sanitized_value = value.gsub(TR8N_HTML_TAGS_REGEX, "")
|
66
66
|
|
67
|
-
if application.
|
67
|
+
if application.to_s == "phrase"
|
68
68
|
words = [sanitized_value]
|
69
69
|
else
|
70
70
|
words = sanitized_value.split(/[\s\/\\]/).uniq
|
data/lib/tr8n/session.rb
CHANGED
@@ -47,15 +47,8 @@ module Tr8n
|
|
47
47
|
host ||= Tr8n.config.application[:host]
|
48
48
|
|
49
49
|
Tr8n.cache.reset_version
|
50
|
-
|
51
|
-
self.application = Tr8n
|
52
|
-
Tr8n.logger.info("Initializing application...")
|
53
|
-
Tr8n::Application.new(:host => host, :key => key, :secret => secret).fetch
|
54
|
-
end
|
55
|
-
|
56
|
-
self.current_source = "/tr8n/core"
|
57
|
-
|
58
|
-
self.application
|
50
|
+
self.current_source = "/tr8n/core" # default source
|
51
|
+
self.application = Tr8n::Application.new(:host => host, :key => key, :secret => secret).fetch
|
59
52
|
end
|
60
53
|
|
61
54
|
def reset
|
data/lib/tr8n/source.rb
CHANGED
@@ -36,25 +36,28 @@ class Tr8n::Source < Tr8n::Base
|
|
36
36
|
has_many :translation_keys
|
37
37
|
|
38
38
|
def self.normalize(url)
|
39
|
-
return nil if url.nil? or url ==
|
39
|
+
return nil if url.nil? or url == ''
|
40
40
|
uri = URI.parse(url)
|
41
41
|
path = uri.path
|
42
|
-
return
|
43
|
-
return path if path ==
|
42
|
+
return '/' if uri.path.nil? or uri.path == ''
|
43
|
+
return path if path == '/'
|
44
44
|
|
45
45
|
# always must start with /
|
46
|
-
path = "/#{path}" if path[0] !=
|
46
|
+
path = "/#{path}" if path[0] != '/'
|
47
47
|
# should not end with /
|
48
|
-
path = path[0..-2] if path[-1] ==
|
48
|
+
path = path[0..-2] if path[-1] == '/'
|
49
49
|
path
|
50
50
|
end
|
51
51
|
|
52
|
+
def self.cache_key(key, locale)
|
53
|
+
"#{locale}/[#{key.gsub(/[\.\/]/, '-')}]"
|
54
|
+
end
|
55
|
+
|
52
56
|
def initialize(attrs = {})
|
53
57
|
super
|
54
58
|
|
55
|
-
self.translation_keys =
|
59
|
+
self.translation_keys = {}
|
56
60
|
if hash_value(attrs, :translation_keys)
|
57
|
-
self.translation_keys = {}
|
58
61
|
hash_value(attrs, :translation_keys).each do |tk|
|
59
62
|
tkey = Tr8n::TranslationKey.new(tk.merge(:application => application))
|
60
63
|
self.translation_keys[tkey.key] = application.cache_translation_key(tkey)
|
@@ -62,43 +65,8 @@ class Tr8n::Source < Tr8n::Base
|
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
keys_with_translations = application.api_client.get("source/translations",
|
69
|
-
{:source => source, :locale => language.locale},
|
70
|
-
{:class => Tr8n::TranslationKey, :attributes => {:application => application}})
|
71
|
-
|
72
|
-
self.attributes[:translation_keys] = {}
|
73
|
-
|
74
|
-
keys_with_translations.each do |tkey|
|
75
|
-
self.attributes[:translation_keys][tkey.key] = application.cache_translation_key(tkey)
|
76
|
-
end
|
77
|
-
|
78
|
-
self.attributes[:translation_keys]
|
79
|
-
end
|
80
|
-
|
81
|
-
#######################################################################################################
|
82
|
-
## Cache Methods
|
83
|
-
#######################################################################################################
|
84
|
-
|
85
|
-
def self.cache_prefix
|
86
|
-
's@'
|
87
|
-
end
|
88
|
-
|
89
|
-
def self.cache_key(source_key, locale)
|
90
|
-
"#{cache_prefix}_[#{locale}]_[#{source_key}]"
|
91
|
-
end
|
92
|
-
|
93
|
-
def to_cache_hash
|
94
|
-
hash = to_hash(:source, :url, :name, :description)
|
95
|
-
if translation_keys and translation_keys.any?
|
96
|
-
hash[:translation_keys] = []
|
97
|
-
translation_keys.values.each do |tkey|
|
98
|
-
hash[:translation_keys] << tkey.to_cache_hash
|
99
|
-
end
|
100
|
-
end
|
101
|
-
hash
|
68
|
+
def translation_key(key)
|
69
|
+
self.translation_keys[key]
|
102
70
|
end
|
103
71
|
|
104
72
|
end
|
@@ -46,14 +46,15 @@
|
|
46
46
|
# [link: {user.name}]
|
47
47
|
#
|
48
48
|
#######################################################################
|
49
|
+
|
49
50
|
module Tr8n
|
50
|
-
module
|
51
|
-
class
|
51
|
+
module Tokenizers
|
52
|
+
class Data
|
52
53
|
|
53
54
|
attr_accessor :text, :context, :tokens, :opts
|
54
55
|
|
55
56
|
def self.supported_tokens
|
56
|
-
[Tr8n::Tokens::Data, Tr8n::Tokens::
|
57
|
+
[Tr8n::Tokens::Data, Tr8n::Tokens::Method, Tr8n::Tokens::Transform]
|
57
58
|
end
|
58
59
|
|
59
60
|
def self.required?(label)
|
@@ -46,9 +46,10 @@
|
|
46
46
|
# [link: {user.name}]
|
47
47
|
#
|
48
48
|
#######################################################################
|
49
|
+
|
49
50
|
module Tr8n
|
50
|
-
module
|
51
|
-
class
|
51
|
+
module Tokenizers
|
52
|
+
class Decoration
|
52
53
|
|
53
54
|
attr_reader :tokens, :fragments, :context, :text, :opts
|
54
55
|
|
@@ -61,7 +62,7 @@ module Tr8n
|
|
61
62
|
RE_TEXT = '[^\[\]]+' #'[\w\s!.:{}\(\)\|,?]*'
|
62
63
|
|
63
64
|
def self.required?(label)
|
64
|
-
label.index(
|
65
|
+
label.index('[')
|
65
66
|
end
|
66
67
|
|
67
68
|
def initialize(text, context = {}, opts = {})
|
@@ -131,7 +132,7 @@ module Tr8n
|
|
131
132
|
default_decoration = default_decoration.clone
|
132
133
|
decoration_token_values = context[token_name.to_sym] || context[token_name.to_s]
|
133
134
|
|
134
|
-
default_decoration.gsub!(
|
135
|
+
default_decoration.gsub!('{$0}', token_value.to_s)
|
135
136
|
|
136
137
|
if decoration_token_values.is_a?(Hash)
|
137
138
|
decoration_token_values.keys.each do |key|
|
@@ -163,7 +164,7 @@ module Tr8n
|
|
163
164
|
end
|
164
165
|
|
165
166
|
if method.is_a?(String)
|
166
|
-
return method.to_s.gsub(
|
167
|
+
return method.to_s.gsub('{$0}', value)
|
167
168
|
end
|
168
169
|
|
169
170
|
Tr8n.logger.error("Invalid decoration token value for #{token} in #{text}")
|
@@ -0,0 +1,346 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#--
|
3
|
+
# Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
|
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 'nokogiri'
|
34
|
+
|
35
|
+
module Tr8n
|
36
|
+
module Tokenizers
|
37
|
+
class Dom
|
38
|
+
|
39
|
+
HTML_SPECIAL_CHAR_REGEX = /(&[^;]*;)/
|
40
|
+
INDEPENDENT_NUMBER_REGEX = /^(\d+)$|^(\d+[.,;\s])|(\s\d+)$|(\s\d+[,;\s])/
|
41
|
+
VERBOSE_DATE_REGEX = /(((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)|(January|February|March|April|May|June|July|August|September|October|November|December))\\s\\d+(,\\s\\d+)*(,*\\sat\\s\\d+:\\d+(\\sUTC))*)/
|
42
|
+
|
43
|
+
attr_accessor :context, :tokens, :options
|
44
|
+
|
45
|
+
def initialize(context = {}, options = {})
|
46
|
+
self.context = context
|
47
|
+
self.options = options
|
48
|
+
reset_context
|
49
|
+
end
|
50
|
+
|
51
|
+
def translate(doc)
|
52
|
+
translate_tree(doc.is_a?(String) ? Nokogiri::HTML.fragment(doc) : doc)
|
53
|
+
end
|
54
|
+
|
55
|
+
def translate_tree(node)
|
56
|
+
if non_translatable_node?(node)
|
57
|
+
return node.children.first.inner_text if node.children.count == 1
|
58
|
+
return ''
|
59
|
+
end
|
60
|
+
|
61
|
+
return translate_tml(node.inner_text) if node.type == 3
|
62
|
+
|
63
|
+
html = ''
|
64
|
+
buffer = ''
|
65
|
+
|
66
|
+
node.children.each do |child|
|
67
|
+
if child.type == 3
|
68
|
+
buffer += child.inner_text
|
69
|
+
elsif inline_node?(child) and has_inline_or_text_siblings?(child) and !between_separators?(child)
|
70
|
+
buffer += generate_tml_tags(child)
|
71
|
+
elsif separator_node?(child)
|
72
|
+
html += translate_tml(buffer) if buffer != ''
|
73
|
+
html += generate_html_token(child)
|
74
|
+
buffer = ''
|
75
|
+
else
|
76
|
+
html += translate_tml(buffer) if buffer != ''
|
77
|
+
|
78
|
+
container_value = translate_tree(child)
|
79
|
+
if ignored_node?(child)
|
80
|
+
html += container_value
|
81
|
+
else
|
82
|
+
html += generate_html_token(child, container_value)
|
83
|
+
end
|
84
|
+
|
85
|
+
buffer = ''
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
html += translate_tml(buffer) if buffer != ''
|
90
|
+
html
|
91
|
+
end
|
92
|
+
|
93
|
+
def non_translatable_node?(node)
|
94
|
+
return false unless node
|
95
|
+
return true if node.type == 1 && (option('nodes.scripts') || []).index(node.name.downcase)
|
96
|
+
return true if node.type == 1 && node.children.length === 0 && node.inner_text == ''
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
def translate_tml(tml)
|
101
|
+
return tml if empty_string?(tml)
|
102
|
+
tml = generate_data_tokens(tml)
|
103
|
+
|
104
|
+
if option('split_sentences')
|
105
|
+
sentences = Tr8n::Utils.split_sentences(tml)
|
106
|
+
translation = tml
|
107
|
+
sentences.each do |sentence|
|
108
|
+
sentence_translation = option('debug') ? debug_translation(sentence) : Tr8n.session.current_language.translate(sentence, tokens, options)
|
109
|
+
translation = translation.gsub(sentence, sentence_translation)
|
110
|
+
end
|
111
|
+
reset_context
|
112
|
+
return translation
|
113
|
+
end
|
114
|
+
|
115
|
+
tml = tml.gsub(/[\n]/, '').gsub(/\s\s+/, ' ').strip
|
116
|
+
|
117
|
+
translation = option('debug') ? debug_translation(tml) : Tr8n.session.current_language.translate(tml, tokens, options)
|
118
|
+
reset_context
|
119
|
+
translation
|
120
|
+
end
|
121
|
+
|
122
|
+
def has_child_nodes?(node)
|
123
|
+
node.children and node.children.length > 0
|
124
|
+
end
|
125
|
+
|
126
|
+
def between_separators?(node)
|
127
|
+
(separator_node?(node.previous_sibling) and !valid_text_node?(node.next_sibling)) or
|
128
|
+
(separator_node?(node.next_sibling) and !valid_text_node?(node.previous_sibling))
|
129
|
+
end
|
130
|
+
|
131
|
+
def generate_tml_tags(node)
|
132
|
+
buffer = ''
|
133
|
+
node.children.each do |child|
|
134
|
+
if child.type == 3
|
135
|
+
buffer += child.inner_text
|
136
|
+
else
|
137
|
+
buffer += generate_tml_tags(child)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
token_context = generate_html_token(node)
|
142
|
+
token = contextualize(adjust_name(node), token_context)
|
143
|
+
value = sanitize_value(buffer)
|
144
|
+
|
145
|
+
return '{' + token + '}' if self_closing_node?(node)
|
146
|
+
return '[' + token + ': ' + value + ']' if short_token?(token, value)
|
147
|
+
|
148
|
+
'[' + token + ']' + value + '[/' + token + ']'
|
149
|
+
end
|
150
|
+
|
151
|
+
def option(name)
|
152
|
+
value = Tr8n::Utils.hash_value(self.options, name)
|
153
|
+
value || Tr8n.config.translator_option(name)
|
154
|
+
end
|
155
|
+
|
156
|
+
def debug_translation(translation)
|
157
|
+
option('debug_format').gsub('{$0}', translation)
|
158
|
+
end
|
159
|
+
|
160
|
+
def empty_string?(tml)
|
161
|
+
tml = tml.gsub(/[\s\n\r\t]/, '')
|
162
|
+
tml == ''
|
163
|
+
end
|
164
|
+
|
165
|
+
def reset_context
|
166
|
+
self.tokens = {}.merge(self.context)
|
167
|
+
end
|
168
|
+
|
169
|
+
def short_token?(token, value)
|
170
|
+
option('nodes.short').index(token.downcase) || value.length < 20
|
171
|
+
end
|
172
|
+
|
173
|
+
def only_child?(node)
|
174
|
+
return false unless node.parent
|
175
|
+
node.parent.children.count == 1
|
176
|
+
end
|
177
|
+
|
178
|
+
def has_inline_or_text_siblings?(node)
|
179
|
+
return false unless node.parent
|
180
|
+
|
181
|
+
node.parent.children.each do |child|
|
182
|
+
unless child == node
|
183
|
+
return true if inline_node?(child) || valid_text_node?(child)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
false
|
188
|
+
end
|
189
|
+
|
190
|
+
def inline_node?(node)
|
191
|
+
(
|
192
|
+
node.type == 1 and
|
193
|
+
(option('nodes.inline') || []).index(node.name.downcase) and
|
194
|
+
!only_child?(node)
|
195
|
+
)
|
196
|
+
end
|
197
|
+
|
198
|
+
def container_node?(node)
|
199
|
+
node.type == 1 && !inline_node?(node)
|
200
|
+
end
|
201
|
+
|
202
|
+
def self_closing_node?(node)
|
203
|
+
!node.children || !node.children.first
|
204
|
+
end
|
205
|
+
|
206
|
+
def ignored_node?(node)
|
207
|
+
return true if (node.type != 1)
|
208
|
+
(option('nodes.ignored') || []).index(node.name.downcase)
|
209
|
+
end
|
210
|
+
|
211
|
+
def valid_text_node?(node)
|
212
|
+
return false unless node
|
213
|
+
node.type == 3 && !empty_string?(node.inner_text)
|
214
|
+
end
|
215
|
+
|
216
|
+
def separator_node?(node)
|
217
|
+
return false unless node
|
218
|
+
node.type == 1 && (option('nodes.splitters') || []).index(node.name.downcase)
|
219
|
+
end
|
220
|
+
|
221
|
+
def sanitize_value(value)
|
222
|
+
value.gsub(/^\s+/, '')
|
223
|
+
end
|
224
|
+
|
225
|
+
def replace_special_characters(text)
|
226
|
+
return text if option('data_tokens.special')
|
227
|
+
|
228
|
+
matches = text.match(HTML_SPECIAL_CHAR_REGEX)
|
229
|
+
matches.each do |match|
|
230
|
+
token = match[1, - 2]
|
231
|
+
self.context[token] = match
|
232
|
+
text = text.gsub(match, "{#{token}}")
|
233
|
+
end
|
234
|
+
|
235
|
+
text
|
236
|
+
end
|
237
|
+
|
238
|
+
def generate_data_tokens(text)
|
239
|
+
return text unless option('data_tokens.numeric')
|
240
|
+
|
241
|
+
matches = text.match(INDEPENDENT_NUMBER_REGEX) || []
|
242
|
+
token_name = option('data_tokens.numeric_name')
|
243
|
+
|
244
|
+
matches.each do |match|
|
245
|
+
value = match.gsub(/[.,;\s]/, '')
|
246
|
+
token = contextualize(token_name, value.to_i)
|
247
|
+
replacement = match.replace(value, "{#{token}}")
|
248
|
+
text = text.gsub(match, match.gsub(value, replacement))
|
249
|
+
end
|
250
|
+
|
251
|
+
text
|
252
|
+
end
|
253
|
+
|
254
|
+
def generate_html_token(node, value = nil)
|
255
|
+
name = node.name.downcase
|
256
|
+
attributes = node.attributes
|
257
|
+
attributes_hash = {}
|
258
|
+
value = (!value ? '{$0}' : value)
|
259
|
+
|
260
|
+
if attributes.length == 0
|
261
|
+
if self_closing_node?(node)
|
262
|
+
return '<' + name + '/>' if %w(br hr).index(name)
|
263
|
+
return '<' + name + '>' + '</' + name + '>'
|
264
|
+
end
|
265
|
+
return '<' + name + '>' + value + '</' + name + '>'
|
266
|
+
end
|
267
|
+
|
268
|
+
attributes.each do |name, attribute|
|
269
|
+
attributes_hash[name] = attribute.value
|
270
|
+
end
|
271
|
+
|
272
|
+
keys = attributes_hash.keys.sort
|
273
|
+
|
274
|
+
attr = []
|
275
|
+
keys.each do |key|
|
276
|
+
quote = attributes_hash[key].index("'") ? '"' : "'"
|
277
|
+
attr << (key + '=' + quote + attributes_hash[key] + quote)
|
278
|
+
end
|
279
|
+
attr = attr.join(' ')
|
280
|
+
|
281
|
+
return '<' + name + ' ' + attr + '>' + '</' + name + '>' if self_closing_node?(node)
|
282
|
+
'<' + name + ' ' + attr + '>' + value + '</' + name + '>'
|
283
|
+
end
|
284
|
+
|
285
|
+
def adjust_name(node)
|
286
|
+
name = node.name.downcase
|
287
|
+
map = option('name_mapping')
|
288
|
+
map[name.to_sym] ? map[name.to_sym] : name
|
289
|
+
end
|
290
|
+
|
291
|
+
def contextualize(name, context)
|
292
|
+
if self.tokens[name] and self.tokens[name] != context
|
293
|
+
index = 0
|
294
|
+
matches = name.match(/\d+$/)
|
295
|
+
if matches and matches.length > 0
|
296
|
+
index = matches[matches.length-1].to_i
|
297
|
+
name = name.gsub('' + index, '')
|
298
|
+
end
|
299
|
+
name += (index + 1).to_s
|
300
|
+
return contextualize(name, context)
|
301
|
+
end
|
302
|
+
|
303
|
+
self.tokens[name] = context
|
304
|
+
name
|
305
|
+
end
|
306
|
+
|
307
|
+
def debug(doc)
|
308
|
+
self.doc = doc
|
309
|
+
debug_tree(self.doc, 0)
|
310
|
+
end
|
311
|
+
|
312
|
+
def debug_tree(node, depth)
|
313
|
+
padding = ('=' * (depth+1))
|
314
|
+
|
315
|
+
Tr8n.logger.log(padding + '=> ' + (node) + ': ' + node_info(node))
|
316
|
+
|
317
|
+
(node.children || []).each do |child|
|
318
|
+
debug_tree(child, depth+1)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def node_info(node)
|
323
|
+
info = []
|
324
|
+
info << node.type
|
325
|
+
|
326
|
+
info << node.tagName if node.type == 1
|
327
|
+
|
328
|
+
if inline_node?(node)
|
329
|
+
info << 'inline'
|
330
|
+
if has_inline_or_text_siblings?(node)
|
331
|
+
info << 'sentence'
|
332
|
+
else
|
333
|
+
info << 'only translatable'
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
info << 'self closing' if self_closing_node?(node)
|
338
|
+
info << 'only child' if only_child?(node)
|
339
|
+
|
340
|
+
return "[#{info.join(', ')}]: " + node.inner_text if node.type == 3
|
341
|
+
"[#{info.join(', ')}]"
|
342
|
+
end
|
343
|
+
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|