Use order in preferred regions list (#91959)

* Use order in preferred regions list

* Use float for score (inf = exact match)
This commit is contained in:
Michael Hansen 2023-04-24 13:12:38 -05:00 committed by GitHub
parent b601fb17d3
commit b4bd3b97f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 73 additions and 23 deletions

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from collections.abc import Iterable from collections.abc import Iterable
from dataclasses import dataclass from dataclasses import dataclass
import math
import operator import operator
import re import re
@ -15,7 +16,7 @@ def preferred_regions(
language: str, language: str,
country: str | None = None, country: str | None = None,
code: str | None = None, code: str | None = None,
) -> Iterable[str | None]: ) -> Iterable[str]:
"""Yield an ordered list of regions for a language based on country/code hints. """Yield an ordered list of regions for a language based on country/code hints.
Regions should be checked for support in the returned order if no other Regions should be checked for support in the returned order if no other
@ -70,7 +71,7 @@ class Dialect:
# Regions are upper-cased # Regions are upper-cased
self.region = self.region.upper() self.region = self.region.upper()
def score(self, dialect: Dialect, country: str | None = None) -> int: def score(self, dialect: Dialect, country: str | None = None) -> float:
"""Return score for match with another dialect where higher is better. """Return score for match with another dialect where higher is better.
Score < 0 indicates a failure to match. Score < 0 indicates a failure to match.
@ -79,27 +80,46 @@ class Dialect:
# Not a match # Not a match
return -1 return -1
if self.region == dialect.region: if (self.region is None) and (dialect.region is None):
# Language + region match # Weak match with no region constraint
return 1 return 1
pref_regions: set[str | None] = set() if (self.region is not None) and (dialect.region is not None):
if (self.region is None) or (dialect.region is None): if self.region == dialect.region:
# Generate a set of preferred regions # Exact language + region match
pref_regions = set( return math.inf
preferred_regions(
self.language, # Regions are both set, but don't match
country=country, return 0
code=self.code,
) # Generate ordered list of preferred regions
pref_regions = list(
preferred_regions(
self.language,
country=country,
code=self.code,
) )
)
# Replace missing regions with preferred try:
regions = pref_regions if self.region is None else {self.region} # Determine score based on position in the preferred regions list.
other_regions = pref_regions if dialect.region is None else {dialect.region} if self.region is not None:
region_idx = pref_regions.index(self.region)
elif dialect.region is not None:
region_idx = pref_regions.index(dialect.region)
else:
# Can't happen, but mypy is not smart enough
raise ValueError()
# Better match if there is overlap in regions # More preferred regions are at the front.
return 2 if regions.intersection(other_regions) else 0 # Add 1 to boost above a weak match where no regions are set.
return 1 + (len(pref_regions) - region_idx)
except ValueError:
# Region was not in preferred list
pass
# Not a preferred region
return 0
@staticmethod @staticmethod
def parse(tag: str) -> Dialect: def parse(tag: str) -> Dialect:

View File

@ -1,6 +1,8 @@
"""Test Home Assistant language util methods.""" """Test Home Assistant language util methods."""
from __future__ import annotations from __future__ import annotations
import pytest
from homeassistant.const import MATCH_ALL from homeassistant.const import MATCH_ALL
from homeassistant.util import language from homeassistant.util import language
@ -95,26 +97,54 @@ def test_language_as_region() -> None:
def test_zh_hant() -> None: def test_zh_hant() -> None:
"""Test that the zh-Hant matches HK or TW first.""" """Test that the zh-Hant matches HK or TW."""
assert language.matches( assert language.matches(
"zh-Hant", "zh-Hant",
["en-US", "en-GB", "zh-CN", "zh-HK", "zh-TW"], ["en-US", "en-GB", "zh-CN", "zh-HK"],
) == [ ) == [
"zh-HK", "zh-HK",
"zh-TW",
"zh-CN", "zh-CN",
] ]
assert language.matches( assert language.matches(
"zh-Hant", "zh-Hant",
["en-US", "en-GB", "zh-CN", "zh-TW", "zh-HK"], ["en-US", "en-GB", "zh-CN", "zh-TW"],
) == [ ) == [
"zh-TW", "zh-TW",
"zh-HK",
"zh-CN", "zh-CN",
] ]
@pytest.mark.parametrize("target", ["zh-Hant", "zh-Hans"])
def test_zh_with_country(target: str) -> None:
"""Test that the zh-Hant/zh-Hans still matches country when provided."""
supported = ["en-US", "en-GB", "zh-CN", "zh-HK", "zh-TW"]
assert (
language.matches(
target,
supported,
country="TW",
)[0]
== "zh-TW"
)
assert (
language.matches(
target,
supported,
country="HK",
)[0]
== "zh-HK"
)
assert (
language.matches(
target,
supported,
country="CN",
)[0]
== "zh-CN"
)
def test_zh_hans() -> None: def test_zh_hans() -> None:
"""Test that the zh-Hans matches CN first.""" """Test that the zh-Hans matches CN first."""
assert language.matches( assert language.matches(