accept_language 1.0.1 → 2.0.0
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 +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"
|