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 +4 -4
- data/lib/tml/api/client.rb +50 -12
- data/lib/tml/application.rb +84 -50
- data/lib/tml/cache.rb +16 -4
- data/lib/tml/cache_adapters/memcache.rb +1 -1
- data/lib/tml/cache_adapters/redis.rb +4 -4
- data/lib/tml/config.rb +2 -1
- data/lib/tml/decorators/html.rb +8 -7
- data/lib/tml/ext/array.rb +1 -1
- data/lib/tml/generators/base.rb +1 -1
- data/lib/tml/generators/file.rb +4 -3
- data/lib/tml/language.rb +37 -8
- data/lib/tml/session.rb +15 -47
- data/lib/tml/source.rb +19 -12
- data/lib/tml/tokenizers/decoration.rb +0 -2
- data/lib/tml/tokens/data.rb +2 -2
- data/lib/tml/tokens/method.rb +1 -1
- data/lib/tml/tokens/transform.rb +2 -2
- data/lib/tml/translation_key.rb +1 -1
- data/lib/tml/utils.rb +27 -23
- data/lib/tml/version.rb +1 -1
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a514613d1a01ea544a896e526f38e5f9843df78
|
4
|
+
data.tar.gz: b1dceccffd886b61d390dbf323da3578b181835f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f86dea985be31eaf6fcfc391615bc1287dd932704c736781834ba7d5acb029ff2f7e251893ff0593d5a1502442e62f392bc472d2f54279712661a4290e8267ed
|
7
|
+
data.tar.gz: 47cb409b52c980688d033f7e5b1588d1a4424d1e501f12bf564e17813b4fca668b2065ba23ccda4c24286a3a703c758c0386b1fc1b1fd45ec264c9a727e26dd1
|
data/lib/tml/api/client.rb
CHANGED
@@ -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 = "
|
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
|
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(
|
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 =>
|
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
|
-
|
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
|
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
|
-
|
211
|
-
|
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(
|
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
|
-
|
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
|
data/lib/tml/application.rb
CHANGED
@@ -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, :
|
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('
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
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
|
-
#
|
147
|
+
# Adds a language to the application
|
101
148
|
def add_language(new_language)
|
102
|
-
|
103
|
-
return
|
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
|
-
|
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
|
-
|
172
|
+
# Returns source by key
|
173
|
+
def source(key, locale)
|
123
174
|
self.sources ||= {}
|
124
|
-
self.sources[
|
125
|
-
:application
|
126
|
-
: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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
return
|
134
|
-
return
|
135
|
-
|
136
|
-
|
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('
|
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(:
|
298
|
+
@api_client ||= Tml::Api::Client.new(application: self)
|
265
299
|
end
|
266
300
|
|
267
301
|
def postoffice
|
268
|
-
@postoffice ||= Tml::Api::PostOffice.new(:
|
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
|
42
|
-
@cache = ::Redis.new(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}
|
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',
|
data/lib/tml/decorators/html.rb
CHANGED
@@ -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
|
-
|
43
|
-
|
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 = '
|
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 = '
|
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
|
52
|
+
def translate_and_join(separator = ', ', description = '', options = {})
|
53
53
|
self.translate(description, options).join(separator).tml_translated
|
54
54
|
end
|
55
55
|
|
data/lib/tml/generators/base.rb
CHANGED
data/lib/tml/generators/file.rb
CHANGED
@@ -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(
|
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.
|
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(
|
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
|
-
#
|
126
|
+
# or
|
118
127
|
# tr(label, tokens = {}, options = {})
|
119
|
-
#
|
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
|
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
|
-
|
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
|
-
|
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(
|
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, :
|
41
|
-
|
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.
|
75
|
-
self.
|
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
|
-
|
86
|
-
|
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
|
100
|
-
|
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
|
-
|
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
|
-
|
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
|
|
data/lib/tml/tokens/data.rb
CHANGED
@@ -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 =
|
336
|
+
value = CGI.escapeHTML(value)
|
337
337
|
end
|
338
338
|
end
|
339
339
|
|
data/lib/tml/tokens/method.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/tml/tokens/transform.rb
CHANGED
@@ -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
|
|
data/lib/tml/translation_key.rb
CHANGED
@@ -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?
|
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.
|
66
|
-
|
65
|
+
def self.cookie_name(app_key)
|
66
|
+
"trex_#{app_key}"
|
67
67
|
end
|
68
68
|
|
69
|
-
def self.
|
70
|
-
|
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(
|
109
|
+
def self.split_sentences(text)
|
110
110
|
sentence_regex = /[^.!?\s][^.!?]*(?:[.!?](?![\'"]?\s|$)[^.!?]*)*[.!?]?[\'"]?(?=\s|$)/
|
111
|
-
|
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
|
-
|
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
|
-
|
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
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
|
+
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-
|
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.
|
115
|
+
rubygems_version: 2.4.5
|
116
116
|
signing_key:
|
117
117
|
specification_version: 4
|
118
118
|
summary: Tml Core Classes
|