rack-i18n_best_langs 0.2 → 0.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.
- data/README.md +41 -5
- data/Rakefile +1 -1
- data/lib/rack/i18n_best_langs.rb +27 -26
- data/spec/i18n-best-langs_spec.rb +11 -1
- metadata +5 -5
data/README.md
CHANGED
@@ -6,7 +6,12 @@ understanding what are the best languages for a site visitor.
|
|
6
6
|
|
7
7
|
If you manage a site that has content many languages and also localized URLs,
|
8
8
|
you will find `rack-i18n_best_langs` very useful, especially when used in
|
9
|
-
conjunction with `rack-i18n_routes
|
9
|
+
conjunction with [`rack-i18n_routes`](https://github.com/gioele/rack-i18n_routes).
|
10
|
+
|
11
|
+
Differently from other similar Rack middleware components,
|
12
|
+
`rack-i18n_best_langs` returns a list of languages in order of guessed
|
13
|
+
importance, not a single language. Also, it does not require Rails or
|
14
|
+
the `i18n` gem.
|
10
15
|
|
11
16
|
|
12
17
|
Features
|
@@ -22,10 +27,40 @@ All these clues are taken into account and evaluated against the list
|
|
22
27
|
of languages available and their preferred order. It is possible to configure
|
23
28
|
which of these clues is the most important.
|
24
29
|
|
25
|
-
An additional clue is available when `AliasMapping`
|
26
|
-
|
27
|
-
|
28
|
-
|
30
|
+
An additional clue is available when `AliasMapping` (part of
|
31
|
+
[rack-i18n_routes](http://rubydoc.info/gems/rack-i18n_routes)) is used as the
|
32
|
+
mapping function: the language in which the path is written. For example,
|
33
|
+
`/articles/the-victory` add preference of English, `/artículos/la-victoria`
|
34
|
+
for Spanish and `/articles/la-victoire` for French.
|
35
|
+
|
36
|
+
|
37
|
+
How it works
|
38
|
+
------------
|
39
|
+
|
40
|
+
Each request is analysed in search for possible clues that can expose what
|
41
|
+
are the best languages to use in the content served in response. Each clue
|
42
|
+
contributes part of the final score of each language; the score is amplified
|
43
|
+
by the weight associated to the clue.
|
44
|
+
|
45
|
+
For example, if a person requests the URL `/article/vittoria/ita` from
|
46
|
+
a browser in a German internet point (thus sending `Accept-Language: de-DE`),
|
47
|
+
their request would lead to the following scores:
|
48
|
+
|
49
|
+
* `ita`: 315 = 0 (not in header) + 15 (partially language of URL) + 300 (path
|
50
|
+
ends in `/ita`);
|
51
|
+
* `eng`: 15 = 0 (not in header) + 15 (partially language of URL) + 0 (path
|
52
|
+
does not end in `/eng`);
|
53
|
+
* `ger`: 3 = 3 (in header) + 0 (not language of URL) + 0 (path does not end
|
54
|
+
in `/ger`).
|
55
|
+
|
56
|
+
The downstream application will find the guessed languages in the
|
57
|
+
`rack.i18n_best_langs` env variable, in order of importance.
|
58
|
+
|
59
|
+
env['rack.i18n_best_langs'] # => [ 'ita', 'eng', 'ger' ]
|
60
|
+
|
61
|
+
At this point, it is up to the application to choose what to do, either change
|
62
|
+
the locale using the `i18n` gem, set up its own locale management system or
|
63
|
+
just keep track of this information.
|
29
64
|
|
30
65
|
|
31
66
|
Examples
|
@@ -102,6 +137,7 @@ You can change these weight with the `:weights` option.
|
|
102
137
|
|
103
138
|
use Rack::I18nBestLangs, FAVORITE_LANGUAGES, :weights => WEIGHTS
|
104
139
|
|
140
|
+
To disable the use of any of the clues, set its weight to zero.
|
105
141
|
|
106
142
|
### Using `AliasMapping`
|
107
143
|
|
data/Rakefile
CHANGED
data/lib/rack/i18n_best_langs.rb
CHANGED
@@ -3,13 +3,19 @@
|
|
3
3
|
# See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
|
4
4
|
# for more details.
|
5
5
|
|
6
|
+
require 'rack'
|
6
7
|
|
7
8
|
require 'rack/language_tag.rb'
|
8
9
|
|
9
|
-
|
10
|
+
class Rack::I18nBestLangs
|
11
|
+
RACK_VARIABLE = 'rack.i18n_best_langs'.freeze
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
+
DEFAULT_WEIGHTS = {
|
14
|
+
:header => 1,
|
15
|
+
:aliases_path => 2,
|
16
|
+
:path => 3,
|
17
|
+
:cookie => 4,
|
18
|
+
}.freeze
|
13
19
|
|
14
20
|
# Create a new I18nBestLangs middleware component.
|
15
21
|
#
|
@@ -31,16 +37,11 @@ class I18nBestLangs
|
|
31
37
|
|
32
38
|
score_base = avail_languages.length
|
33
39
|
|
34
|
-
weights = opts[:weights] ||
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@score_for_header = score_base * (10 ** weight_header)
|
41
|
-
@score_for_aliases_path = score_base * (10 ** weight_aliases_path)
|
42
|
-
@score_for_path = score_base * (10 ** weight_path)
|
43
|
-
@score_for_cookie = score_base * (10 ** weight_cookie)
|
40
|
+
weights = opts[:weights] || DEFAULT_WEIGHTS
|
41
|
+
@score_for_header = score_base * (10 ** weights[:header])
|
42
|
+
@score_for_aliases_path = score_base * (10 ** weights[:aliases_path])
|
43
|
+
@score_for_path = score_base * (10 ** weights[:path])
|
44
|
+
@score_for_cookie = score_base * (10 ** weights[:cookie])
|
44
45
|
|
45
46
|
@avail_languages = {}
|
46
47
|
avail_languages.each_with_index do |lang, i|
|
@@ -49,15 +50,17 @@ class I18nBestLangs
|
|
49
50
|
|
50
51
|
@avail_languages[code] = score
|
51
52
|
end
|
53
|
+
@avail_languages.freeze
|
52
54
|
|
53
|
-
@language_path_regex = regex_for_languages_in_path
|
55
|
+
@language_path_regex = regex_for_languages_in_path.freeze
|
54
56
|
|
55
57
|
@path_mapping_fn = opts[:path_mapping_fn]
|
56
58
|
end
|
57
59
|
|
58
60
|
def call(env)
|
59
61
|
lang_info = find_best_languages(env)
|
60
|
-
|
62
|
+
|
63
|
+
env[RACK_VARIABLE] = lang_info[:languages]
|
61
64
|
env['PATH_INFO'] = lang_info[:path_info]
|
62
65
|
|
63
66
|
return @app.call(env)
|
@@ -89,16 +92,16 @@ class I18nBestLangs
|
|
89
92
|
def extract_language_header(env)
|
90
93
|
header = env['HTTP_ACCEPT_LANGUAGE']
|
91
94
|
|
92
|
-
if (header =~ HEADER_FORMAT)
|
93
|
-
|
94
|
-
|
95
|
-
# FIXME: env.warn
|
96
|
-
return ""
|
95
|
+
if !(header =~ HEADER_FORMAT)
|
96
|
+
env["rack.errors"].puts("Warning: malformed Accept-Language header")
|
97
|
+
return nil
|
97
98
|
end
|
99
|
+
|
100
|
+
return header
|
98
101
|
end
|
99
102
|
|
100
103
|
def extract_language_cookie(env)
|
101
|
-
return Rack::Request.new(env).cookies[
|
104
|
+
return Rack::Request.new(env).cookies[RACK_VARIABLE]
|
102
105
|
end
|
103
106
|
|
104
107
|
def remove_language_from_path(path)
|
@@ -151,11 +154,11 @@ class I18nBestLangs
|
|
151
154
|
end
|
152
155
|
|
153
156
|
def add_score_for_aliases_path(path, langs)
|
154
|
-
if !@path_mapping_fn.respond_to?(:
|
157
|
+
if !@path_mapping_fn.respond_to?(:path_analysis)
|
155
158
|
return
|
156
159
|
end
|
157
160
|
|
158
|
-
ph, aliases_langs = @path_mapping_fn.
|
161
|
+
ph, translation, aliases_langs = @path_mapping_fn.path_analysis(path)
|
159
162
|
aliases_langs.map! { |tag| LanguageTag.parse(tag) }
|
160
163
|
|
161
164
|
lang_uses = aliases_langs.inject(Hash.new(0)) {|freq, lang| freq[lang] += 1; freq }
|
@@ -207,7 +210,5 @@ class I18nBestLangs
|
|
207
210
|
return Regexp.new("\\A#{lang}#{qvalue}?(, ?#{lang}#{qvalue}?)*\\Z")
|
208
211
|
end
|
209
212
|
|
210
|
-
HEADER_FORMAT = self.accept_language_format
|
211
|
-
end
|
212
|
-
|
213
|
+
HEADER_FORMAT = self.accept_language_format.freeze
|
213
214
|
end
|
@@ -29,7 +29,7 @@ describe Rack::I18nBestLangs do
|
|
29
29
|
i18n_opts << extra_opts
|
30
30
|
end
|
31
31
|
|
32
|
-
session = Rack::Test::Session.new(
|
32
|
+
session = Rack::Test::Session.new(app(*i18n_opts))
|
33
33
|
session.request(path, env_opts)
|
34
34
|
|
35
35
|
return session.last_request
|
@@ -128,5 +128,15 @@ describe Rack::I18nBestLangs do
|
|
128
128
|
languages.first.should == 'fra'
|
129
129
|
end
|
130
130
|
end
|
131
|
+
|
132
|
+
context "with malformed headers" do
|
133
|
+
it "warns of malformed ACCEPT_LANGUAGE" do
|
134
|
+
env = request_with('/test', { 'HTTP_ACCEPT_LANGUAGE' => 'fobar/1a' }).env
|
135
|
+
|
136
|
+
# FIXME: simplify code, https://github.com/brynary/rack-test/issues/61
|
137
|
+
errors = env['rack.errors'].instance_variable_get(:@error).instance_variable_get(:@error).string
|
138
|
+
errors.should include('malformed Accept-Language')
|
139
|
+
end
|
140
|
+
end
|
131
141
|
end
|
132
142
|
|
metadata
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-i18n_best_langs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 13
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: "0.
|
8
|
+
- 3
|
9
|
+
version: "0.3"
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Gioele Barabucci
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2012-08-
|
17
|
+
date: 2012-08-10 00:00:00 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: rack
|
@@ -56,7 +56,7 @@ dependencies:
|
|
56
56
|
requirements:
|
57
57
|
- - ">="
|
58
58
|
- !ruby/object:Gem::Version
|
59
|
-
hash:
|
59
|
+
hash: -701993856
|
60
60
|
segments:
|
61
61
|
- 0
|
62
62
|
- 3
|