wovnrb 3.10.2 → 3.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/docker/rails/TestSite/config/application.rb +2 -1
- data/lib/wovnrb/lang.rb +260 -258
- data/lib/wovnrb/store.rb +2 -1
- data/lib/wovnrb/version.rb +1 -1
- data/lib/wovnrb.rb +145 -138
- data/test/lib/lang_test.rb +59 -63
- data/test/lib/url_language_switcher_test.rb +1097 -1097
- data/test/lib/wovnrb_test.rb +112 -0
- metadata +2 -2
data/lib/wovnrb.rb
CHANGED
@@ -1,138 +1,145 @@
|
|
1
|
-
require 'rack'
|
2
|
-
require 'wovnrb/api_translator'
|
3
|
-
require 'wovnrb/headers'
|
4
|
-
require 'wovnrb/store'
|
5
|
-
require 'wovnrb/lang'
|
6
|
-
require 'wovnrb/services/html_converter'
|
7
|
-
require 'wovnrb/services/html_replace_marker'
|
8
|
-
require 'wovnrb/url_language_switcher'
|
9
|
-
require 'nokogiri'
|
10
|
-
require 'active_support'
|
11
|
-
require 'json'
|
12
|
-
require 'wovnrb/helpers/nokogumbo_helper'
|
13
|
-
require 'wovnrb/railtie' if defined?(Rails)
|
14
|
-
require 'wovnrb/version'
|
15
|
-
|
16
|
-
module Wovnrb
|
17
|
-
class Interceptor
|
18
|
-
def initialize(app, opts = {})
|
19
|
-
@app = app
|
20
|
-
@store = Store.instance
|
21
|
-
opts = opts.transform_keys(&:to_s)
|
22
|
-
@store.update_settings(opts)
|
23
|
-
@url_lang_switcher = Wovnrb::UrlLanguageSwitcher.new(@store)
|
24
|
-
end
|
25
|
-
|
26
|
-
def call(env)
|
27
|
-
# disabled by previous Rack middleware
|
28
|
-
return @app.call(env) if Rack::Request.new(env).params['wovn_disable'] == true
|
29
|
-
|
30
|
-
@store.settings.clear_dynamic_settings!
|
31
|
-
return @app.call(env) unless Store.instance.valid_settings?
|
32
|
-
|
33
|
-
@env = env
|
34
|
-
headers = Headers.new(env, @store.settings, @url_lang_switcher)
|
35
|
-
default_lang = @store.settings['default_lang']
|
36
|
-
return @app.call(env) if @store.settings['test_mode'] && @store.settings['test_url'] != headers.url
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
if
|
46
|
-
|
47
|
-
|
48
|
-
return
|
49
|
-
end
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
return output(headers, status, res_headers, body)
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
end
|
1
|
+
require 'rack'
|
2
|
+
require 'wovnrb/api_translator'
|
3
|
+
require 'wovnrb/headers'
|
4
|
+
require 'wovnrb/store'
|
5
|
+
require 'wovnrb/lang'
|
6
|
+
require 'wovnrb/services/html_converter'
|
7
|
+
require 'wovnrb/services/html_replace_marker'
|
8
|
+
require 'wovnrb/url_language_switcher'
|
9
|
+
require 'nokogiri'
|
10
|
+
require 'active_support'
|
11
|
+
require 'json'
|
12
|
+
require 'wovnrb/helpers/nokogumbo_helper'
|
13
|
+
require 'wovnrb/railtie' if defined?(Rails)
|
14
|
+
require 'wovnrb/version'
|
15
|
+
|
16
|
+
module Wovnrb
|
17
|
+
class Interceptor
|
18
|
+
def initialize(app, opts = {})
|
19
|
+
@app = app
|
20
|
+
@store = Store.instance
|
21
|
+
opts = opts.transform_keys(&:to_s)
|
22
|
+
@store.update_settings(opts)
|
23
|
+
@url_lang_switcher = Wovnrb::UrlLanguageSwitcher.new(@store)
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(env)
|
27
|
+
# disabled by previous Rack middleware
|
28
|
+
return @app.call(env) if Rack::Request.new(env).params['wovn_disable'] == true
|
29
|
+
|
30
|
+
@store.settings.clear_dynamic_settings!
|
31
|
+
return @app.call(env) unless Store.instance.valid_settings?
|
32
|
+
|
33
|
+
@env = env
|
34
|
+
headers = Headers.new(env, @store.settings, @url_lang_switcher)
|
35
|
+
default_lang = @store.settings['default_lang']
|
36
|
+
return @app.call(env) if @store.settings['test_mode'] && @store.settings['test_url'] != headers.url
|
37
|
+
|
38
|
+
cookie_lang = Rack::Request.new(env).cookies['wovn_selected_lang']
|
39
|
+
request_lang = headers.lang_code
|
40
|
+
if @store.settings['use_cookie_lang'] && cookie_lang.present? && request_lang != cookie_lang && request_lang == @store.default_lang
|
41
|
+
redirect_headers = headers.redirect(cookie_lang)
|
42
|
+
return [302, redirect_headers, ['']]
|
43
|
+
end
|
44
|
+
|
45
|
+
# redirect if the path is set to the default language (for SEO purposes)
|
46
|
+
if explicit_default_lang?(headers)
|
47
|
+
redirect_headers = headers.redirect(default_lang)
|
48
|
+
return [307, redirect_headers, ['']]
|
49
|
+
end
|
50
|
+
|
51
|
+
# if path containing language code is ignored, do nothing
|
52
|
+
if headers.lang_code != default_lang && ignore_path?(headers.unmasked_pathname_without_trailing_slash)
|
53
|
+
status, res_headers, body = @app.call(env)
|
54
|
+
|
55
|
+
return output(headers, status, res_headers, body)
|
56
|
+
end
|
57
|
+
# pass to application
|
58
|
+
status, res_headers, body = @app.call(headers.request_out)
|
59
|
+
|
60
|
+
# disabled by next Rack middleware
|
61
|
+
return output(headers, status, res_headers, body) unless res_headers['Content-Type']&.include?('html')
|
62
|
+
|
63
|
+
request = Rack::Request.new(env)
|
64
|
+
|
65
|
+
return output(headers, status, res_headers, body) if request.params['wovn_disable'] == true
|
66
|
+
|
67
|
+
@store.settings.update_dynamic_settings!(request.params)
|
68
|
+
return output(headers, status, res_headers, body) if ignore_path?(headers.pathname)
|
69
|
+
|
70
|
+
body = switch_lang(headers, body) unless /^1|302/.match?(status.to_s)
|
71
|
+
|
72
|
+
content_length = 0
|
73
|
+
body.each { |b| content_length += b.respond_to?(:bytesize) ? b.bytesize : 0 }
|
74
|
+
res_headers['Content-Length'] = content_length.to_s
|
75
|
+
|
76
|
+
output(headers, status, res_headers, body)
|
77
|
+
end
|
78
|
+
|
79
|
+
def switch_lang(headers, body)
|
80
|
+
translated_body = []
|
81
|
+
|
82
|
+
# Must use `.each` for to support multiple-chunks in Sinatra
|
83
|
+
string_body = ''
|
84
|
+
body.each { |chunk| string_body += chunk }
|
85
|
+
html_body = Helpers::NokogumboHelper.parse_html(string_body)
|
86
|
+
|
87
|
+
if !wovn_ignored?(html_body) && !amp_page?(html_body)
|
88
|
+
|
89
|
+
html_converter = HtmlConverter.new(html_body, @store, headers, @url_lang_switcher)
|
90
|
+
|
91
|
+
if needs_api?(html_body, headers)
|
92
|
+
converted_html, marker = html_converter.build_api_compatible_html
|
93
|
+
translated_content = ApiTranslator.new(@store, headers, WovnLogger.uuid).translate(converted_html)
|
94
|
+
translated_body.push(marker.revert(translated_content))
|
95
|
+
else
|
96
|
+
string_body = html_converter.build if html_body.html?
|
97
|
+
translated_body.push(string_body)
|
98
|
+
end
|
99
|
+
else
|
100
|
+
translated_body.push(string_body)
|
101
|
+
end
|
102
|
+
|
103
|
+
body.close if body.respond_to?(:close)
|
104
|
+
translated_body
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def output(headers, status, res_headers, body)
|
110
|
+
headers.out(res_headers)
|
111
|
+
[status, res_headers, body]
|
112
|
+
end
|
113
|
+
|
114
|
+
def needs_api?(html_body, headers)
|
115
|
+
headers.lang_code != @store.settings['default_lang'] &&
|
116
|
+
(html_body.html? || @store.settings['translate_fragment'])
|
117
|
+
end
|
118
|
+
|
119
|
+
def wovn_ignored?(html_body)
|
120
|
+
!html_body.xpath('//html[@wovn-ignore or @data-wovn-ignore]').empty?
|
121
|
+
end
|
122
|
+
|
123
|
+
def ignore_path?(path)
|
124
|
+
@store.settings['ignore_globs'].ignore?(path)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Checks if a given HTML body is an Accelerated Mobile Page (AMP).
|
128
|
+
# To do so, it looks at the required attributes for the HTML tag:
|
129
|
+
# https://www.ampproject.org/docs/tutorials/create/basic_markup.
|
130
|
+
#
|
131
|
+
# @param {Nokogiri::HTML5::Document} body The HTML body to check.
|
132
|
+
#
|
133
|
+
# @returns {Boolean} True is the HTML body is an AMP, false otherwise.
|
134
|
+
def amp_page?(html_body)
|
135
|
+
html_attributes = html_body.xpath('//html')[0].try(:attributes) || {}
|
136
|
+
|
137
|
+
!!(html_attributes['amp'] || html_attributes["\u26A1"])
|
138
|
+
end
|
139
|
+
|
140
|
+
def explicit_default_lang?(headers)
|
141
|
+
default_lang, url_pattern = @store.settings.values_at('default_lang', 'url_pattern')
|
142
|
+
default_lang == headers.url_language && url_pattern != 'custom_domain'
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
data/test/lib/lang_test.rb
CHANGED
@@ -1,63 +1,59 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
module Wovnrb
|
4
|
-
class LangTest < WovnMiniTest
|
5
|
-
def test_langs_exist
|
6
|
-
refute_nil(Wovnrb::Lang::LANG)
|
7
|
-
end
|
8
|
-
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
assert_equal(
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
def
|
36
|
-
assert_equal('
|
37
|
-
end
|
38
|
-
|
39
|
-
def
|
40
|
-
assert_equal('
|
41
|
-
end
|
42
|
-
|
43
|
-
def
|
44
|
-
assert_equal('
|
45
|
-
end
|
46
|
-
|
47
|
-
def
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
def
|
52
|
-
assert_nil(Wovnrb::Lang.get_code('
|
53
|
-
end
|
54
|
-
|
55
|
-
def
|
56
|
-
assert_nil(Wovnrb::Lang.get_code(
|
57
|
-
end
|
58
|
-
|
59
|
-
|
60
|
-
assert_nil(Wovnrb::Lang.get_code(nil))
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Wovnrb
|
4
|
+
class LangTest < WovnMiniTest
|
5
|
+
def test_langs_exist
|
6
|
+
refute_nil(Wovnrb::Lang::LANG)
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_keys_exist
|
10
|
+
Wovnrb::Lang::LANG.each do |k, l|
|
11
|
+
assert(l.key?(:name))
|
12
|
+
assert(l.key?(:code))
|
13
|
+
assert(l.key?(:en))
|
14
|
+
assert_equal(k, l[:code])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_iso_639_1_normalization
|
19
|
+
Wovnrb::Lang::LANG.each do |_, l|
|
20
|
+
case l[:code]
|
21
|
+
when 'zh-CHS'
|
22
|
+
assert_equal('zh-Hans', Lang.iso_639_1_normalization('zh-CHS'))
|
23
|
+
when 'zh-CHT'
|
24
|
+
assert_equal('zh-Hant', Lang.iso_639_1_normalization('zh-CHT'))
|
25
|
+
else
|
26
|
+
assert_equal(l[:code], Lang.iso_639_1_normalization(l[:code]))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_get_code_with_valid_code
|
32
|
+
assert_equal('ms', Wovnrb::Lang.get_code('ms'))
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_get_code_with_capital_letters
|
36
|
+
assert_equal('zh-CHT', Wovnrb::Lang.get_code('zh-cht'))
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_get_code_with_valid_english_name
|
40
|
+
assert_equal('pt', Wovnrb::Lang.get_code('Portuguese'))
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_get_code_with_valid_native_name
|
44
|
+
assert_equal('hi', Wovnrb::Lang.get_code('हिन्दी'))
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_get_code_with_invalid_name
|
48
|
+
assert_nil(Wovnrb::Lang.get_code('WOVN4LYFE'))
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_get_code_with_empty_string
|
52
|
+
assert_nil(Wovnrb::Lang.get_code(''))
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_get_code_with_nil
|
56
|
+
assert_nil(Wovnrb::Lang.get_code(nil))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|