Merge pull request #32 from jdejaegh/language-picker

Add language picker to override default Home Assistant language in the integration
This commit is contained in:
Jules 2024-05-24 17:44:32 +02:00 committed by GitHub
commit 4decfe72ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 122 additions and 39 deletions

View file

@ -7,8 +7,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError
from .const import (CONF_DARK_MODE, CONF_STYLE, CONF_USE_DEPRECATED_FORECAST,
CONFIG_FLOW_VERSION, DOMAIN,
from .const import (CONF_DARK_MODE, CONF_LANGUAGE_OVERRIDE, CONF_STYLE,
CONF_USE_DEPRECATED_FORECAST, CONFIG_FLOW_VERSION, DOMAIN,
OPTION_DEPRECATED_FORECAST_NOT_USED, OPTION_STYLE_STD,
PLATFORMS)
from .coordinator import IrmKmiCoordinator
@ -68,6 +68,11 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry):
config_entry.version = 3
hass.config_entries.async_update_entry(config_entry, data=new)
if config_entry.version == 3:
new = new | {CONF_LANGUAGE_OVERRIDE: None}
config_entry.version = 4
hass.config_entries.async_update_entry(config_entry, data=new)
_LOGGER.debug(f"Migration to version {config_entry.version} successful")
return True

View file

@ -16,8 +16,9 @@ from homeassistant.helpers.selector import (EntitySelector,
SelectSelectorMode)
from .api import IrmKmiApiClient
from .const import (CONF_DARK_MODE, CONF_STYLE, CONF_STYLE_OPTIONS,
CONF_USE_DEPRECATED_FORECAST,
from .const import (CONF_DARK_MODE, CONF_LANGUAGE_OVERRIDE,
CONF_LANGUAGE_OVERRIDE_OPTIONS, CONF_STYLE,
CONF_STYLE_OPTIONS, CONF_USE_DEPRECATED_FORECAST,
CONF_USE_DEPRECATED_FORECAST_OPTIONS, CONFIG_FLOW_VERSION,
DOMAIN, OPTION_DEPRECATED_FORECAST_NOT_USED,
OPTION_STYLE_STD, OUT_OF_BENELUX)
@ -49,7 +50,7 @@ class IrmKmiConfigFlow(ConfigFlow, domain=DOMAIN):
if not errors:
api_data = {}
try:
async with async_timeout.timeout(10):
async with async_timeout.timeout(60):
api_data = await IrmKmiApiClient(
session=async_get_clientsession(self.hass)).get_forecasts_coord(
{'lat': zone.attributes[ATTR_LATITUDE],
@ -71,7 +72,8 @@ class IrmKmiConfigFlow(ConfigFlow, domain=DOMAIN):
data={CONF_ZONE: user_input[CONF_ZONE],
CONF_STYLE: user_input[CONF_STYLE],
CONF_DARK_MODE: user_input[CONF_DARK_MODE],
CONF_USE_DEPRECATED_FORECAST: user_input[CONF_USE_DEPRECATED_FORECAST]},
CONF_USE_DEPRECATED_FORECAST: user_input[CONF_USE_DEPRECATED_FORECAST],
CONF_LANGUAGE_OVERRIDE: user_input[CONF_LANGUAGE_OVERRIDE]},
)
return self.async_show_form(
@ -92,7 +94,12 @@ class IrmKmiConfigFlow(ConfigFlow, domain=DOMAIN):
vol.Optional(CONF_USE_DEPRECATED_FORECAST, default=OPTION_DEPRECATED_FORECAST_NOT_USED):
SelectSelector(SelectSelectorConfig(options=CONF_USE_DEPRECATED_FORECAST_OPTIONS,
mode=SelectSelectorMode.DROPDOWN,
translation_key=CONF_USE_DEPRECATED_FORECAST))
translation_key=CONF_USE_DEPRECATED_FORECAST)),
vol.Optional(CONF_LANGUAGE_OVERRIDE, default='none'):
SelectSelector(SelectSelectorConfig(options=CONF_LANGUAGE_OVERRIDE_OPTIONS,
mode=SelectSelectorMode.DROPDOWN,
translation_key=CONF_LANGUAGE_OVERRIDE))
}))
@ -105,6 +112,7 @@ class IrmKmiOptionFlow(OptionsFlow):
async def async_step_init(self, user_input: dict | None = None) -> FlowResult:
"""Manage the options."""
if user_input is not None:
_LOGGER.debug(user_input)
return self.async_create_entry(data=user_input)
return self.async_show_form(
@ -122,7 +130,13 @@ class IrmKmiOptionFlow(OptionsFlow):
default=get_config_value(self.config_entry, CONF_USE_DEPRECATED_FORECAST)):
SelectSelector(SelectSelectorConfig(options=CONF_USE_DEPRECATED_FORECAST_OPTIONS,
mode=SelectSelectorMode.DROPDOWN,
translation_key=CONF_USE_DEPRECATED_FORECAST))
translation_key=CONF_USE_DEPRECATED_FORECAST)),
vol.Optional(CONF_LANGUAGE_OVERRIDE,
default=get_config_value(self.config_entry, CONF_LANGUAGE_OVERRIDE)):
SelectSelector(SelectSelectorConfig(options=CONF_LANGUAGE_OVERRIDE_OPTIONS,
mode=SelectSelectorMode.DROPDOWN,
translation_key=CONF_LANGUAGE_OVERRIDE))
}
),
)

View file

@ -15,7 +15,7 @@ from homeassistant.const import Platform
DOMAIN: Final = 'irm_kmi'
PLATFORMS: Final = [Platform.WEATHER, Platform.CAMERA, Platform.BINARY_SENSOR, Platform.SENSOR]
CONFIG_FLOW_VERSION = 3
CONFIG_FLOW_VERSION = 4
OUT_OF_BENELUX: Final = ["außerhalb der Benelux (Brussels)",
"Hors de Belgique (Bxl)",
@ -58,6 +58,12 @@ CONF_USE_DEPRECATED_FORECAST_OPTIONS: Final = [
OPTION_DEPRECATED_FORECAST_HOURLY
]
CONF_LANGUAGE_OVERRIDE: Final = 'language_override'
CONF_LANGUAGE_OVERRIDE_OPTIONS: Final = [
'none', "fr", "nl", "de", "en"
]
REPAIR_SOLUTION: Final = "repair_solution"
REPAIR_OPT_MOVE: Final = "repair_option_move"
REPAIR_OPT_DELETE: Final = "repair_option_delete"

View file

@ -27,7 +27,7 @@ from .data import (AnimationFrameData, CurrentWeatherData, IrmKmiForecast,
ProcessedCoordinatorData, RadarAnimationData, WarningData)
from .pollen import PollenParser
from .rain_graph import RainGraph
from .utils import disable_from_config, get_config_value
from .utils import disable_from_config, get_config_value, preferred_language
_LOGGER = logging.getLogger(__name__)
@ -128,7 +128,7 @@ class IrmKmiCoordinator(TimestampDataUpdateCoordinator):
localisation = images_from_api[0]
images_from_api = images_from_api[1:]
lang = self.hass.config.language if self.hass.config.language in LANGS else 'en'
lang = preferred_language(self.hass, self._config_entry)
radar_animation = RadarAnimationData(
hint=api_data.get('animation', {}).get('sequenceHint', {}).get(lang),
unit=api_data.get('animation', {}).get('unit', {}).get(lang),
@ -340,6 +340,7 @@ class IrmKmiCoordinator(TimestampDataUpdateCoordinator):
forecasts = list()
n_days = 0
lang = preferred_language(self.hass, self._config_entry)
for (idx, f) in enumerate(data):
precipitation = None
@ -377,7 +378,7 @@ class IrmKmiCoordinator(TimestampDataUpdateCoordinator):
precipitation_probability=f.get('precipChance', None),
wind_bearing=wind_bearing,
is_daytime=is_daytime,
text=f.get('text', {}).get(self.hass.config.language, ""),
text=f.get('text', {}).get(lang, ""),
)
# Swap temperature and templow if needed
if (forecast['native_templow'] is not None
@ -441,6 +442,7 @@ class IrmKmiCoordinator(TimestampDataUpdateCoordinator):
if warning_data is None or not isinstance(warning_data, list) or len(warning_data) == 0:
return []
lang = preferred_language(self.hass, self._config_entry)
result = list()
for data in warning_data:
try:
@ -456,7 +458,6 @@ class IrmKmiCoordinator(TimestampDataUpdateCoordinator):
except TypeError:
level = None
lang = self.hass.config.language if self.hass.config.language in LANGS else 'en'
result.append(
WarningData(
slug=SLUG_MAP.get(warning_id, 'unknown'),

View file

@ -9,9 +9,11 @@
"user": {
"title": "Configuration",
"data": {
"zone": "Zone",
"style": "Style of the radar",
"dark_mode": "Radar dark mode",
"use_deprecated_forecast_attribute": "Use the deprecated forecat attribute"
"use_deprecated_forecast_attribute": "Use the deprecated forecat attribute",
"language_override": "Language"
}
}
},
@ -43,6 +45,15 @@
"repair_option_move": "I moved the zone in Benelux",
"repair_option_delete": "Delete that config entry"
}
},
"language_override": {
"options": {
"none": "Follow Home Assistant server language",
"fr": "French",
"nl": "Dutch",
"de": "German",
"en": "English"
}
}
},
"options": {
@ -52,7 +63,8 @@
"data": {
"style": "Style of the radar",
"dark_mode": "Radar dark mode",
"use_deprecated_forecast_attribute": "Use the deprecated forecat attribute"
"use_deprecated_forecast_attribute": "Use the deprecated forecat attribute",
"language_override": "Language"
}
}
}

View file

@ -9,9 +9,11 @@
"user": {
"title": "Configuration",
"data": {
"zone": "Zone",
"style": "Style du radar",
"dark_mode": "Radar en mode sombre",
"use_deprecated_forecast_attribute": "Utiliser l'attribut forecat (déprécié)"
"use_deprecated_forecast_attribute": "Utiliser l'attribut forecat (déprécié)",
"language_override": "Langue"
}
}
},
@ -43,6 +45,15 @@
"repair_option_move": "J'ai déplacé la zone dans le Benelux",
"repair_option_delete": "Supprimer cette configuration"
}
},
"language_override": {
"options": {
"none": "Langue du serveur Home Assistant",
"fr": "Français",
"nl": "Néerlandais",
"de": "Allemand",
"en": "Anglais"
}
}
},
"options": {
@ -52,7 +63,8 @@
"data": {
"style": "Style du radar",
"dark_mode": "Radar en mode sombre",
"use_deprecated_forecast_attribute": "Utiliser l'attribut forecat (déprécié)"
"use_deprecated_forecast_attribute": "Utiliser l'attribut forecat (déprécié)",
"language_override": "Langue"
}
}
}

View file

@ -9,9 +9,11 @@
"user": {
"title": "Instellingen",
"data": {
"zone": "Zone",
"style": "Radarstijl",
"dark_mode": "Radar in donkere modus",
"use_deprecated_forecast_attribute": "Gebruik het forecat attribuut (afgeschaft)"
"use_deprecated_forecast_attribute": "Gebruik het forecat attribuut (afgeschaft)",
"language_override": "Taal"
}
}
},
@ -43,6 +45,15 @@
"repair_option_move": "Ik heb de zone verplaats naar de Benelux",
"repair_option_delete": "Deze configuratie verwijderen"
}
},
"language_override": {
"options": {
"none": "Zelfde als Home Assistant server taal",
"fr": "Frans",
"nl": "Nederlands",
"de": "Duits",
"en": "Engels"
}
}
},
"options": {
@ -52,7 +63,8 @@
"data": {
"style": "Radarstijl",
"dark_mode": "Radar in donkere modus",
"use_deprecated_forecast_attribute": "Gebruik het forecat attribuut (afgeschaft)"
"use_deprecated_forecast_attribute": "Gebruik het forecat attribuut (afgeschaft)",
"language_override": "Taal"
}
}
}

View file

@ -5,6 +5,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry
from .const import CONF_LANGUAGE_OVERRIDE, LANGS
_LOGGER = logging.getLogger(__name__)
@ -29,3 +31,10 @@ def get_config_value(config_entry: ConfigEntry, key: str) -> Any:
if config_entry.options and key in config_entry.options:
return config_entry.options[key]
return config_entry.data[key]
def preferred_language(hass: HomeAssistant, config_entry: ConfigEntry) -> str:
if get_config_value(config_entry, CONF_LANGUAGE_OVERRIDE) == 'none':
return hass.config.language if hass.config.language in LANGS else 'en'
return get_config_value(config_entry, CONF_LANGUAGE_OVERRIDE)

View file

@ -162,6 +162,8 @@ class IrmKmiWeather(CoordinatorEntity, WeatherEntity):
now = dt.now()
now = now.replace(minute=(now.minute // 10) * 10, second=0, microsecond=0)
# TODO adapt the return value to match the weather.get_forecasts in next breaking change release
# return { 'forecast': [...] }
return [f for f in self.coordinator.data.get('radar_forecast')
if include_past_forecasts or datetime.fromisoformat(f.get('datetime')) >= now]

View file

@ -1,6 +1,6 @@
aiohttp==3.9.5
async-timeout==4.0.3
homeassistant==2024.5.2
homeassistant==2024.5.4
voluptuous==0.13.1
pytz==2024.1
svgwrite==1.4.3

View file

@ -1,5 +1,5 @@
homeassistant==2024.5.2
homeassistant==2024.5.4
pytest
pytest_homeassistant_custom_component==0.13.122
pytest_homeassistant_custom_component==0.13.124
freezegun
isort

View file

@ -15,7 +15,7 @@ from custom_components.irm_kmi.api import (IrmKmiApiError,
from custom_components.irm_kmi.const import (
CONF_DARK_MODE, CONF_STYLE, CONF_USE_DEPRECATED_FORECAST, DOMAIN,
OPTION_DEPRECATED_FORECAST_NOT_USED,
OPTION_DEPRECATED_FORECAST_TWICE_DAILY, OPTION_STYLE_STD)
OPTION_DEPRECATED_FORECAST_TWICE_DAILY, OPTION_STYLE_STD, CONF_LANGUAGE_OVERRIDE)
def get_api_data(fixture: str) -> dict:
@ -52,7 +52,8 @@ def mock_config_entry() -> MockConfigEntry:
data={CONF_ZONE: "zone.home",
CONF_STYLE: OPTION_STYLE_STD,
CONF_DARK_MODE: True,
CONF_USE_DEPRECATED_FORECAST: OPTION_DEPRECATED_FORECAST_NOT_USED},
CONF_USE_DEPRECATED_FORECAST: OPTION_DEPRECATED_FORECAST_NOT_USED,
CONF_LANGUAGE_OVERRIDE: 'none'},
unique_id="zone.home",
)
@ -66,7 +67,8 @@ def mock_config_entry_with_deprecated() -> MockConfigEntry:
data={CONF_ZONE: "zone.home",
CONF_STYLE: OPTION_STYLE_STD,
CONF_DARK_MODE: True,
CONF_USE_DEPRECATED_FORECAST: OPTION_DEPRECATED_FORECAST_TWICE_DAILY},
CONF_USE_DEPRECATED_FORECAST: OPTION_DEPRECATED_FORECAST_TWICE_DAILY,
CONF_LANGUAGE_OVERRIDE: 'none'},
unique_id="zone.home",
)

View file

@ -11,9 +11,10 @@ from pytest_homeassistant_custom_component.common import MockConfigEntry
from custom_components.irm_kmi import async_migrate_entry
from custom_components.irm_kmi.const import (
CONF_DARK_MODE, CONF_STYLE, CONF_USE_DEPRECATED_FORECAST,
CONFIG_FLOW_VERSION, DOMAIN, OPTION_DEPRECATED_FORECAST_NOT_USED,
OPTION_STYLE_SATELLITE, OPTION_STYLE_STD)
CONF_DARK_MODE, CONF_LANGUAGE_OVERRIDE, CONF_STYLE,
CONF_USE_DEPRECATED_FORECAST, CONFIG_FLOW_VERSION, DOMAIN,
OPTION_DEPRECATED_FORECAST_NOT_USED, OPTION_STYLE_SATELLITE,
OPTION_STYLE_STD)
async def test_full_user_flow(
@ -40,7 +41,8 @@ async def test_full_user_flow(
assert result2.get("data") == {CONF_ZONE: ENTITY_ID_HOME,
CONF_STYLE: OPTION_STYLE_STD,
CONF_DARK_MODE: False,
CONF_USE_DEPRECATED_FORECAST: OPTION_DEPRECATED_FORECAST_NOT_USED}
CONF_USE_DEPRECATED_FORECAST: OPTION_DEPRECATED_FORECAST_NOT_USED,
CONF_LANGUAGE_OVERRIDE: 'none'}
async def test_config_flow_out_benelux_zone(
@ -128,7 +130,8 @@ async def test_option_flow(
assert result["data"] == {
CONF_STYLE: OPTION_STYLE_SATELLITE,
CONF_DARK_MODE: True,
CONF_USE_DEPRECATED_FORECAST: OPTION_DEPRECATED_FORECAST_NOT_USED
CONF_USE_DEPRECATED_FORECAST: OPTION_DEPRECATED_FORECAST_NOT_USED,
CONF_LANGUAGE_OVERRIDE: 'none'
}

View file

@ -7,6 +7,7 @@ from homeassistant.components.weather import (ATTR_CONDITION_CLOUDY,
from homeassistant.core import HomeAssistant
from pytest_homeassistant_custom_component.common import MockConfigEntry
from custom_components.irm_kmi.const import CONF_LANGUAGE_OVERRIDE
from custom_components.irm_kmi.coordinator import IrmKmiCoordinator
from custom_components.irm_kmi.data import (CurrentWeatherData, IrmKmiForecast,
ProcessedCoordinatorData,
@ -91,6 +92,7 @@ async def test_daily_forecast(
) -> None:
api_data = get_api_data("forecast.json").get('for', {}).get('daily')
mock_config_entry.data = mock_config_entry.data | {CONF_LANGUAGE_OVERRIDE: 'fr'}
coordinator = IrmKmiCoordinator(hass, mock_config_entry)
result = coordinator.daily_list_to_forecast(api_data)
@ -108,7 +110,7 @@ async def test_daily_forecast(
precipitation_probability=0,
wind_bearing=180,
is_daytime=True,
text='Hey!',
text='Bar',
)
assert result[1] == expected

View file

@ -6,6 +6,7 @@ from pytest_homeassistant_custom_component.common import MockConfigEntry
from custom_components.irm_kmi import IrmKmiCoordinator
from custom_components.irm_kmi.binary_sensor import IrmKmiWarning
from custom_components.irm_kmi.const import CONF_LANGUAGE_OVERRIDE
from custom_components.irm_kmi.sensor import IrmKmiNextWarning
from tests.conftest import get_api_data
@ -65,6 +66,8 @@ async def test_next_warning_when_data_available(
mock_config_entry: MockConfigEntry
) -> None:
api_data = get_api_data("be_forecast_warning.json")
mock_config_entry.data = mock_config_entry.data | {CONF_LANGUAGE_OVERRIDE: 'de'}
coordinator = IrmKmiCoordinator(hass, mock_config_entry)
result = coordinator.warnings_from_data(api_data.get('for', {}).get('warning'))
@ -76,7 +79,7 @@ async def test_next_warning_when_data_available(
assert warning.state == "2024-01-12T06:00:00+00:00"
assert len(warning.extra_state_attributes['next_warnings']) == 2
assert warning.extra_state_attributes['next_warnings_friendly_names'] == "Fog, Ice or snow"
assert warning.extra_state_attributes['next_warnings_friendly_names'] == "Nebel, Glätte"
@freeze_time(datetime.fromisoformat('2024-01-12T07:30:00+01:00'))