tml 4.4.7 → 5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2ee215776a93a3de80da5b0f4f432ea99e44e577
4
- data.tar.gz: b2dd1de37941aaef3224de6a2458cbf5cc74c6ca
3
+ metadata.gz: 5a514613d1a01ea544a896e526f38e5f9843df78
4
+ data.tar.gz: b1dceccffd886b61d390dbf323da3578b181835f
5
5
  SHA512:
6
- metadata.gz: 6a3a8087e25ca982bcc9ed6ed27e74ac53bac437738512bbe1a754f9d23e0ea82e3aaceeae1a6ae425cf53767ff558454a868985b09ff14cbc11a2510ba313a0
7
- data.tar.gz: cc3e5c6524a9690e774a31b3aeefeae710a567b67e1e98bd03cf99b3f12799b7c71a4e75b105cc11e0b0fa8c7ab14115a10436d332b631b2f2d849caeac687d6
6
+ metadata.gz: f86dea985be31eaf6fcfc391615bc1287dd932704c736781834ba7d5acb029ff2f7e251893ff0593d5a1502442e62f392bc472d2f54279712661a4290e8267ed
7
+ data.tar.gz: 47cb409b52c980688d033f7e5b1588d1a4424d1e501f12bf564e17813b4fca668b2065ba23ccda4c24286a3a703c758c0386b1fc1b1fd45ec264c9a727e26dd1
@@ -31,6 +31,8 @@
31
31
  #++
32
32
 
33
33
  require 'faraday'
34
+ require 'zlib'
35
+ require 'stringio'
34
36
 
35
37
  class Tml::Api::Client < Tml::Base
36
38
  CDN_HOST = 'https://cdn.translationexchange.com'
@@ -107,10 +109,12 @@ class Tml::Api::Client < Tml::Base
107
109
  return nil if Tml.cache.version == 'undefined' || Tml.cache.version.to_s == '0'
108
110
 
109
111
  response = nil
110
- cdn_path = "#{Tml.config.access_token}/#{Tml.cache.version}/#{key}.json"
111
- trace_api_call(cdn_path, params, opts) do
112
+ cdn_path = "/#{Tml.config.application[:key]}/#{Tml.cache.version}/#{key}.json.gz"
113
+ trace_api_call(cdn_path, params, opts.merge(:host => cdn_host)) do
112
114
  begin
113
- response = cdn_connection.get(cdn_path, params)
115
+ response = cdn_connection.get do |request|
116
+ prepare_request(request, cdn_path, params)
117
+ end
114
118
  rescue Exception => ex
115
119
  Tml.logger.error("Failed to execute request: #{ex.message[0..255]}")
116
120
  return nil
@@ -119,8 +123,14 @@ class Tml::Api::Client < Tml::Base
119
123
  return if response.status >= 500 and response.status < 600
120
124
  return if response.body.nil? or response.body == '' or response.body.match(/xml/)
121
125
 
126
+ compressed_data = response.body
127
+ return if compressed_data.nil? or compressed_data == ''
128
+
129
+ data = Zlib::GzipReader.new(StringIO.new(compressed_data.to_s)).read
130
+ Tml.logger.debug("Compressed: #{compressed_data.length} Uncompressed: #{data.length}")
131
+
122
132
  begin
123
- data = JSON.parse(response.body)
133
+ data = JSON.parse(data)
124
134
  rescue Exception => ex
125
135
  return nil
126
136
  end
@@ -172,19 +182,31 @@ class Tml::Api::Client < Tml::Base
172
182
  "#{API_PATH}#{path[0] == '/' ? '' : '/'}#{path}"
173
183
  end
174
184
 
185
+ def prepare_request(request, path, params)
186
+ request.options.timeout = 5
187
+ request.options.open_timeout = 2
188
+ request.headers['User-Agent'] = "tml-ruby v#{Tml::VERSION} (Faraday v#{Faraday::VERSION})"
189
+ request.headers['Accept'] = 'application/json'
190
+ request.headers['Accept-Encoding'] = 'gzip, deflate'
191
+ request.url(path, params)
192
+ end
193
+
175
194
  def execute_request(path, params = {}, opts = {})
176
195
  response = nil
177
196
  error = nil
178
197
 
198
+ token = Tml.config.application ? Tml.config.application[:token] : ''
199
+
179
200
  # oauth path is separate from versioned APIs
180
201
  path = prepare_api_path(path)
181
- params = params.merge(:access_token => Tml.config.access_token) unless path.index('oauth')
202
+ params = params.merge(:access_token => token) unless path.index('oauth')
182
203
 
183
204
  if opts[:method] == :post
184
205
  params = params.merge(:api_key => application.key)
185
206
  end
186
207
 
187
- trace_api_call(path, params, opts) do
208
+ @compressed = false
209
+ trace_api_call(path, params, opts.merge(:host => host)) do
188
210
  begin
189
211
  if opts[:method] == :post
190
212
  response = connection.post(path, params)
@@ -193,7 +215,10 @@ class Tml::Api::Client < Tml::Base
193
215
  elsif opts[:method] == :delete
194
216
  response = connection.delete(path, params)
195
217
  else
196
- response = connection.get(path, params)
218
+ response = connection.get do |request|
219
+ @compressed = true
220
+ prepare_request(request, path, params)
221
+ end
197
222
  end
198
223
  rescue Exception => ex
199
224
  Tml.logger.error("Failed to execute request: #{ex.message[0..255]}")
@@ -207,11 +232,20 @@ class Tml::Api::Client < Tml::Base
207
232
  raise Tml::Exception.new("Error: #{response.body}")
208
233
  end
209
234
 
210
- return if response.body.nil? or response.body == ''
211
- return response.body if opts[:raw]
235
+ if @compressed
236
+ compressed_data = response.body
237
+ return if compressed_data.nil? or compressed_data == ''
238
+
239
+ data = Zlib::GzipReader.new(StringIO.new(compressed_data.to_s)).read
240
+ Tml.logger.debug("Compressed: #{compressed_data.length} Uncompressed: #{data.length}")
241
+ else
242
+ data = response.body
243
+ end
244
+
245
+ return data if opts[:raw]
212
246
 
213
247
  begin
214
- data = JSON.parse(response.body)
248
+ data = JSON.parse(data)
215
249
  rescue Exception => ex
216
250
  raise Tml::Exception.new("Failed to parse response: #{ex.message[0..255]}")
217
251
  end
@@ -260,9 +294,13 @@ class Tml::Api::Client < Tml::Base
260
294
  #end
261
295
 
262
296
  if opts[:method] == :post
263
- Tml.logger.debug("post: [#{path}]")
297
+ Tml.logger.debug("post: #{opts[:host]}#{path}")
264
298
  else
265
- Tml.logger.debug("get: #{path}?#{to_query(params)}")
299
+ if params.any?
300
+ Tml.logger.debug("get: #{opts[:host]}#{path}?#{to_query(params)}")
301
+ else
302
+ Tml.logger.debug("get: #{opts[:host]}#{path}")
303
+ end
266
304
  end
267
305
 
268
306
  t0 = Time.now
@@ -34,18 +34,27 @@ require 'faraday'
34
34
 
35
35
  class Tml::Application < Tml::Base
36
36
  attributes :host, :id, :key, :access_token, :name, :description, :threshold, :default_locale, :default_level, :tools
37
- has_many :features, :languages, :featured_locales, :sources, :components, :tokens, :css, :shortcuts, :translations
37
+ has_many :features, :languages, :languages_by_locale, :sources, :tokens, :css, :shortcuts, :translations, :extensions
38
38
 
39
+ # Returns application cache key
39
40
  def self.cache_key
40
41
  'application'
41
42
  end
42
43
 
44
+ # Returns translations cache key
43
45
  def self.translations_cache_key(locale)
44
46
  "#{locale}/translations"
45
47
  end
46
48
 
49
+ # Fetches application definition from the service
47
50
  def fetch
48
- data = api_client.get('applications/current', {:definition => true}, {:cache_key => self.class.cache_key})
51
+ data = api_client.get('projects/current/definition',{
52
+ locale: Tml.session.current_locale,
53
+ source: Tml.session.current_source
54
+ }, {
55
+ cache_key: self.class.cache_key
56
+ })
57
+
49
58
  if data
50
59
  update_attributes(data)
51
60
  else
@@ -58,6 +67,7 @@ class Tml::Application < Tml::Base
58
67
  self
59
68
  end
60
69
 
70
+ # Updates application attributes
61
71
  def update_attributes(attrs)
62
72
  super
63
73
 
@@ -66,79 +76,119 @@ class Tml::Application < Tml::Base
66
76
  self.attributes[:languages] = hash_value(attrs, :languages).collect{ |l| Tml::Language.new(l.merge(:application => self)) }
67
77
  end
68
78
 
79
+ load_extensions(hash_value(attrs, :extensions))
80
+
69
81
  self
70
82
  end
71
83
 
84
+ # Loads application extensions, if any
85
+ def load_extensions(extensions)
86
+ return if extensions.nil?
87
+ source_locale = default_locale
88
+
89
+ cache = Tml.cache
90
+ cache = nil if not Tml.cache.enabled? or Tml.session.inline_mode?
91
+
92
+ if hash_value(extensions, :languages)
93
+ self.languages_by_locale ||= {}
94
+ hash_value(extensions, :languages).each do |locale, data|
95
+ source_locale = locale if locale != source_locale
96
+ cache.store(Tml::Language.cache_key(locale), data) if cache
97
+ self.languages_by_locale[locale] = Tml::Language.new(data.merge(
98
+ locale: locale,
99
+ application: self
100
+ ))
101
+ end
102
+ end
103
+
104
+ if hash_value(extensions, :sources)
105
+ self.sources ||= {}
106
+ hash_value(extensions, :sources).each do |source, data|
107
+ cache.store(Tml::Source.cache_key(source_locale, source), data) if cache
108
+ self.sources[source] ||= Tml::Source.new(
109
+ application: self,
110
+ source: source
111
+ )
112
+ self.sources[source].update_translations(source_locale, data['results'])
113
+ end
114
+ end
115
+ end
116
+
117
+ # Returns language by locale
72
118
  def language(locale = nil)
73
- locale = nil if locale.strip == ''
119
+ locale = nil if locale and locale.strip == ''
74
120
 
75
121
  locale ||= default_locale || Tml.config.default_locale
76
- @languages_by_locale ||= {}
77
- @languages_by_locale[locale] ||= api_client.get(
78
- "languages/#{locale}",
79
- {:definition => true},
80
- {
81
- :class => Tml::Language,
82
- :attributes => {:locale => locale, :application => self},
83
- :cache_key => Tml::Language.cache_key(locale)
84
- }
85
- )
122
+ locale = locale.to_s
123
+
124
+ self.languages_by_locale ||= {}
125
+ self.languages_by_locale[locale] ||= api_client.get("languages/#{locale}/definition", {
126
+ }, {
127
+ class: Tml::Language,
128
+ attributes: {locale: locale, application: self},
129
+ cache_key: Tml::Language.cache_key(locale)
130
+ })
86
131
  rescue Tml::Exception => e
87
132
  Tml.logger.error(e)
88
133
  Tml.logger.error(e.backtrace)
89
- @languages_by_locale[locale] = Tml.config.default_language
134
+ self.languages_by_locale[locale] = Tml.config.default_language
90
135
  end
91
136
 
137
+ # Normalizes and returns current language
138
+ # TODO: verify usage
92
139
  def current_language(locale)
93
- locale = locale.gsub('_', '-')
140
+ locale = locale.gsub('_', '-') if locale
94
141
  lang = language(locale)
95
142
  lang ||= language(locale.split('-').first) if locale.index('-')
96
143
  lang ||= Tml.config.default_language
97
144
  lang
98
145
  end
99
146
 
100
- # Mostly used for testing
147
+ # Adds a language to the application
101
148
  def add_language(new_language)
102
- @languages_by_locale ||= {}
103
- return @languages_by_locale[new_language.locale] if @languages_by_locale[new_language.locale]
149
+ self.languages_by_locale ||= {}
150
+ return self.languages_by_locale[new_language.locale] if self.languages_by_locale[new_language.locale]
104
151
  new_language.application = self
105
152
  self.languages << new_language
106
- @languages_by_locale[new_language.locale] = new_language
153
+ self.languages_by_locale[new_language.locale] = new_language
107
154
  new_language
108
155
  end
109
156
 
157
+ # Returns a list of application supported locales
110
158
  def locales
111
159
  @locales ||= languages.collect{|lang| lang.locale}
112
160
  end
113
161
 
162
+ # Returns tools data
114
163
  def tools
115
164
  @attributes[:tools] || {}
116
165
  end
117
166
 
167
+ # Returns asset url
118
168
  def url_for(path)
119
169
  "#{tools['assets']}#{path}"
120
170
  end
121
171
 
122
- def source(source, locale)
172
+ # Returns source by key
173
+ def source(key, locale)
123
174
  self.sources ||= {}
124
- self.sources[source] ||= Tml::Source.new(
125
- :application => self,
126
- :source => source
175
+ self.sources[key] ||= Tml::Source.new(
176
+ :application => self,
177
+ :source => key
127
178
  ).fetch_translations(locale)
128
179
  end
129
180
 
130
- def component(key, register = true)
131
- key = key.key if key.is_a?(Tml::Component)
132
-
133
- return self.components[key] if self.components[key]
134
- return nil unless register
135
-
136
- self.components[key] ||= api_client.post('components/register', {:component => key}, {:class => Tml::Component, :attributes => {:application => self}})
181
+ # Verifies current source path
182
+ def verify_source_path(source_key, source_path)
183
+ return if Tml.cache.enabled? and not Tml.session.inline_mode?
184
+ return if extensions.nil? or extensions['sources'].nil?
185
+ return unless extensions['sources'][source_key].nil?
186
+ @missing_keys_by_sources ||= {}
187
+ @missing_keys_by_sources[source_path] ||= {}
137
188
  end
138
189
 
139
190
  def register_missing_key(source_key, tkey)
140
191
  return if Tml.cache.enabled? and not Tml.session.inline_mode?
141
-
142
192
  @missing_keys_by_sources ||= {}
143
193
  @missing_keys_by_sources[source_key] ||= {}
144
194
  @missing_keys_by_sources[source_key][tkey.key] ||= tkey
@@ -148,7 +198,6 @@ class Tml::Application < Tml::Base
148
198
  def register_keys(keys)
149
199
  params = []
150
200
  keys.each do |source_key, keys|
151
- next unless keys.values.any?
152
201
  source = Tml::Source.new(:source => source_key, :application => self)
153
202
  params << {:source => source_key, :keys => keys.values.collect{|tkey| tkey.to_hash(:label, :description, :locale, :level)}}
154
203
  source.reset_cache
@@ -167,21 +216,6 @@ class Tml::Application < Tml::Base
167
216
  @missing_keys_by_sources = nil
168
217
  end
169
218
 
170
- def featured_languages
171
- @featured_languages ||= begin
172
- locales = api_client.get('applications/current/featured_locales', {}, {:cache_key => 'featured_locales'})
173
- (locales.nil? or locales.empty?) ? [] : languages.select{|l| locales.include?(l.locale)}
174
- end
175
- rescue
176
- []
177
- end
178
-
179
- def translators
180
- @translators ||= api_client.get('applications/current/translators', {}, {:class => Tml::Translator, :attributes => {:application => self}})
181
- rescue
182
- []
183
- end
184
-
185
219
  def reset_translation_cache
186
220
  self.sources = {}
187
221
  self.translations = {}
@@ -195,7 +229,7 @@ class Tml::Application < Tml::Base
195
229
  results = Tml.cache.fetch(Tml::Application.translations_cache_key(locale)) do
196
230
  data = {}
197
231
  unless Tml.cache.read_only?
198
- api_client.paginate('applications/current/translations', :per_page => 1000) do |translations|
232
+ api_client.paginate('projects/current/translations', :per_page => 1000) do |translations|
199
233
  data.merge!(translations)
200
234
  end
201
235
  end
@@ -261,11 +295,11 @@ class Tml::Application < Tml::Base
261
295
  end
262
296
 
263
297
  def api_client
264
- @api_client ||= Tml::Api::Client.new(:application => self)
298
+ @api_client ||= Tml::Api::Client.new(application: self)
265
299
  end
266
300
 
267
301
  def postoffice
268
- @postoffice ||= Tml::Api::PostOffice.new(:application => self)
302
+ @postoffice ||= Tml::Api::PostOffice.new(application: self)
269
303
  end
270
304
 
271
305
  end
data/lib/tml/cache.rb CHANGED
@@ -34,10 +34,6 @@ module Tml
34
34
 
35
35
  CACHE_VERSION_KEY = 'current_version'
36
36
 
37
- def self.memory
38
- @memory ||= Tml::CacheAdapters::Memory.new
39
- end
40
-
41
37
  def self.cache
42
38
  @cache ||= begin
43
39
  if Tml.config.cache_enabled?
@@ -152,5 +148,21 @@ module Tml
152
148
  # do nothing
153
149
  end
154
150
 
151
+ def strip_extensions(data)
152
+ if data.is_a?(Hash)
153
+ data = data.dup
154
+ data.delete('extensions')
155
+ return data
156
+ end
157
+
158
+ if data.is_a?(String) and data.match(/^\{/)
159
+ data = JSON.parse(data)
160
+ data.delete('extensions')
161
+ data = data.to_json
162
+ end
163
+
164
+ data
165
+ end
166
+
155
167
  end
156
168
  end
@@ -73,7 +73,7 @@ class Tml::CacheAdapters::Memcache < Tml::Cache
73
73
  def store(key, data, opts = {})
74
74
  info("Cache store: #{key}")
75
75
  ttl = opts[:ttl] || Tml.config.cache[:timeout] || 0
76
- @cache.set(versioned_key(key, opts), data, ttl)
76
+ @cache.set(versioned_key(key, opts), strip_extensions(data), ttl)
77
77
  data
78
78
  rescue Exception => ex
79
79
  pp ex
@@ -38,8 +38,8 @@ class Tml::CacheAdapters::Redis < Tml::Cache
38
38
  def initialize
39
39
  config = Tml.config.cache
40
40
 
41
- if config.adapter_config
42
- @cache = ::Redis.new(config.adapter_config)
41
+ if config[:adapter_config]
42
+ @cache = ::Redis.new(config[:adapter_config])
43
43
  else
44
44
  config[:host] ||= 'localhost'
45
45
  config[:port] ||= 6379
@@ -65,7 +65,7 @@ class Tml::CacheAdapters::Redis < Tml::Cache
65
65
  def fetch(key, opts = {})
66
66
  data = @cache.get(versioned_key(key, opts))
67
67
  if data
68
- info("Cache hit: #{key} #{data}")
68
+ info("Cache hit: #{key}")
69
69
 
70
70
  begin
71
71
  return JSON.parse(data)
@@ -95,7 +95,7 @@ class Tml::CacheAdapters::Redis < Tml::Cache
95
95
  ttl = opts[:ttl] || Tml.config.cache[:timeout]
96
96
  versioned_key = versioned_key(key, opts)
97
97
 
98
- @cache.set(versioned_key, data.to_json)
98
+ @cache.set(versioned_key, strip_extensions(data.to_json))
99
99
  @cache.expire(versioned_key, ttl) if ttl and ttl > 0
100
100
  rescue Exception => ex
101
101
  warn("Failed to store data: #{ex.message}")
data/lib/tml/config.rb CHANGED
@@ -76,7 +76,7 @@ module Tml
76
76
  class Config
77
77
  # Configuration Attributes
78
78
  attr_accessor :enabled, :locale, :default_level, :format, :application, :context_rules, :logger, :cache, :default_tokens, :localization
79
- attr_accessor :auto_init
79
+ attr_accessor :auto_init, :source_separator
80
80
 
81
81
  # Used by Rails and Sinatra extensions
82
82
  attr_accessor :current_locale_method, :current_user_method, :translator_options, :i18n_backend
@@ -91,6 +91,7 @@ module Tml
91
91
  @format = :html
92
92
  @subdomains = false
93
93
  @auto_init = true
94
+ @source_separator = '@:@'
94
95
 
95
96
  @locale = {
96
97
  default: 'en',
@@ -35,12 +35,11 @@ class Tml::Decorators::Html < Tml::Decorators::Base
35
35
  def decorate(translated_label, translation_language, target_language, translation_key, options = {})
36
36
  #Tml.logger.info("Decorating #{translated_label} of #{translation_language.locale} to #{target_language.locale}")
37
37
 
38
- # skip decoration if instructed so
39
- return translated_label if options[:skip_decorations]
40
-
41
38
  # if translation key language is the same as target language - skip decorations
42
- return translated_label if translation_key.language == target_language
43
- return translated_label unless inline_mode?
39
+ if options[:skip_decorations] or not inline_mode? or
40
+ (translation_key.application.feature_enabled?(:lock_original_content) and translation_key.language == target_language)
41
+ return translated_label
42
+ end
44
43
 
45
44
  classes = %w(tml_translatable)
46
45
 
@@ -60,7 +59,8 @@ class Tml::Decorators::Html < Tml::Decorators::Base
60
59
  classes << 'tml_fallback'
61
60
  end
62
61
 
63
- element = 'span'
62
+ element = 'tml:label'
63
+ element = 'span' if options[:use_span]
64
64
  element = 'div' if options[:use_div]
65
65
 
66
66
  html = "<#{element} class='#{classes.join(' ')}' data-translation_key='#{translation_key.key}' data-target_locale='#{target_language.locale}'>"
@@ -93,7 +93,8 @@ class Tml::Decorators::Html < Tml::Decorators::Base
93
93
  attrs << "#{key}=\"#{value.to_s.gsub('"', "\"")}\""
94
94
  end
95
95
 
96
- element = 'span'
96
+ element = 'tml:label'
97
+ element = 'span' if options[:use_span]
97
98
  element = 'div' if options[:use_div]
98
99
 
99
100
  html = "<#{element} #{attrs.join(' ')}>"
data/lib/tml/ext/array.rb CHANGED
@@ -49,7 +49,7 @@ class Array
49
49
  end
50
50
 
51
51
  # translates and joins all elements
52
- def translated_and_join(separator = '', description = '', options = {})
52
+ def translate_and_join(separator = ', ', description = '', options = {})
53
53
  self.translate(description, options).join(separator).tml_translated
54
54
  end
55
55
 
@@ -81,7 +81,7 @@ class Tml::Generators::Base
81
81
  end
82
82
 
83
83
  def application
84
- @application ||= api_client.get('applications/current', {:definition => true})
84
+ @application ||= api_client.get('projects/current/definition', {})
85
85
  end
86
86
 
87
87
  def languages
@@ -39,19 +39,20 @@ class Tml::Generators::File < Tml::Generators::Base
39
39
 
40
40
  def execute
41
41
  if cache_version == '0'
42
- log("No releases have been generated yet. Please visit your Dashboard and publish translations.")
42
+ log('No releases have been generated yet. Please visit your Dashboard and publish translations.')
43
43
  else
44
44
  log("Current cache version: #{cache_version}")
45
45
 
46
46
  archive_name = "#{cache_version}.tar.gz"
47
47
  path = "#{cache_path}/#{archive_name}"
48
- url = "#{api_client.cdn_host}/#{Tml.config.access_token}/#{archive_name}"
48
+ url = "#{api_client.cdn_host}/#{Tml.config.application[:key]}/#{archive_name}"
49
+
49
50
  log("Downloading cache file: #{url}")
50
51
  open(path, 'wb') do |file|
51
52
  file << open(url).read
52
53
  end
53
- log('Extracting cache file...')
54
54
 
55
+ log('Extracting cache file...')
55
56
  version_path = "#{cache_path}/#{cache_version}"
56
57
  untar(ungzip(File.new(path)), version_path)
57
58
  log("Cache has been stored in #{version_path}")
data/lib/tml/language.rb CHANGED
@@ -35,17 +35,26 @@ class Tml::Language < Tml::Base
35
35
  attributes :locale, :name, :english_name, :native_name, :right_to_left, :flag_url
36
36
  has_many :contexts, :cases
37
37
 
38
+ # Returns language cache key
38
39
  def self.cache_key(locale)
39
40
  File.join(locale, 'language')
40
41
  end
41
42
 
43
+ # Loads language definition from the service
42
44
  def fetch
43
- update_attributes(application.api_client.get("language/#{locale}", {}, {:cache_key => self.class.cache_key(locale)}))
45
+ update_attributes(application.api_client.get(
46
+ "language/#{locale}/definition",
47
+ {},
48
+ {
49
+ cache_key: self.class.cache_key(locale)
50
+ }
51
+ ))
44
52
  rescue Tml::Exception => ex
45
53
  Tml.logger.error("Failed to load language: #{ex}")
46
54
  self
47
55
  end
48
56
 
57
+
49
58
  def update_attributes(attrs)
50
59
  super
51
60
 
@@ -114,9 +123,9 @@ class Tml::Language < Tml::Base
114
123
  # There are three ways to call the tr method
115
124
  #
116
125
  # tr(label, description = "", tokens = {}, options = {})
117
- # or
126
+ # or
118
127
  # tr(label, tokens = {}, options = {})
119
- # or
128
+ # or
120
129
  # tr(:label => label, :description => "", :tokens => {}, :options => {})
121
130
  ########################################################################################################
122
131
 
@@ -133,12 +142,12 @@ class Tml::Language < Tml::Base
133
142
  :translations => []
134
143
  })
135
144
 
136
- #Tml.logger.info("Translating " + params[:label] + " from: " + translation_key.locale.inspect + " to " + locale.inspect)
145
+ # Tml.logger.info("Translating " + params[:label] + " from: " + translation_key.locale.inspect + " to " + locale.inspect)
137
146
 
138
147
  params[:tokens] ||= {}
139
148
  params[:tokens][:viewing_user] ||= Tml.session.current_user
140
149
 
141
- if Tml.config.disabled? or self.locale == translation_key.locale or application.nil?
150
+ if Tml.config.disabled? or application.nil?
142
151
  return translation_key.substitute_tokens(
143
152
  params[:label],
144
153
  params[:tokens],
@@ -153,11 +162,17 @@ class Tml::Language < Tml::Base
153
162
  return translation_key.translate(self, params[:tokens], params[:options]).tml_translated
154
163
  end
155
164
 
156
- if options[:dynamic] or Tml.session.block_options[:dynamic]
165
+ source_key = current_source(options)
166
+ current_source_path = source_path
157
167
 
168
+ # Dynamic sources are never registered under the parent source
169
+ if hash_value(Tml.session.block_options, :dynamic)
170
+ current_source_path = source_key
171
+ else
172
+ application.verify_source_path(source_key, current_source_path)
158
173
  end
159
174
 
160
- source_key = current_source(options)
175
+ # Tml.logger.debug("#{params[:label]}, #{source_key}")
161
176
 
162
177
  source = application.source(source_key, locale)
163
178
  cached_translations = source.cached_translations(locale, translation_key.key)
@@ -167,11 +182,25 @@ class Tml::Language < Tml::Base
167
182
  else
168
183
  params[:options] ||= {}
169
184
  params[:options][:pending] = true
170
- application.register_missing_key(source_key, translation_key)
185
+ application.register_missing_key(current_source_path, translation_key)
171
186
  end
172
187
 
173
188
  translation_key.translate(self, params[:tokens], params[:options]).tml_translated
174
189
  end
175
190
  alias :tr :translate
176
191
 
192
+ def source_path
193
+ sp = []
194
+
195
+ Tml.session.block_options_queue.each do |opts|
196
+ next unless hash_value(opts, :source)
197
+ sp << hash_value(opts, :source)
198
+ end
199
+
200
+ sp = sp.reverse
201
+ sp.unshift(Tml.session.current_source)
202
+
203
+ sp.join(Tml.config.source_separator)
204
+ end
205
+
177
206
  end
data/lib/tml/session.rb CHANGED
@@ -37,12 +37,8 @@ module Tml
37
37
  end
38
38
 
39
39
  class Session
40
- attr_accessor :application, :current_user, :current_locale, :current_language, :current_translator,
41
- :current_source, :current_component, :block_options, :cookie_params, :access_token, :tools_enabled
42
-
43
- def cookie_name
44
- "trex_#{Tml.config.access_token[0..19]}_translationexchange"
45
- end
40
+ attr_accessor :application, :block_options
41
+ attr_accessor :current_user, :current_locale, :current_language, :current_translator, :current_source
46
42
 
47
43
  def init(opts = {})
48
44
  return unless Tml.config.enabled? and Tml.config.application
@@ -51,53 +47,35 @@ module Tml
51
47
 
52
48
  Tml.cache.reset_version
53
49
 
54
- self.cookie_params = begin
55
- if opts[:cookies] and opts[:cookies][cookie_name]
56
- begin
57
- params = HashWithIndifferentAccess.new(Tml::Utils.decode(opts[:cookies][cookie_name]))
58
- params[:locale] = opts[:locale] if opts[:change_locale]
59
- params
60
- rescue Exception => ex
61
- Tml.logger.error("Failed to parse tml cookie: #{ex.message}")
62
- {}
63
- end
64
- else
65
- {}
66
- end
67
- end
68
-
69
- # Tml.logger.info(self.cookie_params.inspect)
70
-
71
- self.tools_enabled = opts[:tools_enabled]
72
50
  self.current_user = opts[:user]
73
51
  self.current_source = opts[:source] || 'index'
74
- self.current_component = opts[:component]
75
- self.current_locale = self.cookie_params[:locale] || opts[:locale] || Tml.config.default_locale
76
-
77
- if self.cookie_params['translator']
78
- self.current_translator = Tml::Translator.new(self.cookie_params['translator'])
79
- end
52
+ self.current_locale = opts[:locale]
53
+ self.current_translator = opts[:translator]
80
54
 
81
55
  self.application = Tml::Application.new(:host => host).fetch
82
56
 
83
57
  # if inline mode don't use any app cache
84
- if inline_mode?
85
- self.application = self.application.dup
86
- self.application.reset_translation_cache
87
- end
58
+ # if inline_mode?
59
+ # self.application = self.application.dup
60
+ # self.application.reset_translation_cache
61
+ # end
88
62
 
89
63
  if self.current_translator
90
64
  self.current_translator.application = self.application
91
65
  end
92
66
 
67
+ self.current_locale = preferred_locale(opts[:locale])
93
68
  self.current_language = self.application.current_language(self.current_locale)
94
- self.current_locale = self.current_language.locale
95
69
 
96
70
  self
97
71
  end
98
72
 
99
- def tools_enabled?
100
- self.tools_enabled
73
+ def preferred_locale(locales)
74
+ locales = locales.is_a?(String) ? locales.split(',') : locales
75
+ locales.each do |locale|
76
+ return locale if application.locales.include?(locale)
77
+ end
78
+ application.default_locale
101
79
  end
102
80
 
103
81
  def reset
@@ -106,8 +84,6 @@ module Tml
106
84
  self.current_language= nil
107
85
  self.current_translator= nil
108
86
  self.current_source= nil
109
- self.current_component= nil
110
- self.tools_enabled= nil
111
87
  self.block_options= nil
112
88
  end
113
89
 
@@ -177,13 +153,5 @@ module Tml
177
153
  nil
178
154
  end
179
155
 
180
- def current_component_from_block_options
181
- arr = @block_options || []
182
- arr.reverse.each do |opts|
183
- return application.component_by_key(opts[:component]) unless opts[:component].blank?
184
- end
185
- Tml.config.current_component
186
- end
187
-
188
156
  end
189
157
  end
data/lib/tml/source.rb CHANGED
@@ -64,11 +64,28 @@ class Tml::Source < Tml::Base
64
64
  self.key ||= Tml::Source.generate_key(attrs[:source])
65
65
  end
66
66
 
67
+ def update_translations(locale, results)
68
+ self.translations ||= {}
69
+ self.translations[locale] = {}
70
+
71
+ results.each do |key, data|
72
+ translations_data = data.is_a?(Hash) ? data['translations'] : data
73
+ self.translations[locale][key] = translations_data.collect do |t|
74
+ Tml::Translation.new(
75
+ locale: t['locale'] || locale,
76
+ label: t['label'],
77
+ locked: t['locked'],
78
+ context: t['context']
79
+ )
80
+ end
81
+ end
82
+ end
83
+
67
84
  def fetch_translations(locale)
68
85
  self.translations ||= {}
69
86
  return self if self.translations[locale]
70
87
 
71
- self.translations[locale] = {}
88
+ # Tml.logger.debug("Fetching #{source}")
72
89
 
73
90
  results = self.application.api_client.get(
74
91
  "sources/#{self.key}/translations",
@@ -78,17 +95,7 @@ class Tml::Source < Tml::Base
78
95
 
79
96
  return self unless results
80
97
 
81
- results.each do |key, data|
82
- translations_data = data.is_a?(Hash) ? data['translations'] : data
83
- self.translations[locale][key] = translations_data.collect do |t|
84
- Tml::Translation.new(
85
- :locale => t['locale'] || locale,
86
- :label => t['label'],
87
- :locked => t['locked'],
88
- :context => t['context']
89
- )
90
- end
91
- end
98
+ update_translations(locale, results)
92
99
 
93
100
  self
94
101
  rescue Tml::Exception => ex
@@ -182,7 +182,6 @@ module Tml
182
182
  return method.to_s.gsub('{$0}', value)
183
183
  end
184
184
 
185
- Tml.logger.error("Invalid decoration token value for #{token} in #{text}")
186
185
  return value
187
186
  end
188
187
 
@@ -190,7 +189,6 @@ module Tml
190
189
  return default_decoration(token, value)
191
190
  end
192
191
 
193
- Tml.logger.error("Missing decoration token value for #{token} in #{text}")
194
192
  value
195
193
  end
196
194
 
@@ -55,7 +55,7 @@ module Tml
55
55
  end
56
56
 
57
57
  def parse_elements
58
- name_without_parens = @full_name[1..-2]
58
+ name_without_parens = @full_name.gsub(/^%/, '')[1..-2]
59
59
  name_without_case_keys = name_without_parens.split('::').first.strip
60
60
 
61
61
  @short_name = name_without_parens.split(':').first.strip
@@ -333,7 +333,7 @@ module Tml
333
333
 
334
334
  unless Tml.session.block_options[:skip_html_escaping]
335
335
  if options[:safe] == false
336
- value = ERB::Util.html_escape(value)
336
+ value = CGI.escapeHTML(value)
337
337
  end
338
338
  end
339
339
 
@@ -54,7 +54,7 @@ class Tml::Tokens::Method < Tml::Tokens::Data
54
54
 
55
55
  def substitute(label, context, language, options = {})
56
56
  object = Tml::Utils.hash_value(context, object_name)
57
- raise Tml::Exception.new("Missing value for a token: #{full_name}") unless object
57
+ return label unless object
58
58
  object_value = sanitize(object.send(object_method_name), object, language, options.merge(:safe => false))
59
59
  label.gsub(full_name, object_value)
60
60
  end
@@ -61,7 +61,7 @@ class Tml::Tokens::Transform < Tml::Tokens::Data
61
61
  end
62
62
 
63
63
  def parse_elements
64
- name_without_parens = @full_name[1..-2]
64
+ name_without_parens = @full_name.gsub(/^%/, '')[1..-2]
65
65
  name_without_pipes = name_without_parens.split('|').first.strip
66
66
  name_without_case_keys = name_without_pipes.split('::').first.strip
67
67
 
@@ -69,7 +69,7 @@ class Tml::Tokens::Transform < Tml::Tokens::Data
69
69
  @case_keys = name_without_pipes.scan(/(::\w+)/).flatten.uniq.collect{|c| c.gsub('::', '')}
70
70
  @context_keys = name_without_case_keys.scan(/(:\w+)/).flatten.uniq.collect{|c| c.gsub(':', '')}
71
71
 
72
- @pipe_separator = (full_name.index("||") ? "||" : "|")
72
+ @pipe_separator = (full_name.index('||') ? '||' : '|')
73
73
  @piped_params = name_without_parens.split(pipe_separator).last.split(",").collect{|param| param.strip}
74
74
  end
75
75
 
@@ -110,7 +110,7 @@ class Tml::TranslationKey < Tml::Base
110
110
  end
111
111
 
112
112
  def translate(language, token_values = {}, options = {})
113
- if Tml.config.disabled? or language.locale == self.locale
113
+ if Tml.config.disabled?
114
114
  return substitute_tokens(label, token_values, language, options.merge(:fallback => false))
115
115
  end
116
116
 
data/lib/tml/utils.rb CHANGED
@@ -62,19 +62,12 @@ module Tml
62
62
  (0..16).to_a.map{|a| rand(16).to_s(16)}.join
63
63
  end
64
64
 
65
- def self.hash_value(hash, key)
66
- hash[key.to_s] || hash[key.to_sym]
65
+ def self.cookie_name(app_key)
66
+ "trex_#{app_key}"
67
67
  end
68
68
 
69
- def self.split_by_sentence(text)
70
- sentence_regex = /[^.!?\s][^.!?]*(?:[.!?](?![\'"]?\s|$)[^.!?]*)*[.!?]?[\'"]?(?=\s|$)/
71
-
72
- sentences = []
73
- text.scan(sentence_regex).each do |s|
74
- sentences << s
75
- end
76
-
77
- sentences
69
+ def self.hash_value(hash, key)
70
+ hash[key.to_s] || hash[key.to_sym]
78
71
  end
79
72
 
80
73
  def self.load_json(file_path, env = nil)
@@ -91,36 +84,47 @@ module Tml
91
84
  yaml['defaults'].rmerge(yaml[env] || {})
92
85
  end
93
86
 
94
- def self.decode(data)
87
+ def self.decode(data, secret = nil)
95
88
  payload = URI::decode(data)
96
89
  payload = Base64.decode64(payload)
97
- JSON.parse(payload)
90
+ data = JSON.parse(payload)
91
+
92
+ # TODO: Verify signature
93
+
94
+ data
98
95
  rescue Exception => ex
99
96
  {}
100
97
  end
101
98
 
102
- def self.encode(params)
99
+ def self.encode(params, secret = nil)
100
+
101
+ # TODO: Add signature
102
+
103
103
  payload = Base64.encode64(params.to_json)
104
104
  URI::encode(payload)
105
105
  rescue Exception => ex
106
106
  ''
107
107
  end
108
108
 
109
- def self.split_sentences(paragraph)
109
+ def self.split_sentences(text)
110
110
  sentence_regex = /[^.!?\s][^.!?]*(?:[.!?](?![\'"]?\s|$)[^.!?]*)*[.!?]?[\'"]?(?=\s|$)/
111
- paragraph.match(sentence_regex)
111
+
112
+ sentences = []
113
+ text.scan(sentence_regex).each do |s|
114
+ sentences << s
115
+ end
116
+
117
+ sentences
112
118
  end
113
119
 
114
- ######################################################################
115
- # Author: Iain Hecker
116
- # reference: http://github.com/iain/http_accept_language
117
- ######################################################################
118
- def self.browser_accepted_locales(request)
119
- request.env['HTTP_ACCEPT_LANGUAGE'].split(/\s*,\s*/).collect do |l|
120
+ def self.browser_accepted_locales(header)
121
+ header.split(/\s*,\s*/).collect do |l|
120
122
  l += ';q=1.0' unless l =~ /;q=\d+\.\d+$/
121
123
  l.split(';q=')
122
124
  end.sort do |x,y|
123
- raise Tml::Exception.new('Not correctly formatted') unless x.first =~ /^[a-z\-]+$/i
125
+ unless x.first =~ /^[a-z\-]+$/i
126
+ raise Tml::Exception.new('Not correctly formatted')
127
+ end
124
128
  y.last.to_f <=> x.last.to_f
125
129
  end.collect do |l|
126
130
  l.first.downcase.gsub(/-[a-z]+$/i) { |x| x.upcase }
data/lib/tml/version.rb CHANGED
@@ -30,5 +30,5 @@
30
30
  #++
31
31
 
32
32
  module Tml
33
- VERSION = '4.4.7'
33
+ VERSION = '5.0.1'
34
34
  end
metadata CHANGED
@@ -1,41 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tml
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.7
4
+ version: 5.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Berkovich
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-23 00:00:00.000000000 Z
11
+ date: 2015-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0.8'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.8'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: nokogiri
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.5'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.5'
41
41
  description: Tml core classes that can be used by any Ruby framework
@@ -102,17 +102,17 @@ require_paths:
102
102
  - lib
103
103
  required_ruby_version: !ruby/object:Gem::Requirement
104
104
  requirements:
105
- - - '>='
105
+ - - ">="
106
106
  - !ruby/object:Gem::Version
107
107
  version: '0'
108
108
  required_rubygems_version: !ruby/object:Gem::Requirement
109
109
  requirements:
110
- - - '>='
110
+ - - ">="
111
111
  - !ruby/object:Gem::Version
112
112
  version: '0'
113
113
  requirements: []
114
114
  rubyforge_project:
115
- rubygems_version: 2.4.1
115
+ rubygems_version: 2.4.5
116
116
  signing_key:
117
117
  specification_version: 4
118
118
  summary: Tml Core Classes