Skip Accept-Language tags that match no supported locale - #19
Merged
kipcole9 merged 1 commit intoSep 27, 2026
Merged
Conversation
Localize.validate_locale/1 falls back to the first supported locale for a tag with no close match, so best_match/1 always took the first tag. Accept a tag only when CLDR matches it within the distance of unrelated languages, and return the supported locale when that match crosses languages.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Before
With
supported_locales: [:"de-CH", :"en-CH", :"fr-CH", :"it-CH"]anddefault_locale: :"de-CH":es-ES,fr-CH;q=0.9,en;q=0.8es-ES,:es,:"de-CH"ja,en;q=0.9ja,:ja,:"de-CH"pt-BR,en;q=0.8pt-BR,:pt,:"de-CH"gsw-CH,de;q=0.9gsw-CH,:gsw,:"de-CH"rm-CHrm-CH,:rm,:"de-CH"Problem
best_match/1takes the first tag in the header.Localize.validate_locale/1falls back to the first supported locale when nothing is close, so thenot is_nil(id)guard never rejects a tag and the later candidates (fr-CH,en) are never tried.language: :es,cldr_locale_id: :"de-CH").PutLocalestores it in the session and Gettext getsde, while a Spanish/French visitor who listed French second is served German.:accept_languageoption documents "the best matched configured locale".After
es-ES,fr-CH;q=0.9,en;q=0.8fr-CH,:fr,:"fr-CH"ja,en;q=0.9en,:en,:"en-CH"pt-BR,en;q=0.8en,:en,:"en-CH"gsw-CH,de;q=0.9de-CH,:de,:"de-CH"rm-CHde-CH,:de,:"de-CH"en-US,en;q=0.9en-US,:en,:"en-CH"(unchanged)zh-TW{:error, %Localize.UnknownLocaleError{}}, soPutLocalemoves on to its next source or the defaultDetails
Cause:
Localize.AcceptLanguage.best_match/1inlib/localize/accept_language.exaccepted any tag thatLocalize.validate_locale/1resolved. That function always resolves a valid tag (its docs say so under "Valid tags always match"), becauseLocalize.LanguageTag.best_match/2falls back to the first supported locale at its default threshold of 80.Design: after
validate_locale/1, check the resolvedcldr_locale_idwithLocalize.LanguageTag.best_match(requested, [requested.cldr_locale_id], 79). The distance between unrelated languages is 80, so 79 keeps every real CLDR match (en-UStoen-CHat 5,gswtodeat 4,rmtodeat 20) and rejects only the fallback. Scoring the one resolved id keeps the per-request cost small, also whensupported_localesis not configured. When CLDR matches across languages (gsw,rm), the tag of the supported locale is returned, solanguagematches the served data; same-language matches keep the requested tag (en-USwithen-CHdata), as before. Threshold0, which thevalidate_locale/1docs suggest for strict checks, would rejecten-USagainsten-CH.Tests:
test/accept_language_test.exs. Four existing tests asserted the fallback: Romanian, Polish and Spanish are not in the testsupported_locales, and each expected the unsupported language back (with:endata). They now expect the next supported candidate. New tests coveres-ES,fr-CHtofr,gsw-CHtode, and an error when no tag matches. The new and updated tests fail onmain. Also checked withlocalize1.3.0 and ade-CH/en-CH/fr-CH/it-CHconfig throughPutLocale(the tables above).Checks:
mix test(270 passed),mix format --check-formatted,mix compile --warnings-as-errors.mix credo --strictreports only the existingSigil_q.InterpolationTestmodule-name issue.AI disclosure: AI models helped us find this issue, write the change, and review it.