tml 4.4.7 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
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