i18n-backend-jargon 0.2.3
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 +7 -0
- data/lib/i18n/backend/jargon.rb +202 -0
- data/lib/i18n/backend/jargon/etag_http_client.rb +33 -0
- data/lib/i18n/backend/jargon/null_cache.rb +19 -0
- data/lib/i18n/backend/jargon/version.rb +7 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 23ce2a5f4c297d66c0132207381587951f95399c
|
4
|
+
data.tar.gz: 6262386043b006bebfcbde5cb7dab809ad736b3f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cc9fbb888f0c66417878cdae746ca71592f45530a3abc63f158075fc91b66da1636515492cde7c3476b910c38f7da8ad9588e5a16ceb806369acfa49b160984a
|
7
|
+
data.tar.gz: e43273ddbd12a2d59aba075473976bde7abaaf610b2698f9312ef8037618ed7eb0006bd24f26eaf75a9d01f57b88c7cf960150240de04f4b3324a2bf0664bad6
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
require 'i18n/backend/transliterator'
|
3
|
+
require 'i18n/backend/base'
|
4
|
+
require 'gem_of_thrones'
|
5
|
+
require 'i18n/backend/jargon/version'
|
6
|
+
require 'i18n/backend/jargon/etag_http_client'
|
7
|
+
require 'i18n/backend/jargon/null_cache'
|
8
|
+
|
9
|
+
module I18n
|
10
|
+
module Backend
|
11
|
+
class Jargon
|
12
|
+
include ::I18n::Backend::Base
|
13
|
+
|
14
|
+
def initialize(options)
|
15
|
+
@config = {
|
16
|
+
host: 'http://localhost',
|
17
|
+
http_open_timeout: 1,
|
18
|
+
http_read_timeout: 1,
|
19
|
+
polling_interval: 10*60,
|
20
|
+
cache: NullCache.new,
|
21
|
+
poll: true,
|
22
|
+
exception_handler: lambda{|e| $stderr.puts e },
|
23
|
+
memory_cache_size: 10
|
24
|
+
}.merge(options)
|
25
|
+
raise ArgumentError if @config[:uuid].nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialized?
|
29
|
+
@initialized ||= false
|
30
|
+
end
|
31
|
+
|
32
|
+
def stop_polling
|
33
|
+
@stop_polling = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def reload!
|
37
|
+
@initialized = false
|
38
|
+
@translations = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def available_locales
|
42
|
+
@available_locales ||= download_available_locales
|
43
|
+
end
|
44
|
+
|
45
|
+
def locale_path(locale)
|
46
|
+
localization_path + "/#{locale}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def localization_path
|
50
|
+
"api/uuid/#{@config[:uuid]}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def translate(locale, key, options = {})
|
54
|
+
raise InvalidLocale.new(locale) unless locale
|
55
|
+
entry = key && lookup(locale, key, options[:scope], options)
|
56
|
+
|
57
|
+
if options.empty?
|
58
|
+
entry = resolve(locale, key, entry, options)
|
59
|
+
else
|
60
|
+
count, default = options.values_at(:count, :default)
|
61
|
+
values = options.except(*RESERVED_KEYS)
|
62
|
+
entry = entry.nil? && default ?
|
63
|
+
default(locale, key, default, options) : resolve(locale, key, entry, options)
|
64
|
+
end
|
65
|
+
|
66
|
+
throw(:exception, I18n::MissingTranslation.new(locale, key, options)) if entry.nil?
|
67
|
+
entry = entry.dup if entry.is_a?(String)
|
68
|
+
|
69
|
+
entry = pluralize(locale, entry, count) if count
|
70
|
+
entry = interpolate(locale, entry, values) if values
|
71
|
+
entry
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def download_available_locales
|
77
|
+
init_translations unless initialized?
|
78
|
+
download_localization
|
79
|
+
end
|
80
|
+
|
81
|
+
def init_translations
|
82
|
+
@http_client = EtagHttpClient.new(@config)
|
83
|
+
@translations = {}
|
84
|
+
start_polling if @config[:poll]
|
85
|
+
@initialized = true
|
86
|
+
end
|
87
|
+
|
88
|
+
def start_polling
|
89
|
+
Thread.new do
|
90
|
+
until @stop_polling
|
91
|
+
sleep(@config[:polling_interval])
|
92
|
+
update_caches
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def lookup(locale, key, scope = [], options = {})
|
98
|
+
init_translations unless initialized?
|
99
|
+
key = ::I18n.normalize_keys(locale, key, scope, options[:separator])[1..-1].join('.')
|
100
|
+
lookup_key translations(locale), key
|
101
|
+
end
|
102
|
+
|
103
|
+
def resolve(locale, object, subject, options = {})
|
104
|
+
return subject if options[:resolve] == false
|
105
|
+
result = catch(:exception) do
|
106
|
+
case subject
|
107
|
+
when Symbol
|
108
|
+
I18n.translate(subject, options.merge(:locale => locale, :throw => true))
|
109
|
+
when Proc
|
110
|
+
date_or_time = options.delete(:object) || object
|
111
|
+
resolve(locale, object, subject.call(date_or_time, options))
|
112
|
+
else
|
113
|
+
subject
|
114
|
+
end
|
115
|
+
end
|
116
|
+
result unless result.is_a?(MissingTranslation)
|
117
|
+
end
|
118
|
+
|
119
|
+
def translations(locale)
|
120
|
+
translations_from_cache(locale)
|
121
|
+
@translations[locale]
|
122
|
+
end
|
123
|
+
|
124
|
+
def update_caches
|
125
|
+
@translations.keys.each do |locale|
|
126
|
+
if @config[:cache].is_a?(NullCache)
|
127
|
+
download_and_cache_translations(locale)
|
128
|
+
else
|
129
|
+
locked_update_cache(locale)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def locked_update_cache(locale)
|
135
|
+
@aspirants ||= {}
|
136
|
+
aspirant = @aspirants[locale] ||= GemOfThrones.new(
|
137
|
+
:cache => @config[:cache],
|
138
|
+
:timeout => (@config[:polling_interval] * 3).ceil,
|
139
|
+
:cache_key => "i18n/backend/http/locked_update_caches/#{locale}"
|
140
|
+
)
|
141
|
+
if aspirant.rise_to_power
|
142
|
+
download_and_cache_translations(locale)
|
143
|
+
else
|
144
|
+
update_memory_cache_from_cache(locale)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def update_memory_cache_from_cache(locale)
|
149
|
+
@translations[locale] = translations_from_cache(locale)
|
150
|
+
end
|
151
|
+
|
152
|
+
def translations_from_cache(locale)
|
153
|
+
translations = @config[:cache].read(cache_key(locale))
|
154
|
+
if translations.nil?
|
155
|
+
download_and_cache_translations(locale)
|
156
|
+
else
|
157
|
+
@translations[locale] = translations
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def cache_key(locale)
|
162
|
+
"i18n/backend/http/translations/#{locale}"
|
163
|
+
end
|
164
|
+
|
165
|
+
def download_and_cache_translations(locale)
|
166
|
+
@http_client.download(locale_path(locale)) do |result|
|
167
|
+
translations = parse_locale(result)
|
168
|
+
@config[:cache].write(cache_key(locale), translations)
|
169
|
+
@translations[locale] = translations
|
170
|
+
end
|
171
|
+
rescue => e
|
172
|
+
@config[:exception_handler].call(e)
|
173
|
+
@translations[locale] = {} # do not write distributed cache
|
174
|
+
end
|
175
|
+
|
176
|
+
def download_localization
|
177
|
+
@http_client.download(localization_path) do |result|
|
178
|
+
parse_localization(result)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def parse_locale(body)
|
183
|
+
j = JSON.parse(JSON.parse(body)['locale']['json'])
|
184
|
+
flat_hash(j).map{ |k,v| [k.sub(/\.$/, ''), v] }.to_h
|
185
|
+
end
|
186
|
+
|
187
|
+
def parse_localization(body)
|
188
|
+
JSON.load(body)['localization']['available_locales']
|
189
|
+
end
|
190
|
+
|
191
|
+
def flat_hash(hash, k = '')
|
192
|
+
return {k => hash} unless hash.is_a?(Hash)
|
193
|
+
hash.inject({}){ |h, v| h.merge! flat_hash(v[-1], k +v[0].to_s + '.') }
|
194
|
+
end
|
195
|
+
|
196
|
+
# hook for extension with other resolution method
|
197
|
+
def lookup_key(translations, key)
|
198
|
+
translations[key]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module I18n
|
4
|
+
module Backend
|
5
|
+
class Jargon
|
6
|
+
class EtagHttpClient
|
7
|
+
def initialize(options)
|
8
|
+
@options = options
|
9
|
+
@etags = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def download(path)
|
13
|
+
@client ||= Faraday.new(@options[:host])
|
14
|
+
response = @client.get(path) do |request|
|
15
|
+
request.headers["If-None-Match"] = @etags[path] if @etags[path]
|
16
|
+
request.headers["Accept"] = 'application/json'
|
17
|
+
request.options[:timeout] = @options[:http_read_timeout]
|
18
|
+
request.options[:open_timeout] = @options[:http_open_timeout]
|
19
|
+
end
|
20
|
+
|
21
|
+
@etags[path] = response['ETag']
|
22
|
+
|
23
|
+
case response.status
|
24
|
+
when 200 then yield response.body
|
25
|
+
when 304
|
26
|
+
else
|
27
|
+
raise "Failed request: #{response.inspect}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: i18n-backend-jargon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Grosser
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: i18n
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: gem_of_thrones
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: faraday
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description:
|
56
|
+
email: michael@grosser.it
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- lib/i18n/backend/jargon.rb
|
62
|
+
- lib/i18n/backend/jargon/etag_http_client.rb
|
63
|
+
- lib/i18n/backend/jargon/null_cache.rb
|
64
|
+
- lib/i18n/backend/jargon/version.rb
|
65
|
+
homepage: http://github.com/grosser/
|
66
|
+
licenses:
|
67
|
+
- MIT
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.2.2
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: Rails I18n Backend for Http APIs with etag-aware background polling and memory+[memcache]
|
89
|
+
caching
|
90
|
+
test_files: []
|