diff --git a/homeassistant/components/bryant_evolution/climate.py b/homeassistant/components/bryant_evolution/climate.py index a2e89661afb..dd9bba9c9f6 100644 --- a/homeassistant/components/bryant_evolution/climate.py +++ b/homeassistant/components/bryant_evolution/climate.py @@ -189,7 +189,7 @@ class BryantEvolutionClimate(ClimateEntity): return HVACAction.HEATING raise HomeAssistantError( translation_domain=DOMAIN, - translation_key="failed_to_parse_hvac_mode", + translation_key="failed_to_parse_hvac_action", translation_placeholders={ "mode_and_active": mode_and_active, "current_temperature": str(self.current_temperature), diff --git a/homeassistant/components/bryant_evolution/strings.json b/homeassistant/components/bryant_evolution/strings.json index 845314c566d..cab575baf7f 100644 --- a/homeassistant/components/bryant_evolution/strings.json +++ b/homeassistant/components/bryant_evolution/strings.json @@ -24,7 +24,7 @@ }, "exceptions": { "failed_to_parse_hvac_action": { - "message": "Could not determine HVAC action: {mode_and_active}, {self.current_temperature}, {self.target_temperature_low}" + "message": "Could not determine HVAC action: {mode_and_active}, {current_temperature}, {target_temperature_low}" }, "failed_to_parse_hvac_mode": { "message": "Cannot parse response to HVACMode: {mode}" diff --git a/homeassistant/components/overkiz/config_flow.py b/homeassistant/components/overkiz/config_flow.py index 43c7b3f3044..e5fca781433 100644 --- a/homeassistant/components/overkiz/config_flow.py +++ b/homeassistant/components/overkiz/config_flow.py @@ -229,7 +229,7 @@ class OverkizConfigFlow(ConfigFlow, domain=DOMAIN): """Handle the local authentication step via config flow.""" errors = {} description_placeholders = { - "somfy-developer-mode-docs": "https://github.com/Somfy-Developer/Somfy-TaHoma-Developer-Mode#getting-started" + "somfy_developer_mode_docs": "https://github.com/Somfy-Developer/Somfy-TaHoma-Developer-Mode#getting-started" } if user_input: diff --git a/homeassistant/components/overkiz/strings.json b/homeassistant/components/overkiz/strings.json index 653088aae8d..7e55067e80b 100644 --- a/homeassistant/components/overkiz/strings.json +++ b/homeassistant/components/overkiz/strings.json @@ -41,7 +41,7 @@ "token": "Token generated by the app used to control your device.", "verify_ssl": "Verify the SSL certificate. Select this only if you are connecting via the hostname." }, - "description": "By activating the [Developer Mode of your TaHoma box]({somfy-developer-mode-docs}), you can authorize third-party software (like Home Assistant) to connect to it via your local network.\n\n1. Open the TaHoma By Somfy application on your device.\n2. Navigate to the Help & advanced features -> Advanced features menu in the application.\n3. Activate Developer Mode by tapping 7 times on the version number of your gateway (like 2025.1.4-11).\n4. Generate a token from the Developer Mode menu to authenticate your API calls.\n\n5. Enter the generated token below and update the host to include your Gateway PIN or the IP address of your gateway." + "description": "By activating the [Developer Mode of your TaHoma box]({somfy_developer_mode_docs}), you can authorize third-party software (like Home Assistant) to connect to it via your local network.\n\n1. Open the TaHoma By Somfy application on your device.\n2. Navigate to the Help & advanced features -> Advanced features menu in the application.\n3. Activate Developer Mode by tapping 7 times on the version number of your gateway (like 2025.1.4-11).\n4. Generate a token from the Developer Mode menu to authenticate your API calls.\n\n5. Enter the generated token below and update the host to include your Gateway PIN or the IP address of your gateway." }, "local_or_cloud": { "data": { diff --git a/script/hassfest/translations.py b/script/hassfest/translations.py index 05b08b59123..9c5258d202c 100644 --- a/script/hassfest/translations.py +++ b/script/hassfest/translations.py @@ -5,6 +5,7 @@ from __future__ import annotations from functools import partial import json import re +import string from typing import Any import voluptuous as vol @@ -131,10 +132,12 @@ def translation_value_validator(value: Any) -> str: - prevents string with HTML - prevents strings with single quoted placeholders + - prevents strings with placeholders using invalid identifiers - prevents combined translations """ string_value = cv.string_with_no_html(value) string_value = string_no_single_quoted_placeholders(string_value) + string_value = validate_placeholders(string_value) if RE_COMBINED_REFERENCE.search(string_value): raise vol.Invalid("the string should not contain combined translations") if string_value != string_value.strip(): @@ -151,6 +154,19 @@ def string_no_single_quoted_placeholders(value: str) -> str: return value +def validate_placeholders(value: str) -> str: + """Validate that placeholders in translations use valid identifiers.""" + formatter = string.Formatter() + + for _, field_name, _, _ in formatter.parse(value): + if field_name: # skip literal text segments + if not field_name.isidentifier(): + raise vol.Invalid( + "placeholders must be valid identifiers ([a-zA-Z_][a-zA-Z0-9_]*)" + ) + return value + + def gen_data_entry_schema( *, config: Config,