Guard for unnecessary translations and fix offenders (#183292)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Josef Zweck
2026-09-27 21:14:00 +02:00
committed by GitHub
co-authored by Copilot
parent d559a0b542
commit fe6e893a02
34 changed files with 155 additions and 55 deletions
@@ -2,7 +2,6 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_location%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"wrong_location": "[%key:component::airly::config::error::wrong_location%]"
},
"error": {
@@ -25,7 +25,7 @@ from homeassistant.config_entries import (
ConfigFlowResult,
)
from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PIN
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
@@ -289,7 +289,9 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
def _async_check_and_update_in_progress(self, host: str, unique_id: str) -> None:
"""Check for in-progress flows and update them with identifiers if needed."""
if self.hass.config_entries.flow.async_has_matching_flow(self):
raise AbortFlow("already_in_progress")
raise AbortFlow(
"already_in_progress", translation_domain=HOMEASSISTANT_DOMAIN
)
@override
def is_matching(self, other_flow: Self) -> bool:
@@ -2,7 +2,6 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"backoff": "Device does not accept pairing requests at this time (you might have entered an invalid PIN code too many times), try again later.",
"device_did_not_pair": "No attempt to finish pairing process was made from the device.",
"device_not_found": "Device was not found during discovery, please try adding it again.",
@@ -15,6 +15,7 @@ from homeassistant.components.bluetooth import (
)
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_ADDRESS
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN
from .const import DOMAIN
@@ -174,7 +175,9 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN):
return await self.async_step_get_encryption_key()
# Otherwise there wasn't actually encryption so abort
return self.async_abort(reason="reauth_successful")
return self.async_abort(
reason="reauth_successful", translation_domain=HOMEASSISTANT_DOMAIN
)
def _async_get_or_create_entry(
self, bindkey: str | None = None
+1 -2
View File
@@ -2,8 +2,7 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
},
"error": {
"decryption_failed": "The provided bindkey did not work, sensor data could not be decrypted. Please check it and try again.",
@@ -17,6 +17,7 @@ from homeassistant.const import (
CONF_PROTOCOL,
CONF_USERNAME,
)
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
@@ -192,7 +193,9 @@ class Elkm1ConfigFlow(ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="already_configured")
self.host = host
if self.hass.config_entries.flow.async_has_matching_flow(self):
return self.async_abort(reason="already_in_progress")
return self.async_abort(
reason="already_in_progress", translation_domain=HOMEASSISTANT_DOMAIN
)
# Handled ignored case since _async_current_entries
# is called with include_ignore=False
self._abort_if_unique_id_configured()
@@ -3,7 +3,6 @@
"abort": {
"address_already_configured": "An Elk-M1 with this address is already configured",
"already_configured": "An Elk-M1 with this prefix is already configured",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"reconfigure_successful": "Successfully reconfigured Elk-M1 integration",
@@ -260,6 +260,7 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
)
if not errors:
# successful authentication, update config
# Shares the local string with the unique ID abort in async_step_user
return self.async_update_reload_and_abort(
reauth_entry,
data_updates=user_input
@@ -268,6 +269,7 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
if isinstance(envoy.auth, EnvoyTokenAuth)
else {}
),
reason="reauth_successful",
)
if token:
token_days_left = token_lifetime(token)
@@ -22,7 +22,7 @@ from homeassistant.config_entries import (
OptionsFlow,
)
from homeassistant.const import CONF_DEVICE, CONF_HOST
from homeassistant.core import callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.dispatcher import async_dispatcher_send
@@ -156,7 +156,9 @@ class FluxLedConfigFlow(ConfigFlow, domain=DOMAIN):
host = device[ATTR_IPADDR]
self.host = host
if self.hass.config_entries.flow.async_has_matching_flow(self):
return self.async_abort(reason="already_in_progress")
return self.async_abort(
reason="already_in_progress", translation_domain=HOMEASSISTANT_DOMAIN
)
if not device[ATTR_MODEL_DESCRIPTION]:
mac_address = device[ATTR_ID]
assert mac_address is not None
@@ -2,7 +2,6 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
},
"error": {
@@ -17,6 +17,7 @@ from homeassistant.const import (
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN
from homeassistant.helpers.selector import (
TextSelector,
TextSelectorConfig,
@@ -166,7 +167,9 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN):
self._abort_if_unique_id_configured({CONF_HOST: self._url})
if self.hass.config_entries.flow.async_has_matching_flow(self):
return self.async_abort(reason="already_in_progress")
return self.async_abort(
reason="already_in_progress", translation_domain=HOMEASSISTANT_DOMAIN
)
# update old and user-configured config entries
for entry in self._async_current_entries(include_ignore=False):
@@ -8,7 +8,6 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"ignore_ip6_link_local": "IPv6 link local address is not supported.",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"not_supported": "Connected to FRITZ!Box but it's unable to control Smart Home devices."
@@ -2,7 +2,6 @@
"config": {
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"error": {
@@ -5,7 +5,6 @@
"device_not_supported": "This device is not supported",
"invalid_discovery_parameters": "Invalid discovery parameters",
"reauth_enable_api_successful": "Enabling API was successful",
"reauth_successful": "Authorization successful",
"unknown_error": "[%key:common::config_flow::error::unknown%]",
"unsupported_api_version": "Detected unsupported API version",
"wrong_device": "The configured device is not the same found on this IP address."
@@ -13,7 +13,7 @@ import probatio
from homeassistant import config_entries
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, FlowType
from homeassistant.const import CONF_HOST
from homeassistant.core import callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, callback
from homeassistant.helpers.selector import (
SelectOptionDict,
SelectSelector,
@@ -88,7 +88,9 @@ class IZoneConfigFlow(ConfigFlow, domain=DOMAIN):
request as part of start-up, and the import step itself will not be repeated.
"""
if self._async_in_progress(include_uninitialized=True):
return self.async_abort(reason="already_in_progress")
return self.async_abort(
reason="already_in_progress", translation_domain=HOMEASSISTANT_DOMAIN
)
try:
await izone_discovery.async_ensure_discovery(self.hass)
@@ -2,7 +2,6 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"continue_setup": "Continue setting up the discovered iZone controller.",
"discovery_failed": "Failed to start iZone discovery. Make sure your network is properly configured.",
"discovery_started": "iZone discovery has started. Your controllers will appear as discovered devices under Settings \u003e Devices \u0026 services.",
@@ -6,9 +6,7 @@
},
"config": {
"abort": {
"already_configured": "This LibreNMS instance is already configured.",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"already_configured": "This LibreNMS instance is already configured."
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
@@ -5,7 +5,6 @@
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"wrong_device": "The device at this address is not the one this entry was set up for."
},
"error": {
@@ -1,8 +1,7 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
@@ -2,7 +2,6 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"unique_id_mismatch": "The authenticated Monarch Money account does not match the configured account."
},
"error": {
@@ -13,7 +13,7 @@ from homeassistant.config_entries import (
OptionsFlowWithReload,
)
from homeassistant.const import CONF_IP_ADDRESS, CONF_MAC
from homeassistant.core import callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig
@@ -244,7 +244,10 @@ class NoboHubConfigFlow(ConfigFlow, domain=DOMAIN):
# No-op: IP unchanged and the running integration already
# proves it works. Skip the reload to avoid a needless
# reconnect.
return self.async_abort(reason="reconfigure_successful")
return self.async_abort(
reason="reconfigure_successful",
translation_domain=HOMEASSISTANT_DOMAIN,
)
return self.async_update_reload_and_abort(
reconfigure_entry,
data_updates={CONF_IP_ADDRESS: new_ip},
@@ -2,8 +2,7 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_discover": "Could not detect a Nobø Ecohub at the discovered IP address.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"cannot_discover": "Could not detect a Nobø Ecohub at the discovered IP address."
},
"error": {
"cannot_connect": "Failed to connect - check serial number",
@@ -26,7 +26,7 @@ from homeassistant.const import (
CONF_PORT,
CONF_TOKEN,
)
from homeassistant.core import callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import format_mac
@@ -489,7 +489,9 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN):
@callback
def _async_abort_if_host_already_in_progress(self) -> None:
if self.hass.config_entries.flow.async_has_matching_flow(self):
raise AbortFlow("already_in_progress")
raise AbortFlow(
"already_in_progress", translation_domain=HOMEASSISTANT_DOMAIN
)
@override
def is_matching(self, other_flow: Self) -> bool:
@@ -2,7 +2,6 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"auth_missing": "Home Assistant is not authorized to connect to this Samsung TV. Check your TV's External Device Manager settings to authorize Home Assistant.",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"id_missing": "This Samsung device doesn't have a serial number to identify it.",
@@ -9,7 +9,7 @@ import probatio
from homeassistant.config_entries import ConfigEntryState, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_MODEL, CONF_NAME
from homeassistant.core import callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
@@ -82,7 +82,9 @@ class SteamistConfigFlow(ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="already_configured")
self.host = host
if self.hass.config_entries.flow.async_has_matching_flow(self):
return self.async_abort(reason="already_in_progress")
return self.async_abort(
reason="already_in_progress", translation_domain=HOMEASSISTANT_DOMAIN
)
if not device.name:
discovery = await async_discover_device(self.hass, device.ipaddress)
if not discovery:
@@ -2,7 +2,6 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"not_steamist_device": "Not a steamist device"
@@ -36,7 +36,7 @@ from homeassistant.const import (
CONF_PORT,
CONF_USERNAME,
)
from homeassistant.core import callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from homeassistant.helpers.typing import DiscoveryInfoType
@@ -793,7 +793,10 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
self.hass.async_create_task(
self._async_reload_requires_auth_entries(), eager_start=False
)
return self.async_abort(reason="reauth_successful")
return self.async_abort(
reason="reauth_successful",
translation_domain=HOMEASSISTANT_DOMAIN,
)
# Old config entries will not have these values.
alias = entry_data.get(CONF_ALIAS) or "unknown"
+1 -2
View File
@@ -3,8 +3,7 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
},
"error": {
"camera_creds": "You have to set both username and password",
@@ -4,8 +4,6 @@
"already_configured": "UniFi Network site is already configured",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"configuration_updated": "Configuration updated",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"unknown_site_id": "Previously configured UniFi Network site can no longer be found"
},
"error": {
@@ -23,6 +23,7 @@ from homeassistant.components.bluetooth import (
)
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_ADDRESS, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -354,7 +355,9 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN):
return await self.async_step_get_encryption_key_4_5_choose_method()
# Otherwise there wasn't actually encryption so abort
return self.async_abort(reason="reauth_successful")
return self.async_abort(
reason="reauth_successful", translation_domain=HOMEASSISTANT_DOMAIN
)
def _async_get_or_create_entry(
self, bindkey: str | None = None
@@ -3,8 +3,7 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"api_error": "Error while communicating with Mi API: {error_detail}",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
},
"error": {
"api_device_not_found": "The device was not found in your Mi account.",
@@ -30,7 +30,7 @@ from homeassistant.config_entries import (
ConfigFlowResult,
)
from homeassistant.const import CONF_NAME, CONF_URL
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import selector
@@ -468,7 +468,10 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
return await self.async_step_rf_region()
if config_updates := self._addon_config_updates:
if not self._async_acquire_addon_ownership():
return self.async_abort(reason="already_in_progress")
return self.async_abort(
reason="already_in_progress",
translation_domain=HOMEASSISTANT_DOMAIN,
)
# If we have updates to the add-on config,
# set them before starting the add-on.
self._addon_config_updates = {}
@@ -634,7 +637,9 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
# at least for a short time.
# Zeroconf flows never touch the add-on,
# so an idle discovery prompt should not block USB discovery.
return self.async_abort(reason="already_in_progress")
return self.async_abort(
reason="already_in_progress", translation_domain=HOMEASSISTANT_DOMAIN
)
if current_config_entries := self._async_current_entries(include_ignore=False):
self._reconfigure_config_entry = next(
(
@@ -785,7 +790,9 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
# prompt should not block the add-on discovery.
if flow["context"].get("source") != SOURCE_ZEROCONF
):
return self.async_abort(reason="already_in_progress")
return self.async_abort(
reason="already_in_progress", translation_domain=HOMEASSISTANT_DOMAIN
)
if discovery_info.slug != ADDON_SLUG:
return self.async_abort(reason="not_zwave_js_addon")
@@ -1270,7 +1277,9 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
)
if not self._async_acquire_addon_ownership():
return self.async_abort(reason="already_in_progress")
return self.async_abort(
reason="already_in_progress", translation_domain=HOMEASSISTANT_DOMAIN
)
# Remaining prompts, e.g. for other discovered adapters,
# are superseded by this migration.
@@ -1477,7 +1486,10 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
}
if not self._async_acquire_addon_ownership():
return self.async_abort(reason="already_in_progress")
return self.async_abort(
reason="already_in_progress",
translation_domain=HOMEASSISTANT_DOMAIN,
)
addon_config_updates = self._addon_config_updates | addon_config_updates
self._addon_config_updates = {}
@@ -1719,7 +1731,10 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
# Config entry already has correct config
return self.async_abort(reason="already_configured")
if not self._async_acquire_addon_ownership():
return self.async_abort(reason="already_in_progress")
return self.async_abort(
reason="already_in_progress",
translation_domain=HOMEASSISTANT_DOMAIN,
)
await self._addon_setup.async_set_addon_config(
{CONF_ADDON_SOCKET: discovery_info.socket_path}
)
@@ -1741,7 +1756,10 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
flow["context"].get("unique_id") == placeholder_unique_id
for flow in self._async_in_progress()
):
return self.async_abort(reason="already_in_progress")
return self.async_abort(
reason="already_in_progress",
translation_domain=HOMEASSISTANT_DOMAIN,
)
# We are not aborting if home ID configured
# here, we just want to make sure that it's set
# We will update a USB based config entry
@@ -104,7 +104,6 @@
"addon_start_failed": "Failed to start the Z-Wave JS app.",
"addon_stop_failed": "Failed to stop the Z-Wave JS app.",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"backup_failed": "Failed to back up network.",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"config_entry_not_loaded": "The Z-Wave configuration entry is not loaded. Please try again when the configuration entry is loaded.",
+75 -1
View File
@@ -60,6 +60,7 @@ from homeassistant.components import repairs
from homeassistant.config_entries import (
DISCOVERY_SOURCES,
ConfigEntriesFlowManager,
ConfigSubentryFlowManager,
FlowResult,
OptionsFlowManager,
)
@@ -1013,6 +1014,45 @@ async def _validate_translation(
)
async def _validate_abort_translation_not_duplicated(
hass: HomeAssistant,
translation_errors: dict[str, str],
ignore_translations_for_mock_domains: set[str],
category: str,
component: str,
translation_domain: str,
key: str,
*,
shared_category: str | None = None,
shared_key: str | None = None,
) -> None:
"""Raise if an integration duplicates an abort translated by another domain.
A local translation with different wording is kept, since the integration
may raise the same reason itself to show its own wording.
"""
if component in ignore_translations_for_mock_domains:
return
full_key = f"component.{component}.{category}.{key}"
translations = await async_get_translations(hass, "en", category, [component])
if (translation := translations.get(full_key)) is None:
return
shared_category = shared_category or category
shared_translations = await async_get_translations(
hass, "en", shared_category, [translation_domain]
)
if translation == shared_translations.get(
f"component.{translation_domain}.{shared_category}.{shared_key or key}"
):
translation_errors[full_key] = (
f"Translation `{category}.{key}` of {component} duplicates the one of "
f"{translation_domain}, which translates this abort. Please remove it "
f"from homeassistant/components/{component}/strings.json and pass "
f'translation_domain="{translation_domain}" wherever {component} '
"raises this reason itself"
)
@pytest.fixture
def ignore_missing_translations() -> str | list[str]:
"""Ignore specific missing translations.
@@ -1147,6 +1187,27 @@ async def _check_config_flow_result_translations(
**(issue.translation_placeholders or {}),
**(description_placeholders or {}),
}
elif isinstance(manager, ConfigSubentryFlowManager):
# Subentry flows are only checked for duplicated shared abort translations
if (
result["type"] is FlowResultType.ABORT
and (abort_domain := result.get("translation_domain")) is not None
and (entry := flow.hass.config_entries.async_get_entry(flow.handler[0]))
and abort_domain != entry.domain
):
# The frontend resolves a shared subentry abort from the config section
await _validate_abort_translation_not_duplicated(
flow.hass,
translation_errors,
ignore_translations_for_mock_domains,
"config_subentries",
entry.domain,
abort_domain,
f"{flow.handler[1]}.abort.{result['reason']}",
shared_category="config",
shared_key=f"abort.{result['reason']}",
)
return
else:
return
@@ -1181,11 +1242,22 @@ async def _check_config_flow_result_translations(
return
if result["type"] is FlowResultType.ABORT:
abort_domain = result.get("translation_domain")
if abort_domain is not None and abort_domain != integration:
await _validate_abort_translation_not_duplicated(
flow.hass,
translation_errors,
ignore_translations_for_mock_domains,
category,
integration,
abort_domain,
f"{key_prefix}abort.{result['reason']}",
)
# We don't need translations for a discovery flow which immediately
# aborts, since such flows won't be seen by users
if not flow.__flow_seen_before and flow.source in DISCOVERY_SOURCES:
return
if (abort_domain := result.get("translation_domain")) is not None:
if abort_domain is not None:
integration = abort_domain
await _validate_translation(
flow.hass,
@@ -1330,6 +1402,8 @@ async def check_translations(
Current checks:
- data entry flow results (ConfigFlow/OptionsFlow/RepairFlow)
- data entry flow aborts translated by another domain are not duplicated locally
(also for ConfigSubentryFlow)
- issue registry entries
- action (service) exceptions
"""