Skip to content

Commit 7cec3aa

Browse files
authored
Hassfest check for invalid localization placeholders (home-assistant#155216)
1 parent 1ddb39f commit 7cec3aa

File tree

5 files changed

+20
-4
lines changed

5 files changed

+20
-4
lines changed

homeassistant/components/bryant_evolution/climate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ async def _read_hvac_action(self) -> HVACAction:
189189
return HVACAction.HEATING
190190
raise HomeAssistantError(
191191
translation_domain=DOMAIN,
192-
translation_key="failed_to_parse_hvac_mode",
192+
translation_key="failed_to_parse_hvac_action",
193193
translation_placeholders={
194194
"mode_and_active": mode_and_active,
195195
"current_temperature": str(self.current_temperature),

homeassistant/components/bryant_evolution/strings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
},
2525
"exceptions": {
2626
"failed_to_parse_hvac_action": {
27-
"message": "Could not determine HVAC action: {mode_and_active}, {self.current_temperature}, {self.target_temperature_low}"
27+
"message": "Could not determine HVAC action: {mode_and_active}, {current_temperature}, {target_temperature_low}"
2828
},
2929
"failed_to_parse_hvac_mode": {
3030
"message": "Cannot parse response to HVACMode: {mode}"

homeassistant/components/overkiz/config_flow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ async def async_step_local(
229229
"""Handle the local authentication step via config flow."""
230230
errors = {}
231231
description_placeholders = {
232-
"somfy-developer-mode-docs": "https://github.com/Somfy-Developer/Somfy-TaHoma-Developer-Mode#getting-started"
232+
"somfy_developer_mode_docs": "https://github.com/Somfy-Developer/Somfy-TaHoma-Developer-Mode#getting-started"
233233
}
234234

235235
if user_input:

homeassistant/components/overkiz/strings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"token": "Token generated by the app used to control your device.",
4242
"verify_ssl": "Verify the SSL certificate. Select this only if you are connecting via the hostname."
4343
},
44-
"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."
44+
"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."
4545
},
4646
"local_or_cloud": {
4747
"data": {

script/hassfest/translations.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from functools import partial
66
import json
77
import re
8+
import string
89
from typing import Any
910

1011
import voluptuous as vol
@@ -131,10 +132,12 @@ def translation_value_validator(value: Any) -> str:
131132
132133
- prevents string with HTML
133134
- prevents strings with single quoted placeholders
135+
- prevents strings with placeholders using invalid identifiers
134136
- prevents combined translations
135137
"""
136138
string_value = cv.string_with_no_html(value)
137139
string_value = string_no_single_quoted_placeholders(string_value)
140+
string_value = validate_placeholders(string_value)
138141
if RE_COMBINED_REFERENCE.search(string_value):
139142
raise vol.Invalid("the string should not contain combined translations")
140143
if string_value != string_value.strip():
@@ -151,6 +154,19 @@ def string_no_single_quoted_placeholders(value: str) -> str:
151154
return value
152155

153156

157+
def validate_placeholders(value: str) -> str:
158+
"""Validate that placeholders in translations use valid identifiers."""
159+
formatter = string.Formatter()
160+
161+
for _, field_name, _, _ in formatter.parse(value):
162+
if field_name: # skip literal text segments
163+
if not field_name.isidentifier():
164+
raise vol.Invalid(
165+
"placeholders must be valid identifiers ([a-zA-Z_][a-zA-Z0-9_]*)"
166+
)
167+
return value
168+
169+
154170
def gen_data_entry_schema(
155171
*,
156172
config: Config,

0 commit comments

Comments
 (0)