mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Add support for service translations (#95984)
This commit is contained in:
parent
f12f8bca03
commit
f054de0ad5
@ -1,17 +1,11 @@
|
||||
# Describes the format for available light services
|
||||
|
||||
turn_on:
|
||||
name: Turn on
|
||||
description: >
|
||||
Turn on one or more lights and adjust properties of the light, even when
|
||||
they are turned on already.
|
||||
target:
|
||||
entity:
|
||||
domain: light
|
||||
fields:
|
||||
transition:
|
||||
name: Transition
|
||||
description: Duration it takes to get to next state.
|
||||
filter:
|
||||
supported_features:
|
||||
- light.LightEntityFeature.TRANSITION
|
||||
@ -21,8 +15,6 @@ turn_on:
|
||||
max: 300
|
||||
unit_of_measurement: seconds
|
||||
rgb_color:
|
||||
name: Color
|
||||
description: The color for the light (based on RGB - red, green, blue).
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -34,8 +26,6 @@ turn_on:
|
||||
selector:
|
||||
color_rgb:
|
||||
rgbw_color:
|
||||
name: RGBW-color
|
||||
description: A list containing four integers between 0 and 255 representing the RGBW (red, green, blue, white) color for the light.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -49,8 +39,6 @@ turn_on:
|
||||
selector:
|
||||
object:
|
||||
rgbww_color:
|
||||
name: RGBWW-color
|
||||
description: A list containing five integers between 0 and 255 representing the RGBWW (red, green, blue, cold white, warm white) color for the light.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -64,8 +52,6 @@ turn_on:
|
||||
selector:
|
||||
object:
|
||||
color_name:
|
||||
name: Color name
|
||||
description: A human readable color name.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -77,6 +63,7 @@ turn_on:
|
||||
advanced: true
|
||||
selector:
|
||||
select:
|
||||
translation_key: color_name
|
||||
options:
|
||||
- "homeassistant"
|
||||
- "aliceblue"
|
||||
@ -228,8 +215,6 @@ turn_on:
|
||||
- "yellow"
|
||||
- "yellowgreen"
|
||||
hs_color:
|
||||
name: Hue/Sat color
|
||||
description: Color for the light in hue/sat format. Hue is 0-360 and Sat is 0-100.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -243,8 +228,6 @@ turn_on:
|
||||
selector:
|
||||
object:
|
||||
xy_color:
|
||||
name: XY-color
|
||||
description: Color for the light in XY-format.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -258,8 +241,6 @@ turn_on:
|
||||
selector:
|
||||
object:
|
||||
color_temp:
|
||||
name: Color temperature
|
||||
description: Color temperature for the light in mireds.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -274,8 +255,6 @@ turn_on:
|
||||
min_mireds: 153
|
||||
max_mireds: 500
|
||||
kelvin:
|
||||
name: Color temperature (Kelvin)
|
||||
description: Color temperature for the light in Kelvin.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -293,10 +272,6 @@ turn_on:
|
||||
step: 100
|
||||
unit_of_measurement: K
|
||||
brightness:
|
||||
name: Brightness value
|
||||
description: Number indicating brightness, where 0 turns the light
|
||||
off, 1 is the minimum brightness and 255 is the maximum brightness
|
||||
supported by the light.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -313,10 +288,6 @@ turn_on:
|
||||
min: 0
|
||||
max: 255
|
||||
brightness_pct:
|
||||
name: Brightness
|
||||
description: Number indicating percentage of full brightness, where 0
|
||||
turns the light off, 1 is the minimum brightness and 100 is the maximum
|
||||
brightness supported by the light.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -333,8 +304,6 @@ turn_on:
|
||||
max: 100
|
||||
unit_of_measurement: "%"
|
||||
brightness_step:
|
||||
name: Brightness step value
|
||||
description: Change brightness by an amount.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -351,8 +320,6 @@ turn_on:
|
||||
min: -225
|
||||
max: 255
|
||||
brightness_step_pct:
|
||||
name: Brightness step
|
||||
description: Change brightness by a percentage.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -369,8 +336,6 @@ turn_on:
|
||||
max: 100
|
||||
unit_of_measurement: "%"
|
||||
white:
|
||||
name: White
|
||||
description: Set the light to white mode.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -381,15 +346,11 @@ turn_on:
|
||||
value: true
|
||||
label: Enabled
|
||||
profile:
|
||||
name: Profile
|
||||
description: Name of a light profile to use.
|
||||
advanced: true
|
||||
example: relax
|
||||
selector:
|
||||
text:
|
||||
flash:
|
||||
name: Flash
|
||||
description: If the light should flash.
|
||||
filter:
|
||||
supported_features:
|
||||
- light.LightEntityFeature.FLASH
|
||||
@ -402,8 +363,6 @@ turn_on:
|
||||
- label: "Short"
|
||||
value: "short"
|
||||
effect:
|
||||
name: Effect
|
||||
description: Light effect.
|
||||
filter:
|
||||
supported_features:
|
||||
- light.LightEntityFeature.EFFECT
|
||||
@ -411,15 +370,11 @@ turn_on:
|
||||
text:
|
||||
|
||||
turn_off:
|
||||
name: Turn off
|
||||
description: Turns off one or more lights.
|
||||
target:
|
||||
entity:
|
||||
domain: light
|
||||
fields:
|
||||
transition:
|
||||
name: Transition
|
||||
description: Duration it takes to get to next state.
|
||||
filter:
|
||||
supported_features:
|
||||
- light.LightEntityFeature.TRANSITION
|
||||
@ -429,8 +384,6 @@ turn_off:
|
||||
max: 300
|
||||
unit_of_measurement: seconds
|
||||
flash:
|
||||
name: Flash
|
||||
description: If the light should flash.
|
||||
filter:
|
||||
supported_features:
|
||||
- light.LightEntityFeature.FLASH
|
||||
@ -444,17 +397,11 @@ turn_off:
|
||||
value: "short"
|
||||
|
||||
toggle:
|
||||
name: Toggle
|
||||
description: >
|
||||
Toggles one or more lights, from on to off, or, off to on, based on their
|
||||
current state.
|
||||
target:
|
||||
entity:
|
||||
domain: light
|
||||
fields:
|
||||
transition:
|
||||
name: Transition
|
||||
description: Duration it takes to get to next state.
|
||||
filter:
|
||||
supported_features:
|
||||
- light.LightEntityFeature.TRANSITION
|
||||
@ -464,8 +411,6 @@ toggle:
|
||||
max: 300
|
||||
unit_of_measurement: seconds
|
||||
rgb_color:
|
||||
name: RGB-color
|
||||
description: Color for the light in RGB-format.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -479,8 +424,6 @@ toggle:
|
||||
selector:
|
||||
object:
|
||||
color_name:
|
||||
name: Color name
|
||||
description: A human readable color name.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -492,6 +435,7 @@ toggle:
|
||||
advanced: true
|
||||
selector:
|
||||
select:
|
||||
translation_key: color_name
|
||||
options:
|
||||
- "homeassistant"
|
||||
- "aliceblue"
|
||||
@ -643,8 +587,6 @@ toggle:
|
||||
- "yellow"
|
||||
- "yellowgreen"
|
||||
hs_color:
|
||||
name: Hue/Sat color
|
||||
description: Color for the light in hue/sat format. Hue is 0-360 and Sat is 0-100.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -658,8 +600,6 @@ toggle:
|
||||
selector:
|
||||
object:
|
||||
xy_color:
|
||||
name: XY-color
|
||||
description: Color for the light in XY-format.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -673,8 +613,6 @@ toggle:
|
||||
selector:
|
||||
object:
|
||||
color_temp:
|
||||
name: Color temperature (mireds)
|
||||
description: Color temperature for the light in mireds.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -688,8 +626,6 @@ toggle:
|
||||
selector:
|
||||
color_temp:
|
||||
kelvin:
|
||||
name: Color temperature (Kelvin)
|
||||
description: Color temperature for the light in Kelvin.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -707,10 +643,6 @@ toggle:
|
||||
step: 100
|
||||
unit_of_measurement: K
|
||||
brightness:
|
||||
name: Brightness value
|
||||
description: Number indicating brightness, where 0 turns the light
|
||||
off, 1 is the minimum brightness and 255 is the maximum brightness
|
||||
supported by the light.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -727,10 +659,6 @@ toggle:
|
||||
min: 0
|
||||
max: 255
|
||||
brightness_pct:
|
||||
name: Brightness
|
||||
description: Number indicating percentage of full brightness, where 0
|
||||
turns the light off, 1 is the minimum brightness and 100 is the maximum
|
||||
brightness supported by the light.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -747,8 +675,6 @@ toggle:
|
||||
max: 100
|
||||
unit_of_measurement: "%"
|
||||
white:
|
||||
name: White
|
||||
description: Set the light to white mode.
|
||||
filter:
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
@ -759,15 +685,11 @@ toggle:
|
||||
value: true
|
||||
label: Enabled
|
||||
profile:
|
||||
name: Profile
|
||||
description: Name of a light profile to use.
|
||||
advanced: true
|
||||
example: relax
|
||||
selector:
|
||||
text:
|
||||
flash:
|
||||
name: Flash
|
||||
description: If the light should flash.
|
||||
filter:
|
||||
supported_features:
|
||||
- light.LightEntityFeature.FLASH
|
||||
@ -780,8 +702,6 @@ toggle:
|
||||
- label: "Short"
|
||||
value: "short"
|
||||
effect:
|
||||
name: Effect
|
||||
description: Light effect.
|
||||
filter:
|
||||
supported_features:
|
||||
- light.LightEntityFeature.EFFECT
|
||||
|
@ -86,5 +86,307 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"color_name": {
|
||||
"options": {
|
||||
"homeassistant": "Home Assistant",
|
||||
"aliceblue": "Alice blue",
|
||||
"antiquewhite": "Antique white",
|
||||
"aqua": "Aqua",
|
||||
"aquamarine": "Aquamarine",
|
||||
"azure": "Azure",
|
||||
"beige": "Beige",
|
||||
"bisque": "Bisque",
|
||||
"blanchedalmond": "Blanched almond",
|
||||
"blue": "Blue",
|
||||
"blueviolet": "Blue violet",
|
||||
"brown": "Brown",
|
||||
"burlywood": "Burlywood",
|
||||
"cadetblue": "Cadet blue",
|
||||
"chartreuse": "Chartreuse",
|
||||
"chocolate": "Chocolate",
|
||||
"coral": "Coral",
|
||||
"cornflowerblue": "Cornflower blue",
|
||||
"cornsilk": "Cornsilk",
|
||||
"crimson": "Crimson",
|
||||
"cyan": "Cyan",
|
||||
"darkblue": "Dark blue",
|
||||
"darkcyan": "Dark cyan",
|
||||
"darkgoldenrod": "Dark goldenrod",
|
||||
"darkgray": "Dark gray",
|
||||
"darkgreen": "Dark green",
|
||||
"darkgrey": "Dark grey",
|
||||
"darkkhaki": "Dark khaki",
|
||||
"darkmagenta": "Dark magenta",
|
||||
"darkolivegreen": "Dark olive green",
|
||||
"darkorange": "Dark orange",
|
||||
"darkorchid": "Dark orchid",
|
||||
"darkred": "Dark red",
|
||||
"darksalmon": "Dark salmon",
|
||||
"darkseagreen": "Dark sea green",
|
||||
"darkslateblue": "Dark slate blue",
|
||||
"darkslategray": "Dark slate gray",
|
||||
"darkslategrey": "Dark slate grey",
|
||||
"darkturquoise": "Dark turquoise",
|
||||
"darkviolet": "Dark violet",
|
||||
"deeppink": "Deep pink",
|
||||
"deepskyblue": "Deep sky blue",
|
||||
"dimgray": "Dim gray",
|
||||
"dimgrey": "Dim grey",
|
||||
"dodgerblue": "Dodger blue",
|
||||
"firebrick": "Fire brick",
|
||||
"floralwhite": "Floral white",
|
||||
"forestgreen": "Forest green",
|
||||
"fuchsia": "Fuchsia",
|
||||
"gainsboro": "Gainsboro",
|
||||
"ghostwhite": "Ghost white",
|
||||
"gold": "Gold",
|
||||
"goldenrod": "Goldenrod",
|
||||
"gray": "Gray",
|
||||
"green": "Green",
|
||||
"greenyellow": "Green yellow",
|
||||
"grey": "Grey",
|
||||
"honeydew": "Honeydew",
|
||||
"hotpink": "Hot pink",
|
||||
"indianred": "Indian red",
|
||||
"indigo": "Indigo",
|
||||
"ivory": "Ivory",
|
||||
"khaki": "Khaki",
|
||||
"lavender": "Lavender",
|
||||
"lavenderblush": "Lavender blush",
|
||||
"lawngreen": "Lawn green",
|
||||
"lemonchiffon": "Lemon chiffon",
|
||||
"lightblue": "Light blue",
|
||||
"lightcoral": "Light coral",
|
||||
"lightcyan": "Light cyan",
|
||||
"lightgoldenrodyellow": "Light goldenrod yellow",
|
||||
"lightgray": "Light gray",
|
||||
"lightgreen": "Light green",
|
||||
"lightgrey": "Light grey",
|
||||
"lightpink": "Light pink",
|
||||
"lightsalmon": "Light salmon",
|
||||
"lightseagreen": "Light sea green",
|
||||
"lightskyblue": "Light sky blue",
|
||||
"lightslategray": "Light slate gray",
|
||||
"lightslategrey": "Light slate grey",
|
||||
"lightsteelblue": "Light steel blue",
|
||||
"lightyellow": "Light yellow",
|
||||
"lime": "Lime",
|
||||
"limegreen": "Lime green",
|
||||
"linen": "Linen",
|
||||
"magenta": "Magenta",
|
||||
"maroon": "Maroon",
|
||||
"mediumaquamarine": "Medium aquamarine",
|
||||
"mediumblue": "Medium blue",
|
||||
"mediumorchid": "Medium orchid",
|
||||
"mediumpurple": "Medium purple",
|
||||
"mediumseagreen": "Medium sea green",
|
||||
"mediumslateblue": "Medium slate blue",
|
||||
"mediumspringgreen": "Medium spring green",
|
||||
"mediumturquoise": "Medium turquoise",
|
||||
"mediumvioletred": "Medium violet red",
|
||||
"midnightblue": "Midnight blue",
|
||||
"mintcream": "Mint cream",
|
||||
"mistyrose": "Misty rose",
|
||||
"moccasin": "Moccasin",
|
||||
"navajowhite": "Navajo white",
|
||||
"navy": "Navy",
|
||||
"navyblue": "Navy blue",
|
||||
"oldlace": "Old lace",
|
||||
"olive": "Olive",
|
||||
"olivedrab": "Olive drab",
|
||||
"orange": "Orange",
|
||||
"orangered": "Orange red",
|
||||
"orchid": "Orchid",
|
||||
"palegoldenrod": "Pale goldenrod",
|
||||
"palegreen": "Pale green",
|
||||
"paleturquoise": "Pale turquoise",
|
||||
"palevioletred": "Pale violet red",
|
||||
"papayawhip": "Papaya whip",
|
||||
"peachpuff": "Peach puff",
|
||||
"peru": "Peru",
|
||||
"pink": "Pink",
|
||||
"plum": "Plum",
|
||||
"powderblue": "Powder blue",
|
||||
"purple": "Purple",
|
||||
"red": "Red",
|
||||
"rosybrown": "Rosy brown",
|
||||
"royalblue": "Royal blue",
|
||||
"saddlebrown": "Saddle brown",
|
||||
"salmon": "Salmon",
|
||||
"sandybrown": "Sandy brown",
|
||||
"seagreen": "Sea green",
|
||||
"seashell": "Seashell",
|
||||
"sienna": "Sienna",
|
||||
"silver": "Silver",
|
||||
"skyblue": "Sky blue",
|
||||
"slateblue": "Slate blue",
|
||||
"slategray": "Slate gray",
|
||||
"slategrey": "Slate grey",
|
||||
"snow": "Snow",
|
||||
"springgreen": "Spring green",
|
||||
"steelblue": "Steel blue",
|
||||
"tan": "Tan",
|
||||
"teal": "Teal",
|
||||
"thistle": "Thistle",
|
||||
"tomato": "Tomato",
|
||||
"turquoise": "Turquoise",
|
||||
"violet": "Violet",
|
||||
"wheat": "Wheat",
|
||||
"white": "White",
|
||||
"whitesmoke": "White smoke",
|
||||
"yellow": "Yellow",
|
||||
"yellowgreen": "Yellow green"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"turn_on": {
|
||||
"name": "Turn on",
|
||||
"description": "Turn on one or more lights and adjust properties of the light, even when they are turned on already.",
|
||||
"fields": {
|
||||
"transition": {
|
||||
"name": "Transition",
|
||||
"description": "Duration it takes to get to next state."
|
||||
},
|
||||
"rgb_color": {
|
||||
"name": "Color",
|
||||
"description": "The color in RGB format. A list of three integers between 0 and 255 representing the values of red, green, and blue."
|
||||
},
|
||||
"rgbw_color": {
|
||||
"name": "RGBW-color",
|
||||
"description": "The color in RGBW format. A list of four integers between 0 and 255 representing the values of red, green, blue, and white."
|
||||
},
|
||||
"rgbww_color": {
|
||||
"name": "RGBWW-color",
|
||||
"description": "The color in RGBWW format. A list of five integers between 0 and 255 representing the values of red, green, blue, cold white, and warm white."
|
||||
},
|
||||
"color_name": {
|
||||
"name": "Color name",
|
||||
"description": "A human readable color name."
|
||||
},
|
||||
"hs_color": {
|
||||
"name": "Hue/Sat color",
|
||||
"description": "Color in hue/sat format. A list of two integers. Hue is 0-360 and Sat is 0-100."
|
||||
},
|
||||
"xy_color": {
|
||||
"name": "XY-color",
|
||||
"description": "Color in XY-format. A list of two decimal numbers between 0 and 1."
|
||||
},
|
||||
"color_temp": {
|
||||
"name": "Color temperature",
|
||||
"description": "Color temperature in mireds."
|
||||
},
|
||||
"kelvin": {
|
||||
"name": "Color temperature",
|
||||
"description": "Color temperature in Kelvin."
|
||||
},
|
||||
"brightness": {
|
||||
"name": "Brightness value",
|
||||
"description": "Number indicating brightness, where 0 turns the light off, 1 is the minimum brightness, and 255 is the maximum brightness."
|
||||
},
|
||||
"brightness_pct": {
|
||||
"name": "Brightness",
|
||||
"description": "Number indicating the percentage of full brightness, where 0 turns the light off, 1 is the minimum brightness, and 100 is the maximum brightness."
|
||||
},
|
||||
"brightness_step": {
|
||||
"name": "Brightness step value",
|
||||
"description": "Change brightness by an amount."
|
||||
},
|
||||
"brightness_step_pct": {
|
||||
"name": "Brightness step",
|
||||
"description": "Change brightness by a percentage."
|
||||
},
|
||||
"white": {
|
||||
"name": "White",
|
||||
"description": "Set the light to white mode."
|
||||
},
|
||||
"profile": {
|
||||
"name": "Profile",
|
||||
"description": "Name of a light profile to use."
|
||||
},
|
||||
"flash": {
|
||||
"name": "Flash",
|
||||
"description": "If the light should flash."
|
||||
},
|
||||
"effect": {
|
||||
"name": "Effect",
|
||||
"description": "Light effect."
|
||||
}
|
||||
}
|
||||
},
|
||||
"turn_off": {
|
||||
"name": "Turn off",
|
||||
"description": "Turn off one or more lights.",
|
||||
"fields": {
|
||||
"transition": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::transition::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::transition::description%]"
|
||||
},
|
||||
"flash": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::flash::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::flash::description%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"name": "Toggle",
|
||||
"description": "Toggles one or more lights, from on to off, or, off to on, based on their current state.",
|
||||
"fields": {
|
||||
"transition": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::transition::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::transition::description%]"
|
||||
},
|
||||
"rgb_color": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::rgb_color::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::rgb_color::description%]"
|
||||
},
|
||||
"color_name": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::color_name::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::color_name::description%]"
|
||||
},
|
||||
"hs_color": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::hs_color::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::hs_color::description%]"
|
||||
},
|
||||
"xy_color": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::xy_color::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::xy_color::description%]"
|
||||
},
|
||||
"color_temp": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::color_temp::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::color_temp::description%]"
|
||||
},
|
||||
"kelvin": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::kelvin::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::kelvin::description%]"
|
||||
},
|
||||
"brightness": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::brightness::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::brightness::description%]"
|
||||
},
|
||||
"brightness_pct": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::brightness_pct::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::brightness_pct::description%]"
|
||||
},
|
||||
"white": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::white::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::white::description%]"
|
||||
},
|
||||
"profile": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::profile::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::profile::description%]"
|
||||
},
|
||||
"flash": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::flash::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::flash::description%]"
|
||||
},
|
||||
"effect": {
|
||||
"name": "[%key:component::light::services::turn_on::fields::effect::name%]",
|
||||
"description": "[%key:component::light::services::turn_on::fields::effect::description%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ from . import (
|
||||
device_registry,
|
||||
entity_registry,
|
||||
template,
|
||||
translation,
|
||||
)
|
||||
from .selector import TargetSelector
|
||||
from .typing import ConfigType, TemplateVarsType
|
||||
@ -607,6 +608,11 @@ async def async_get_all_descriptions(
|
||||
)
|
||||
loaded = dict(zip(missing, contents))
|
||||
|
||||
# Load translations for all service domains
|
||||
translations = await translation.async_get_translations(
|
||||
hass, "en", "services", list(services)
|
||||
)
|
||||
|
||||
# Build response
|
||||
descriptions: dict[str, dict[str, Any]] = {}
|
||||
for domain, services_map in services.items():
|
||||
@ -616,37 +622,62 @@ async def async_get_all_descriptions(
|
||||
for service_name in services_map:
|
||||
cache_key = (domain, service_name)
|
||||
description = descriptions_cache.get(cache_key)
|
||||
if description is not None:
|
||||
domain_descriptions[service_name] = description
|
||||
continue
|
||||
|
||||
# Cache missing descriptions
|
||||
if description is None:
|
||||
domain_yaml = loaded.get(domain) or {}
|
||||
# The YAML may be empty for dynamically defined
|
||||
# services (ie shell_command) that never call
|
||||
# service.async_set_service_schema for the dynamic
|
||||
# service
|
||||
domain_yaml = loaded.get(domain) or {}
|
||||
# The YAML may be empty for dynamically defined
|
||||
# services (ie shell_command) that never call
|
||||
# service.async_set_service_schema for the dynamic
|
||||
# service
|
||||
|
||||
yaml_description = domain_yaml.get( # type: ignore[union-attr]
|
||||
service_name, {}
|
||||
)
|
||||
yaml_description = domain_yaml.get( # type: ignore[union-attr]
|
||||
service_name, {}
|
||||
)
|
||||
|
||||
# Don't warn for missing services, because it triggers false
|
||||
# positives for things like scripts, that register as a service
|
||||
description = {
|
||||
"name": yaml_description.get("name", ""),
|
||||
"description": yaml_description.get("description", ""),
|
||||
"fields": yaml_description.get("fields", {}),
|
||||
# Don't warn for missing services, because it triggers false
|
||||
# positives for things like scripts, that register as a service
|
||||
#
|
||||
# When name & description are in the translations use those;
|
||||
# otherwise fallback to backwards compatible behavior from
|
||||
# the time when we didn't have translations for descriptions yet.
|
||||
# This mimics the behavior of the frontend.
|
||||
description = {
|
||||
"name": translations.get(
|
||||
f"component.{domain}.services.{service_name}.name",
|
||||
yaml_description.get("name", ""),
|
||||
),
|
||||
"description": translations.get(
|
||||
f"component.{domain}.services.{service_name}.description",
|
||||
yaml_description.get("description", ""),
|
||||
),
|
||||
"fields": dict(yaml_description.get("fields", {})),
|
||||
}
|
||||
|
||||
# Translate fields names & descriptions as well
|
||||
for field_name, field_schema in description["fields"].items():
|
||||
if name := translations.get(
|
||||
f"component.{domain}.services.{service_name}.fields.{field_name}.name"
|
||||
):
|
||||
field_schema["name"] = name
|
||||
if desc := translations.get(
|
||||
f"component.{domain}.services.{service_name}.fields.{field_name}.description"
|
||||
):
|
||||
field_schema["description"] = desc
|
||||
|
||||
if "target" in yaml_description:
|
||||
description["target"] = yaml_description["target"]
|
||||
|
||||
if (
|
||||
response := hass.services.supports_response(domain, service_name)
|
||||
) != SupportsResponse.NONE:
|
||||
description["response"] = {
|
||||
"optional": response == SupportsResponse.OPTIONAL,
|
||||
}
|
||||
|
||||
if "target" in yaml_description:
|
||||
description["target"] = yaml_description["target"]
|
||||
|
||||
if (
|
||||
response := hass.services.supports_response(domain, service_name)
|
||||
) != SupportsResponse.NONE:
|
||||
description["response"] = {
|
||||
"optional": response == SupportsResponse.OPTIONAL,
|
||||
}
|
||||
|
||||
descriptions_cache[cache_key] = description
|
||||
descriptions_cache[cache_key] = description
|
||||
|
||||
domain_descriptions[service_name] = description
|
||||
|
||||
|
@ -302,7 +302,7 @@ async def async_get_translations(
|
||||
components = set(integrations)
|
||||
elif config_flow:
|
||||
components = (await async_get_config_flows(hass)) - hass.config.components
|
||||
elif category in ("state", "entity_component"):
|
||||
elif category in ("state", "entity_component", "services"):
|
||||
components = set(hass.config.components)
|
||||
else:
|
||||
# Only 'state' supports merging, so remove platforms from selection
|
||||
|
@ -1,6 +1,8 @@
|
||||
"""Validate dependencies."""
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
from typing import Any
|
||||
@ -25,7 +27,7 @@ def exists(value: Any) -> Any:
|
||||
|
||||
FIELD_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("description"): str,
|
||||
vol.Optional("description"): str,
|
||||
vol.Optional("name"): str,
|
||||
vol.Optional("example"): exists,
|
||||
vol.Optional("default"): exists,
|
||||
@ -46,7 +48,7 @@ FIELD_SCHEMA = vol.Schema(
|
||||
|
||||
SERVICE_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("description"): str,
|
||||
vol.Optional("description"): str,
|
||||
vol.Optional("name"): str,
|
||||
vol.Optional("target"): vol.Any(selector.TargetSelector.CONFIG_SCHEMA, None),
|
||||
vol.Optional("fields"): vol.Schema({str: FIELD_SCHEMA}),
|
||||
@ -70,7 +72,7 @@ def grep_dir(path: pathlib.Path, glob_pattern: str, search_pattern: str) -> bool
|
||||
return False
|
||||
|
||||
|
||||
def validate_services(integration: Integration) -> None:
|
||||
def validate_services(config: Config, integration: Integration) -> None:
|
||||
"""Validate services."""
|
||||
try:
|
||||
data = load_yaml(str(integration.path / "services.yaml"))
|
||||
@ -92,15 +94,75 @@ def validate_services(integration: Integration) -> None:
|
||||
return
|
||||
|
||||
try:
|
||||
SERVICES_SCHEMA(data)
|
||||
services = SERVICES_SCHEMA(data)
|
||||
except vol.Invalid as err:
|
||||
integration.add_error(
|
||||
"services", f"Invalid services.yaml: {humanize_error(data, err)}"
|
||||
)
|
||||
|
||||
# Try loading translation strings
|
||||
if integration.core:
|
||||
strings_file = integration.path / "strings.json"
|
||||
else:
|
||||
# For custom integrations, use the en.json file
|
||||
strings_file = integration.path / "translations/en.json"
|
||||
|
||||
strings = {}
|
||||
if strings_file.is_file():
|
||||
with contextlib.suppress(ValueError):
|
||||
strings = json.loads(strings_file.read_text())
|
||||
|
||||
# For each service in the integration, check if the description if set,
|
||||
# if not, check if it's in the strings file. If not, add an error.
|
||||
for service_name, service_schema in services.items():
|
||||
if "name" not in service_schema:
|
||||
try:
|
||||
strings["services"][service_name]["name"]
|
||||
except KeyError:
|
||||
integration.add_error(
|
||||
"services",
|
||||
f"Service {service_name} has no name and is not in the translations file",
|
||||
)
|
||||
|
||||
if "description" not in service_schema:
|
||||
try:
|
||||
strings["services"][service_name]["description"]
|
||||
except KeyError:
|
||||
integration.add_error(
|
||||
"services",
|
||||
f"Service {service_name} has no description and is not in the translations file",
|
||||
)
|
||||
|
||||
# The same check is done for the description in each of the fields of the
|
||||
# service schema.
|
||||
for field_name, field_schema in service_schema.get("fields", {}).items():
|
||||
if "description" not in field_schema:
|
||||
try:
|
||||
strings["services"][service_name]["fields"][field_name][
|
||||
"description"
|
||||
]
|
||||
except KeyError:
|
||||
integration.add_error(
|
||||
"services",
|
||||
f"Service {service_name} has a field {field_name} with no description and is not in the translations file",
|
||||
)
|
||||
|
||||
if "selector" in field_schema:
|
||||
with contextlib.suppress(KeyError):
|
||||
translation_key = field_schema["selector"]["select"][
|
||||
"translation_key"
|
||||
]
|
||||
try:
|
||||
strings["selector"][translation_key]
|
||||
except KeyError:
|
||||
integration.add_error(
|
||||
"services",
|
||||
f"Service {service_name} has a field {field_name} with a selector with a translation key {translation_key} that is not in the translations file",
|
||||
)
|
||||
|
||||
|
||||
def validate(integrations: dict[str, Integration], config: Config) -> None:
|
||||
"""Handle dependencies for integrations."""
|
||||
# check services.yaml is cool
|
||||
for integration in integrations.values():
|
||||
validate_services(integration)
|
||||
validate_services(config, integration)
|
||||
|
@ -326,6 +326,20 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema:
|
||||
),
|
||||
slug_validator=cv.slug,
|
||||
),
|
||||
vol.Optional("services"): cv.schema_with_slug_keys(
|
||||
{
|
||||
vol.Required("name"): translation_value_validator,
|
||||
vol.Required("description"): translation_value_validator,
|
||||
vol.Optional("fields"): cv.schema_with_slug_keys(
|
||||
{
|
||||
vol.Required("name"): str,
|
||||
vol.Required("description"): translation_value_validator,
|
||||
},
|
||||
slug_validator=translation_key_validator,
|
||||
),
|
||||
},
|
||||
slug_validator=translation_key_validator,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
"""Test service helpers."""
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Iterable
|
||||
from copy import deepcopy
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
@ -556,13 +558,47 @@ async def test_async_get_all_descriptions(hass: HomeAssistant) -> None:
|
||||
|
||||
logger = hass.components.logger
|
||||
logger_config = {logger.DOMAIN: {}}
|
||||
await async_setup_component(hass, logger.DOMAIN, logger_config)
|
||||
descriptions = await service.async_get_all_descriptions(hass)
|
||||
|
||||
async def async_get_translations(
|
||||
hass: HomeAssistant,
|
||||
language: str,
|
||||
category: str,
|
||||
integrations: Iterable[str] | None = None,
|
||||
config_flow: bool | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Return all backend translations."""
|
||||
translation_key_prefix = f"component.{logger.DOMAIN}.services.set_default_level"
|
||||
return {
|
||||
f"{translation_key_prefix}.name": "Translated name",
|
||||
f"{translation_key_prefix}.description": "Translated description",
|
||||
f"{translation_key_prefix}.fields.level.name": "Field name",
|
||||
f"{translation_key_prefix}.fields.level.description": "Field description",
|
||||
}
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.service.translation.async_get_translations",
|
||||
side_effect=async_get_translations,
|
||||
):
|
||||
await async_setup_component(hass, logger.DOMAIN, logger_config)
|
||||
descriptions = await service.async_get_all_descriptions(hass)
|
||||
|
||||
assert len(descriptions) == 2
|
||||
|
||||
assert "description" in descriptions[logger.DOMAIN]["set_level"]
|
||||
assert "fields" in descriptions[logger.DOMAIN]["set_level"]
|
||||
assert descriptions[logger.DOMAIN]["set_default_level"]["name"] == "Translated name"
|
||||
assert (
|
||||
descriptions[logger.DOMAIN]["set_default_level"]["description"]
|
||||
== "Translated description"
|
||||
)
|
||||
assert (
|
||||
descriptions[logger.DOMAIN]["set_default_level"]["fields"]["level"]["name"]
|
||||
== "Field name"
|
||||
)
|
||||
assert (
|
||||
descriptions[logger.DOMAIN]["set_default_level"]["fields"]["level"][
|
||||
"description"
|
||||
]
|
||||
== "Field description"
|
||||
)
|
||||
|
||||
hass.services.async_register(logger.DOMAIN, "new_service", lambda x: None, None)
|
||||
service.async_set_service_schema(
|
||||
@ -602,7 +638,6 @@ async def test_async_get_all_descriptions(hass: HomeAssistant) -> None:
|
||||
"another_service_with_response",
|
||||
{"description": "response service"},
|
||||
)
|
||||
|
||||
descriptions = await service.async_get_all_descriptions(hass)
|
||||
assert "another_new_service" in descriptions[logger.DOMAIN]
|
||||
assert "service_with_optional_response" in descriptions[logger.DOMAIN]
|
||||
|
Loading…
x
Reference in New Issue
Block a user