mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 10:47:10 +00:00
Allow configuring country and language in core config (#81734)
* Allow configuring country and language in core config * Add script for updating list of countries * Use black for formatting * Fix quoting * Move country codes to a separate file * Address review comments * Add generated/countries.py * Get default language from owner account * Remove unused variable * Add script to generate list of supported languages * Add tests * Fix stale docsring * Use format_python_namespace * Correct async_user_store * Improve typing * Fix with_store decorator * Initialize language in core store migration * Fix startup * Tweak * Apply suggestions from code review Co-authored-by: Franck Nijhof <git@frenck.dev> * Update storage.py Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
parent
09c3df7eb2
commit
e1338adf1a
@ -24,7 +24,7 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: codespell
|
- id: codespell
|
||||||
args:
|
args:
|
||||||
- --ignore-words-list=alot,ba,bre,datas,dof,dur,ether,farenheit,haa,hass,hist,iam,iff,iif,incomfort,ines,ist,lightsensor,mut,nd,pres,pullrequests,referer,rime,ser,serie,sur,te,technik,ue,uint,visability,wan,wanna,withing
|
- --ignore-words-list=alot,ba,bre,datas,dof,dur,ether,farenheit,fo,haa,hass,hist,iam,iff,iif,incomfort,ines,ist,lightsensor,mut,nd,pres,pullrequests,referer,rime,ser,serie,sur,te,technik,ue,uint,visability,wan,wanna,withing
|
||||||
- --skip="./.*,*.csv,*.json"
|
- --skip="./.*,*.csv,*.json"
|
||||||
- --quiet-level=2
|
- --quiet-level=2
|
||||||
exclude_types: [csv, json]
|
exclude_types: [csv, json]
|
||||||
|
@ -53,6 +53,7 @@ ERROR_LOG_FILENAME = "home-assistant.log"
|
|||||||
|
|
||||||
# hass.data key for logging information.
|
# hass.data key for logging information.
|
||||||
DATA_LOGGING = "logging"
|
DATA_LOGGING = "logging"
|
||||||
|
DATA_REGISTRIES_LOADED = "bootstrap_registries_loaded"
|
||||||
|
|
||||||
LOG_SLOW_STARTUP_INTERVAL = 60
|
LOG_SLOW_STARTUP_INTERVAL = 60
|
||||||
SLOW_STARTUP_CHECK_INTERVAL = 1
|
SLOW_STARTUP_CHECK_INTERVAL = 1
|
||||||
@ -216,6 +217,32 @@ def open_hass_ui(hass: core.HomeAssistant) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def load_registries(hass: core.HomeAssistant) -> None:
|
||||||
|
"""Load the registries and cache the result of platform.uname().processor."""
|
||||||
|
if DATA_REGISTRIES_LOADED in hass.data:
|
||||||
|
return
|
||||||
|
hass.data[DATA_REGISTRIES_LOADED] = None
|
||||||
|
|
||||||
|
def _cache_uname_processor() -> None:
|
||||||
|
"""Cache the result of platform.uname().processor in the executor.
|
||||||
|
|
||||||
|
Multiple modules call this function at startup which
|
||||||
|
executes a blocking subprocess call. This is a problem for the
|
||||||
|
asyncio event loop. By primeing the cache of uname we can
|
||||||
|
avoid the blocking call in the event loop.
|
||||||
|
"""
|
||||||
|
platform.uname().processor # pylint: disable=expression-not-assigned
|
||||||
|
|
||||||
|
# Load the registries and cache the result of platform.uname().processor
|
||||||
|
await asyncio.gather(
|
||||||
|
area_registry.async_load(hass),
|
||||||
|
device_registry.async_load(hass),
|
||||||
|
entity_registry.async_load(hass),
|
||||||
|
issue_registry.async_load(hass),
|
||||||
|
hass.async_add_executor_job(_cache_uname_processor),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_from_config_dict(
|
async def async_from_config_dict(
|
||||||
config: ConfigType, hass: core.HomeAssistant
|
config: ConfigType, hass: core.HomeAssistant
|
||||||
) -> core.HomeAssistant | None:
|
) -> core.HomeAssistant | None:
|
||||||
@ -228,6 +255,7 @@ async def async_from_config_dict(
|
|||||||
|
|
||||||
hass.config_entries = config_entries.ConfigEntries(hass, config)
|
hass.config_entries = config_entries.ConfigEntries(hass, config)
|
||||||
await hass.config_entries.async_initialize()
|
await hass.config_entries.async_initialize()
|
||||||
|
await load_registries(hass)
|
||||||
|
|
||||||
# Set up core.
|
# Set up core.
|
||||||
_LOGGER.debug("Setting up %s", CORE_INTEGRATIONS)
|
_LOGGER.debug("Setting up %s", CORE_INTEGRATIONS)
|
||||||
@ -530,25 +558,6 @@ async def _async_set_up_integrations(
|
|||||||
|
|
||||||
_LOGGER.info("Domains to be set up: %s", domains_to_setup)
|
_LOGGER.info("Domains to be set up: %s", domains_to_setup)
|
||||||
|
|
||||||
def _cache_uname_processor() -> None:
|
|
||||||
"""Cache the result of platform.uname().processor in the executor.
|
|
||||||
|
|
||||||
Multiple modules call this function at startup which
|
|
||||||
executes a blocking subprocess call. This is a problem for the
|
|
||||||
asyncio event loop. By primeing the cache of uname we can
|
|
||||||
avoid the blocking call in the event loop.
|
|
||||||
"""
|
|
||||||
platform.uname().processor # pylint: disable=expression-not-assigned
|
|
||||||
|
|
||||||
# Load the registries and cache the result of platform.uname().processor
|
|
||||||
await asyncio.gather(
|
|
||||||
area_registry.async_load(hass),
|
|
||||||
device_registry.async_load(hass),
|
|
||||||
entity_registry.async_load(hass),
|
|
||||||
issue_registry.async_load(hass),
|
|
||||||
hass.async_add_executor_job(_cache_uname_processor),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize recorder
|
# Initialize recorder
|
||||||
if "recorder" in domains_to_setup:
|
if "recorder" in domains_to_setup:
|
||||||
recorder.async_initialize_recorder(hass)
|
recorder.async_initialize_recorder(hass)
|
||||||
|
@ -49,6 +49,8 @@ class CheckConfigView(HomeAssistantView):
|
|||||||
vol.Optional("external_url"): vol.Any(cv.url_no_path, None),
|
vol.Optional("external_url"): vol.Any(cv.url_no_path, None),
|
||||||
vol.Optional("internal_url"): vol.Any(cv.url_no_path, None),
|
vol.Optional("internal_url"): vol.Any(cv.url_no_path, None),
|
||||||
vol.Optional("currency"): cv.currency,
|
vol.Optional("currency"): cv.currency,
|
||||||
|
vol.Optional("country"): cv.country,
|
||||||
|
vol.Optional("language"): cv.language,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
|
@ -9,20 +9,47 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.components import websocket_api
|
from homeassistant.components import websocket_api
|
||||||
from homeassistant.components.websocket_api.connection import ActiveConnection
|
from homeassistant.components.websocket_api.connection import ActiveConnection
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.storage import Store
|
from homeassistant.helpers.storage import Store
|
||||||
|
|
||||||
DATA_STORAGE = "frontend_storage"
|
DATA_STORAGE = "frontend_storage"
|
||||||
STORAGE_VERSION_USER_DATA = 1
|
STORAGE_VERSION_USER_DATA = 1
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _initialize_frontend_storage(hass: HomeAssistant) -> None:
|
||||||
|
"""Set up frontend storage."""
|
||||||
|
if DATA_STORAGE in hass.data:
|
||||||
|
return
|
||||||
|
hass.data[DATA_STORAGE] = ({}, {})
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_frontend_storage(hass: HomeAssistant) -> None:
|
async def async_setup_frontend_storage(hass: HomeAssistant) -> None:
|
||||||
"""Set up frontend storage."""
|
"""Set up frontend storage."""
|
||||||
hass.data[DATA_STORAGE] = ({}, {})
|
_initialize_frontend_storage(hass)
|
||||||
websocket_api.async_register_command(hass, websocket_set_user_data)
|
websocket_api.async_register_command(hass, websocket_set_user_data)
|
||||||
websocket_api.async_register_command(hass, websocket_get_user_data)
|
websocket_api.async_register_command(hass, websocket_get_user_data)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_user_store(
|
||||||
|
hass: HomeAssistant, user_id: str
|
||||||
|
) -> tuple[Store, dict[str, Any]]:
|
||||||
|
"""Access a user store."""
|
||||||
|
_initialize_frontend_storage(hass)
|
||||||
|
stores, data = hass.data[DATA_STORAGE]
|
||||||
|
if (store := stores.get(user_id)) is None:
|
||||||
|
store = stores[user_id] = Store(
|
||||||
|
hass,
|
||||||
|
STORAGE_VERSION_USER_DATA,
|
||||||
|
f"frontend.user_data_{user_id}",
|
||||||
|
)
|
||||||
|
|
||||||
|
if user_id not in data:
|
||||||
|
data[user_id] = await store.async_load() or {}
|
||||||
|
|
||||||
|
return store, data[user_id]
|
||||||
|
|
||||||
|
|
||||||
def with_store(orig_func: Callable) -> Callable:
|
def with_store(orig_func: Callable) -> Callable:
|
||||||
"""Decorate function to provide data."""
|
"""Decorate function to provide data."""
|
||||||
|
|
||||||
@ -31,20 +58,11 @@ def with_store(orig_func: Callable) -> Callable:
|
|||||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict
|
hass: HomeAssistant, connection: ActiveConnection, msg: dict
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Provide user specific data and store to function."""
|
"""Provide user specific data and store to function."""
|
||||||
stores, data = hass.data[DATA_STORAGE]
|
|
||||||
user_id = connection.user.id
|
user_id = connection.user.id
|
||||||
|
|
||||||
if (store := stores.get(user_id)) is None:
|
store, user_data = await async_user_store(hass, user_id)
|
||||||
store = stores[user_id] = Store(
|
|
||||||
hass,
|
|
||||||
STORAGE_VERSION_USER_DATA,
|
|
||||||
f"frontend.user_data_{connection.user.id}",
|
|
||||||
)
|
|
||||||
|
|
||||||
if user_id not in data:
|
await orig_func(hass, connection, msg, store, user_data)
|
||||||
data[user_id] = await store.async_load() or {}
|
|
||||||
|
|
||||||
await orig_func(hass, connection, msg, store, data[user_id])
|
|
||||||
|
|
||||||
return with_store_func
|
return with_store_func
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ from .const import (
|
|||||||
CONF_ALLOWLIST_EXTERNAL_URLS,
|
CONF_ALLOWLIST_EXTERNAL_URLS,
|
||||||
CONF_AUTH_MFA_MODULES,
|
CONF_AUTH_MFA_MODULES,
|
||||||
CONF_AUTH_PROVIDERS,
|
CONF_AUTH_PROVIDERS,
|
||||||
|
CONF_COUNTRY,
|
||||||
CONF_CURRENCY,
|
CONF_CURRENCY,
|
||||||
CONF_CUSTOMIZE,
|
CONF_CUSTOMIZE,
|
||||||
CONF_CUSTOMIZE_DOMAIN,
|
CONF_CUSTOMIZE_DOMAIN,
|
||||||
@ -35,6 +36,7 @@ from .const import (
|
|||||||
CONF_EXTERNAL_URL,
|
CONF_EXTERNAL_URL,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_INTERNAL_URL,
|
CONF_INTERNAL_URL,
|
||||||
|
CONF_LANGUAGE,
|
||||||
CONF_LATITUDE,
|
CONF_LATITUDE,
|
||||||
CONF_LEGACY_TEMPLATES,
|
CONF_LEGACY_TEMPLATES,
|
||||||
CONF_LONGITUDE,
|
CONF_LONGITUDE,
|
||||||
@ -281,6 +283,8 @@ CORE_CONFIG_SCHEMA = vol.All(
|
|||||||
vol.Optional(CONF_MEDIA_DIRS): cv.schema_with_slug_keys(vol.IsDir()),
|
vol.Optional(CONF_MEDIA_DIRS): cv.schema_with_slug_keys(vol.IsDir()),
|
||||||
vol.Optional(CONF_LEGACY_TEMPLATES): cv.boolean,
|
vol.Optional(CONF_LEGACY_TEMPLATES): cv.boolean,
|
||||||
vol.Optional(CONF_CURRENCY): _validate_currency,
|
vol.Optional(CONF_CURRENCY): _validate_currency,
|
||||||
|
vol.Optional(CONF_COUNTRY): cv.country,
|
||||||
|
vol.Optional(CONF_LANGUAGE): cv.language,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
_filter_bad_internal_external_urls,
|
_filter_bad_internal_external_urls,
|
||||||
@ -560,6 +564,8 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: dict) -> Non
|
|||||||
CONF_EXTERNAL_URL,
|
CONF_EXTERNAL_URL,
|
||||||
CONF_INTERNAL_URL,
|
CONF_INTERNAL_URL,
|
||||||
CONF_CURRENCY,
|
CONF_CURRENCY,
|
||||||
|
CONF_COUNTRY,
|
||||||
|
CONF_LANGUAGE,
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
hac.config_source = ConfigSource.YAML
|
hac.config_source = ConfigSource.YAML
|
||||||
@ -574,6 +580,8 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: dict) -> Non
|
|||||||
(CONF_MEDIA_DIRS, "media_dirs"),
|
(CONF_MEDIA_DIRS, "media_dirs"),
|
||||||
(CONF_LEGACY_TEMPLATES, "legacy_templates"),
|
(CONF_LEGACY_TEMPLATES, "legacy_templates"),
|
||||||
(CONF_CURRENCY, "currency"),
|
(CONF_CURRENCY, "currency"),
|
||||||
|
(CONF_COUNTRY, "country"),
|
||||||
|
(CONF_LANGUAGE, "language"),
|
||||||
):
|
):
|
||||||
if key in config:
|
if key in config:
|
||||||
setattr(hac, attr, config[key])
|
setattr(hac, attr, config[key])
|
||||||
|
@ -121,6 +121,7 @@ CONF_CONDITIONS: Final = "conditions"
|
|||||||
CONF_CONTINUE_ON_ERROR: Final = "continue_on_error"
|
CONF_CONTINUE_ON_ERROR: Final = "continue_on_error"
|
||||||
CONF_CONTINUE_ON_TIMEOUT: Final = "continue_on_timeout"
|
CONF_CONTINUE_ON_TIMEOUT: Final = "continue_on_timeout"
|
||||||
CONF_COUNT: Final = "count"
|
CONF_COUNT: Final = "count"
|
||||||
|
CONF_COUNTRY: Final = "country"
|
||||||
CONF_COVERS: Final = "covers"
|
CONF_COVERS: Final = "covers"
|
||||||
CONF_CURRENCY: Final = "currency"
|
CONF_CURRENCY: Final = "currency"
|
||||||
CONF_CUSTOMIZE: Final = "customize"
|
CONF_CUSTOMIZE: Final = "customize"
|
||||||
@ -175,6 +176,7 @@ CONF_IF: Final = "if"
|
|||||||
CONF_INCLUDE: Final = "include"
|
CONF_INCLUDE: Final = "include"
|
||||||
CONF_INTERNAL_URL: Final = "internal_url"
|
CONF_INTERNAL_URL: Final = "internal_url"
|
||||||
CONF_IP_ADDRESS: Final = "ip_address"
|
CONF_IP_ADDRESS: Final = "ip_address"
|
||||||
|
CONF_LANGUAGE: Final = "language"
|
||||||
CONF_LATITUDE: Final = "latitude"
|
CONF_LATITUDE: Final = "latitude"
|
||||||
CONF_LEGACY_TEMPLATES: Final = "legacy_templates"
|
CONF_LEGACY_TEMPLATES: Final = "legacy_templates"
|
||||||
CONF_LIGHTS: Final = "lights"
|
CONF_LIGHTS: Final = "lights"
|
||||||
|
@ -15,6 +15,7 @@ from collections.abc import (
|
|||||||
Iterable,
|
Iterable,
|
||||||
Mapping,
|
Mapping,
|
||||||
)
|
)
|
||||||
|
from contextlib import suppress
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
import datetime
|
import datetime
|
||||||
import enum
|
import enum
|
||||||
@ -113,7 +114,7 @@ CALLBACK_TYPE = Callable[[], None] # pylint: disable=invalid-name
|
|||||||
|
|
||||||
CORE_STORAGE_KEY = "core.config"
|
CORE_STORAGE_KEY = "core.config"
|
||||||
CORE_STORAGE_VERSION = 1
|
CORE_STORAGE_VERSION = 1
|
||||||
CORE_STORAGE_MINOR_VERSION = 2
|
CORE_STORAGE_MINOR_VERSION = 3
|
||||||
|
|
||||||
DOMAIN = "homeassistant"
|
DOMAIN = "homeassistant"
|
||||||
|
|
||||||
@ -1807,6 +1808,8 @@ class Config:
|
|||||||
self.internal_url: str | None = None
|
self.internal_url: str | None = None
|
||||||
self.external_url: str | None = None
|
self.external_url: str | None = None
|
||||||
self.currency: str = "EUR"
|
self.currency: str = "EUR"
|
||||||
|
self.country: str | None = None
|
||||||
|
self.language: str = "en"
|
||||||
|
|
||||||
self.config_source: ConfigSource = ConfigSource.DEFAULT
|
self.config_source: ConfigSource = ConfigSource.DEFAULT
|
||||||
|
|
||||||
@ -1913,6 +1916,8 @@ class Config:
|
|||||||
"external_url": self.external_url,
|
"external_url": self.external_url,
|
||||||
"internal_url": self.internal_url,
|
"internal_url": self.internal_url,
|
||||||
"currency": self.currency,
|
"currency": self.currency,
|
||||||
|
"country": self.country,
|
||||||
|
"language": self.language,
|
||||||
}
|
}
|
||||||
|
|
||||||
def set_time_zone(self, time_zone_str: str) -> None:
|
def set_time_zone(self, time_zone_str: str) -> None:
|
||||||
@ -1938,6 +1943,8 @@ class Config:
|
|||||||
external_url: str | dict[Any, Any] | None = _UNDEF,
|
external_url: str | dict[Any, Any] | None = _UNDEF,
|
||||||
internal_url: str | dict[Any, Any] | None = _UNDEF,
|
internal_url: str | dict[Any, Any] | None = _UNDEF,
|
||||||
currency: str | None = None,
|
currency: str | None = None,
|
||||||
|
country: str | dict[Any, Any] | None = _UNDEF,
|
||||||
|
language: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Update the configuration from a dictionary."""
|
"""Update the configuration from a dictionary."""
|
||||||
self.config_source = source
|
self.config_source = source
|
||||||
@ -1962,6 +1969,10 @@ class Config:
|
|||||||
self.internal_url = cast(Optional[str], internal_url)
|
self.internal_url = cast(Optional[str], internal_url)
|
||||||
if currency is not None:
|
if currency is not None:
|
||||||
self.currency = currency
|
self.currency = currency
|
||||||
|
if country is not _UNDEF:
|
||||||
|
self.country = cast(Optional[str], country)
|
||||||
|
if language is not None:
|
||||||
|
self.language = language
|
||||||
|
|
||||||
async def async_update(self, **kwargs: Any) -> None:
|
async def async_update(self, **kwargs: Any) -> None:
|
||||||
"""Update the configuration from a dictionary."""
|
"""Update the configuration from a dictionary."""
|
||||||
@ -1999,6 +2010,8 @@ class Config:
|
|||||||
external_url=data.get("external_url", _UNDEF),
|
external_url=data.get("external_url", _UNDEF),
|
||||||
internal_url=data.get("internal_url", _UNDEF),
|
internal_url=data.get("internal_url", _UNDEF),
|
||||||
currency=data.get("currency"),
|
currency=data.get("currency"),
|
||||||
|
country=data.get("country"),
|
||||||
|
language=data.get("language"),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _async_store(self) -> None:
|
async def _async_store(self) -> None:
|
||||||
@ -2015,6 +2028,8 @@ class Config:
|
|||||||
"external_url": self.external_url,
|
"external_url": self.external_url,
|
||||||
"internal_url": self.internal_url,
|
"internal_url": self.internal_url,
|
||||||
"currency": self.currency,
|
"currency": self.currency,
|
||||||
|
"country": self.country,
|
||||||
|
"language": self.language,
|
||||||
}
|
}
|
||||||
|
|
||||||
await self._store.async_save(data)
|
await self._store.async_save(data)
|
||||||
@ -2053,6 +2068,35 @@ class Config:
|
|||||||
data["unit_system_v2"] = self._original_unit_system
|
data["unit_system_v2"] = self._original_unit_system
|
||||||
if data["unit_system_v2"] == _CONF_UNIT_SYSTEM_IMPERIAL:
|
if data["unit_system_v2"] == _CONF_UNIT_SYSTEM_IMPERIAL:
|
||||||
data["unit_system_v2"] = _CONF_UNIT_SYSTEM_US_CUSTOMARY
|
data["unit_system_v2"] = _CONF_UNIT_SYSTEM_US_CUSTOMARY
|
||||||
|
if old_major_version == 1 and old_minor_version < 3:
|
||||||
|
# In 1.3, we add the key "language", initialize it from the owner account
|
||||||
|
data["language"] = "en"
|
||||||
|
try:
|
||||||
|
owner = await self.hass.auth.async_get_owner()
|
||||||
|
if owner is not None:
|
||||||
|
# pylint: disable-next=import-outside-toplevel
|
||||||
|
from .components.frontend import storage as frontend_store
|
||||||
|
|
||||||
|
# pylint: disable-next=import-outside-toplevel
|
||||||
|
from .helpers import config_validation as cv
|
||||||
|
|
||||||
|
_, owner_data = await frontend_store.async_user_store(
|
||||||
|
self.hass, owner.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
"language" in owner_data
|
||||||
|
and "language" in owner_data["language"]
|
||||||
|
):
|
||||||
|
with suppress(vol.InInvalid):
|
||||||
|
# pylint: disable-next=protected-access
|
||||||
|
data["language"] = cv.language(
|
||||||
|
owner_data["language"]["language"]
|
||||||
|
)
|
||||||
|
# pylint: disable-next=broad-except
|
||||||
|
except Exception:
|
||||||
|
_LOGGER.exception("Unexpected error during core config migration")
|
||||||
|
|
||||||
if old_major_version > 1:
|
if old_major_version > 1:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
return data
|
return data
|
||||||
|
260
homeassistant/generated/countries.py
Normal file
260
homeassistant/generated/countries.py
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
"""This file is automatically generated.
|
||||||
|
|
||||||
|
To update, run python3 -m script.countries
|
||||||
|
|
||||||
|
The values are directly corresponding to the ISO 3166 standard. If you need changes
|
||||||
|
to the political situation in the world, please contact the ISO 3166 working group.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
COUNTRIES = {
|
||||||
|
"AD",
|
||||||
|
"AE",
|
||||||
|
"AF",
|
||||||
|
"AG",
|
||||||
|
"AI",
|
||||||
|
"AL",
|
||||||
|
"AM",
|
||||||
|
"AO",
|
||||||
|
"AQ",
|
||||||
|
"AR",
|
||||||
|
"AS",
|
||||||
|
"AT",
|
||||||
|
"AU",
|
||||||
|
"AW",
|
||||||
|
"AX",
|
||||||
|
"AZ",
|
||||||
|
"BA",
|
||||||
|
"BB",
|
||||||
|
"BD",
|
||||||
|
"BE",
|
||||||
|
"BF",
|
||||||
|
"BG",
|
||||||
|
"BH",
|
||||||
|
"BI",
|
||||||
|
"BJ",
|
||||||
|
"BL",
|
||||||
|
"BM",
|
||||||
|
"BN",
|
||||||
|
"BO",
|
||||||
|
"BQ",
|
||||||
|
"BR",
|
||||||
|
"BS",
|
||||||
|
"BT",
|
||||||
|
"BV",
|
||||||
|
"BW",
|
||||||
|
"BY",
|
||||||
|
"BZ",
|
||||||
|
"CA",
|
||||||
|
"CC",
|
||||||
|
"CD",
|
||||||
|
"CF",
|
||||||
|
"CG",
|
||||||
|
"CH",
|
||||||
|
"CI",
|
||||||
|
"CK",
|
||||||
|
"CL",
|
||||||
|
"CM",
|
||||||
|
"CN",
|
||||||
|
"CO",
|
||||||
|
"CR",
|
||||||
|
"CU",
|
||||||
|
"CV",
|
||||||
|
"CW",
|
||||||
|
"CX",
|
||||||
|
"CY",
|
||||||
|
"CZ",
|
||||||
|
"DE",
|
||||||
|
"DJ",
|
||||||
|
"DK",
|
||||||
|
"DM",
|
||||||
|
"DO",
|
||||||
|
"DZ",
|
||||||
|
"EC",
|
||||||
|
"EE",
|
||||||
|
"EG",
|
||||||
|
"EH",
|
||||||
|
"ER",
|
||||||
|
"ES",
|
||||||
|
"ET",
|
||||||
|
"FI",
|
||||||
|
"FJ",
|
||||||
|
"FK",
|
||||||
|
"FM",
|
||||||
|
"FO",
|
||||||
|
"FR",
|
||||||
|
"GA",
|
||||||
|
"GB",
|
||||||
|
"GD",
|
||||||
|
"GE",
|
||||||
|
"GF",
|
||||||
|
"GG",
|
||||||
|
"GH",
|
||||||
|
"GI",
|
||||||
|
"GL",
|
||||||
|
"GM",
|
||||||
|
"GN",
|
||||||
|
"GP",
|
||||||
|
"GQ",
|
||||||
|
"GR",
|
||||||
|
"GS",
|
||||||
|
"GT",
|
||||||
|
"GU",
|
||||||
|
"GW",
|
||||||
|
"GY",
|
||||||
|
"HK",
|
||||||
|
"HM",
|
||||||
|
"HN",
|
||||||
|
"HR",
|
||||||
|
"HT",
|
||||||
|
"HU",
|
||||||
|
"ID",
|
||||||
|
"IE",
|
||||||
|
"IL",
|
||||||
|
"IM",
|
||||||
|
"IN",
|
||||||
|
"IO",
|
||||||
|
"IQ",
|
||||||
|
"IR",
|
||||||
|
"IS",
|
||||||
|
"IT",
|
||||||
|
"JE",
|
||||||
|
"JM",
|
||||||
|
"JO",
|
||||||
|
"JP",
|
||||||
|
"KE",
|
||||||
|
"KG",
|
||||||
|
"KH",
|
||||||
|
"KI",
|
||||||
|
"KM",
|
||||||
|
"KN",
|
||||||
|
"KP",
|
||||||
|
"KR",
|
||||||
|
"KW",
|
||||||
|
"KY",
|
||||||
|
"KZ",
|
||||||
|
"LA",
|
||||||
|
"LB",
|
||||||
|
"LC",
|
||||||
|
"LI",
|
||||||
|
"LK",
|
||||||
|
"LR",
|
||||||
|
"LS",
|
||||||
|
"LT",
|
||||||
|
"LU",
|
||||||
|
"LV",
|
||||||
|
"LY",
|
||||||
|
"MA",
|
||||||
|
"MC",
|
||||||
|
"MD",
|
||||||
|
"ME",
|
||||||
|
"MF",
|
||||||
|
"MG",
|
||||||
|
"MH",
|
||||||
|
"MK",
|
||||||
|
"ML",
|
||||||
|
"MM",
|
||||||
|
"MN",
|
||||||
|
"MO",
|
||||||
|
"MP",
|
||||||
|
"MQ",
|
||||||
|
"MR",
|
||||||
|
"MS",
|
||||||
|
"MT",
|
||||||
|
"MU",
|
||||||
|
"MV",
|
||||||
|
"MW",
|
||||||
|
"MX",
|
||||||
|
"MY",
|
||||||
|
"MZ",
|
||||||
|
"NA",
|
||||||
|
"NC",
|
||||||
|
"NE",
|
||||||
|
"NF",
|
||||||
|
"NG",
|
||||||
|
"NI",
|
||||||
|
"NL",
|
||||||
|
"NO",
|
||||||
|
"NP",
|
||||||
|
"NR",
|
||||||
|
"NU",
|
||||||
|
"NZ",
|
||||||
|
"OM",
|
||||||
|
"PA",
|
||||||
|
"PE",
|
||||||
|
"PF",
|
||||||
|
"PG",
|
||||||
|
"PH",
|
||||||
|
"PK",
|
||||||
|
"PL",
|
||||||
|
"PM",
|
||||||
|
"PN",
|
||||||
|
"PR",
|
||||||
|
"PS",
|
||||||
|
"PT",
|
||||||
|
"PW",
|
||||||
|
"PY",
|
||||||
|
"QA",
|
||||||
|
"RE",
|
||||||
|
"RO",
|
||||||
|
"RS",
|
||||||
|
"RU",
|
||||||
|
"RW",
|
||||||
|
"SA",
|
||||||
|
"SB",
|
||||||
|
"SC",
|
||||||
|
"SD",
|
||||||
|
"SE",
|
||||||
|
"SG",
|
||||||
|
"SH",
|
||||||
|
"SI",
|
||||||
|
"SJ",
|
||||||
|
"SK",
|
||||||
|
"SL",
|
||||||
|
"SM",
|
||||||
|
"SN",
|
||||||
|
"SO",
|
||||||
|
"SR",
|
||||||
|
"SS",
|
||||||
|
"ST",
|
||||||
|
"SV",
|
||||||
|
"SX",
|
||||||
|
"SY",
|
||||||
|
"SZ",
|
||||||
|
"TC",
|
||||||
|
"TD",
|
||||||
|
"TF",
|
||||||
|
"TG",
|
||||||
|
"TH",
|
||||||
|
"TJ",
|
||||||
|
"TK",
|
||||||
|
"TL",
|
||||||
|
"TM",
|
||||||
|
"TN",
|
||||||
|
"TO",
|
||||||
|
"TR",
|
||||||
|
"TT",
|
||||||
|
"TV",
|
||||||
|
"TW",
|
||||||
|
"TZ",
|
||||||
|
"UA",
|
||||||
|
"UG",
|
||||||
|
"UM",
|
||||||
|
"US",
|
||||||
|
"UY",
|
||||||
|
"UZ",
|
||||||
|
"VA",
|
||||||
|
"VC",
|
||||||
|
"VE",
|
||||||
|
"VG",
|
||||||
|
"VI",
|
||||||
|
"VN",
|
||||||
|
"VU",
|
||||||
|
"WF",
|
||||||
|
"WS",
|
||||||
|
"YE",
|
||||||
|
"YT",
|
||||||
|
"ZA",
|
||||||
|
"ZM",
|
||||||
|
"ZW",
|
||||||
|
}
|
68
homeassistant/generated/languages.py
Normal file
68
homeassistant/generated/languages.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
"""This file is automatically generated.
|
||||||
|
|
||||||
|
To update, run python3 -m script.languages [frontend_tag]
|
||||||
|
"""
|
||||||
|
|
||||||
|
LANGUAGES = {
|
||||||
|
"af",
|
||||||
|
"ar",
|
||||||
|
"bg",
|
||||||
|
"bn",
|
||||||
|
"bs",
|
||||||
|
"ca",
|
||||||
|
"cs",
|
||||||
|
"cy",
|
||||||
|
"da",
|
||||||
|
"de",
|
||||||
|
"el",
|
||||||
|
"en",
|
||||||
|
"en-GB",
|
||||||
|
"eo",
|
||||||
|
"es",
|
||||||
|
"es-419",
|
||||||
|
"et",
|
||||||
|
"eu",
|
||||||
|
"fa",
|
||||||
|
"fi",
|
||||||
|
"fr",
|
||||||
|
"fy",
|
||||||
|
"gl",
|
||||||
|
"gsw",
|
||||||
|
"he",
|
||||||
|
"hi",
|
||||||
|
"hr",
|
||||||
|
"hu",
|
||||||
|
"hy",
|
||||||
|
"id",
|
||||||
|
"is",
|
||||||
|
"it",
|
||||||
|
"ja",
|
||||||
|
"ka",
|
||||||
|
"ko",
|
||||||
|
"lb",
|
||||||
|
"lt",
|
||||||
|
"lv",
|
||||||
|
"ml",
|
||||||
|
"nb",
|
||||||
|
"nl",
|
||||||
|
"nn",
|
||||||
|
"pl",
|
||||||
|
"pt",
|
||||||
|
"pt-BR",
|
||||||
|
"ro",
|
||||||
|
"ru",
|
||||||
|
"sk",
|
||||||
|
"sl",
|
||||||
|
"sr",
|
||||||
|
"sr-Latn",
|
||||||
|
"sv",
|
||||||
|
"ta",
|
||||||
|
"te",
|
||||||
|
"th",
|
||||||
|
"tr",
|
||||||
|
"uk",
|
||||||
|
"ur",
|
||||||
|
"vi",
|
||||||
|
"zh-Hans",
|
||||||
|
"zh-Hant",
|
||||||
|
}
|
@ -89,6 +89,8 @@ from homeassistant.const import (
|
|||||||
from homeassistant.core import split_entity_id, valid_entity_id
|
from homeassistant.core import split_entity_id, valid_entity_id
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
from homeassistant.generated import currencies
|
from homeassistant.generated import currencies
|
||||||
|
from homeassistant.generated.countries import COUNTRIES
|
||||||
|
from homeassistant.generated.languages import LANGUAGES
|
||||||
from homeassistant.util import raise_if_invalid_path, slugify as util_slugify
|
from homeassistant.util import raise_if_invalid_path, slugify as util_slugify
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
@ -1662,3 +1664,7 @@ currency = vol.In(
|
|||||||
historic_currency = vol.In(
|
historic_currency = vol.In(
|
||||||
currencies.HISTORIC_CURRENCIES, msg="invalid ISO 4217 formatted historic currency"
|
currencies.HISTORIC_CURRENCIES, msg="invalid ISO 4217 formatted historic currency"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
country = vol.In(COUNTRIES, msg="invalid ISO 3166 formatted country")
|
||||||
|
|
||||||
|
language = vol.In(LANGUAGES, msg="invalid RFC 5646 formatted language")
|
||||||
|
27
script/countries.py
Normal file
27
script/countries.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
"""Helper script to update country list.
|
||||||
|
|
||||||
|
ISO does not publish a machine readable list free of charge, so the list is generated
|
||||||
|
with help of the pycountry package.
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pycountry
|
||||||
|
|
||||||
|
from .hassfest.serializer import format_python_namespace
|
||||||
|
|
||||||
|
countries = {x.alpha_2 for x in pycountry.countries}
|
||||||
|
|
||||||
|
generator_string = """script.countries
|
||||||
|
|
||||||
|
The values are directly corresponding to the ISO 3166 standard. If you need changes
|
||||||
|
to the political situation in the world, please contact the ISO 3166 working group.
|
||||||
|
"""
|
||||||
|
|
||||||
|
Path("homeassistant/generated/countries.py").write_text(
|
||||||
|
format_python_namespace(
|
||||||
|
{
|
||||||
|
"COUNTRIES": countries,
|
||||||
|
},
|
||||||
|
generator=generator_string,
|
||||||
|
)
|
||||||
|
)
|
25
script/languages.py
Normal file
25
script/languages.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
"""Helper script to update language list from the frontend source."""
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from .hassfest.serializer import format_python_namespace
|
||||||
|
|
||||||
|
tag = sys.argv[1] if len(sys.argv) > 1 else "dev"
|
||||||
|
|
||||||
|
req = requests.get(
|
||||||
|
f"https://raw.githubusercontent.com/home-assistant/frontend/{tag}/src/translations/translationMetadata.json"
|
||||||
|
)
|
||||||
|
data = json.loads(req.content)
|
||||||
|
languages = set(data.keys())
|
||||||
|
|
||||||
|
Path("homeassistant/generated/languages.py").write_text(
|
||||||
|
format_python_namespace(
|
||||||
|
{
|
||||||
|
"LANGUAGES": languages,
|
||||||
|
},
|
||||||
|
generator="script.languages [frontend_tag]",
|
||||||
|
)
|
||||||
|
)
|
@ -22,7 +22,7 @@ from unittest.mock import AsyncMock, Mock, patch
|
|||||||
from aiohttp.test_utils import unused_port as get_test_instance_port # noqa: F401
|
from aiohttp.test_utils import unused_port as get_test_instance_port # noqa: F401
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import auth, config_entries, core as ha, loader
|
from homeassistant import auth, bootstrap, config_entries, core as ha, loader
|
||||||
from homeassistant.auth import (
|
from homeassistant.auth import (
|
||||||
auth_store,
|
auth_store,
|
||||||
models as auth_models,
|
models as auth_models,
|
||||||
@ -306,6 +306,7 @@ async def async_test_home_assistant(loop, load_registries=True):
|
|||||||
issue_registry.async_load(hass),
|
issue_registry.async_load(hass),
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
hass.data[bootstrap.DATA_REGISTRIES_LOADED] = None
|
||||||
|
|
||||||
hass.state = ha.CoreState.running
|
hass.state = ha.CoreState.running
|
||||||
|
|
||||||
|
@ -60,6 +60,8 @@ async def test_websocket_core_update(hass, client):
|
|||||||
assert hass.config.external_url != "https://www.example.com"
|
assert hass.config.external_url != "https://www.example.com"
|
||||||
assert hass.config.internal_url != "http://example.com"
|
assert hass.config.internal_url != "http://example.com"
|
||||||
assert hass.config.currency == "EUR"
|
assert hass.config.currency == "EUR"
|
||||||
|
assert hass.config.country != "SE"
|
||||||
|
assert hass.config.language != "sv"
|
||||||
|
|
||||||
with patch("homeassistant.util.dt.set_default_time_zone") as mock_set_tz:
|
with patch("homeassistant.util.dt.set_default_time_zone") as mock_set_tz:
|
||||||
await client.send_json(
|
await client.send_json(
|
||||||
@ -75,6 +77,8 @@ async def test_websocket_core_update(hass, client):
|
|||||||
"external_url": "https://www.example.com",
|
"external_url": "https://www.example.com",
|
||||||
"internal_url": "http://example.local",
|
"internal_url": "http://example.local",
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
|
"country": "SE",
|
||||||
|
"language": "sv",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1344,3 +1344,27 @@ def test_historic_currency():
|
|||||||
|
|
||||||
for value in ("DEM", "NLG"):
|
for value in ("DEM", "NLG"):
|
||||||
assert schema(value)
|
assert schema(value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_country():
|
||||||
|
"""Test country validator."""
|
||||||
|
schema = vol.Schema(cv.country)
|
||||||
|
|
||||||
|
for value in (None, "Candyland", "USA"):
|
||||||
|
with pytest.raises(vol.MultipleInvalid):
|
||||||
|
schema(value)
|
||||||
|
|
||||||
|
for value in ("NL", "SE"):
|
||||||
|
assert schema(value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_language():
|
||||||
|
"""Test language validator."""
|
||||||
|
schema = vol.Schema(cv.language)
|
||||||
|
|
||||||
|
for value in (None, "Klingon", "english"):
|
||||||
|
with pytest.raises(vol.MultipleInvalid):
|
||||||
|
schema(value)
|
||||||
|
|
||||||
|
for value in ("en", "sv"):
|
||||||
|
assert schema(value)
|
||||||
|
@ -40,7 +40,7 @@ from homeassistant.util.unit_system import (
|
|||||||
)
|
)
|
||||||
from homeassistant.util.yaml import SECRET_YAML
|
from homeassistant.util.yaml import SECRET_YAML
|
||||||
|
|
||||||
from tests.common import get_test_config_dir, patch_yaml_files
|
from tests.common import MockUser, get_test_config_dir, patch_yaml_files
|
||||||
|
|
||||||
CONFIG_DIR = get_test_config_dir()
|
CONFIG_DIR = get_test_config_dir()
|
||||||
YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE)
|
YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE)
|
||||||
@ -214,6 +214,8 @@ def test_core_config_schema():
|
|||||||
{"customize": "bla"},
|
{"customize": "bla"},
|
||||||
{"customize": {"light.sensor": 100}},
|
{"customize": {"light.sensor": 100}},
|
||||||
{"customize": {"entity_id": []}},
|
{"customize": {"entity_id": []}},
|
||||||
|
{"country": "xx"},
|
||||||
|
{"language": "xx"},
|
||||||
):
|
):
|
||||||
with pytest.raises(MultipleInvalid):
|
with pytest.raises(MultipleInvalid):
|
||||||
config_util.CORE_CONFIG_SCHEMA(value)
|
config_util.CORE_CONFIG_SCHEMA(value)
|
||||||
@ -228,6 +230,8 @@ def test_core_config_schema():
|
|||||||
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC,
|
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC,
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
"customize": {"sensor.temperature": {"hidden": True}},
|
"customize": {"sensor.temperature": {"hidden": True}},
|
||||||
|
"country": "SE",
|
||||||
|
"language": "sv",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -393,9 +397,12 @@ async def test_loading_configuration_from_storage(hass, hass_storage):
|
|||||||
"external_url": "https://www.example.com",
|
"external_url": "https://www.example.com",
|
||||||
"internal_url": "http://example.local",
|
"internal_url": "http://example.local",
|
||||||
"currency": "EUR",
|
"currency": "EUR",
|
||||||
|
"country": "SE",
|
||||||
|
"language": "sv",
|
||||||
},
|
},
|
||||||
"key": "core.config",
|
"key": "core.config",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
|
"minor_version": 3,
|
||||||
}
|
}
|
||||||
await config_util.async_process_ha_core_config(
|
await config_util.async_process_ha_core_config(
|
||||||
hass, {"allowlist_external_dirs": "/etc"}
|
hass, {"allowlist_external_dirs": "/etc"}
|
||||||
@ -410,6 +417,8 @@ async def test_loading_configuration_from_storage(hass, hass_storage):
|
|||||||
assert hass.config.external_url == "https://www.example.com"
|
assert hass.config.external_url == "https://www.example.com"
|
||||||
assert hass.config.internal_url == "http://example.local"
|
assert hass.config.internal_url == "http://example.local"
|
||||||
assert hass.config.currency == "EUR"
|
assert hass.config.currency == "EUR"
|
||||||
|
assert hass.config.country == "SE"
|
||||||
|
assert hass.config.language == "sv"
|
||||||
assert len(hass.config.allowlist_external_dirs) == 3
|
assert len(hass.config.allowlist_external_dirs) == 3
|
||||||
assert "/etc" in hass.config.allowlist_external_dirs
|
assert "/etc" in hass.config.allowlist_external_dirs
|
||||||
assert hass.config.config_source is ConfigSource.STORAGE
|
assert hass.config.config_source is ConfigSource.STORAGE
|
||||||
@ -475,10 +484,15 @@ async def test_migration_and_updating_configuration(hass, hass_storage):
|
|||||||
expected_new_core_data["data"]["currency"] = "USD"
|
expected_new_core_data["data"]["currency"] = "USD"
|
||||||
# 1.1 -> 1.2 store migration with migrated unit system
|
# 1.1 -> 1.2 store migration with migrated unit system
|
||||||
expected_new_core_data["data"]["unit_system_v2"] = "us_customary"
|
expected_new_core_data["data"]["unit_system_v2"] = "us_customary"
|
||||||
expected_new_core_data["minor_version"] = 2
|
expected_new_core_data["minor_version"] = 3
|
||||||
|
# defaults for country and language
|
||||||
|
expected_new_core_data["data"]["country"] = None
|
||||||
|
expected_new_core_data["data"]["language"] = "en"
|
||||||
assert hass_storage["core.config"] == expected_new_core_data
|
assert hass_storage["core.config"] == expected_new_core_data
|
||||||
assert hass.config.latitude == 50
|
assert hass.config.latitude == 50
|
||||||
assert hass.config.currency == "USD"
|
assert hass.config.currency == "USD"
|
||||||
|
assert hass.config.country is None
|
||||||
|
assert hass.config.language == "en"
|
||||||
|
|
||||||
|
|
||||||
async def test_override_stored_configuration(hass, hass_storage):
|
async def test_override_stored_configuration(hass, hass_storage):
|
||||||
@ -527,6 +541,8 @@ async def test_loading_configuration(hass):
|
|||||||
"media_dirs": {"mymedia": "/usr"},
|
"media_dirs": {"mymedia": "/usr"},
|
||||||
"legacy_templates": True,
|
"legacy_templates": True,
|
||||||
"currency": "EUR",
|
"currency": "EUR",
|
||||||
|
"country": "SE",
|
||||||
|
"language": "sv",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -545,6 +561,74 @@ async def test_loading_configuration(hass):
|
|||||||
assert hass.config.config_source is ConfigSource.YAML
|
assert hass.config.config_source is ConfigSource.YAML
|
||||||
assert hass.config.legacy_templates is True
|
assert hass.config.legacy_templates is True
|
||||||
assert hass.config.currency == "EUR"
|
assert hass.config.currency == "EUR"
|
||||||
|
assert hass.config.country == "SE"
|
||||||
|
assert hass.config.language == "sv"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"minor_version, users, user_data, default_language",
|
||||||
|
(
|
||||||
|
(2, (), {}, "en"),
|
||||||
|
(2, ({"is_owner": True},), {}, "en"),
|
||||||
|
(
|
||||||
|
2,
|
||||||
|
({"id": "user1", "is_owner": True},),
|
||||||
|
{"user1": {"language": {"language": "sv"}}},
|
||||||
|
"sv",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
2,
|
||||||
|
({"id": "user1", "is_owner": False},),
|
||||||
|
{"user1": {"language": {"language": "sv"}}},
|
||||||
|
"en",
|
||||||
|
),
|
||||||
|
(3, (), {}, "en"),
|
||||||
|
(3, ({"is_owner": True},), {}, "en"),
|
||||||
|
(
|
||||||
|
3,
|
||||||
|
({"id": "user1", "is_owner": True},),
|
||||||
|
{"user1": {"language": {"language": "sv"}}},
|
||||||
|
"en",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
3,
|
||||||
|
({"id": "user1", "is_owner": False},),
|
||||||
|
{"user1": {"language": {"language": "sv"}}},
|
||||||
|
"en",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def test_language_default(
|
||||||
|
hass, hass_storage, minor_version, users, user_data, default_language
|
||||||
|
):
|
||||||
|
"""Test language config default to owner user's language during migration.
|
||||||
|
|
||||||
|
This should only happen if the core store version < 1.3
|
||||||
|
"""
|
||||||
|
core_data = {
|
||||||
|
"data": {},
|
||||||
|
"key": "core.config",
|
||||||
|
"version": 1,
|
||||||
|
"minor_version": minor_version,
|
||||||
|
}
|
||||||
|
hass_storage["core.config"] = dict(core_data)
|
||||||
|
|
||||||
|
for user_config in users:
|
||||||
|
user = MockUser(**user_config).add_to_hass(hass)
|
||||||
|
if user.id not in user_data:
|
||||||
|
continue
|
||||||
|
storage_key = f"frontend.user_data_{user.id}"
|
||||||
|
hass_storage[storage_key] = {
|
||||||
|
"key": storage_key,
|
||||||
|
"version": 1,
|
||||||
|
"data": user_data[user.id],
|
||||||
|
}
|
||||||
|
|
||||||
|
await config_util.async_process_ha_core_config(
|
||||||
|
hass,
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
assert hass.config.language == default_language
|
||||||
|
|
||||||
|
|
||||||
async def test_loading_configuration_default_media_dirs_docker(hass):
|
async def test_loading_configuration_default_media_dirs_docker(hass):
|
||||||
|
@ -948,6 +948,8 @@ async def test_config_defaults():
|
|||||||
assert config.safe_mode is False
|
assert config.safe_mode is False
|
||||||
assert config.legacy_templates is False
|
assert config.legacy_templates is False
|
||||||
assert config.currency == "EUR"
|
assert config.currency == "EUR"
|
||||||
|
assert config.country is None
|
||||||
|
assert config.language == "en"
|
||||||
|
|
||||||
|
|
||||||
async def test_config_path_with_file():
|
async def test_config_path_with_file():
|
||||||
@ -989,6 +991,8 @@ async def test_config_as_dict():
|
|||||||
"external_url": None,
|
"external_url": None,
|
||||||
"internal_url": None,
|
"internal_url": None,
|
||||||
"currency": "EUR",
|
"currency": "EUR",
|
||||||
|
"country": None,
|
||||||
|
"language": "en",
|
||||||
}
|
}
|
||||||
|
|
||||||
assert expected == config.as_dict()
|
assert expected == config.as_dict()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user