Add support for service translations (#95984)
This commit is contained in:
parent
f12f8bca03
commit
f054de0ad5
7 changed files with 483 additions and 119 deletions
|
@ -1,17 +1,11 @@
|
||||||
# Describes the format for available light services
|
# Describes the format for available light services
|
||||||
|
|
||||||
turn_on:
|
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:
|
target:
|
||||||
entity:
|
entity:
|
||||||
domain: light
|
domain: light
|
||||||
fields:
|
fields:
|
||||||
transition:
|
transition:
|
||||||
name: Transition
|
|
||||||
description: Duration it takes to get to next state.
|
|
||||||
filter:
|
filter:
|
||||||
supported_features:
|
supported_features:
|
||||||
- light.LightEntityFeature.TRANSITION
|
- light.LightEntityFeature.TRANSITION
|
||||||
|
@ -21,8 +15,6 @@ turn_on:
|
||||||
max: 300
|
max: 300
|
||||||
unit_of_measurement: seconds
|
unit_of_measurement: seconds
|
||||||
rgb_color:
|
rgb_color:
|
||||||
name: Color
|
|
||||||
description: The color for the light (based on RGB - red, green, blue).
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -34,8 +26,6 @@ turn_on:
|
||||||
selector:
|
selector:
|
||||||
color_rgb:
|
color_rgb:
|
||||||
rgbw_color:
|
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:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -49,8 +39,6 @@ turn_on:
|
||||||
selector:
|
selector:
|
||||||
object:
|
object:
|
||||||
rgbww_color:
|
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:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -64,8 +52,6 @@ turn_on:
|
||||||
selector:
|
selector:
|
||||||
object:
|
object:
|
||||||
color_name:
|
color_name:
|
||||||
name: Color name
|
|
||||||
description: A human readable color name.
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -77,6 +63,7 @@ turn_on:
|
||||||
advanced: true
|
advanced: true
|
||||||
selector:
|
selector:
|
||||||
select:
|
select:
|
||||||
|
translation_key: color_name
|
||||||
options:
|
options:
|
||||||
- "homeassistant"
|
- "homeassistant"
|
||||||
- "aliceblue"
|
- "aliceblue"
|
||||||
|
@ -228,8 +215,6 @@ turn_on:
|
||||||
- "yellow"
|
- "yellow"
|
||||||
- "yellowgreen"
|
- "yellowgreen"
|
||||||
hs_color:
|
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:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -243,8 +228,6 @@ turn_on:
|
||||||
selector:
|
selector:
|
||||||
object:
|
object:
|
||||||
xy_color:
|
xy_color:
|
||||||
name: XY-color
|
|
||||||
description: Color for the light in XY-format.
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -258,8 +241,6 @@ turn_on:
|
||||||
selector:
|
selector:
|
||||||
object:
|
object:
|
||||||
color_temp:
|
color_temp:
|
||||||
name: Color temperature
|
|
||||||
description: Color temperature for the light in mireds.
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -274,8 +255,6 @@ turn_on:
|
||||||
min_mireds: 153
|
min_mireds: 153
|
||||||
max_mireds: 500
|
max_mireds: 500
|
||||||
kelvin:
|
kelvin:
|
||||||
name: Color temperature (Kelvin)
|
|
||||||
description: Color temperature for the light in Kelvin.
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -293,10 +272,6 @@ turn_on:
|
||||||
step: 100
|
step: 100
|
||||||
unit_of_measurement: K
|
unit_of_measurement: K
|
||||||
brightness:
|
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:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -313,10 +288,6 @@ turn_on:
|
||||||
min: 0
|
min: 0
|
||||||
max: 255
|
max: 255
|
||||||
brightness_pct:
|
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:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -333,8 +304,6 @@ turn_on:
|
||||||
max: 100
|
max: 100
|
||||||
unit_of_measurement: "%"
|
unit_of_measurement: "%"
|
||||||
brightness_step:
|
brightness_step:
|
||||||
name: Brightness step value
|
|
||||||
description: Change brightness by an amount.
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -351,8 +320,6 @@ turn_on:
|
||||||
min: -225
|
min: -225
|
||||||
max: 255
|
max: 255
|
||||||
brightness_step_pct:
|
brightness_step_pct:
|
||||||
name: Brightness step
|
|
||||||
description: Change brightness by a percentage.
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -369,8 +336,6 @@ turn_on:
|
||||||
max: 100
|
max: 100
|
||||||
unit_of_measurement: "%"
|
unit_of_measurement: "%"
|
||||||
white:
|
white:
|
||||||
name: White
|
|
||||||
description: Set the light to white mode.
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -381,15 +346,11 @@ turn_on:
|
||||||
value: true
|
value: true
|
||||||
label: Enabled
|
label: Enabled
|
||||||
profile:
|
profile:
|
||||||
name: Profile
|
|
||||||
description: Name of a light profile to use.
|
|
||||||
advanced: true
|
advanced: true
|
||||||
example: relax
|
example: relax
|
||||||
selector:
|
selector:
|
||||||
text:
|
text:
|
||||||
flash:
|
flash:
|
||||||
name: Flash
|
|
||||||
description: If the light should flash.
|
|
||||||
filter:
|
filter:
|
||||||
supported_features:
|
supported_features:
|
||||||
- light.LightEntityFeature.FLASH
|
- light.LightEntityFeature.FLASH
|
||||||
|
@ -402,8 +363,6 @@ turn_on:
|
||||||
- label: "Short"
|
- label: "Short"
|
||||||
value: "short"
|
value: "short"
|
||||||
effect:
|
effect:
|
||||||
name: Effect
|
|
||||||
description: Light effect.
|
|
||||||
filter:
|
filter:
|
||||||
supported_features:
|
supported_features:
|
||||||
- light.LightEntityFeature.EFFECT
|
- light.LightEntityFeature.EFFECT
|
||||||
|
@ -411,15 +370,11 @@ turn_on:
|
||||||
text:
|
text:
|
||||||
|
|
||||||
turn_off:
|
turn_off:
|
||||||
name: Turn off
|
|
||||||
description: Turns off one or more lights.
|
|
||||||
target:
|
target:
|
||||||
entity:
|
entity:
|
||||||
domain: light
|
domain: light
|
||||||
fields:
|
fields:
|
||||||
transition:
|
transition:
|
||||||
name: Transition
|
|
||||||
description: Duration it takes to get to next state.
|
|
||||||
filter:
|
filter:
|
||||||
supported_features:
|
supported_features:
|
||||||
- light.LightEntityFeature.TRANSITION
|
- light.LightEntityFeature.TRANSITION
|
||||||
|
@ -429,8 +384,6 @@ turn_off:
|
||||||
max: 300
|
max: 300
|
||||||
unit_of_measurement: seconds
|
unit_of_measurement: seconds
|
||||||
flash:
|
flash:
|
||||||
name: Flash
|
|
||||||
description: If the light should flash.
|
|
||||||
filter:
|
filter:
|
||||||
supported_features:
|
supported_features:
|
||||||
- light.LightEntityFeature.FLASH
|
- light.LightEntityFeature.FLASH
|
||||||
|
@ -444,17 +397,11 @@ turn_off:
|
||||||
value: "short"
|
value: "short"
|
||||||
|
|
||||||
toggle:
|
toggle:
|
||||||
name: Toggle
|
|
||||||
description: >
|
|
||||||
Toggles one or more lights, from on to off, or, off to on, based on their
|
|
||||||
current state.
|
|
||||||
target:
|
target:
|
||||||
entity:
|
entity:
|
||||||
domain: light
|
domain: light
|
||||||
fields:
|
fields:
|
||||||
transition:
|
transition:
|
||||||
name: Transition
|
|
||||||
description: Duration it takes to get to next state.
|
|
||||||
filter:
|
filter:
|
||||||
supported_features:
|
supported_features:
|
||||||
- light.LightEntityFeature.TRANSITION
|
- light.LightEntityFeature.TRANSITION
|
||||||
|
@ -464,8 +411,6 @@ toggle:
|
||||||
max: 300
|
max: 300
|
||||||
unit_of_measurement: seconds
|
unit_of_measurement: seconds
|
||||||
rgb_color:
|
rgb_color:
|
||||||
name: RGB-color
|
|
||||||
description: Color for the light in RGB-format.
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -479,8 +424,6 @@ toggle:
|
||||||
selector:
|
selector:
|
||||||
object:
|
object:
|
||||||
color_name:
|
color_name:
|
||||||
name: Color name
|
|
||||||
description: A human readable color name.
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -492,6 +435,7 @@ toggle:
|
||||||
advanced: true
|
advanced: true
|
||||||
selector:
|
selector:
|
||||||
select:
|
select:
|
||||||
|
translation_key: color_name
|
||||||
options:
|
options:
|
||||||
- "homeassistant"
|
- "homeassistant"
|
||||||
- "aliceblue"
|
- "aliceblue"
|
||||||
|
@ -643,8 +587,6 @@ toggle:
|
||||||
- "yellow"
|
- "yellow"
|
||||||
- "yellowgreen"
|
- "yellowgreen"
|
||||||
hs_color:
|
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:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -658,8 +600,6 @@ toggle:
|
||||||
selector:
|
selector:
|
||||||
object:
|
object:
|
||||||
xy_color:
|
xy_color:
|
||||||
name: XY-color
|
|
||||||
description: Color for the light in XY-format.
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -673,8 +613,6 @@ toggle:
|
||||||
selector:
|
selector:
|
||||||
object:
|
object:
|
||||||
color_temp:
|
color_temp:
|
||||||
name: Color temperature (mireds)
|
|
||||||
description: Color temperature for the light in mireds.
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -688,8 +626,6 @@ toggle:
|
||||||
selector:
|
selector:
|
||||||
color_temp:
|
color_temp:
|
||||||
kelvin:
|
kelvin:
|
||||||
name: Color temperature (Kelvin)
|
|
||||||
description: Color temperature for the light in Kelvin.
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -707,10 +643,6 @@ toggle:
|
||||||
step: 100
|
step: 100
|
||||||
unit_of_measurement: K
|
unit_of_measurement: K
|
||||||
brightness:
|
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:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -727,10 +659,6 @@ toggle:
|
||||||
min: 0
|
min: 0
|
||||||
max: 255
|
max: 255
|
||||||
brightness_pct:
|
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:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -747,8 +675,6 @@ toggle:
|
||||||
max: 100
|
max: 100
|
||||||
unit_of_measurement: "%"
|
unit_of_measurement: "%"
|
||||||
white:
|
white:
|
||||||
name: White
|
|
||||||
description: Set the light to white mode.
|
|
||||||
filter:
|
filter:
|
||||||
attribute:
|
attribute:
|
||||||
supported_color_modes:
|
supported_color_modes:
|
||||||
|
@ -759,15 +685,11 @@ toggle:
|
||||||
value: true
|
value: true
|
||||||
label: Enabled
|
label: Enabled
|
||||||
profile:
|
profile:
|
||||||
name: Profile
|
|
||||||
description: Name of a light profile to use.
|
|
||||||
advanced: true
|
advanced: true
|
||||||
example: relax
|
example: relax
|
||||||
selector:
|
selector:
|
||||||
text:
|
text:
|
||||||
flash:
|
flash:
|
||||||
name: Flash
|
|
||||||
description: If the light should flash.
|
|
||||||
filter:
|
filter:
|
||||||
supported_features:
|
supported_features:
|
||||||
- light.LightEntityFeature.FLASH
|
- light.LightEntityFeature.FLASH
|
||||||
|
@ -780,8 +702,6 @@ toggle:
|
||||||
- label: "Short"
|
- label: "Short"
|
||||||
value: "short"
|
value: "short"
|
||||||
effect:
|
effect:
|
||||||
name: Effect
|
|
||||||
description: Light effect.
|
|
||||||
filter:
|
filter:
|
||||||
supported_features:
|
supported_features:
|
||||||
- light.LightEntityFeature.EFFECT
|
- 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,
|
device_registry,
|
||||||
entity_registry,
|
entity_registry,
|
||||||
template,
|
template,
|
||||||
|
translation,
|
||||||
)
|
)
|
||||||
from .selector import TargetSelector
|
from .selector import TargetSelector
|
||||||
from .typing import ConfigType, TemplateVarsType
|
from .typing import ConfigType, TemplateVarsType
|
||||||
|
@ -607,6 +608,11 @@ async def async_get_all_descriptions(
|
||||||
)
|
)
|
||||||
loaded = dict(zip(missing, contents))
|
loaded = dict(zip(missing, contents))
|
||||||
|
|
||||||
|
# Load translations for all service domains
|
||||||
|
translations = await translation.async_get_translations(
|
||||||
|
hass, "en", "services", list(services)
|
||||||
|
)
|
||||||
|
|
||||||
# Build response
|
# Build response
|
||||||
descriptions: dict[str, dict[str, Any]] = {}
|
descriptions: dict[str, dict[str, Any]] = {}
|
||||||
for domain, services_map in services.items():
|
for domain, services_map in services.items():
|
||||||
|
@ -616,8 +622,11 @@ async def async_get_all_descriptions(
|
||||||
for service_name in services_map:
|
for service_name in services_map:
|
||||||
cache_key = (domain, service_name)
|
cache_key = (domain, service_name)
|
||||||
description = descriptions_cache.get(cache_key)
|
description = descriptions_cache.get(cache_key)
|
||||||
|
if description is not None:
|
||||||
|
domain_descriptions[service_name] = description
|
||||||
|
continue
|
||||||
|
|
||||||
# Cache missing descriptions
|
# Cache missing descriptions
|
||||||
if description is None:
|
|
||||||
domain_yaml = loaded.get(domain) or {}
|
domain_yaml = loaded.get(domain) or {}
|
||||||
# The YAML may be empty for dynamically defined
|
# The YAML may be empty for dynamically defined
|
||||||
# services (ie shell_command) that never call
|
# services (ie shell_command) that never call
|
||||||
|
@ -630,12 +639,34 @@ async def async_get_all_descriptions(
|
||||||
|
|
||||||
# Don't warn for missing services, because it triggers false
|
# Don't warn for missing services, because it triggers false
|
||||||
# positives for things like scripts, that register as a service
|
# 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 = {
|
description = {
|
||||||
"name": yaml_description.get("name", ""),
|
"name": translations.get(
|
||||||
"description": yaml_description.get("description", ""),
|
f"component.{domain}.services.{service_name}.name",
|
||||||
"fields": yaml_description.get("fields", {}),
|
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:
|
if "target" in yaml_description:
|
||||||
description["target"] = yaml_description["target"]
|
description["target"] = yaml_description["target"]
|
||||||
|
|
||||||
|
|
|
@ -302,7 +302,7 @@ async def async_get_translations(
|
||||||
components = set(integrations)
|
components = set(integrations)
|
||||||
elif config_flow:
|
elif config_flow:
|
||||||
components = (await async_get_config_flows(hass)) - hass.config.components
|
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)
|
components = set(hass.config.components)
|
||||||
else:
|
else:
|
||||||
# Only 'state' supports merging, so remove platforms from selection
|
# Only 'state' supports merging, so remove platforms from selection
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
"""Validate dependencies."""
|
"""Validate dependencies."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import json
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
@ -25,7 +27,7 @@ def exists(value: Any) -> Any:
|
||||||
|
|
||||||
FIELD_SCHEMA = vol.Schema(
|
FIELD_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required("description"): str,
|
vol.Optional("description"): str,
|
||||||
vol.Optional("name"): str,
|
vol.Optional("name"): str,
|
||||||
vol.Optional("example"): exists,
|
vol.Optional("example"): exists,
|
||||||
vol.Optional("default"): exists,
|
vol.Optional("default"): exists,
|
||||||
|
@ -46,7 +48,7 @@ FIELD_SCHEMA = vol.Schema(
|
||||||
|
|
||||||
SERVICE_SCHEMA = vol.Schema(
|
SERVICE_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required("description"): str,
|
vol.Optional("description"): str,
|
||||||
vol.Optional("name"): str,
|
vol.Optional("name"): str,
|
||||||
vol.Optional("target"): vol.Any(selector.TargetSelector.CONFIG_SCHEMA, None),
|
vol.Optional("target"): vol.Any(selector.TargetSelector.CONFIG_SCHEMA, None),
|
||||||
vol.Optional("fields"): vol.Schema({str: FIELD_SCHEMA}),
|
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
|
return False
|
||||||
|
|
||||||
|
|
||||||
def validate_services(integration: Integration) -> None:
|
def validate_services(config: Config, integration: Integration) -> None:
|
||||||
"""Validate services."""
|
"""Validate services."""
|
||||||
try:
|
try:
|
||||||
data = load_yaml(str(integration.path / "services.yaml"))
|
data = load_yaml(str(integration.path / "services.yaml"))
|
||||||
|
@ -92,15 +94,75 @@ def validate_services(integration: Integration) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
SERVICES_SCHEMA(data)
|
services = SERVICES_SCHEMA(data)
|
||||||
except vol.Invalid as err:
|
except vol.Invalid as err:
|
||||||
integration.add_error(
|
integration.add_error(
|
||||||
"services", f"Invalid services.yaml: {humanize_error(data, err)}"
|
"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:
|
def validate(integrations: dict[str, Integration], config: Config) -> None:
|
||||||
"""Handle dependencies for integrations."""
|
"""Handle dependencies for integrations."""
|
||||||
# check services.yaml is cool
|
# check services.yaml is cool
|
||||||
for integration in integrations.values():
|
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,
|
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."""
|
"""Test service helpers."""
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from collections.abc import Iterable
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from typing import Any
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -556,13 +558,47 @@ async def test_async_get_all_descriptions(hass: HomeAssistant) -> None:
|
||||||
|
|
||||||
logger = hass.components.logger
|
logger = hass.components.logger
|
||||||
logger_config = {logger.DOMAIN: {}}
|
logger_config = {logger.DOMAIN: {}}
|
||||||
|
|
||||||
|
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)
|
await async_setup_component(hass, logger.DOMAIN, logger_config)
|
||||||
descriptions = await service.async_get_all_descriptions(hass)
|
descriptions = await service.async_get_all_descriptions(hass)
|
||||||
|
|
||||||
assert len(descriptions) == 2
|
assert len(descriptions) == 2
|
||||||
|
|
||||||
assert "description" in descriptions[logger.DOMAIN]["set_level"]
|
assert descriptions[logger.DOMAIN]["set_default_level"]["name"] == "Translated name"
|
||||||
assert "fields" in descriptions[logger.DOMAIN]["set_level"]
|
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)
|
hass.services.async_register(logger.DOMAIN, "new_service", lambda x: None, None)
|
||||||
service.async_set_service_schema(
|
service.async_set_service_schema(
|
||||||
|
@ -602,7 +638,6 @@ async def test_async_get_all_descriptions(hass: HomeAssistant) -> None:
|
||||||
"another_service_with_response",
|
"another_service_with_response",
|
||||||
{"description": "response service"},
|
{"description": "response service"},
|
||||||
)
|
)
|
||||||
|
|
||||||
descriptions = await service.async_get_all_descriptions(hass)
|
descriptions = await service.async_get_all_descriptions(hass)
|
||||||
assert "another_new_service" in descriptions[logger.DOMAIN]
|
assert "another_new_service" in descriptions[logger.DOMAIN]
|
||||||
assert "service_with_optional_response" in descriptions[logger.DOMAIN]
|
assert "service_with_optional_response" in descriptions[logger.DOMAIN]
|
||||||
|
|
Loading…
Add table
Reference in a new issue