tr8n_client_sdk 4.1.3 → 4.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/views/tr8n_client_sdk/tags/_language_selector.html.erb +53 -5
- data/lib/tr8n/cache_adapters/rails.rb +100 -0
- data/lib/tr8n_client_sdk.rb +1 -0
- data/lib/tr8n_client_sdk/extensions/action_common_methods.rb +16 -4
- data/lib/tr8n_client_sdk/extensions/action_controller_extension.rb +47 -16
- data/lib/tr8n_client_sdk/extensions/action_view_extension.rb +11 -6
- data/lib/tr8n_client_sdk/extensions/string.rb +44 -0
- data/lib/tr8n_client_sdk/railtie.rb +2 -0
- data/lib/tr8n_client_sdk/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9826d9afd314a52e2df7f0b362a2365d293812b7
|
4
|
+
data.tar.gz: 771b6ba185227163c3dcf99a151d59162eb7685e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 228ea21e988c0c8e98b3e2366858dfebbc3ec22ef2acd2c3284b362c6c0693dd36fe32eee833f6c67111f507e45884157b491689e063773826f67cd4e9d23cb6
|
7
|
+
data.tar.gz: 3c0af5a81b95fe4150b0e36336b0ddd1a8bb977ede7a605072d31bb9529b282e4c6fc2750aed38258df2e6f0eb3dad50648a80c9eb87a42815b2bc3afe537e3d
|
@@ -1,5 +1,53 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
<% if opts[:type] == :dropdown %>
|
2
|
+
<script>
|
3
|
+
function tr8n_change_locale(selector) {
|
4
|
+
var query_parts = window.location.href.split('?');
|
5
|
+
var query = query_parts.length > 1 ? query_parts[1] : null;
|
6
|
+
var params = {};
|
7
|
+
if (query) {
|
8
|
+
var vars = query.split('&');
|
9
|
+
for (var i = 0; i < vars.length; i++) {
|
10
|
+
var pair = vars[i].split('=');
|
11
|
+
params[pair[0]] = pair[1];
|
12
|
+
}
|
13
|
+
}
|
14
|
+
params['locale'] = selector.options[selector.selectedIndex].value;
|
15
|
+
|
16
|
+
query = [];
|
17
|
+
var keys = Object.keys(params);
|
18
|
+
for (var i = 0; i < keys.length; i++) {
|
19
|
+
query.push(encodeURIComponent(keys[i]) + "=" + encodeURIComponent(params[keys[i]]));
|
20
|
+
}
|
21
|
+
|
22
|
+
var destination = query_parts[0] + '?' + query.join("&");
|
23
|
+
window.location = destination;
|
24
|
+
}
|
25
|
+
</script>
|
26
|
+
<select id="tr8n_language_selector" onchange="tr8n_change_locale(this)" style="<%=opts[:style]%>" class="<%=opts[:class]%>">
|
27
|
+
<% tr8n_application.languages.each do |lang| %>
|
28
|
+
<option dir='ltr' value="<%=lang.locale%>" <%="selected" if lang.locale == tr8n_current_locale %>>
|
29
|
+
<% if opts[:language] == :native %>
|
30
|
+
<%= lang.native_name %>
|
31
|
+
<% elsif opts[:language] == :english %>
|
32
|
+
<%= lang.english_name %>
|
33
|
+
<% else %>
|
34
|
+
<%= lang.name %>
|
35
|
+
<% end %>
|
36
|
+
</option>
|
37
|
+
<% end %>
|
38
|
+
</select>
|
39
|
+
<% else %>
|
40
|
+
<a href="#" onClick="Tr8n.UI.LanguageSelector.show(); return false;">
|
41
|
+
<% unless opts[:hide_flag] %>
|
42
|
+
<%= image_tag(tr8n_current_language.flag_url, :style => "align:middle") %>
|
43
|
+
|
44
|
+
<% end %>
|
45
|
+
<% if opts[:language] == :native %>
|
46
|
+
<%= tr8n_current_language.native_name %>
|
47
|
+
<% elsif opts[:language] == :english %>
|
48
|
+
<%= tr8n_current_language.english_name %>
|
49
|
+
<% else %>
|
50
|
+
<%= tr8n_current_language.name %>
|
51
|
+
<% end %>
|
52
|
+
</a>
|
53
|
+
<% end %>
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#--
|
3
|
+
# Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
|
4
|
+
#
|
5
|
+
# _______ _ _ _ ______ _
|
6
|
+
# |__ __| | | | | (_) | ____| | |
|
7
|
+
# | |_ __ __ _ _ __ ___| | __ _| |_ _ ___ _ __ | |__ __ _____| |__ __ _ _ __ __ _ ___
|
8
|
+
# | | '__/ _` | '_ \/ __| |/ _` | __| |/ _ \| '_ \| __| \ \/ / __| '_ \ / _` | '_ \ / _` |/ _ \
|
9
|
+
# | | | | (_| | | | \__ \ | (_| | |_| | (_) | | | | |____ > < (__| | | | (_| | | | | (_| | __/
|
10
|
+
# |_|_| \__,_|_| |_|___/_|\__,_|\__|_|\___/|_| |_|______/_/\_\___|_| |_|\__,_|_| |_|\__, |\___|
|
11
|
+
# __/ |
|
12
|
+
# |___/
|
13
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
14
|
+
# a copy of this software and associated documentation files (the
|
15
|
+
# "Software"), to deal in the Software without restriction, including
|
16
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
17
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
18
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
19
|
+
# the following conditions:
|
20
|
+
#
|
21
|
+
# The above copyright notice and this permission notice shall be
|
22
|
+
# included in all copies or substantial portions of the Software.
|
23
|
+
#
|
24
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
25
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
26
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
27
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
28
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
29
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
30
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
31
|
+
#++
|
32
|
+
|
33
|
+
require 'dalli' if defined?(Dalli)
|
34
|
+
|
35
|
+
class Tr8n::CacheAdapters::Rails < Tr8n::Cache
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
Tr8n.logger.info("Initializing Rails cache...")
|
39
|
+
@cache = Rails.cache
|
40
|
+
end
|
41
|
+
|
42
|
+
def cache_name
|
43
|
+
@cache.class.name
|
44
|
+
end
|
45
|
+
|
46
|
+
def read_only?
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
def fetch(key, opts = {})
|
51
|
+
miss = false
|
52
|
+
data = @cache.fetch(versioned_key(key, opts)) do
|
53
|
+
info("Cache miss: #{key}")
|
54
|
+
miss = true
|
55
|
+
if block_given?
|
56
|
+
yield
|
57
|
+
else
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
info("Cache hit: #{key}") unless miss
|
62
|
+
data
|
63
|
+
rescue Exception => ex
|
64
|
+
warn("Failed to retrieve data: #{ex.message}")
|
65
|
+
return nil unless block_given?
|
66
|
+
yield
|
67
|
+
end
|
68
|
+
|
69
|
+
def store(key, data, opts = {})
|
70
|
+
info("Cache store: #{key}")
|
71
|
+
@cache.write(versioned_key(key, opts), data)
|
72
|
+
data
|
73
|
+
rescue Exception => ex
|
74
|
+
warn("Failed to store data: #{ex.message}")
|
75
|
+
key
|
76
|
+
end
|
77
|
+
|
78
|
+
def delete(key, opts = {})
|
79
|
+
info("Cache delete: #{key}")
|
80
|
+
@cache.delete(versioned_key(key, opts))
|
81
|
+
key
|
82
|
+
rescue Exception => ex
|
83
|
+
warn("Failed to delete data: #{ex.message}")
|
84
|
+
key
|
85
|
+
end
|
86
|
+
|
87
|
+
def exist?(key, opts = {})
|
88
|
+
data = @cache.fetch(versioned_key(key, opts))
|
89
|
+
not data.nil?
|
90
|
+
rescue Exception => ex
|
91
|
+
warn("Failed to check if key exists: #{ex.message}")
|
92
|
+
false
|
93
|
+
end
|
94
|
+
|
95
|
+
def clear(opts = {})
|
96
|
+
info("Cache clear")
|
97
|
+
rescue Exception => ex
|
98
|
+
warn("Failed to clear cache: #{ex.message}")
|
99
|
+
end
|
100
|
+
end
|
data/lib/tr8n_client_sdk.rb
CHANGED
@@ -99,20 +99,32 @@ module Tr8nClientSdk
|
|
99
99
|
## Common methods - wrappers
|
100
100
|
######################################################################
|
101
101
|
|
102
|
+
def tr8n_session
|
103
|
+
Tr8n.session
|
104
|
+
end
|
105
|
+
|
102
106
|
def tr8n_application
|
103
|
-
|
107
|
+
tr8n_session.application
|
104
108
|
end
|
105
109
|
|
106
110
|
def tr8n_current_user
|
107
|
-
|
111
|
+
tr8n_session.current_user
|
108
112
|
end
|
109
113
|
|
110
114
|
def tr8n_current_translator
|
111
|
-
|
115
|
+
tr8n_session.current_translator
|
116
|
+
end
|
117
|
+
|
118
|
+
def tr8n_current_locale
|
119
|
+
tr8n_session.current_language.locale
|
112
120
|
end
|
113
121
|
|
114
122
|
def tr8n_current_language
|
115
|
-
|
123
|
+
tr8n_session.current_language
|
124
|
+
end
|
125
|
+
|
126
|
+
def tr8n_language_dir
|
127
|
+
tr8n_current_language.dir
|
116
128
|
end
|
117
129
|
|
118
130
|
end
|
@@ -37,6 +37,13 @@ module Tr8nClientSdk
|
|
37
37
|
base.send(:include, InstanceMethods)
|
38
38
|
base.before_filter :tr8n_init_client_sdk
|
39
39
|
base.after_filter :tr8n_reset_client_sdk
|
40
|
+
if defined? base.rescue_from
|
41
|
+
base.rescue_from 'Tr8n::Exception' do |e|
|
42
|
+
Tr8n.logger.error(e)
|
43
|
+
Tr8n.logger.error(e.backtrace)
|
44
|
+
raise e
|
45
|
+
end
|
46
|
+
end
|
40
47
|
end
|
41
48
|
|
42
49
|
module InstanceMethods
|
@@ -65,20 +72,55 @@ module Tr8nClientSdk
|
|
65
72
|
nil
|
66
73
|
end
|
67
74
|
|
75
|
+
def tr8n_cookie_name
|
76
|
+
"tr8n_#{tr8n_application.key}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def tr8n_cookie
|
80
|
+
request.cookies[tr8n_cookie_name]
|
81
|
+
end
|
82
|
+
|
83
|
+
def tr8n_cookie_params
|
84
|
+
@tr8n_cookie_params ||= begin
|
85
|
+
if tr8n_cookie
|
86
|
+
cookie_params = Tr8n::Utils.decode_and_verify_params(tr8n_cookie, tr8n_application.secret)
|
87
|
+
Tr8n.logger.info(cookie_params.inspect)
|
88
|
+
cookie_params
|
89
|
+
else
|
90
|
+
{}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def tr8n_translator_locale
|
96
|
+
tr8n_cookie_params["locale"]
|
97
|
+
end
|
98
|
+
|
68
99
|
def tr8n_init_current_locale
|
69
100
|
self.send(Tr8n.config.current_locale_method) if Tr8n.config.current_locale_method
|
70
101
|
rescue
|
102
|
+
# fallback onto the cookie params from the service
|
103
|
+
return tr8n_translator_locale if tr8n_translator_locale
|
104
|
+
|
71
105
|
# fallback to the default session based locale implementation
|
72
106
|
# choose the first language from the accepted languages header
|
73
107
|
session[:locale] = tr8n_user_preferred_locale unless session[:locale]
|
74
108
|
session[:locale] = params[:locale] if params[:locale]
|
75
|
-
session[:locale]
|
109
|
+
session[:locale] || Tr8n.config.default_locale
|
76
110
|
end
|
77
111
|
|
78
112
|
def tr8n_init_current_user
|
79
113
|
self.send(Tr8n.config.current_user_method) if Tr8n.config.current_user_method
|
80
114
|
rescue
|
81
|
-
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
|
118
|
+
def tr8n_init_current_translator
|
119
|
+
if tr8n_cookie_params["translator"]
|
120
|
+
Tr8n::Translator.new(tr8n_cookie_params["translator"].merge(:application => tr8n_application))
|
121
|
+
else
|
122
|
+
nil
|
123
|
+
end
|
82
124
|
end
|
83
125
|
|
84
126
|
def tr8n_init_client_sdk
|
@@ -88,21 +130,10 @@ module Tr8nClientSdk
|
|
88
130
|
|
89
131
|
Tr8n.session.init
|
90
132
|
|
91
|
-
translator = nil
|
92
|
-
|
93
|
-
cookie_name = "tr8n_#{tr8n_application.key}"
|
94
|
-
if request.cookies[cookie_name]
|
95
|
-
cookie_params = Tr8n::Utils.decode_and_verify_params(request.cookies[cookie_name], tr8n_application.secret)
|
96
|
-
Tr8n.logger.info(cookie_params.inspect)
|
97
|
-
locale = cookie_params["locale"]
|
98
|
-
translator = Tr8n::Translator.new(cookie_params["translator"].merge(:application => tr8n_application)) unless cookie_params["translator"].nil?
|
99
|
-
end
|
100
|
-
|
101
|
-
locale ||= tr8n_init_current_locale || Tr8n.config.default_locale
|
102
|
-
|
103
133
|
Tr8n.session.current_user = tr8n_init_current_user
|
104
|
-
Tr8n.session.
|
105
|
-
Tr8n.session.
|
134
|
+
Tr8n.session.current_language = tr8n_application.language(tr8n_init_current_locale)
|
135
|
+
Tr8n.session.current_translator = tr8n_init_current_translator
|
136
|
+
|
106
137
|
Tr8n.session.current_source = tr8n_source
|
107
138
|
Tr8n.session.current_component = tr8n_component
|
108
139
|
end
|
@@ -128,6 +128,7 @@ module Tr8nClientSdk
|
|
128
128
|
Thread.current[:block_options].pop
|
129
129
|
ret
|
130
130
|
end
|
131
|
+
alias_method :tr8n_block, :tr8n_with_options_tag
|
131
132
|
|
132
133
|
def tr8n_when_string_tag(time, opts = {})
|
133
134
|
elapsed_seconds = Time.now - time
|
@@ -137,12 +138,12 @@ module Tr8nClientSdk
|
|
137
138
|
tr('a moment ago', 'Time reference')
|
138
139
|
elsif elapsed_seconds < 55.minutes
|
139
140
|
elapsed_minutes = (elapsed_seconds / 1.minute).to_i
|
140
|
-
tr(
|
141
|
+
tr('{minutes || minute} ago', 'Time reference', :minutes => elapsed_minutes)
|
141
142
|
elsif elapsed_seconds < 1.75.hours
|
142
|
-
tr(
|
143
|
+
tr('about an hour ago', 'Time reference')
|
143
144
|
elsif elapsed_seconds < 12.hours
|
144
145
|
elapsed_hours = (elapsed_seconds / 1.hour).to_i
|
145
|
-
tr(
|
146
|
+
tr('{hours || hour} ago', 'Time reference', :hours => elapsed_hours)
|
146
147
|
elsif time.today_in_time_zone?
|
147
148
|
display_time(time, :time_am_pm)
|
148
149
|
elsif time.yesterday_in_time_zone?
|
@@ -150,16 +151,16 @@ module Tr8nClientSdk
|
|
150
151
|
elsif elapsed_seconds < 5.days
|
151
152
|
time.tr(:day_time).gsub('/ ', '/').sub(/^[0:]*/,"")
|
152
153
|
elsif time.same_year_in_time_zone?
|
153
|
-
time.tr(:monthname_abbr_time).gsub('/ ', '/').sub(/^[0:]*/,
|
154
|
+
time.tr(:monthname_abbr_time).gsub('/ ', '/').sub(/^[0:]*/, '')
|
154
155
|
else
|
155
|
-
time.tr(:monthname_abbr_year_time).gsub('/ ', '/').sub(/^[0:]*/,
|
156
|
+
time.tr(:monthname_abbr_year_time).gsub('/ ', '/').sub(/^[0:]*/, '')
|
156
157
|
end
|
157
158
|
end
|
158
159
|
|
159
160
|
def tr8n_url_tag(path)
|
160
161
|
tr8n_application.url_for(path)
|
161
162
|
end
|
162
|
-
|
163
|
+
|
163
164
|
######################################################################
|
164
165
|
## Language Direction Support
|
165
166
|
######################################################################
|
@@ -176,5 +177,9 @@ module Tr8nClientSdk
|
|
176
177
|
"dir='#{lang.dir}'".html_safe
|
177
178
|
end
|
178
179
|
|
180
|
+
def tr8n_lang_attribute_tag(lang = tr8n_current_language)
|
181
|
+
"lang=#{lang.locale}".html_safe
|
182
|
+
end
|
183
|
+
|
179
184
|
end
|
180
185
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
|
3
|
+
#
|
4
|
+
# _______ _ _ _ ______ _
|
5
|
+
# |__ __| | | | | (_) | ____| | |
|
6
|
+
# | |_ __ __ _ _ __ ___| | __ _| |_ _ ___ _ __ | |__ __ _____| |__ __ _ _ __ __ _ ___
|
7
|
+
# | | '__/ _` | '_ \/ __| |/ _` | __| |/ _ \| '_ \| __| \ \/ / __| '_ \ / _` | '_ \ / _` |/ _ \
|
8
|
+
# | | | | (_| | | | \__ \ | (_| | |_| | (_) | | | | |____ > < (__| | | | (_| | | | | (_| | __/
|
9
|
+
# |_|_| \__,_|_| |_|___/_|\__,_|\__|_|\___/|_| |_|______/_/\_\___|_| |_|\__,_|_| |_|\__, |\___|
|
10
|
+
# __/ |
|
11
|
+
# |___/
|
12
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
13
|
+
# a copy of this software and associated documentation files (the
|
14
|
+
# "Software"), to deal in the Software without restriction, including
|
15
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
16
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
17
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
18
|
+
# the following conditions:
|
19
|
+
#
|
20
|
+
# The above copyright notice and this permission notice shall be
|
21
|
+
# included in all copies or substantial portions of the Software.
|
22
|
+
#
|
23
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
24
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
25
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
26
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
27
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
28
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
29
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
30
|
+
#++
|
31
|
+
|
32
|
+
class String
|
33
|
+
|
34
|
+
def tr8n_translated
|
35
|
+
return self if frozen?
|
36
|
+
@tr8n_translated = true
|
37
|
+
self.html_safe
|
38
|
+
end
|
39
|
+
|
40
|
+
def tr8n_translated?
|
41
|
+
@tr8n_translated
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -37,6 +37,8 @@ require File.join(File.dirname(__FILE__), 'extensions/action_common_methods')
|
|
37
37
|
require File.join(File.dirname(__FILE__), 'extensions/action_view_extension')
|
38
38
|
require File.join(File.dirname(__FILE__), 'extensions/action_controller_extension')
|
39
39
|
|
40
|
+
require File.join(File.dirname(__FILE__), 'extensions/string')
|
41
|
+
|
40
42
|
module Tr8nClientSdk
|
41
43
|
class Railtie < ::Rails::Railtie #:nodoc:
|
42
44
|
initializer 'tr8n_client_sdk' do |app|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tr8n_client_sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.1.
|
4
|
+
version: 4.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Berkovich
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-08-
|
11
|
+
date: 2014-08-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -54,11 +54,13 @@ files:
|
|
54
54
|
- app/views/tr8n_client_sdk/tags/_scripts.html.erb
|
55
55
|
- config/routes.rb
|
56
56
|
- lib/tasks/tr8n_client_sdk.rake
|
57
|
+
- lib/tr8n/cache_adapters/rails.rb
|
57
58
|
- lib/tr8n_client_sdk.rb
|
58
59
|
- lib/tr8n_client_sdk/engine.rb
|
59
60
|
- lib/tr8n_client_sdk/extensions/action_common_methods.rb
|
60
61
|
- lib/tr8n_client_sdk/extensions/action_controller_extension.rb
|
61
62
|
- lib/tr8n_client_sdk/extensions/action_view_extension.rb
|
63
|
+
- lib/tr8n_client_sdk/extensions/string.rb
|
62
64
|
- lib/tr8n_client_sdk/railtie.rb
|
63
65
|
- lib/tr8n_client_sdk/version.rb
|
64
66
|
homepage: https://github.com/tr8n/tr8n_rails_clientsdk
|