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 +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
|