-
Notifications
You must be signed in to change notification settings - Fork 7
Understanding Quality Values
Quality values (or q-values) express the user's relative preference for each language. They are the key mechanism browsers use to communicate how much a user wants each language, not just which languages they accept.
A quality value is a decimal number between 0 and 1 that indicates preference:
-
q=1— Maximum preference (I really want this language) -
q=0.5— Medium preference (This is acceptable) -
q=0— Not acceptable (I don't want this language)
;q= suffix:
Accept-Language: en;q=1, fr;q=0.8, de;q=0.5
When no quality value is specified, the default is 1 (maximum preference):
Accept-Language: en, fr;q=0.8
This is equivalent to:
Accept-Language: en;q=1, fr;q=0.8
# Both produce the same result
AcceptLanguage.parse("en, fr;q=0.8").match(:en, :fr)
# => :en
AcceptLanguage.parse("en;q=1, fr;q=0.8").match(:en, :fr)
# => :enThe gem selects the available language with the highest quality value:
parser = AcceptLanguage.parse("fr;q=0.7, en;q=0.9, de;q=0.8")
parser.match(:en, :fr, :de)
# => :en (q=0.9 is the highest)
parser.match(:fr, :de)
# => :de (q=0.8 beats q=0.7)
parser.match(:fr)
# => :fr (only option available)Think of quality values as a ranked preference list:
Header: "da, en-GB;q=0.8, en;q=0.7, *;q=0.5" Priority ranking: ┌─────────────┬─────────┬──────────────────────┐ │ Rank │ Q-Value │ Language │ ├─────────────┼─────────┼──────────────────────┤ │ 1st choice │ 1.0 │ da (Danish) │ │ 2nd choice │ 0.8 │ en-GB (UK English) │ │ 3rd choice │ 0.7 │ en (English) │ │ Last resort │ 0.5 │ * (anything else) │ └─────────────┴─────────┴──────────────────────┘
Per RFC 7231 Section 5.3.1, quality values must:
- Be between
0and1 - Have at most 3 decimal places
| Value | Valid? | Notes |
|---|---|---|
1
|
✓ | Maximum preference |
0
|
✓ | Not acceptable |
0.8
|
✓ | One decimal place |
0.85
|
✓ | Two decimal places |
0.123
|
✓ | Three decimal places (maximum) |
1.0
|
✓ | Equivalent to 1 |
1.000
|
✓ | Equivalent to 1 |
0.1234
|
✗ | Too many decimal places |
1.5
|
✗ | Greater than 1 |
-0.5
|
✗ | Negative value |
.5
|
✗ | Missing leading zero |
Invalid quality values are silently ignored, and the associated language is skipped:
# "en" has invalid q-value (> 1), so it's ignored
AcceptLanguage.parse("en;q=1.5, fr;q=0.8").match(:en, :fr)
# => :frWhen two languages have the same quality value, the one declared first in the header wins:
# Same q-value, different order
AcceptLanguage.parse("en;q=0.8, fr;q=0.8").match(:en, :fr)
# => :en (declared first)
AcceptLanguage.parse("fr;q=0.8, en;q=0.8").match(:en, :fr)
# => :fr (declared first)This ensures deterministic results: the same header always produces the same match.
A quality value of 0 means the language is explicitly not acceptable. This is different from simply not listing the language:
# English is explicitly excluded
AcceptLanguage.parse("*, en;q=0").match(:en, :fr)
# => :fr
# Even with wildcard, en is rejected
AcceptLanguage.parse("*, en;q=0").match(:en)
# => nilSee Excluding Languages for more details on this powerful feature.
A user configures their browser with these preferences:
- French (France) — primary
- French — secondary
- English — fallback
Accept-Language: fr-FR, fr;q=0.9, en;q=0.8
Your app supports :en, :fr, and :de:
header = "fr-FR, fr;q=0.9, en;q=0.8"
AcceptLanguage.parse(header).match(:en, :fr, :de)
# => :frThe user wanted fr-FR (q=1), but since you don't have it, the gem falls back to fr (q=0.9).
Some users have very specific preferences:
header = "de-CH;q=1, de-AT;q=0.9, de-DE;q=0.8, de;q=0.7, en;q=0.5"
# Your app supports German variants
AcceptLanguage.parse(header).match(:de, :"de-DE", :"de-AT")
# => :"de-AT" (q=0.9, since de-CH is unavailable)- Basic Filtering Explained — How prefix matching works with quality values
-
Working with Wildcards — Using
*as a catch-all fallback -
Excluding Languages — Deep dive into
q=0exclusions