mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 20:27:08 +00:00
Import state strings (#34451)
This commit is contained in:
parent
095d2718f3
commit
d560e8245a
@ -22,5 +22,19 @@
|
|||||||
"armed_away": "{entity_name} armed away",
|
"armed_away": "{entity_name} armed away",
|
||||||
"armed_night": "{entity_name} armed night"
|
"armed_night": "{entity_name} armed night"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"armed": "Armed",
|
||||||
|
"disarmed": "Disarmed",
|
||||||
|
"armed_home": "Armed home",
|
||||||
|
"armed_away": "Armed away",
|
||||||
|
"armed_night": "Armed night",
|
||||||
|
"armed_custom_bypass": "Armed custom bypass",
|
||||||
|
"pending": "Pending",
|
||||||
|
"arming": "Arming",
|
||||||
|
"disarming": "Disarming",
|
||||||
|
"triggered": "Triggered"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1,9 @@
|
|||||||
{ "title": "Automation" }
|
{
|
||||||
|
"title": "Automation",
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on": "[%key:common::state::on%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -89,5 +89,87 @@
|
|||||||
"turned_on": "{entity_name} turned on",
|
"turned_on": "{entity_name} turned on",
|
||||||
"turned_off": "{entity_name} turned off"
|
"turned_off": "{entity_name} turned off"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"battery": {
|
||||||
|
"off": "Normal",
|
||||||
|
"on": "Low"
|
||||||
|
},
|
||||||
|
"cold": {
|
||||||
|
"off": "[%key:component::binary_sensor::state::battery::off%]",
|
||||||
|
"on": "Cold"
|
||||||
|
},
|
||||||
|
"connectivity": {
|
||||||
|
"off": "[%key:common::state::disconnected%]",
|
||||||
|
"on": "[%key:common::state::connected%]"
|
||||||
|
},
|
||||||
|
"door": {
|
||||||
|
"off": "[%key:common::state::closed%]",
|
||||||
|
"on": "[%key:common::state::open%]"
|
||||||
|
},
|
||||||
|
"garage_door": {
|
||||||
|
"off": "[%key:common::state::closed%]",
|
||||||
|
"on": "[%key:common::state::open%]"
|
||||||
|
},
|
||||||
|
"gas": {
|
||||||
|
"off": "Clear",
|
||||||
|
"on": "Detected"
|
||||||
|
},
|
||||||
|
"heat": {
|
||||||
|
"off": "[%key:component::binary_sensor::state::battery::off%]",
|
||||||
|
"on": "Hot"
|
||||||
|
},
|
||||||
|
"lock": {
|
||||||
|
"off": "[%key:common::state::locked%]",
|
||||||
|
"on": "[%key:common::state::unlocked%]"
|
||||||
|
},
|
||||||
|
"moisture": {
|
||||||
|
"off": "Dry",
|
||||||
|
"on": "Wet"
|
||||||
|
},
|
||||||
|
"motion": {
|
||||||
|
"off": "[%key:component::binary_sensor::state::gas::off%]",
|
||||||
|
"on": "[%key:component::binary_sensor::state::gas::on%]"
|
||||||
|
},
|
||||||
|
"occupancy": {
|
||||||
|
"off": "[%key:component::binary_sensor::state::gas::off%]",
|
||||||
|
"on": "[%key:component::binary_sensor::state::gas::on%]"
|
||||||
|
},
|
||||||
|
"opening": {
|
||||||
|
"off": "[%key:common::state::closed%]",
|
||||||
|
"on": "[%key:common::state::open%]"
|
||||||
|
},
|
||||||
|
"presence": {
|
||||||
|
"off": "[%key:component::device_tracker::state::not_home%]",
|
||||||
|
"on": "[%key:component::device_tracker::state::home%]"
|
||||||
|
},
|
||||||
|
"problem": {
|
||||||
|
"off": "OK",
|
||||||
|
"on": "Problem"
|
||||||
|
},
|
||||||
|
"safety": {
|
||||||
|
"off": "Safe",
|
||||||
|
"on": "Unsafe"
|
||||||
|
},
|
||||||
|
"smoke": {
|
||||||
|
"off": "[%key:component::binary_sensor::state::gas::off%]",
|
||||||
|
"on": "[%key:component::binary_sensor::state::gas::on%]"
|
||||||
|
},
|
||||||
|
"sound": {
|
||||||
|
"off": "[%key:component::binary_sensor::state::gas::off%]",
|
||||||
|
"on": "[%key:component::binary_sensor::state::gas::on%]"
|
||||||
|
},
|
||||||
|
"vibration": {
|
||||||
|
"off": "[%key:component::binary_sensor::state::gas::off%]",
|
||||||
|
"on": "[%key:component::binary_sensor::state::gas::on%]"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"off": "[%key:common::state::closed%]",
|
||||||
|
"on": "[%key:common::state::open%]"
|
||||||
|
},
|
||||||
|
"_": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on": "[%key:common::state::on%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1,9 @@
|
|||||||
{ "title": "Calendar" }
|
{
|
||||||
|
"title": "Calendar",
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on": "[%key:common::state::on%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1 +1,10 @@
|
|||||||
{ "title": "Camera" }
|
{
|
||||||
|
"title": "Camera",
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"recording": "Recording",
|
||||||
|
"streaming": "Streaming",
|
||||||
|
"idle": "[%key:common::state::idle%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,5 +14,16 @@
|
|||||||
"set_hvac_mode": "Change HVAC mode on {entity_name}",
|
"set_hvac_mode": "Change HVAC mode on {entity_name}",
|
||||||
"set_preset_mode": "Change preset on {entity_name}"
|
"set_preset_mode": "Change preset on {entity_name}"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"heat": "Heat",
|
||||||
|
"cool": "Cool",
|
||||||
|
"heat_cool": "Heat/Cool",
|
||||||
|
"auto": "Auto",
|
||||||
|
"dry": "Dry",
|
||||||
|
"fan_only": "Fan only"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1,9 @@
|
|||||||
{ "title": "Configurator" }
|
{
|
||||||
|
"title": "Configurator",
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"configure": "Configure",
|
||||||
|
"configured": "Configured"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -25,5 +25,14 @@
|
|||||||
"position": "{entity_name} position changes",
|
"position": "{entity_name} position changes",
|
||||||
"tilt_position": "{entity_name} tilt position changes"
|
"tilt_position": "{entity_name} tilt position changes"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"open": "[%key:common::state::open%]",
|
||||||
|
"opening": "Opening",
|
||||||
|
"closed": "[%key:common::state::closed%]",
|
||||||
|
"closing": "Closing",
|
||||||
|
"stopped": "Stopped"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,11 @@
|
|||||||
"is_home": "{entity_name} is home",
|
"is_home": "{entity_name} is home",
|
||||||
"is_not_home": "{entity_name} is not home"
|
"is_not_home": "{entity_name} is not home"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"home": "[%key:common::state::home%]",
|
||||||
|
"not_home": "[%key:common::state::not_home%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,5 +13,11 @@
|
|||||||
"turn_on": "Turn on {entity_name}",
|
"turn_on": "Turn on {entity_name}",
|
||||||
"turn_off": "Turn off {entity_name}"
|
"turn_off": "Turn off {entity_name}"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on": "[%key:common::state::on%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1,17 @@
|
|||||||
{ "title": "Group" }
|
{
|
||||||
|
"title": "Group",
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on": "[%key:common::state::on%]",
|
||||||
|
"home": "[%key:component::device_tracker::state::home%]",
|
||||||
|
"not_home": "[%key:component::device_tracker::state::not_home%]",
|
||||||
|
"open": "[%key:common::state::open%]",
|
||||||
|
"closed": "[%key:common::state::closed%]",
|
||||||
|
"locked": "[%key:common::state::locked%]",
|
||||||
|
"unlocked": "[%key:common::state::unlocked%]",
|
||||||
|
"ok": "[%key:component::binary_sensor::state::problem::off%]",
|
||||||
|
"problem": "[%key:component::binary_sensor::state::problem::on%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1 +1,9 @@
|
|||||||
{ "title": "Input boolean" }
|
{
|
||||||
|
"title": "Input boolean",
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on": "[%key:common::state::on%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,5 +17,11 @@
|
|||||||
"turned_on": "{entity_name} turned on",
|
"turned_on": "{entity_name} turned on",
|
||||||
"turned_off": "{entity_name} turned off"
|
"turned_off": "{entity_name} turned off"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on": "[%key:common::state::on%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,5 +14,11 @@
|
|||||||
"locked": "{entity_name} locked",
|
"locked": "{entity_name} locked",
|
||||||
"unlocked": "{entity_name} unlocked"
|
"unlocked": "{entity_name} unlocked"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"locked": "[%key:common::state::locked%]",
|
||||||
|
"unlocked": "[%key:common::state::unlocked%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,5 +8,15 @@
|
|||||||
"is_paused": "{entity_name} is paused",
|
"is_paused": "{entity_name} is paused",
|
||||||
"is_playing": "{entity_name} is playing"
|
"is_playing": "{entity_name} is playing"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on": "[%key:common::state::on%]",
|
||||||
|
"playing": "Playing",
|
||||||
|
"paused": "[%key:common::state::paused%]",
|
||||||
|
"idle": "[%key:common::state::idle%]",
|
||||||
|
"standby": "[%key:common::state::standby%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1,9 @@
|
|||||||
{ "title": "Person" }
|
{
|
||||||
|
"title": "Person",
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"home": "[%key:common::state::home%]",
|
||||||
|
"not_home": "[%key:common::state::not_home%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1 +1,9 @@
|
|||||||
{ "title": "Plant Monitor" }
|
{
|
||||||
|
"title": "Plant Monitor",
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"ok": "[%key:component::binary_sensor::state::problem::off%]",
|
||||||
|
"problem": "[%key:component::binary_sensor::state::problem::on%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1 +1,9 @@
|
|||||||
{ "title": "Remote" }
|
{
|
||||||
|
"title": "Remote",
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on": "[%key:common::state::on%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1 +1,9 @@
|
|||||||
{ "title": "Script" }
|
{
|
||||||
|
"title": "Script",
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on": "[%key:common::state::on%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,5 +23,11 @@
|
|||||||
"timestamp": "{entity_name} timestamp changes",
|
"timestamp": "{entity_name} timestamp changes",
|
||||||
"value": "{entity_name} value changes"
|
"value": "{entity_name} value changes"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on": "[%key:common::state::on%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1,9 @@
|
|||||||
{ "title": "Sun" }
|
{
|
||||||
|
"title": "Sun",
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"above_horizon": "Above horizon",
|
||||||
|
"below_horizon": "Below horizon"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,5 +14,11 @@
|
|||||||
"turned_on": "{entity_name} turned on",
|
"turned_on": "{entity_name} turned on",
|
||||||
"turned_off": "{entity_name} turned off"
|
"turned_off": "{entity_name} turned off"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on": "[%key:common::state::on%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
homeassistant/components/timer/strings.json
Normal file
9
homeassistant/components/timer/strings.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"active": "[%key:common::state::active%]",
|
||||||
|
"idle": "[%key:common::state::idle%]",
|
||||||
|
"paused": "[%key:common::state::paused%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,5 +13,17 @@
|
|||||||
"clean": "Let {entity_name} clean",
|
"clean": "Let {entity_name} clean",
|
||||||
"dock": "Let {entity_name} return to the dock"
|
"dock": "Let {entity_name} return to the dock"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"cleaning": "Cleaning",
|
||||||
|
"docked": "Docked",
|
||||||
|
"error": "Error",
|
||||||
|
"idle": "[%key:common::state::idle%]",
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on": "[%key:common::state::on%]",
|
||||||
|
"paused": "[%key:common::state::paused%]",
|
||||||
|
"returning": "Returning to dock"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
21
homeassistant/components/weather/strings.json
Normal file
21
homeassistant/components/weather/strings.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"_": {
|
||||||
|
"clear-night": "Clear, night",
|
||||||
|
"cloudy": "Cloudy",
|
||||||
|
"exceptional": "Exceptional",
|
||||||
|
"fog": "Fog",
|
||||||
|
"hail": "Hail",
|
||||||
|
"lightning": "Lightning",
|
||||||
|
"lightning-rainy": "Lightning, rainy",
|
||||||
|
"partlycloudy": "Partly cloudy",
|
||||||
|
"pouring": "Pouring",
|
||||||
|
"rainy": "Rainy",
|
||||||
|
"snowy": "Snowy",
|
||||||
|
"snowy-rainy": "Snowy, rainy",
|
||||||
|
"sunny": "Sunny",
|
||||||
|
"windy": "Windy",
|
||||||
|
"windy-variant": "Windy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,5 +17,17 @@
|
|||||||
"already_configured": "Z-Wave is already configured",
|
"already_configured": "Z-Wave is already configured",
|
||||||
"one_instance_only": "Component only supports one Z-Wave instance"
|
"one_instance_only": "Component only supports one Z-Wave instance"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"query_stage": {
|
||||||
|
"initializing": "[%key:component::zwave::state::_::initializing%]",
|
||||||
|
"dead": "[%key:component::zwave::state::_::dead%]"
|
||||||
|
},
|
||||||
|
"_": {
|
||||||
|
"initializing": "Initializing",
|
||||||
|
"dead": "Dead",
|
||||||
|
"sleeping": "Sleeping",
|
||||||
|
"ready": "Ready"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
homeassistant/strings.json
Normal file
20
homeassistant/strings.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"state": {
|
||||||
|
"off": "Off",
|
||||||
|
"on": "On",
|
||||||
|
"open": "Open",
|
||||||
|
"closed": "Closed",
|
||||||
|
"connected": "Connected",
|
||||||
|
"disconnected": "Disconnected",
|
||||||
|
"locked": "Locked",
|
||||||
|
"unlocked": "Unlocked",
|
||||||
|
"active": "Active",
|
||||||
|
"idle": "Idle",
|
||||||
|
"standby": "Standby",
|
||||||
|
"paused": "Paused",
|
||||||
|
"home": "Home",
|
||||||
|
"not_home": "Away"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,10 @@
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
|
from script.translations import upload
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from voluptuous.humanize import humanize_error
|
from voluptuous.humanize import humanize_error
|
||||||
|
|
||||||
@ -18,6 +20,8 @@ UNDEFINED = 0
|
|||||||
REQUIRED = 1
|
REQUIRED = 1
|
||||||
REMOVED = 2
|
REMOVED = 2
|
||||||
|
|
||||||
|
RE_REFERENCE = r"\[\%key:(.+)\%\]"
|
||||||
|
|
||||||
REMOVED_TITLE_MSG = (
|
REMOVED_TITLE_MSG = (
|
||||||
"config.title key has been moved out of config and into the root of strings.json. "
|
"config.title key has been moved out of config and into the root of strings.json. "
|
||||||
"Starting Home Assistant 0.109 you only need to define this key in the root "
|
"Starting Home Assistant 0.109 you only need to define this key in the root "
|
||||||
@ -26,6 +30,19 @@ REMOVED_TITLE_MSG = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def find_references(strings, prefix, found):
|
||||||
|
"""Find references."""
|
||||||
|
for key, value in strings.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
find_references(value, f"{prefix}::{key}", found)
|
||||||
|
continue
|
||||||
|
|
||||||
|
match = re.match(RE_REFERENCE, value)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
found.append({"source": f"{prefix}::{key}", "ref": match.groups()[0]})
|
||||||
|
|
||||||
|
|
||||||
def removed_title_validator(config, integration, value):
|
def removed_title_validator(config, integration, value):
|
||||||
"""Mark removed title."""
|
"""Mark removed title."""
|
||||||
if not config.specific_integrations:
|
if not config.specific_integrations:
|
||||||
@ -36,6 +53,14 @@ def removed_title_validator(config, integration, value):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def lowercase_validator(value):
|
||||||
|
"""Validate value is lowercase."""
|
||||||
|
if value.lower() != value:
|
||||||
|
raise vol.Invalid("Needs to be lowercase")
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def gen_data_entry_schema(
|
def gen_data_entry_schema(
|
||||||
*,
|
*,
|
||||||
config: Config,
|
config: Config,
|
||||||
@ -92,7 +117,8 @@ def gen_strings_schema(config: Config, integration: Integration):
|
|||||||
vol.Optional("trigger_subtype"): {str: str},
|
vol.Optional("trigger_subtype"): {str: str},
|
||||||
},
|
},
|
||||||
vol.Optional("state"): cv.schema_with_slug_keys(
|
vol.Optional("state"): cv.schema_with_slug_keys(
|
||||||
cv.schema_with_slug_keys(str)
|
cv.schema_with_slug_keys(str, slug_validator=lowercase_validator),
|
||||||
|
slug_validator=vol.Any("_", cv.slug),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -115,10 +141,23 @@ def gen_auth_schema(config: Config, integration: Integration):
|
|||||||
|
|
||||||
|
|
||||||
def gen_platform_strings_schema(config: Config, integration: Integration):
|
def gen_platform_strings_schema(config: Config, integration: Integration):
|
||||||
"""Generate platform strings schema like strings.sensor.json."""
|
"""Generate platform strings schema like strings.sensor.json.
|
||||||
|
|
||||||
|
Example of valid data:
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"moon__phase": {
|
||||||
|
"full": "Full"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
def device_class_validator(value):
|
def device_class_validator(value):
|
||||||
"""Key validator."""
|
"""Key validator for platorm states.
|
||||||
|
|
||||||
|
Platform states are only allowed to provide states for device classes they prefix.
|
||||||
|
"""
|
||||||
if not value.startswith(f"{integration.domain}__"):
|
if not value.startswith(f"{integration.domain}__"):
|
||||||
raise vol.Invalid(
|
raise vol.Invalid(
|
||||||
f"Device class need to start with '{integration.domain}__'. Key {value} is invalid"
|
f"Device class need to start with '{integration.domain}__'. Key {value} is invalid"
|
||||||
@ -128,14 +167,17 @@ def gen_platform_strings_schema(config: Config, integration: Integration):
|
|||||||
slugged = slugify(slug_friendly)
|
slugged = slugify(slug_friendly)
|
||||||
|
|
||||||
if slug_friendly != slugged:
|
if slug_friendly != slugged:
|
||||||
raise vol.Invalid(f"invalid device class {value}")
|
raise vol.Invalid(
|
||||||
|
f"invalid device class {value}. After domain__, needs to be all lowercase, no spaces."
|
||||||
|
)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
return vol.Schema(
|
return vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional("state"): cv.schema_with_slug_keys(
|
vol.Optional("state"): cv.schema_with_slug_keys(
|
||||||
cv.schema_with_slug_keys(str), slug_validator=device_class_validator
|
cv.schema_with_slug_keys(str, slug_validator=lowercase_validator),
|
||||||
|
slug_validator=device_class_validator,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -144,9 +186,10 @@ def gen_platform_strings_schema(config: Config, integration: Integration):
|
|||||||
ONBOARDING_SCHEMA = vol.Schema({vol.Required("area"): {str: str}})
|
ONBOARDING_SCHEMA = vol.Schema({vol.Required("area"): {str: str}})
|
||||||
|
|
||||||
|
|
||||||
def validate_translation_file(config: Config, integration: Integration):
|
def validate_translation_file(config: Config, integration: Integration, all_strings):
|
||||||
"""Validate translation files for integration."""
|
"""Validate translation files for integration."""
|
||||||
strings_file = integration.path / "strings.json"
|
strings_file = integration.path / "strings.json"
|
||||||
|
references = []
|
||||||
|
|
||||||
if strings_file.is_file():
|
if strings_file.is_file():
|
||||||
strings = json.loads(strings_file.read_text())
|
strings = json.loads(strings_file.read_text())
|
||||||
@ -164,6 +207,8 @@ def validate_translation_file(config: Config, integration: Integration):
|
|||||||
integration.add_error(
|
integration.add_error(
|
||||||
"translations", f"Invalid strings.json: {humanize_error(strings, err)}"
|
"translations", f"Invalid strings.json: {humanize_error(strings, err)}"
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
find_references(strings, "strings.json", references)
|
||||||
|
|
||||||
for path in integration.path.glob("strings.*.json"):
|
for path in integration.path.glob("strings.*.json"):
|
||||||
strings = json.loads(path.read_text())
|
strings = json.loads(path.read_text())
|
||||||
@ -177,9 +222,35 @@ def validate_translation_file(config: Config, integration: Integration):
|
|||||||
integration.add_warning("translations", msg)
|
integration.add_warning("translations", msg)
|
||||||
else:
|
else:
|
||||||
integration.add_error("translations", msg)
|
integration.add_error("translations", msg)
|
||||||
|
else:
|
||||||
|
find_references(strings, path.name, references)
|
||||||
|
|
||||||
|
if config.specific_integrations:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Validate references
|
||||||
|
for reference in references:
|
||||||
|
parts = reference["ref"].split("::")
|
||||||
|
search = all_strings
|
||||||
|
key = parts.pop(0)
|
||||||
|
while parts and key in search:
|
||||||
|
search = search[key]
|
||||||
|
key = parts.pop(0)
|
||||||
|
|
||||||
|
if parts:
|
||||||
|
print(key, list(search))
|
||||||
|
integration.add_error(
|
||||||
|
"translations",
|
||||||
|
f"{reference['source']} contains invalid reference {reference['ref']}: Could not find {key}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate(integrations: Dict[str, Integration], config: Config):
|
def validate(integrations: Dict[str, Integration], config: Config):
|
||||||
"""Handle JSON files inside integrations."""
|
"""Handle JSON files inside integrations."""
|
||||||
|
if config.specific_integrations:
|
||||||
|
all_strings = None
|
||||||
|
else:
|
||||||
|
all_strings = upload.generate_upload_data()
|
||||||
|
|
||||||
for integration in integrations.values():
|
for integration in integrations.values():
|
||||||
validate_translation_file(config, integration)
|
validate_translation_file(config, integration, all_strings)
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
"""Migrate things."""
|
"""Migrate things."""
|
||||||
import json
|
import json
|
||||||
|
import pathlib
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
import re
|
||||||
|
|
||||||
from .const import CORE_PROJECT_ID, FRONTEND_PROJECT_ID, INTEGRATIONS_DIR
|
from .const import CORE_PROJECT_ID, FRONTEND_PROJECT_ID, INTEGRATIONS_DIR
|
||||||
from .lokalise import get_api
|
from .lokalise import get_api
|
||||||
|
|
||||||
|
FRONTEND_REPO = pathlib.Path("../frontend/")
|
||||||
|
|
||||||
|
|
||||||
def create_lookup(results):
|
def create_lookup(results):
|
||||||
"""Create a lookup table by key name."""
|
"""Create a lookup table by key name."""
|
||||||
@ -47,30 +51,53 @@ def rename_keys(project_id, to_migrate):
|
|||||||
pprint(lokalise.keys_bulk_update(updates))
|
pprint(lokalise.keys_bulk_update(updates))
|
||||||
|
|
||||||
|
|
||||||
|
def list_keys_helper(lokalise, keys, params={}, *, validate=True):
|
||||||
|
"""List keys in chunks so it doesn't exceed max URL length."""
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for i in range(0, len(keys), 100):
|
||||||
|
filter_keys = keys[i : i + 100]
|
||||||
|
from_key_data = lokalise.keys_list(
|
||||||
|
{
|
||||||
|
**params,
|
||||||
|
"filter_keys": ",".join(filter_keys),
|
||||||
|
"limit": len(filter_keys) + 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(from_key_data) == len(filter_keys) or not validate:
|
||||||
|
results.extend(from_key_data)
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"Lookin up keys in Lokalise returns {len(from_key_data)} results, expected {len(keys)}"
|
||||||
|
)
|
||||||
|
searched = set(filter_keys)
|
||||||
|
returned = set(create_lookup(from_key_data))
|
||||||
|
print("Not found:", ", ".join(searched - returned))
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
def migrate_project_keys_translations(from_project_id, to_project_id, to_migrate):
|
def migrate_project_keys_translations(from_project_id, to_project_id, to_migrate):
|
||||||
"""Migrate keys and translations from one project to another.
|
"""Migrate keys and translations from one project to another.
|
||||||
|
|
||||||
to_migrate is Dict[from_key] = to_key.
|
to_migrate is Dict[from_key] = to_key.
|
||||||
"""
|
"""
|
||||||
from_lokalise = get_api(from_project_id)
|
from_lokalise = get_api(from_project_id)
|
||||||
to_lokalise = get_api(to_project_id, True)
|
to_lokalise = get_api(to_project_id)
|
||||||
|
|
||||||
from_key_data = from_lokalise.keys_list(
|
|
||||||
{"filter_keys": ",".join(to_migrate), "include_translations": 1}
|
|
||||||
)
|
|
||||||
if len(from_key_data) != len(to_migrate):
|
|
||||||
print(
|
|
||||||
f"Lookin up keys in Lokalise returns {len(from_key_data)} results, expected {len(to_migrate)}"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
from_key_lookup = create_lookup(from_key_data)
|
|
||||||
|
|
||||||
# Fetch keys in target
|
# Fetch keys in target
|
||||||
# We are going to skip migrating existing keys
|
# We are going to skip migrating existing keys
|
||||||
to_key_data = to_lokalise.keys_list(
|
print("Checking which target keys exist..")
|
||||||
{"filter_keys": ",".join(to_migrate.values()), "include_translations": 1}
|
try:
|
||||||
)
|
to_key_data = list_keys_helper(
|
||||||
|
to_lokalise, list(to_migrate.values()), validate=False
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
return
|
||||||
|
|
||||||
existing = set(create_lookup(to_key_data))
|
existing = set(create_lookup(to_key_data))
|
||||||
|
|
||||||
missing = [key for key in to_migrate.values() if key not in existing]
|
missing = [key for key in to_migrate.values() if key not in existing]
|
||||||
@ -79,6 +106,19 @@ def migrate_project_keys_translations(from_project_id, to_project_id, to_migrate
|
|||||||
print("All keys to migrate exist already, nothing to do")
|
print("All keys to migrate exist already, nothing to do")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Fetch keys whose translations we're importing
|
||||||
|
print("Fetch translations that we're importing..")
|
||||||
|
try:
|
||||||
|
from_key_data = list_keys_helper(
|
||||||
|
from_lokalise,
|
||||||
|
[key for key, value in to_migrate.items() if value not in existing],
|
||||||
|
{"include_translations": 1},
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
return
|
||||||
|
|
||||||
|
from_key_lookup = create_lookup(from_key_data)
|
||||||
|
|
||||||
print("Creating", ", ".join(missing))
|
print("Creating", ", ".join(missing))
|
||||||
to_key_lookup = create_lookup(
|
to_key_lookup = create_lookup(
|
||||||
to_lokalise.keys_create(
|
to_lokalise.keys_create(
|
||||||
@ -169,24 +209,145 @@ def interactive_update():
|
|||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
STATE_REWRITE = {
|
||||||
|
"Off": "[%key:common::state::off%]",
|
||||||
|
"On": "[%key:common::state::on%]",
|
||||||
|
"Unknown": "[%key:common::state::unknown%]",
|
||||||
|
"Unavailable": "[%key:common::state::unavailable%]",
|
||||||
|
"Open": "[%key:common::state::open%]",
|
||||||
|
"Closed": "[%key:common::state::closed%]",
|
||||||
|
"Connected": "[%key:common::state::connected%]",
|
||||||
|
"Disconnected": "[%key:common::state::disconnected%]",
|
||||||
|
"Locked": "[%key:common::state::locked%]",
|
||||||
|
"Unlocked": "[%key:common::state::unlocked%]",
|
||||||
|
"Active": "[%key:common::state::active%]",
|
||||||
|
"active": "[%key:common::state::active%]",
|
||||||
|
"Standby": "[%key:common::state::standby%]",
|
||||||
|
"Idle": "[%key:common::state::idle%]",
|
||||||
|
"idle": "[%key:common::state::idle%]",
|
||||||
|
"Paused": "[%key:common::state::paused%]",
|
||||||
|
"paused": "[%key:common::state::paused%]",
|
||||||
|
"Home": "[%key:common::state::home%]",
|
||||||
|
"Away": "[%key:common::state::not_home%]",
|
||||||
|
"[%key:state::default::off%]": "[%key:common::state::off%]",
|
||||||
|
"[%key:state::default::on%]": "[%key:common::state::on%]",
|
||||||
|
"[%key:state::cover::open%]": "[%key:common::state::open%]",
|
||||||
|
"[%key:state::cover::closed%]": "[%key:common::state::closed%]",
|
||||||
|
"[%key:state::lock::locked%]": "[%key:common::state::locked%]",
|
||||||
|
"[%key:state::lock::unlocked%]": "[%key:common::state::unlocked%]",
|
||||||
|
}
|
||||||
|
SKIP_DOMAIN = {"default", "scene"}
|
||||||
|
STATES_WITH_DEV_CLASS = {"binary_sensor", "zwave"}
|
||||||
|
GROUP_DELETE = {"opening", "closing", "stopped"} # They don't exist
|
||||||
|
|
||||||
|
|
||||||
|
def find_frontend_states():
|
||||||
|
"""Find frontend states.
|
||||||
|
|
||||||
|
Source key -> target key
|
||||||
|
Add key to integrations strings.json
|
||||||
|
"""
|
||||||
|
frontend_states = json.loads(
|
||||||
|
(FRONTEND_REPO / "src/translations/en.json").read_text()
|
||||||
|
)["state"]
|
||||||
|
|
||||||
|
# domain => state object
|
||||||
|
to_write = {}
|
||||||
|
to_migrate = {}
|
||||||
|
|
||||||
|
for domain, states in frontend_states.items():
|
||||||
|
if domain in SKIP_DOMAIN:
|
||||||
|
continue
|
||||||
|
|
||||||
|
to_key_base = f"component::{domain}::state"
|
||||||
|
from_key_base = f"state::{domain}"
|
||||||
|
|
||||||
|
if domain in STATES_WITH_DEV_CLASS:
|
||||||
|
|
||||||
|
domain_to_write = dict(states)
|
||||||
|
|
||||||
|
for device_class, dev_class_states in domain_to_write.items():
|
||||||
|
to_device_class = "_" if device_class == "default" else device_class
|
||||||
|
for key in dev_class_states:
|
||||||
|
to_migrate[
|
||||||
|
f"{from_key_base}::{device_class}::{key}"
|
||||||
|
] = f"{to_key_base}::{to_device_class}::{key}"
|
||||||
|
|
||||||
|
# Rewrite "default" device class to _
|
||||||
|
if "default" in domain_to_write:
|
||||||
|
domain_to_write["_"] = domain_to_write.pop("default")
|
||||||
|
|
||||||
|
else:
|
||||||
|
if domain == "group":
|
||||||
|
for key in GROUP_DELETE:
|
||||||
|
states.pop(key)
|
||||||
|
|
||||||
|
domain_to_write = {"_": states}
|
||||||
|
|
||||||
|
for key in states:
|
||||||
|
to_migrate[f"{from_key_base}::{key}"] = f"{to_key_base}::_::{key}"
|
||||||
|
|
||||||
|
# Map out common values with
|
||||||
|
for dev_class_states in domain_to_write.values():
|
||||||
|
for key, value in dev_class_states.copy().items():
|
||||||
|
if value in STATE_REWRITE:
|
||||||
|
dev_class_states[key] = STATE_REWRITE[value]
|
||||||
|
continue
|
||||||
|
|
||||||
|
match = re.match(r"\[\%key:state::(\w+)::(.+)\%\]", value)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
|
||||||
|
dev_class_states[key] = "[%key:component::{}::state::{}%]".format(
|
||||||
|
*match.groups()
|
||||||
|
)
|
||||||
|
|
||||||
|
to_write[domain] = domain_to_write
|
||||||
|
|
||||||
|
for domain, state in to_write.items():
|
||||||
|
strings = INTEGRATIONS_DIR / domain / "strings.json"
|
||||||
|
if strings.is_file():
|
||||||
|
content = json.loads(strings.read_text())
|
||||||
|
else:
|
||||||
|
content = {}
|
||||||
|
|
||||||
|
content["state"] = state
|
||||||
|
strings.write_text(json.dumps(content, indent=2) + "\n")
|
||||||
|
|
||||||
|
pprint(to_migrate)
|
||||||
|
|
||||||
|
print()
|
||||||
|
while input("Type YES to confirm: ") != "YES":
|
||||||
|
pass
|
||||||
|
|
||||||
|
migrate_project_keys_translations(FRONTEND_PROJECT_ID, CORE_PROJECT_ID, to_migrate)
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
"""Migrate translations."""
|
"""Migrate translations."""
|
||||||
rename_keys(
|
# Import new common keys
|
||||||
CORE_PROJECT_ID,
|
# migrate_project_keys_translations(
|
||||||
{
|
# FRONTEND_PROJECT_ID,
|
||||||
"component::moon::platform::sensor::state::new_moon": "component::moon::platform::sensor::state::moon__phase::new_moon",
|
# CORE_PROJECT_ID,
|
||||||
"component::moon::platform::sensor::state::waxing_crescent": "component::moon::platform::sensor::state::moon__phase::waxing_crescent",
|
# {
|
||||||
"component::moon::platform::sensor::state::first_quarter": "component::moon::platform::sensor::state::moon__phase::first_quarter",
|
# "state::default::off": "common::state::off",
|
||||||
"component::moon::platform::sensor::state::waxing_gibbous": "component::moon::platform::sensor::state::moon__phase::waxing_gibbous",
|
# "state::default::on": "common::state::on",
|
||||||
"component::moon::platform::sensor::state::full_moon": "component::moon::platform::sensor::state::moon__phase::full_moon",
|
# "state::cover::open": "common::state::open",
|
||||||
"component::moon::platform::sensor::state::waning_gibbous": "component::moon::platform::sensor::state::moon__phase::waning_gibbous",
|
# "state::cover::closed": "common::state::closed",
|
||||||
"component::moon::platform::sensor::state::last_quarter": "component::moon::platform::sensor::state::moon__phase::last_quarter",
|
# "state::binary_sensor::connectivity::on": "common::state::connected",
|
||||||
"component::moon::platform::sensor::state::waning_crescent": "component::moon::platform::sensor::state::moon__phase::waning_crescent",
|
# "state::binary_sensor::connectivity::off": "common::state::disconnected",
|
||||||
"component::season::platform::sensor::state::spring": "component::season::platform::sensor::state::season__season__::spring",
|
# "state::lock::locked": "common::state::locked",
|
||||||
"component::season::platform::sensor::state::summer": "component::season::platform::sensor::state::season__season__::summer",
|
# "state::lock::unlocked": "common::state::unlocked",
|
||||||
"component::season::platform::sensor::state::autumn": "component::season::platform::sensor::state::season__season__::autumn",
|
# "state::timer::active": "common::state::active",
|
||||||
"component::season::platform::sensor::state::winter": "component::season::platform::sensor::state::season__season__::winter",
|
# "state::camera::idle": "common::state::idle",
|
||||||
},
|
# "state::media_player::standby": "common::state::standby",
|
||||||
)
|
# "state::media_player::paused": "common::state::paused",
|
||||||
|
# "state::device_tracker::home": "common::state::home",
|
||||||
|
# "state::device_tracker::not_home": "common::state::not_home",
|
||||||
|
# },
|
||||||
|
# )
|
||||||
|
|
||||||
|
find_frontend_states()
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
@ -51,7 +51,8 @@ def run_upload_docker():
|
|||||||
|
|
||||||
def generate_upload_data():
|
def generate_upload_data():
|
||||||
"""Generate the data for uploading."""
|
"""Generate the data for uploading."""
|
||||||
translations = {"component": {}}
|
translations = json.loads((INTEGRATIONS_DIR.parent / "strings.json").read_text())
|
||||||
|
translations["component"] = {}
|
||||||
|
|
||||||
for path in INTEGRATIONS_DIR.glob(f"*{os.sep}strings*.json"):
|
for path in INTEGRATIONS_DIR.glob(f"*{os.sep}strings*.json"):
|
||||||
component = path.parent.name
|
component = path.parent.name
|
||||||
|
Loading…
x
Reference in New Issue
Block a user