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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/lib/tr8n/api_client.rb +28 -18
  4. data/lib/tr8n/application.rb +37 -64
  5. data/lib/tr8n/cache.rb +8 -41
  6. data/lib/tr8n/cache_adapters/file.rb +1 -2
  7. data/lib/tr8n/cache_adapters/memcache.rb +2 -2
  8. data/lib/tr8n/cache_adapters/redis.rb +2 -2
  9. data/lib/tr8n/config.rb +48 -13
  10. data/lib/tr8n/decorators/html.rb +3 -10
  11. data/lib/tr8n/language.rb +25 -112
  12. data/lib/tr8n/language_case.rb +1 -1
  13. data/lib/tr8n/session.rb +2 -9
  14. data/lib/tr8n/source.rb +12 -44
  15. data/lib/tr8n/{tokens/data_tokenizer.rb → tokenizers/data.rb} +4 -3
  16. data/lib/tr8n/{tokens/decoration_tokenizer.rb → tokenizers/decoration.rb} +6 -5
  17. data/lib/tr8n/tokenizers/dom.rb +346 -0
  18. data/lib/tr8n/tokens/data.rb +15 -22
  19. data/lib/tr8n/tokens/transform.rb +24 -12
  20. data/lib/tr8n/translation_key.rb +6 -29
  21. data/lib/tr8n/utils.rb +5 -0
  22. data/lib/tr8n_core/version.rb +1 -1
  23. data/lib/tr8n_core.rb +7 -26
  24. metadata +30 -100
  25. data/lib/tr8n/cache_adapters/cdb.rb +0 -87
  26. data/lib/tr8n/tokens/hidden.rb +0 -57
  27. data/spec/application_spec.rb +0 -52
  28. data/spec/base_spec.rb +0 -19
  29. data/spec/cache/adapters/file_spec.rb +0 -32
  30. data/spec/cache/adapters/memcache_spec.rb +0 -15
  31. data/spec/cache/generators/file_generator_spec.rb +0 -30
  32. data/spec/config_spec.rb +0 -32
  33. data/spec/decorator_spec.rb +0 -12
  34. data/spec/decorators/base_spec.rb +0 -14
  35. data/spec/decorators/default_spec.rb +0 -12
  36. data/spec/decorators/html_spec.rb +0 -50
  37. data/spec/ext/array_spec.rb +0 -12
  38. data/spec/ext/hash_spec.rb +0 -15
  39. data/spec/ext/string_spec.rb +0 -10
  40. data/spec/fixtures/application.json +0 -112
  41. data/spec/fixtures/languages/en-US.json +0 -1424
  42. data/spec/fixtures/languages/es.json +0 -291
  43. data/spec/fixtures/languages/ru.json +0 -582
  44. data/spec/fixtures/translations/ru/basic.json +0 -56
  45. data/spec/fixtures/translations/ru/counters.json +0 -43
  46. data/spec/fixtures/translations/ru/genders.json +0 -171
  47. data/spec/fixtures/translations/ru/last_names.txt +0 -200
  48. data/spec/fixtures/translations/ru/names.json +0 -1
  49. data/spec/fixtures/translations/ru/names.txt +0 -458
  50. data/spec/language_case_rule_spec.rb +0 -57
  51. data/spec/language_case_spec.rb +0 -58
  52. data/spec/language_context_rule_spec.rb +0 -75
  53. data/spec/language_context_spec.rb +0 -333
  54. data/spec/language_spec.rb +0 -615
  55. data/spec/logger_spec.rb +0 -15
  56. data/spec/rules_engine/evaluator_spec.rb +0 -150
  57. data/spec/rules_engine/parser_spec.rb +0 -31
  58. data/spec/source_spec.rb +0 -13
  59. data/spec/spec_helper.rb +0 -101
  60. data/spec/tokens/data_spec.rb +0 -114
  61. data/spec/tokens/data_tokenizer_spec.rb +0 -29
  62. data/spec/tokens/decoration_tokenizer_spec.rb +0 -81
  63. data/spec/tokens/hidden_spec.rb +0 -24
  64. data/spec/tokens/method_spec.rb +0 -84
  65. data/spec/tokens/transform_spec.rb +0 -50
  66. data/spec/translation_key_spec.rb +0 -96
  67. data/spec/translation_spec.rb +0 -24
  68. 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
- key_locale = hash_value(params[:options], :locale) || hash_value(Tr8n.session.block_options, :locale) || Tr8n.config.default_locale
123
- key_level = hash_value(params[:options], :level) || hash_value(Tr8n.session.block_options, :level) || Tr8n.config.default_level
124
-
125
- temp_key = Tr8n::TranslationKey.new({
126
- :application => application,
127
- :label => params[:label],
128
- :description => params[:description],
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 == key_locale
139
- return temp_key.substitute_tokens(params[:label], params[:tokens], self, params[:options]).tr8n_translated
140
- end
141
-
142
- translation_key = application.translation_key(temp_key.key)
143
- if translation_key
144
- return translation_key.translate(self, params[:tokens], params[:options])
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
- unless translations.is_a?(Array)
202
- translations = [translations]
203
- end
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
- source = application.source(source_key)
214
- source_translation_keys = source.fetch_translations_for_language(self, options)
215
- if source_translation_keys[translation_key.key]
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(translation_key, source)
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
@@ -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.to_sym == :phrase
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.cache.fetch(Tr8n::Application.cache_key(key)) do
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 "/" if uri.path.nil? or uri.path == ""
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 = nil
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 fetch_translations_for_language(language, options = {})
66
- return translation_keys if translation_keys
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 Tokens
51
- class DataTokenizer
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::Hidden, Tr8n::Tokens::Method, Tr8n::Tokens::Transform]
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 Tokens
51
- class DecorationTokenizer
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!("{$0}", token_value.to_s)
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("{$0}", value)
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