Skip to content

Commit 10299b2

Browse files
jbouwhemontnemery
andauthored
Add description placeholders to service translation strings (home-assistant#154984)
Co-authored-by: Erik Montnemery <[email protected]>
1 parent 26444d8 commit 10299b2

File tree

9 files changed

+211
-16
lines changed

9 files changed

+211
-16
lines changed

homeassistant/components/kitchen_sink/__init__.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
UnitOfTemperature,
3232
UnitOfVolume,
3333
)
34-
from homeassistant.core import HomeAssistant, ServiceCall, callback
34+
from homeassistant.core import HomeAssistant, ServiceCall, ServiceResponse, callback
3535
from homeassistant.helpers import config_validation as cv
3636
from homeassistant.helpers.device_registry import DeviceEntry
3737
from homeassistant.helpers.issue_registry import (
@@ -81,11 +81,22 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
8181
)
8282

8383
@callback
84-
def service_handler(call: ServiceCall | None = None) -> None:
84+
def service_handler(call: ServiceCall | None = None) -> ServiceResponse:
8585
"""Do nothing."""
86+
return None
8687

8788
hass.services.async_register(
88-
DOMAIN, "test_service_1", service_handler, SCHEMA_SERVICE_TEST_SERVICE_1
89+
DOMAIN,
90+
"test_service_1",
91+
service_handler,
92+
SCHEMA_SERVICE_TEST_SERVICE_1,
93+
description_placeholders={
94+
"meep_1": "foo",
95+
"meep_2": "bar",
96+
"meep_3": "beer",
97+
"meep_4": "milk",
98+
"meep_5": "https://example.com",
99+
},
89100
)
90101

91102
return True

homeassistant/components/kitchen_sink/strings.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,16 @@
117117
},
118118
"services": {
119119
"test_service_1": {
120-
"description": "Fake action for testing",
120+
"description": "Fake action for testing {meep_2}",
121121
"fields": {
122122
"field_1": {
123-
"description": "Number of seconds",
124-
"name": "Field 1"
123+
"description": "Number of seconds {meep_4}",
124+
"example": "Example: {meep_5}",
125+
"name": "Field 1 {meep_3}"
125126
},
126127
"field_2": {
127128
"description": "Mode",
129+
"example": "Field 2 example",
128130
"name": "Field 2"
129131
},
130132
"field_3": {
@@ -136,7 +138,7 @@
136138
"name": "Field 4"
137139
}
138140
},
139-
"name": "Test action 1",
141+
"name": "Test action {meep_1}",
140142
"sections": {
141143
"advanced_fields": {
142144
"description": "Some very advanced things",

homeassistant/core.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,7 +2426,14 @@ class SupportsResponse(enum.StrEnum):
24262426
class Service:
24272427
"""Representation of a callable service."""
24282428

2429-
__slots__ = ["domain", "job", "schema", "service", "supports_response"]
2429+
__slots__ = [
2430+
"description_placeholders",
2431+
"domain",
2432+
"job",
2433+
"schema",
2434+
"service",
2435+
"supports_response",
2436+
]
24302437

24312438
def __init__(
24322439
self,
@@ -2443,11 +2450,13 @@ def __init__(
24432450
context: Context | None = None,
24442451
supports_response: SupportsResponse = SupportsResponse.NONE,
24452452
job_type: HassJobType | None = None,
2453+
description_placeholders: Mapping[str, str] | None = None,
24462454
) -> None:
24472455
"""Initialize a service."""
24482456
self.job = HassJob(func, f"service {domain}.{service}", job_type=job_type)
24492457
self.schema = schema
24502458
self.supports_response = supports_response
2459+
self.description_placeholders = description_placeholders
24512460

24522461

24532462
class ServiceCall:
@@ -2590,6 +2599,8 @@ def async_register(
25902599
schema: VolSchemaType | None = None,
25912600
supports_response: SupportsResponse = SupportsResponse.NONE,
25922601
job_type: HassJobType | None = None,
2602+
*,
2603+
description_placeholders: Mapping[str, str] | None = None,
25932604
) -> None:
25942605
"""Register a service.
25952606
@@ -2599,7 +2610,13 @@ def async_register(
25992610
"""
26002611
self._hass.verify_event_loop_thread("hass.services.async_register")
26012612
self._async_register(
2602-
domain, service, service_func, schema, supports_response, job_type
2613+
domain,
2614+
service,
2615+
service_func,
2616+
schema,
2617+
supports_response,
2618+
job_type,
2619+
description_placeholders,
26032620
)
26042621

26052622
@callback
@@ -2617,6 +2634,7 @@ def _async_register(
26172634
schema: VolSchemaType | None = None,
26182635
supports_response: SupportsResponse = SupportsResponse.NONE,
26192636
job_type: HassJobType | None = None,
2637+
description_placeholders: Mapping[str, str] | None = None,
26202638
) -> None:
26212639
"""Register a service.
26222640
@@ -2633,6 +2651,7 @@ def _async_register(
26332651
service,
26342652
supports_response=supports_response,
26352653
job_type=job_type,
2654+
description_placeholders=description_placeholders,
26362655
)
26372656

26382657
if domain in self._services:

homeassistant/helpers/entity_component.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
import asyncio
6-
from collections.abc import Callable, Iterable
6+
from collections.abc import Callable, Iterable, Mapping
77
from datetime import timedelta
88
import logging
99
from types import ModuleType
@@ -251,6 +251,8 @@ def async_register_entity_service(
251251
func: str | Callable[..., Any],
252252
required_features: list[int] | None = None,
253253
supports_response: SupportsResponse = SupportsResponse.NONE,
254+
*,
255+
description_placeholders: Mapping[str, str] | None = None,
254256
) -> None:
255257
"""Register an entity service."""
256258
service.async_register_entity_service(
@@ -263,6 +265,7 @@ def async_register_entity_service(
263265
required_features=required_features,
264266
schema=schema,
265267
supports_response=supports_response,
268+
description_placeholders=description_placeholders,
266269
)
267270

268271
async def async_setup_platform(

homeassistant/helpers/entity_platform.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
import asyncio
6-
from collections.abc import Awaitable, Callable, Coroutine, Iterable
6+
from collections.abc import Awaitable, Callable, Coroutine, Iterable, Mapping
77
from contextvars import ContextVar
88
from datetime import timedelta
99
from logging import Logger, getLogger
@@ -1081,6 +1081,7 @@ def async_register_entity_service(
10811081
supports_response: SupportsResponse = SupportsResponse.NONE,
10821082
*,
10831083
entity_device_classes: Iterable[str | None] | None = None,
1084+
description_placeholders: Mapping[str, str] | None = None,
10841085
) -> None:
10851086
"""Register an entity service.
10861087
@@ -1100,6 +1101,7 @@ def async_register_entity_service(
11001101
required_features=required_features,
11011102
schema=schema,
11021103
supports_response=supports_response,
1104+
description_placeholders=description_placeholders,
11031105
)
11041106

11051107
async def _async_update_entity_states(self) -> None:

homeassistant/helpers/service.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
import asyncio
6-
from collections.abc import Callable, Coroutine, Iterable
6+
from collections.abc import Callable, Coroutine, Iterable, Mapping
77
import dataclasses
88
from enum import Enum
99
from functools import cache, partial
@@ -612,6 +612,8 @@ async def async_get_all_descriptions(
612612
# Don't warn for missing services, because it triggers false
613613
# positives for things like scripts, that register as a service
614614
description = {"fields": yaml_description.get("fields", {})}
615+
if description_placeholders := service.description_placeholders:
616+
description["description_placeholders"] = description_placeholders
615617

616618
for item in ("description", "name", "target"):
617619
if item in yaml_description:
@@ -955,6 +957,8 @@ def async_register_admin_service(
955957
],
956958
schema: VolSchemaType = vol.Schema({}, extra=vol.PREVENT_EXTRA),
957959
supports_response: SupportsResponse = SupportsResponse.NONE,
960+
*,
961+
description_placeholders: Mapping[str, str] | None = None,
958962
) -> None:
959963
"""Register a service that requires admin access."""
960964
hass.services.async_register(
@@ -967,6 +971,7 @@ def async_register_admin_service(
967971
),
968972
schema,
969973
supports_response,
974+
description_placeholders=description_placeholders,
970975
)
971976

972977

@@ -1112,6 +1117,7 @@ def async_register_entity_service(
11121117
domain: str,
11131118
name: str,
11141119
*,
1120+
description_placeholders: Mapping[str, str] | None = None,
11151121
entity_device_classes: Iterable[str | None] | None = None,
11161122
entities: dict[str, Entity],
11171123
func: str | Callable[..., Any],
@@ -1145,6 +1151,7 @@ def async_register_entity_service(
11451151
schema,
11461152
supports_response,
11471153
job_type=job_type,
1154+
description_placeholders=description_placeholders,
11481155
)
11491156

11501157

@@ -1154,6 +1161,7 @@ def async_register_platform_entity_service(
11541161
service_domain: str,
11551162
service_name: str,
11561163
*,
1164+
description_placeholders: Mapping[str, str] | None = None,
11571165
entity_device_classes: Iterable[str | None] | None = None,
11581166
entity_domain: str,
11591167
func: str | Callable[..., Any],
@@ -1191,4 +1199,5 @@ def get_entities() -> dict[str, Entity]:
11911199
schema,
11921200
supports_response,
11931201
job_type=HassJobType.Coroutinefunction,
1202+
description_placeholders=description_placeholders,
11941203
)

tests/helpers/test_entity_component.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from homeassistant.helpers import config_validation as cv, discovery
2727
from homeassistant.helpers.entity_component import EntityComponent, async_update_entity
2828
from homeassistant.helpers.entity_platform import AddEntitiesCallback
29+
from homeassistant.helpers.service import async_get_all_descriptions
2930
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
3031
from homeassistant.setup import async_setup_component
3132
from homeassistant.util import dt as dt_util
@@ -511,7 +512,7 @@ async def test_register_entity_service(
511512
schema: dict | None,
512513
service_data: dict,
513514
) -> None:
514-
"""Test registering an enttiy service and calling it."""
515+
"""Test registering an entity service and calling it."""
515516
entity = MockEntity(entity_id=f"{DOMAIN}.entity")
516517
calls = []
517518

@@ -525,7 +526,16 @@ def appender(**kwargs):
525526
await component.async_setup({})
526527
await component.async_add_entities([entity])
527528

528-
component.async_register_entity_service("hello", schema, "async_called_by_service")
529+
component.async_register_entity_service(
530+
"hello",
531+
schema,
532+
"async_called_by_service",
533+
description_placeholders={"test_placeholder": "beer"},
534+
)
535+
descriptions = await async_get_all_descriptions(hass)
536+
assert descriptions["test_domain"]["hello"]["description_placeholders"] == {
537+
"test_placeholder": "beer"
538+
}
529539

530540
with pytest.raises(vol.Invalid):
531541
await hass.services.async_call(

tests/helpers/test_entity_platform.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
AddConfigEntryEntitiesCallback,
4141
AddEntitiesCallback,
4242
)
43+
from homeassistant.helpers.service import async_get_all_descriptions
4344
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
4445
from homeassistant.util import dt as dt_util
4546

@@ -1639,11 +1640,21 @@ async def test_platforms_sharing_services(hass: HomeAssistant) -> None:
16391640
def handle_service(entity, data):
16401641
entities.append(entity)
16411642

1642-
entity_platform1.async_register_entity_service("hello", {}, handle_service)
1643+
entity_platform1.async_register_entity_service(
1644+
"hello", {}, handle_service, description_placeholders={"drink": "beer"}
1645+
)
16431646
entity_platform2.async_register_entity_service(
1644-
"hello", {}, Mock(side_effect=AssertionError("Should not be called"))
1647+
"hello",
1648+
{},
1649+
Mock(side_effect=AssertionError("Should not be called")),
1650+
description_placeholders={"drink": "milk"},
16451651
)
16461652

1653+
descriptions = await async_get_all_descriptions(hass)
1654+
assert descriptions["mock_platform"]["hello"]["description_placeholders"] == {
1655+
"drink": "beer"
1656+
}
1657+
16471658
await hass.services.async_call(
16481659
"mock_platform", "hello", {"entity_id": "all"}, blocking=True
16491660
)

0 commit comments

Comments
 (0)