accept_language 2.1.0 → 2.1.1
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 +19 -1
- data/lib/accept_language/matcher.rb +25 -15
- data/lib/accept_language/parser.rb +15 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c1d4f90fc40c062ac4f250c1c3b8ac8540796b232cf63b7d5c25b5e2e3c9124e
|
|
4
|
+
data.tar.gz: 150bb9def9e5f4799c432df6d6c364fedf88132b117d28e79a6ed44f467dcc70
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 537d924a23dc3c0fe8fb523556a6da653e16471b6b8ca94e0ee57f36fa1d6842103e380c3ea1e2dbd468b06a65a698ecdbf8923235f4eac0031a52801ba446f8
|
|
7
|
+
data.tar.gz: 8e48f57d2f0a4005483d29cf5503accc39eecffe19f78e8848dfa216422d1b2792f50cc5199c938674a483ef90ca20127fbfe452c0eebae9e30b36d7b8dc0bc2
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AcceptLanguage
|
|
2
2
|
|
|
3
|
-
A lightweight, thread-safe Ruby library for parsing `Accept-Language` HTTP headers as defined in [RFC 2616](https://tools.ietf.org/html/rfc2616#section-14.4).
|
|
3
|
+
A lightweight, thread-safe Ruby library for parsing `Accept-Language` HTTP headers as defined in [RFC 2616](https://tools.ietf.org/html/rfc2616#section-14.4), with full support for [BCP 47](https://tools.ietf.org/html/bcp47) language tags.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/cyril/accept_language.rb/tags)
|
|
6
6
|
[](https://rubydoc.info/github/cyril/accept_language.rb/main)
|
|
@@ -69,6 +69,24 @@ AcceptLanguage.parse("en-GB").match("en-gb") # => "en-gb"
|
|
|
69
69
|
AcceptLanguage.parse("en-gb").match("en-GB") # => "en-GB"
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
### BCP 47 support
|
|
73
|
+
|
|
74
|
+
This library supports [BCP 47](https://tools.ietf.org/html/bcp47) language tags, including:
|
|
75
|
+
|
|
76
|
+
- **Script subtags**: `zh-Hans` (Simplified Chinese), `zh-Hant` (Traditional Chinese)
|
|
77
|
+
- **Region subtags**: `en-US`, `pt-BR`
|
|
78
|
+
- **Variant subtags**: `sl-nedis` (Slovenian Nadiza dialect), `de-1996` (German orthography reform)
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
# Script variants
|
|
82
|
+
AcceptLanguage.parse("zh-Hans").match(:"zh-Hans-CN", :"zh-Hant-TW")
|
|
83
|
+
# => :"zh-Hans-CN"
|
|
84
|
+
|
|
85
|
+
# Orthography variants (numeric subtags)
|
|
86
|
+
AcceptLanguage.parse("de-1996, de;q=0.9").match(:"de-CH-1996", :"de-CH")
|
|
87
|
+
# => :"de-CH-1996"
|
|
88
|
+
```
|
|
89
|
+
|
|
72
90
|
## Rails integration
|
|
73
91
|
|
|
74
92
|
```ruby
|
|
@@ -21,6 +21,10 @@ module AcceptLanguage
|
|
|
21
21
|
|
|
22
22
|
languages_range.select do |langtag, quality|
|
|
23
23
|
if quality.zero?
|
|
24
|
+
# Exclude specific language tags, but NOT the wildcard.
|
|
25
|
+
# When "*;q=0" is specified, all non-listed languages become
|
|
26
|
+
# unacceptable implicitly (they won't match any preferred_langtags).
|
|
27
|
+
# Adding "*" to excluded_langtags would break prefix_match? logic.
|
|
24
28
|
@excluded_langtags << langtag unless wildcard?(langtag)
|
|
25
29
|
else
|
|
26
30
|
level = (quality * 1_000).to_i
|
|
@@ -61,37 +65,43 @@ module AcceptLanguage
|
|
|
61
65
|
end
|
|
62
66
|
|
|
63
67
|
def find_matching_tag(preferred_tag, available_langtags)
|
|
64
|
-
|
|
65
|
-
available_langtags.find { |tag| tag.match?(pattern) }
|
|
68
|
+
available_langtags.find { |tag| prefix_match?(preferred_tag, String(tag.downcase)) }
|
|
66
69
|
end
|
|
67
70
|
|
|
68
71
|
def any_other_langtag(*available_langtags)
|
|
72
|
+
langtags = preferred_langtags - [WILDCARD]
|
|
73
|
+
|
|
69
74
|
available_langtags.find do |available_langtag|
|
|
70
|
-
|
|
71
|
-
langtags.none?
|
|
72
|
-
pattern = /\A#{::Regexp.escape(tag)}/i
|
|
73
|
-
available_langtag.match?(pattern)
|
|
74
|
-
end
|
|
75
|
+
available_downcased = available_langtag.downcase
|
|
76
|
+
langtags.none? { |tag| prefix_match?(tag, String(available_downcased)) }
|
|
75
77
|
end
|
|
76
78
|
end
|
|
77
79
|
|
|
78
80
|
def drop_unacceptable(*available_langtags)
|
|
79
|
-
available_langtags.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
langtags + ::Set[available_langtag]
|
|
81
|
+
available_langtags.each_with_object(::Set[]) do |available_langtag, langtags|
|
|
82
|
+
langtags << available_langtag unless unacceptable?(available_langtag)
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def unacceptable?(langtag)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
langtag.match?(pattern)
|
|
90
|
-
end
|
|
87
|
+
langtag_downcased = langtag.downcase
|
|
88
|
+
excluded_langtags.any? { |excluded_tag| prefix_match?(excluded_tag, String(langtag_downcased)) }
|
|
91
89
|
end
|
|
92
90
|
|
|
93
91
|
def wildcard?(value)
|
|
94
92
|
value.eql?(WILDCARD)
|
|
95
93
|
end
|
|
94
|
+
|
|
95
|
+
# Implements RFC 2616 Section 14.4 prefix matching rule:
|
|
96
|
+
# "A language-range matches a language-tag if it exactly equals the tag,
|
|
97
|
+
# or if it exactly equals a prefix of the tag such that the first tag
|
|
98
|
+
# character following the prefix is '-'."
|
|
99
|
+
#
|
|
100
|
+
# @param prefix [String] The language-range to match (downcased)
|
|
101
|
+
# @param tag [String] The language-tag to test (downcased)
|
|
102
|
+
# @return [Boolean] true if prefix matches tag per RFC 2616 rules
|
|
103
|
+
def prefix_match?(prefix, tag)
|
|
104
|
+
tag == prefix || tag.start_with?("#{prefix}-")
|
|
105
|
+
end
|
|
96
106
|
end
|
|
97
107
|
end
|
|
@@ -22,8 +22,21 @@ module AcceptLanguage
|
|
|
22
22
|
# @api private
|
|
23
23
|
SUFFIX = ";q="
|
|
24
24
|
# @api private
|
|
25
|
-
|
|
25
|
+
# RFC 2616 Section 3.9 qvalue syntax:
|
|
26
|
+
# qvalue = ( "0" [ "." 0*3DIGIT ] ) | ( "1" [ "." 0*3("0") ] )
|
|
27
|
+
QVALUE_PATTERN = /\A(?:0(?:\.[0-9]{1,3})?|1(?:\.0{1,3})?)\z/
|
|
26
28
|
# @api private
|
|
29
|
+
# Language tag pattern supporting BCP 47 (RFC 5646) alphanumeric subtags.
|
|
30
|
+
#
|
|
31
|
+
# RFC 2616 Section 3.10 references RFC 1766, which only allowed ALPHA in subtags.
|
|
32
|
+
# However, BCP 47 (the current standard) permits alphanumeric subtags:
|
|
33
|
+
# subtag = 1*8alphanum
|
|
34
|
+
# alphanum = ALPHA / DIGIT
|
|
35
|
+
#
|
|
36
|
+
# Examples of valid BCP 47 tags with numeric subtags:
|
|
37
|
+
# - "de-CH-1996" (German, Switzerland, orthography variant 1996)
|
|
38
|
+
# - "sl-IT-nedis" (Slovenian, Italy, Nadiza dialect)
|
|
39
|
+
# - "zh-Hans-CN" (Chinese, Simplified script, China)
|
|
27
40
|
LANGTAG_PATTERN = /\A(?:\*|[a-zA-Z]{1,8}(?:-[a-zA-Z0-9]{1,8})*)\z/
|
|
28
41
|
|
|
29
42
|
# @api private
|
|
@@ -54,7 +67,7 @@ module AcceptLanguage
|
|
|
54
67
|
private
|
|
55
68
|
|
|
56
69
|
def import(field)
|
|
57
|
-
"#{field}".delete(SPACE).split(SEPARATOR).inject({}) do |hash, lang|
|
|
70
|
+
"#{field}".downcase.delete(SPACE).split(SEPARATOR).inject({}) do |hash, lang|
|
|
58
71
|
tag, quality = lang.split(SUFFIX)
|
|
59
72
|
next hash unless valid_tag?(tag)
|
|
60
73
|
|
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: 2.1.
|
|
4
|
+
version: 2.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cyril Kato
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-01-
|
|
11
|
+
date: 2026-01-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bigdecimal
|