accept_language 1.0.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +16 -17
- data/lib/accept_language.rb +9 -10
- data/lib/accept_language/matcher.rb +81 -0
- data/lib/accept_language/parser.rb +32 -13
- metadata +4 -4
- data/lib/accept_language/intersection.rb +0 -58
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1b6659d931a56171b880e543a4364fdd0212cf1664d9fa32d04f29c9142b1a6
|
4
|
+
data.tar.gz: 259ddc12dc7ef62325a5fe634dd0bcb284f1dce0d9629d929858751dcc8cd936
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1566e1af8e2cd77e96eebebf25e7f694237f1562396d42ee001349d6fb75503fe022a8325e208db9cd24e354d90253496e11af66f1bd5b7704e8b54ac347263
|
7
|
+
data.tar.gz: 0d7d2c59183e9eae327e61f4ccae25c050f5a82eb511041b2c86c4478548c7144ea10fa78f9c9a4d4b501fdbf8375b72e001e77f73d0820272c139fa9dad5972
|
data/README.md
CHANGED
@@ -28,25 +28,24 @@ Or install it yourself as:
|
|
28
28
|
|
29
29
|
It's intended to be used in a Web server that supports some level of internationalization (i18n), but can be used anytime an `Accept-Language` header string is available.
|
30
30
|
|
31
|
-
|
31
|
+
In order to help facilitate better i18n, the lib try to find the intersection of the languages the user prefers and the languages your application supports.
|
32
32
|
|
33
|
-
|
34
|
-
AcceptLanguage.parse("da, en-gb;q=0.8, en;q=0.7") # => {:da=>1.0, :"en-gb"=>0.8, :en=>0.7}
|
35
|
-
AcceptLanguage.parse("fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5") # => {:"fr-ch"=>1.0, :fr=>0.9, :en=>0.8, :de=>0.7, :*=>0.5}
|
36
|
-
```
|
37
|
-
|
38
|
-
In order to help facilitate better i18n, a method is provided to return the intersection of the languages the user prefers and the languages your application supports.
|
39
|
-
|
40
|
-
Examples:
|
33
|
+
Some examples:
|
41
34
|
|
42
35
|
```ruby
|
43
|
-
AcceptLanguage.
|
44
|
-
AcceptLanguage.
|
45
|
-
AcceptLanguage.
|
46
|
-
AcceptLanguage.
|
47
|
-
AcceptLanguage.
|
48
|
-
AcceptLanguage.
|
49
|
-
AcceptLanguage.
|
36
|
+
AcceptLanguage.parse("da, en-GB;q=0.8, en;q=0.7").match(:en, :da) # => :da
|
37
|
+
AcceptLanguage.parse("da, en;q=0.8, ug;q=0.9").match("en-GB", "ug-CN") # => "ug-CN"
|
38
|
+
AcceptLanguage.parse("da, en-GB;q=0.8, en;q=0.7").match(:ja) # => nil
|
39
|
+
AcceptLanguage.parse("fr-CH").match(:fr) # => nil
|
40
|
+
AcceptLanguage.parse("de, zh;q=0.4, fr;q=0").match(:fr) # => nil
|
41
|
+
AcceptLanguage.parse("de, zh;q=0.4, *;q=0.5, fr;q=0").match(:ar) # => :ar
|
42
|
+
AcceptLanguage.parse("uz-latn-uz").match("uz-Latn-UZ") # => "uz-Latn-UZ"
|
43
|
+
AcceptLanguage.parse("foo;q=0.1").match(:FoO) # => :FoO
|
44
|
+
AcceptLanguage.parse("foo").match("bar") # => nil
|
45
|
+
AcceptLanguage.parse("*").match("BaZ") # => "BaZ"
|
46
|
+
AcceptLanguage.parse("*;q=0").match("foobar") # => nil
|
47
|
+
AcceptLanguage.parse("en, en;q=0").match("en") # => nil
|
48
|
+
AcceptLanguage.parse("*, en;q=0").match("en") # => nil
|
50
49
|
```
|
51
50
|
|
52
51
|
### Rails integration example
|
@@ -64,7 +63,7 @@ class ApplicationController < ActionController::Base
|
|
64
63
|
return I18n.default_locale unless request.headers.key?("HTTP_ACCEPT_LANGUAGE")
|
65
64
|
|
66
65
|
string = request.headers.fetch("HTTP_ACCEPT_LANGUAGE")
|
67
|
-
locale = AcceptLanguage.
|
66
|
+
locale = AcceptLanguage.parse(string).match(*I18n.available_locales)
|
68
67
|
|
69
68
|
# If the server cannot serve any matching language,
|
70
69
|
# it can theoretically send back a 406 (Not Acceptable) error code.
|
data/lib/accept_language.rb
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Tiny library for parsing the Accept-Language header.
|
4
|
+
# @example
|
5
|
+
# AcceptLanguage.parse("da, en-GB;q=0.8, en;q=0.7") # => #<AcceptLanguage::Parser:0x00007 @languages_range={"da"=>0.1e1, "en-GB"=>0.8e0, "en"=>0.7e0}>
|
6
|
+
# @see https://tools.ietf.org/html/rfc2616#section-14.4
|
4
7
|
module AcceptLanguage
|
8
|
+
# @note Parse an Accept-Language header field into a language range.
|
5
9
|
# @example
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# @example
|
12
|
-
# AcceptLanguage.parse('ja, en-gb;q=0.8, en;q=0.7') # => { ja: 1.0, "en-gb": 0.8, en: 0.7 }
|
13
|
-
def self.parse(raw_input, two_letter_truncate: false)
|
14
|
-
Parser.call(raw_input, two_letter_truncate: two_letter_truncate)
|
10
|
+
# parse("da, en-GB;q=0.8, en;q=0.7") # => #<AcceptLanguage::Parser:0x00007 @languages_range={"da"=>0.1e1, "en-GB"=>0.8e0, "en"=>0.7e0}>
|
11
|
+
# @return [#match] a parser that responds to #match.
|
12
|
+
def self.parse(field)
|
13
|
+
Parser.new(field)
|
15
14
|
end
|
16
15
|
end
|
17
16
|
|
18
|
-
require_relative "accept_language/
|
17
|
+
require_relative "accept_language/parser"
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AcceptLanguage
|
4
|
+
# @note Compare an Accept-Language header value with your application's
|
5
|
+
# supported languages to find the common languages that could be presented
|
6
|
+
# to a user.
|
7
|
+
# @example
|
8
|
+
# Matcher.new("da" => 1.0, "en-GB" => 0.8, "en" => 0.7).call(:ug, :kk, :ru, :en) # => :en
|
9
|
+
# Matcher.new("da" => 1.0, "en-GB" => 0.8, "en" => 0.7).call(:fr, :en, :"en-GB") # => :"en-GB"
|
10
|
+
class Matcher
|
11
|
+
attr_reader :excluded_langtags, :preferred_langtags
|
12
|
+
|
13
|
+
# @param [Hash<String, BigDecimal>] languages_range A list of accepted
|
14
|
+
# languages with their respective qualities.
|
15
|
+
def initialize(**languages_range)
|
16
|
+
@excluded_langtags = Set[]
|
17
|
+
langtags = []
|
18
|
+
|
19
|
+
languages_range.select do |langtag, quality|
|
20
|
+
if quality.zero?
|
21
|
+
@excluded_langtags << langtag unless langtag.eql?("*")
|
22
|
+
else
|
23
|
+
level = (quality * 1_000).to_i
|
24
|
+
langtags[level] = langtag
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
@preferred_langtags = langtags.compact.reverse
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param [Array<String, Symbol>] available_langtags The list of available
|
32
|
+
# languages.
|
33
|
+
# @example Uyghur, Kazakh, Russian and English languages are available.
|
34
|
+
# call(:ug, :kk, :ru, :en)
|
35
|
+
# @return [String, Symbol, nil] The language that best matches.
|
36
|
+
def call(*available_langtags)
|
37
|
+
available_langtags = drop_unacceptable(*available_langtags)
|
38
|
+
|
39
|
+
preferred_langtags.each do |preferred_tag|
|
40
|
+
if preferred_tag.eql?("*")
|
41
|
+
langtag = any_other_langtag(*available_langtags)
|
42
|
+
return langtag unless langtag.nil?
|
43
|
+
else
|
44
|
+
available_langtags.each do |available_langtag|
|
45
|
+
return available_langtag if available_langtag.match?(/\A#{preferred_tag}/i)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def any_other_langtag(*available_langtags)
|
56
|
+
available_langtags.find do |available_langtag|
|
57
|
+
langtags = preferred_langtags - ["*"]
|
58
|
+
|
59
|
+
langtags.none? do |langtag|
|
60
|
+
available_langtag.match?(/\A#{langtag}/i)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def drop_unacceptable(*available_langtags)
|
66
|
+
available_langtags.inject(Set[]) do |langtags, available_langtag|
|
67
|
+
next langtags if unacceptable?(available_langtag)
|
68
|
+
|
69
|
+
langtags + Set[available_langtag]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def unacceptable?(langtag)
|
74
|
+
excluded_langtags.any? do |excluded_langtag|
|
75
|
+
langtag.match?(/\A#{excluded_langtag}/i)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
require "set"
|
@@ -1,26 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AcceptLanguage
|
4
|
+
# @note Parser for Accept-Language header fields.
|
4
5
|
# @example
|
5
|
-
#
|
6
|
-
# @note Parse an Accept-Language header value into a hash of tag and quality.
|
6
|
+
# Parser.new("da, en-GB;q=0.8, en;q=0.7") # => #<AcceptLanguage::Parser:0x00007 @languages_range={"da"=>0.1e1, "en-GB"=>0.8e0, "en"=>0.7e0}>
|
7
7
|
# @see https://tools.ietf.org/html/rfc2616#section-14.4
|
8
|
-
|
9
|
-
|
10
|
-
raw_input.to_s.delete(" ").split(",").inject({}) do |hash, lang|
|
11
|
-
tag, quality = lang.split(/;q=/i)
|
12
|
-
next hash if tag.nil?
|
8
|
+
class Parser
|
9
|
+
attr_reader :languages_range
|
13
10
|
|
14
|
-
|
11
|
+
# @param [String] field The Accept-Language header field to parse.
|
12
|
+
# @see https://tools.ietf.org/html/rfc2616#section-14.4
|
13
|
+
def initialize(field)
|
14
|
+
@languages_range = import(field)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [Array<String, Symbol>] available_langtags The list of available
|
18
|
+
# languages.
|
19
|
+
# @example Uyghur, Kazakh, Russian and English languages are available.
|
20
|
+
# match(:ug, :kk, :ru, :en)
|
21
|
+
# @return [String, Symbol, nil] The language that best matches.
|
22
|
+
def match(*available_langtags)
|
23
|
+
Matcher.new(**languages_range).call(*available_langtags)
|
24
|
+
end
|
15
25
|
|
16
|
-
|
17
|
-
tag = tag[0, 2].to_sym
|
18
|
-
next hash if hash.key?(tag)
|
19
|
-
end
|
26
|
+
private
|
20
27
|
|
21
|
-
|
28
|
+
# @example
|
29
|
+
# import('da, en-GB;q=0.8, en;q=0.7') # => {"da"=>0.1e1, "en-GB"=>0.8e0, "en"=>0.7e0}
|
30
|
+
# @return [Hash<String, BigDecimal>] A list of accepted languages with their
|
31
|
+
# respective qualities.
|
32
|
+
def import(field)
|
33
|
+
field.delete(" ").split(",").inject({}) do |hash, lang|
|
34
|
+
tag, quality = lang.split(/;q=/i)
|
35
|
+
next hash if tag.nil?
|
36
|
+
|
37
|
+
quality = quality.nil? ? BigDecimal("1") : BigDecimal(quality)
|
22
38
|
hash.merge(tag => quality)
|
23
39
|
end
|
24
40
|
end
|
25
41
|
end
|
26
42
|
end
|
43
|
+
|
44
|
+
require "bigdecimal"
|
45
|
+
require_relative "matcher"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: accept_language
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-05-
|
11
|
+
date: 2021-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -160,7 +160,7 @@ files:
|
|
160
160
|
- LICENSE.md
|
161
161
|
- README.md
|
162
162
|
- lib/accept_language.rb
|
163
|
-
- lib/accept_language/
|
163
|
+
- lib/accept_language/matcher.rb
|
164
164
|
- lib/accept_language/parser.rb
|
165
165
|
homepage: https://github.com/cyril/accept_language.rb
|
166
166
|
licenses:
|
@@ -174,7 +174,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
174
174
|
requirements:
|
175
175
|
- - ">="
|
176
176
|
- !ruby/object:Gem::Version
|
177
|
-
version:
|
177
|
+
version: 3.0.0
|
178
178
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
179
179
|
requirements:
|
180
180
|
- - ">="
|
@@ -1,58 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AcceptLanguage
|
4
|
-
# @example
|
5
|
-
# AcceptLanguage::Intersection.new('ja, en-gb;q=0.8, en;q=0.7', :ar, :ja).call # => :ja
|
6
|
-
# @note Compare an Accept-Language header value with your application's
|
7
|
-
# supported languages to find the common languages that could be presented
|
8
|
-
# to a user.
|
9
|
-
# @see https://tools.ietf.org/html/rfc2616#section-14.4
|
10
|
-
class Intersection
|
11
|
-
attr_reader :preferences, :supported_langs
|
12
|
-
|
13
|
-
def initialize(raw_input, *supported_langs, two_letter_truncate: true)
|
14
|
-
@preferences = Parser.call(raw_input, two_letter_truncate: two_letter_truncate)
|
15
|
-
|
16
|
-
@supported_langs = supported_langs.map do |lang|
|
17
|
-
lang = lang.downcase
|
18
|
-
lang = lang[0, 2] if two_letter_truncate
|
19
|
-
lang.to_sym
|
20
|
-
end.uniq
|
21
|
-
end
|
22
|
-
|
23
|
-
def call
|
24
|
-
qualities_without_zero_in_desc_order.each do |quality|
|
25
|
-
tag = preferences.key(quality)
|
26
|
-
|
27
|
-
if wildcard?(tag)
|
28
|
-
lang = any_tag_not_matched_by_any_other_range
|
29
|
-
return lang unless lang.nil?
|
30
|
-
end
|
31
|
-
|
32
|
-
return tag if supported_langs.include?(tag)
|
33
|
-
end
|
34
|
-
|
35
|
-
nil
|
36
|
-
end
|
37
|
-
|
38
|
-
protected
|
39
|
-
|
40
|
-
def any_tag_not_matched_by_any_other_range
|
41
|
-
every_tag_not_matched_by_any_other_range.first
|
42
|
-
end
|
43
|
-
|
44
|
-
def every_tag_not_matched_by_any_other_range
|
45
|
-
supported_langs - preferences.keys
|
46
|
-
end
|
47
|
-
|
48
|
-
def qualities_without_zero_in_desc_order
|
49
|
-
preferences.values.reject(&:zero?).uniq.sort.reverse
|
50
|
-
end
|
51
|
-
|
52
|
-
def wildcard?(value)
|
53
|
-
value.equal?(:*)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
require_relative "parser"
|