mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-13 20:36:35 +00:00
Ask specific translations (#5560)
This commit is contained in:
parent
f91b46e88c
commit
d45a674652
@ -70,6 +70,7 @@ const createWebpackConfig = ({
|
||||
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
||||
__VERSION__: JSON.stringify(version),
|
||||
__DEMO__: false,
|
||||
__BACKWARDS_COMPAT__: false,
|
||||
__STATIC_PATH__: "/static/",
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
isProdBuild ? "production" : "development"
|
||||
@ -221,6 +222,9 @@ const createCastConfig = ({ isProdBuild, latestBuild }) => {
|
||||
outputRoot: paths.cast_root,
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
defineOverlay: {
|
||||
__BACKWARDS_COMPAT__: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -17,6 +17,7 @@ export const mockLovelace = (
|
||||
);
|
||||
|
||||
hass.mockWS("lovelace/config/save", () => Promise.resolve());
|
||||
hass.mockWS("lovelace/resources", () => Promise.resolve([]));
|
||||
};
|
||||
|
||||
customElements.whenDefined("hui-view").then(() => {
|
||||
|
@ -2,473 +2,6 @@ import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockTranslations = (hass: MockHomeAssistant) => {
|
||||
hass.mockWS("frontend/get_translations", () => ({
|
||||
resources: {
|
||||
"component.lifx.config.abort.no_devices_found":
|
||||
"No LIFX devices found on the network.",
|
||||
"component.lifx.config.abort.single_instance_allowed":
|
||||
"Only a single configuration of LIFX is possible.",
|
||||
"component.lifx.config.step.confirm.description":
|
||||
"Do you want to set up LIFX?",
|
||||
"component.lifx.config.step.confirm.title": "LIFX",
|
||||
"component.lifx.config.title": "LIFX",
|
||||
"component.hangouts.config.abort.already_configured":
|
||||
"Google Hangouts is already configured",
|
||||
"component.hangouts.config.abort.unknown": "Unknown error occurred.",
|
||||
"component.hangouts.config.error.invalid_2fa":
|
||||
"Invalid 2 Factor Authentication, please try again.",
|
||||
"component.hangouts.config.error.invalid_2fa_method":
|
||||
"Invalid 2FA Method (Verify on Phone).",
|
||||
"component.hangouts.config.error.invalid_login":
|
||||
"Invalid Login, please try again.",
|
||||
"component.hangouts.config.step.2fa.data.2fa": "2FA Pin",
|
||||
"component.hangouts.config.step.2fa.title": "2-Factor-Authentication",
|
||||
"component.hangouts.config.step.user.data.email": "E-Mail Address",
|
||||
"component.hangouts.config.step.user.data.password": "Password",
|
||||
"component.hangouts.config.step.user.title": "Google Hangouts Login",
|
||||
"component.hangouts.config.title": "Google Hangouts",
|
||||
"component.rainmachine.config.error.identifier_exists":
|
||||
"Account already registered",
|
||||
"component.rainmachine.config.error.invalid_credentials":
|
||||
"Invalid credentials",
|
||||
"component.rainmachine.config.step.user.data.ip_address":
|
||||
"Hostname or IP Address",
|
||||
"component.rainmachine.config.step.user.data.password": "Password",
|
||||
"component.rainmachine.config.step.user.data.port": "Port",
|
||||
"component.rainmachine.config.step.user.title":
|
||||
"Fill in your information",
|
||||
"component.rainmachine.config.title": "RainMachine",
|
||||
"component.homematicip_cloud.config.abort.already_configured":
|
||||
"Access point is already configured",
|
||||
"component.homematicip_cloud.config.abort.connection_aborted":
|
||||
"Could not connect to HMIP server",
|
||||
"component.homematicip_cloud.config.abort.unknown":
|
||||
"Unknown error occurred.",
|
||||
"component.homematicip_cloud.config.error.invalid_pin":
|
||||
"Invalid PIN, please try again.",
|
||||
"component.homematicip_cloud.config.error.press_the_button":
|
||||
"Please press the blue button.",
|
||||
"component.homematicip_cloud.config.error.register_failed":
|
||||
"Failed to register, please try again.",
|
||||
"component.homematicip_cloud.config.error.timeout_button":
|
||||
"Blue button press timeout, please try again.",
|
||||
"component.homematicip_cloud.config.step.init.data.hapid":
|
||||
"Access point ID (SGTIN)",
|
||||
"component.homematicip_cloud.config.step.init.data.name":
|
||||
"Name (optional, used as name prefix for all devices)",
|
||||
"component.homematicip_cloud.config.step.init.data.pin":
|
||||
"Pin Code (optional)",
|
||||
"component.homematicip_cloud.config.step.init.title":
|
||||
"Pick HomematicIP Access point",
|
||||
"component.homematicip_cloud.config.step.link.description":
|
||||
"Press the blue button on the access point and the submit button to register HomematicIP with Home Assistant.\n\n",
|
||||
"component.homematicip_cloud.config.step.link.title": "Link Access point",
|
||||
"component.homematicip_cloud.config.title": "HomematicIP Cloud",
|
||||
"component.daikin.config.abort.already_configured":
|
||||
"Device is already configured",
|
||||
"component.daikin.config.abort.device_fail":
|
||||
"Unexpected error creating device.",
|
||||
"component.daikin.config.abort.device_timeout":
|
||||
"Timeout connecting to the device.",
|
||||
"component.daikin.config.step.user.data.host": "Host",
|
||||
"component.daikin.config.step.user.description":
|
||||
"Enter IP address of your Daikin AC.",
|
||||
"component.daikin.config.step.user.title": "Configure Daikin AC",
|
||||
"component.daikin.config.title": "Daikin AC",
|
||||
"component.unifi.config.abort.already_configured":
|
||||
"Controller site is already configured",
|
||||
"component.unifi.config.abort.user_privilege":
|
||||
"User needs to be administrator",
|
||||
"component.unifi.config.error.faulty_credentials": "Bad user credentials",
|
||||
"component.unifi.config.error.service_unavailable":
|
||||
"No service available",
|
||||
"component.unifi.config.step.user.data.host": "Host",
|
||||
"component.unifi.config.step.user.data.password": "Password",
|
||||
"component.unifi.config.step.user.data.port": "Port",
|
||||
"component.unifi.config.step.user.data.site": "Site ID",
|
||||
"component.unifi.config.step.user.data.username": "User name",
|
||||
"component.unifi.config.step.user.data.verify_ssl":
|
||||
"Controller using proper certificate",
|
||||
"component.unifi.config.step.user.title": "Set up UniFi Controller",
|
||||
"component.unifi.config.title": "UniFi Controller",
|
||||
"component.nest.config.abort.already_setup":
|
||||
"You can only configure a single Nest account.",
|
||||
"component.nest.config.abort.authorize_url_fail":
|
||||
"Unknown error generating an authorize url.",
|
||||
"component.nest.config.abort.authorize_url_timeout":
|
||||
"Timeout generating authorize url.",
|
||||
"component.nest.config.abort.no_flows":
|
||||
"You need to configure Nest before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/integrations/nest/).",
|
||||
"component.nest.config.error.internal_error":
|
||||
"Internal error validating code",
|
||||
"component.nest.config.error.invalid_code": "Invalid code",
|
||||
"component.nest.config.error.timeout": "Timeout validating code",
|
||||
"component.nest.config.error.unknown": "Unknown error validating code",
|
||||
"component.nest.config.step.init.data.flow_impl": "Provider",
|
||||
"component.nest.config.step.init.description":
|
||||
"Pick via which authentication provider you want to authenticate with Nest.",
|
||||
"component.nest.config.step.init.title": "Authentication Provider",
|
||||
"component.nest.config.step.link.data.code": "Pin code",
|
||||
"component.nest.config.step.link.description":
|
||||
"To link your Nest account, [authorize your account]({url}).\n\nAfter authorization, copy-paste the provided pin code below.",
|
||||
"component.nest.config.step.link.title": "Link Nest Account",
|
||||
"component.nest.config.title": "Nest",
|
||||
"component.mailgun.config.abort.not_internet_accessible":
|
||||
"Your Home Assistant instance needs to be accessible from the internet to receive Mailgun messages.",
|
||||
"component.mailgun.config.abort.one_instance_allowed":
|
||||
"Only a single instance is necessary.",
|
||||
"component.mailgun.config.create_entry.default":
|
||||
"To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data.",
|
||||
"component.mailgun.config.step.user.description":
|
||||
"Are you sure you want to set up Mailgun?",
|
||||
"component.mailgun.config.step.user.title": "Set up the Mailgun Webhook",
|
||||
"component.mailgun.config.title": "Mailgun",
|
||||
"component.tellduslive.config.abort.already_setup":
|
||||
"TelldusLive is already configured",
|
||||
"component.tellduslive.config.abort.authorize_url_fail":
|
||||
"Unknown error generating an authorize url.",
|
||||
"component.tellduslive.config.abort.authorize_url_timeout":
|
||||
"Timeout generating authorize url.",
|
||||
"component.tellduslive.config.abort.unknown": "Unknown error occurred",
|
||||
"component.tellduslive.config.error.auth_error":
|
||||
"Authentication error, please try again",
|
||||
"component.tellduslive.config.step.auth.description":
|
||||
"To link your TelldusLive account:\n 1. Click the link below\n 2. Login to Telldus Live\n 3. Authorize **{app_name}** (click **Yes**).\n 4. Come back here and click **SUBMIT**.\n\n [Link TelldusLive account]({auth_url})",
|
||||
"component.tellduslive.config.step.auth.title":
|
||||
"Authenticate against TelldusLive",
|
||||
"component.tellduslive.config.step.user.data.host": "Host",
|
||||
"component.tellduslive.config.step.user.title": "Pick endpoint.",
|
||||
"component.tellduslive.config.title": "Telldus Live",
|
||||
"component.esphome.config.abort.already_configured":
|
||||
"ESP is already configured",
|
||||
"component.esphome.config.error.connection_error":
|
||||
"Can't connect to ESP. Please make sure your YAML file contains an 'api:' line.",
|
||||
"component.esphome.config.error.invalid_password": "Invalid password!",
|
||||
"component.esphome.config.error.resolve_error":
|
||||
"Can't resolve address of the ESP. If this error persists, please set a static IP address: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips",
|
||||
"component.esphome.config.step.authenticate.data.password": "Password",
|
||||
"component.esphome.config.step.authenticate.description":
|
||||
"Please enter the password you set in your configuration.",
|
||||
"component.esphome.config.step.authenticate.title": "Enter Password",
|
||||
"component.esphome.config.step.user.data.host": "Host",
|
||||
"component.esphome.config.step.user.data.port": "Port",
|
||||
"component.esphome.config.step.user.description":
|
||||
"Please enter connection settings of your [ESPHome](https://esphomelib.com/) node.",
|
||||
"component.esphome.config.step.user.title": "ESPHome",
|
||||
"component.esphome.config.title": "ESPHome",
|
||||
"component.luftdaten.config.error.communication_error":
|
||||
"Unable to communicate with the Luftdaten API",
|
||||
"component.luftdaten.config.error.invalid_sensor":
|
||||
"Sensor not available or invalid",
|
||||
"component.luftdaten.config.error.sensor_exists":
|
||||
"Sensor already registered",
|
||||
"component.luftdaten.config.step.user.data.show_on_map": "Show on map",
|
||||
"component.luftdaten.config.step.user.data.station_id":
|
||||
"Luftdaten Sensor ID",
|
||||
"component.luftdaten.config.step.user.title": "Define Luftdaten",
|
||||
"component.luftdaten.config.title": "Luftdaten",
|
||||
"component.upnp.config.abort.already_configured":
|
||||
"UPnP/IGD is already configured",
|
||||
"component.upnp.config.abort.incomplete_device":
|
||||
"Ignoring incomplete UPnP device",
|
||||
"component.upnp.config.abort.no_devices_discovered":
|
||||
"No UPnP/IGDs discovered",
|
||||
"component.upnp.config.abort.no_devices_found":
|
||||
"No UPnP/IGD devices found on the network.",
|
||||
"component.upnp.config.abort.no_sensors_or_port_mapping":
|
||||
"Enable at least sensors or port mapping",
|
||||
"component.upnp.config.abort.single_instance_allowed":
|
||||
"Only a single configuration of UPnP/IGD is necessary.",
|
||||
"component.upnp.config.step.confirm.description":
|
||||
"Do you want to set up UPnP/IGD?",
|
||||
"component.upnp.config.step.confirm.title": "UPnP/IGD",
|
||||
"component.upnp.config.step.init.title": "UPnP/IGD",
|
||||
"component.upnp.config.step.user.data.enable_port_mapping":
|
||||
"Enable port mapping for Home Assistant",
|
||||
"component.upnp.config.step.user.data.enable_sensors":
|
||||
"Add traffic sensors",
|
||||
"component.upnp.config.step.user.data.igd": "UPnP/IGD",
|
||||
"component.upnp.config.step.user.title":
|
||||
"Configuration options for the UPnP/IGD",
|
||||
"component.upnp.config.title": "UPnP/IGD",
|
||||
"component.point.config.abort.already_setup":
|
||||
"You can only configure a Point account.",
|
||||
"component.point.config.abort.authorize_url_fail":
|
||||
"Unknown error generating an authorize url.",
|
||||
"component.point.config.abort.authorize_url_timeout":
|
||||
"Timeout generating authorize url.",
|
||||
"component.point.config.abort.external_setup":
|
||||
"Point successfully configured from another flow.",
|
||||
"component.point.config.abort.no_flows":
|
||||
"You need to configure Point before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/integrations/point/).",
|
||||
"component.point.config.create_entry.default":
|
||||
"Successfully authenticated with Minut for your Point device(s)",
|
||||
"component.point.config.error.follow_link":
|
||||
"Please follow the link and authenticate before pressing Submit",
|
||||
"component.point.config.error.no_token": "Not authenticated with Minut",
|
||||
"component.point.config.step.auth.description":
|
||||
"Please follow the link below and <b>Accept</b> access to your Minut account, then come back and press <b>Submit</b> below.\n\n[Link]({authorization_url})",
|
||||
"component.point.config.step.auth.title": "Authenticate Point",
|
||||
"component.point.config.step.user.data.flow_impl": "Provider",
|
||||
"component.point.config.step.user.description":
|
||||
"Pick via which authentication provider you want to authenticate with Point.",
|
||||
"component.point.config.step.user.title": "Authentication Provider",
|
||||
"component.point.config.title": "Minut Point",
|
||||
"component.auth.mfa_setup.notify.abort.no_available_service":
|
||||
"No notification services available.",
|
||||
"component.auth.mfa_setup.notify.error.invalid_code":
|
||||
"Invalid code, please try again.",
|
||||
"component.auth.mfa_setup.notify.step.init.description":
|
||||
"Please select one of the notification services:",
|
||||
"component.auth.mfa_setup.notify.step.init.title":
|
||||
"Set up one-time password delivered by notify component",
|
||||
"component.auth.mfa_setup.notify.step.setup.description":
|
||||
"A one-time password has been sent via **notify.{notify_service}**. Please enter it below:",
|
||||
"component.auth.mfa_setup.notify.step.setup.title": "Verify setup",
|
||||
"component.auth.mfa_setup.notify.title": "Notify One-Time Password",
|
||||
"component.auth.mfa_setup.totp.error.invalid_code":
|
||||
"Invalid code, please try again. If you get this error consistently, please make sure the clock of your Home Assistant system is accurate.",
|
||||
"component.auth.mfa_setup.totp.step.init.description":
|
||||
"To activate two factor authentication using time-based one-time passwords, scan the QR code with your authentication app. If you don't have one, we recommend either [Google Authenticator](https://support.google.com/accounts/answer/1066447) or [Authy](https://authy.com/).\n\n{qr_code}\n\nAfter scanning the code, enter the six digit code from your app to verify the setup. If you have problems scanning the QR code, do a manual setup with code **`{code}`**.",
|
||||
"component.auth.mfa_setup.totp.step.init.title":
|
||||
"Set up two-factor authentication using TOTP",
|
||||
"component.auth.mfa_setup.totp.title": "TOTP",
|
||||
"component.emulated_roku.config.abort.name_exists": "Name already exists",
|
||||
"component.emulated_roku.config.step.user.data.advertise_ip":
|
||||
"Advertise IP",
|
||||
"component.emulated_roku.config.step.user.data.advertise_port":
|
||||
"Advertise port",
|
||||
"component.emulated_roku.config.step.user.data.host_ip": "Host IP",
|
||||
"component.emulated_roku.config.step.user.data.listen_port":
|
||||
"Listen port",
|
||||
"component.emulated_roku.config.step.user.data.name": "Name",
|
||||
"component.emulated_roku.config.step.user.data.upnp_bind_multicast":
|
||||
"Bind multicast (True/False)",
|
||||
"component.emulated_roku.config.step.user.title":
|
||||
"Define server configuration",
|
||||
"component.emulated_roku.config.title": "EmulatedRoku",
|
||||
"component.owntracks.config.abort.one_instance_allowed":
|
||||
"Only a single instance is necessary.",
|
||||
"component.owntracks.config.create_entry.default":
|
||||
"\n\nOn Android, open [the OwnTracks app]({android_url}), go to preferences -> connection. Change the following settings:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: `<Your name>`\n - Device ID: `<Your device name>`\n\nOn iOS, open [the OwnTracks app]({ios_url}), tap (i) icon in top left -> settings. Change the following settings:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: `<Your name>`\n\n{secret}\n\nSee [the documentation]({docs_url}) for more information.",
|
||||
"component.owntracks.config.step.user.description":
|
||||
"Are you sure you want to set up OwnTracks?",
|
||||
"component.owntracks.config.step.user.title": "Set up OwnTracks",
|
||||
"component.owntracks.config.title": "OwnTracks",
|
||||
"component.zone.config.error.name_exists": "Name already exists",
|
||||
"component.zone.config.step.init.data.icon": "Icon",
|
||||
"component.zone.config.step.init.data.latitude": "Latitude",
|
||||
"component.zone.config.step.init.data.longitude": "Longitude",
|
||||
"component.zone.config.step.init.data.name": "Name",
|
||||
"component.zone.config.step.init.data.passive": "Passive",
|
||||
"component.zone.config.step.init.data.radius": "Radius",
|
||||
"component.zone.config.step.init.title": "Define zone parameters",
|
||||
"component.zone.config.title": "Zone",
|
||||
"component.hue.config.abort.all_configured":
|
||||
"All Philips Hue bridges are already configured",
|
||||
"component.hue.config.abort.already_configured":
|
||||
"Bridge is already configured",
|
||||
"component.hue.config.abort.cannot_connect":
|
||||
"Unable to connect to the bridge",
|
||||
"component.hue.config.abort.discover_timeout":
|
||||
"Unable to discover Hue bridges",
|
||||
"component.hue.config.abort.no_bridges":
|
||||
"No Philips Hue bridges discovered",
|
||||
"component.hue.config.abort.unknown": "Unknown error occurred",
|
||||
"component.hue.config.error.linking": "Unknown linking error occurred.",
|
||||
"component.hue.config.error.register_failed":
|
||||
"Failed to register, please try again",
|
||||
"component.hue.config.step.init.data.host": "Host",
|
||||
"component.hue.config.step.init.title": "Pick Hue bridge",
|
||||
"component.hue.config.step.link.description":
|
||||
"Press the button on the bridge to register Philips Hue with Home Assistant.\n\n",
|
||||
"component.hue.config.step.link.title": "Link Hub",
|
||||
"component.hue.config.title": "Philips Hue",
|
||||
"component.tradfri.config.abort.already_configured":
|
||||
"Bridge is already configured",
|
||||
"component.tradfri.config.error.cannot_connect":
|
||||
"Unable to connect to the gateway.",
|
||||
"component.tradfri.config.error.invalid_key":
|
||||
"Failed to register with provided key. If this keeps happening, try restarting the gateway.",
|
||||
"component.tradfri.config.error.timeout": "Timeout validating the code.",
|
||||
"component.tradfri.config.step.auth.data.host": "Host",
|
||||
"component.tradfri.config.step.auth.data.security_code": "Security Code",
|
||||
"component.tradfri.config.step.auth.description":
|
||||
"You can find the security code on the back of your gateway.",
|
||||
"component.tradfri.config.step.auth.title": "Enter security code",
|
||||
"component.tradfri.config.title": "IKEA TRÅDFRI",
|
||||
"component.mqtt.config.abort.single_instance_allowed":
|
||||
"Only a single configuration of MQTT is allowed.",
|
||||
"component.mqtt.config.error.cannot_connect":
|
||||
"Unable to connect to the broker.",
|
||||
"component.mqtt.config.step.broker.data.broker": "Broker",
|
||||
"component.mqtt.config.step.broker.data.discovery": "Enable discovery",
|
||||
"component.mqtt.config.step.broker.data.password": "Password",
|
||||
"component.mqtt.config.step.broker.data.port": "Port",
|
||||
"component.mqtt.config.step.broker.data.username": "Username",
|
||||
"component.mqtt.config.step.broker.description":
|
||||
"Please enter the connection information of your MQTT broker.",
|
||||
"component.mqtt.config.step.broker.title": "MQTT",
|
||||
"component.mqtt.config.step.hassio_confirm.data.discovery":
|
||||
"Enable discovery",
|
||||
"component.mqtt.config.step.hassio_confirm.description":
|
||||
"Do you want to configure Home Assistant to connect to the MQTT broker provided by the hass.io add-on {addon}?",
|
||||
"component.mqtt.config.step.hassio_confirm.title":
|
||||
"MQTT Broker via Hass.io add-on",
|
||||
"component.mqtt.config.title": "MQTT",
|
||||
"component.geofency.config.abort.not_internet_accessible":
|
||||
"Your Home Assistant instance needs to be accessible from the internet to receive messages from Geofency.",
|
||||
"component.geofency.config.abort.one_instance_allowed":
|
||||
"Only a single instance is necessary.",
|
||||
"component.geofency.config.create_entry.default":
|
||||
"To send events to Home Assistant, you will need to setup the webhook feature in Geofency.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details.",
|
||||
"component.geofency.config.step.user.description":
|
||||
"Are you sure you want to set up the Geofency Webhook?",
|
||||
"component.geofency.config.step.user.title":
|
||||
"Set up the Geofency Webhook",
|
||||
"component.geofency.config.title": "Geofency Webhook",
|
||||
"component.simplisafe.config.error.identifier_exists":
|
||||
"Account already registered",
|
||||
"component.simplisafe.config.error.invalid_credentials":
|
||||
"Invalid credentials",
|
||||
"component.simplisafe.config.step.user.data.code":
|
||||
"Code (for Home Assistant)",
|
||||
"component.simplisafe.config.step.user.data.password": "Password",
|
||||
"component.simplisafe.config.step.user.data.username": "Email Address",
|
||||
"component.simplisafe.config.step.user.title": "Fill in your information",
|
||||
"component.simplisafe.config.title": "SimpliSafe",
|
||||
"component.dialogflow.config.abort.not_internet_accessible":
|
||||
"Your Home Assistant instance needs to be accessible from the internet to receive Dialogflow messages.",
|
||||
"component.dialogflow.config.abort.one_instance_allowed":
|
||||
"Only a single instance is necessary.",
|
||||
"component.dialogflow.config.create_entry.default":
|
||||
"To send events to Home Assistant, you will need to setup [webhook integration of Dialogflow]({dialogflow_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) for further details.",
|
||||
"component.dialogflow.config.step.user.description":
|
||||
"Are you sure you want to set up Dialogflow?",
|
||||
"component.dialogflow.config.step.user.title":
|
||||
"Set up the Dialogflow Webhook",
|
||||
"component.dialogflow.config.title": "Dialogflow",
|
||||
"component.deconz.config.abort.already_configured":
|
||||
"Bridge is already configured",
|
||||
"component.deconz.config.abort.no_bridges":
|
||||
"No deCONZ bridges discovered",
|
||||
"component.deconz.config.abort.one_instance_only":
|
||||
"Component only supports one deCONZ instance",
|
||||
"component.deconz.config.error.no_key": "Couldn't get an API key",
|
||||
"component.deconz.config.step.init.data.host": "Host",
|
||||
"component.deconz.config.step.init.data.port": "Port",
|
||||
"component.deconz.config.step.init.title": "Define deCONZ gateway",
|
||||
"component.deconz.config.step.link.description":
|
||||
'Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ system settings\n2. Press "Unlock Gateway" button',
|
||||
"component.deconz.config.step.link.title": "Link with deCONZ",
|
||||
"component.deconz.config.step.options.data.allow_clip_sensor":
|
||||
"Allow importing virtual sensors",
|
||||
"component.deconz.config.step.options.data.allow_deconz_groups":
|
||||
"Allow importing deCONZ groups",
|
||||
"component.deconz.config.step.options.title":
|
||||
"Extra configuration options for deCONZ",
|
||||
"component.deconz.config.title": "deCONZ Zigbee gateway",
|
||||
"component.openuv.config.error.identifier_exists":
|
||||
"Coordinates already registered",
|
||||
"component.openuv.config.error.invalid_api_key": "Invalid API key",
|
||||
"component.openuv.config.step.user.data.api_key": "OpenUV API Key",
|
||||
"component.openuv.config.step.user.data.elevation": "Elevation",
|
||||
"component.openuv.config.step.user.data.latitude": "Latitude",
|
||||
"component.openuv.config.step.user.data.longitude": "Longitude",
|
||||
"component.openuv.config.step.user.title": "Fill in your information",
|
||||
"component.openuv.config.title": "OpenUV",
|
||||
"component.locative.config.title": "Locative Webhook",
|
||||
"component.locative.config.step.user.title":
|
||||
"Set up the Locative Webhook",
|
||||
"component.locative.config.step.user.description":
|
||||
"Are you sure you want to set up the Locative Webhook?",
|
||||
"component.locative.config.abort.one_instance_allowed":
|
||||
"Only a single instance is necessary.",
|
||||
"component.locative.config.abort.not_internet_accessible":
|
||||
"Your Home Assistant instance needs to be accessible from the internet to receive messages from Geofency.",
|
||||
"component.locative.config.create_entry.default":
|
||||
"To send locations to Home Assistant, you will need to setup the webhook feature in the Locative app.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details.",
|
||||
"component.ios.config.abort.single_instance_allowed":
|
||||
"Only a single configuration of Home Assistant iOS is necessary.",
|
||||
"component.ios.config.step.confirm.description":
|
||||
"Do you want to set up the Home Assistant iOS component?",
|
||||
"component.ios.config.step.confirm.title": "Home Assistant iOS",
|
||||
"component.ios.config.title": "Home Assistant iOS",
|
||||
"component.smhi.config.error.name_exists": "Name already exists",
|
||||
"component.smhi.config.error.wrong_location": "Location Sweden only",
|
||||
"component.smhi.config.step.user.data.latitude": "Latitude",
|
||||
"component.smhi.config.step.user.data.longitude": "Longitude",
|
||||
"component.smhi.config.step.user.data.name": "Name",
|
||||
"component.smhi.config.step.user.title": "Location in Sweden",
|
||||
"component.smhi.config.title": "Swedish weather service (SMHI)",
|
||||
"component.sonos.config.abort.no_devices_found":
|
||||
"No Sonos devices found on the network.",
|
||||
"component.sonos.config.abort.single_instance_allowed":
|
||||
"Only a single configuration of Sonos is necessary.",
|
||||
"component.sonos.config.step.confirm.description":
|
||||
"Do you want to set up Sonos?",
|
||||
"component.sonos.config.step.confirm.title": "Sonos",
|
||||
"component.sonos.config.title": "Sonos",
|
||||
"component.ifttt.config.abort.not_internet_accessible":
|
||||
"Your Home Assistant instance needs to be accessible from the internet to receive IFTTT messages.",
|
||||
"component.ifttt.config.abort.one_instance_allowed":
|
||||
"Only a single instance is necessary.",
|
||||
"component.ifttt.config.create_entry.default":
|
||||
'To send events to Home Assistant, you will need to use the "Make a web request" action from the [IFTTT Webhook applet]({applet_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data.',
|
||||
"component.ifttt.config.step.user.description":
|
||||
"Are you sure you want to set up IFTTT?",
|
||||
"component.ifttt.config.step.user.title":
|
||||
"Set up the IFTTT Webhook Applet",
|
||||
"component.ifttt.config.title": "IFTTT",
|
||||
"component.twilio.config.abort.not_internet_accessible":
|
||||
"Your Home Assistant instance needs to be accessible from the internet to receive Twilio messages.",
|
||||
"component.twilio.config.abort.one_instance_allowed":
|
||||
"Only a single instance is necessary.",
|
||||
"component.twilio.config.create_entry.default":
|
||||
"To send events to Home Assistant, you will need to setup [Webhooks with Twilio]({twilio_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data.",
|
||||
"component.twilio.config.step.user.description":
|
||||
"Are you sure you want to set up Twilio?",
|
||||
"component.twilio.config.step.user.title": "Set up the Twilio Webhook",
|
||||
"component.twilio.config.title": "Twilio",
|
||||
"component.zha.config.abort.single_instance_allowed":
|
||||
"Only a single configuration of ZHA is allowed.",
|
||||
"component.zha.config.error.cannot_connect":
|
||||
"Unable to connect to ZHA device.",
|
||||
"component.zha.config.step.user.data.radio_type": "Radio Type",
|
||||
"component.zha.config.step.user.data.usb_path": "USB Device Path",
|
||||
"component.zha.config.step.user.title": "ZHA",
|
||||
"component.zha.config.title": "ZHA",
|
||||
"component.gpslogger.config.title": "GPSLogger Webhook",
|
||||
"component.gpslogger.config.step.user.title":
|
||||
"Set up the GPSLogger Webhook",
|
||||
"component.gpslogger.config.step.user.description":
|
||||
"Are you sure you want to set up the GPSLogger Webhook?",
|
||||
"component.gpslogger.config.abort.one_instance_allowed":
|
||||
"Only a single instance is necessary.",
|
||||
"component.gpslogger.config.abort.not_internet_accessible":
|
||||
"Your Home Assistant instance needs to be accessible from the internet to receive messages from GPSLogger.",
|
||||
"component.gpslogger.config.create_entry.default":
|
||||
"To send events to Home Assistant, you will need to setup the webhook feature in GPSLogger.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details.",
|
||||
"component.zwave.config.abort.already_configured":
|
||||
"Z-Wave is already configured",
|
||||
"component.zwave.config.abort.one_instance_only":
|
||||
"Component only supports one Z-Wave instance",
|
||||
"component.zwave.config.error.option_error":
|
||||
"Z-Wave validation failed. Is the path to the USB stick correct?",
|
||||
"component.zwave.config.step.user.data.network_key":
|
||||
"Network Key (leave blank to auto-generate)",
|
||||
"component.zwave.config.step.user.data.usb_path": "USB Path",
|
||||
"component.zwave.config.step.user.description":
|
||||
"See https://www.home-assistant.io/docs/z-wave/installation/ for information on the configuration variables",
|
||||
"component.zwave.config.step.user.title": "Set up Z-Wave",
|
||||
"component.zwave.config.title": "Z-Wave",
|
||||
"component.cast.config.abort.no_devices_found":
|
||||
"No Google Cast devices found on the network.",
|
||||
"component.cast.config.abort.single_instance_allowed":
|
||||
"Only a single configuration of Google Cast is necessary.",
|
||||
"component.cast.config.step.confirm.description":
|
||||
"Do you want to set up Google Cast?",
|
||||
"component.cast.config.step.confirm.title": "Google Cast",
|
||||
"component.cast.config.title": "Google Cast",
|
||||
},
|
||||
resources: {},
|
||||
}));
|
||||
};
|
||||
|
@ -3,6 +3,7 @@ import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { DataEntryFlowProgress, DataEntryFlowStep } from "./data_entry_flow";
|
||||
import { domainToName } from "./integration";
|
||||
|
||||
export const DISCOVERY_SOURCES = ["unignore", "homekit", "ssdp", "zeroconf"];
|
||||
|
||||
@ -75,7 +76,7 @@ export const localizeConfigFlowTitle = (
|
||||
const placeholders = flow.context.title_placeholders || {};
|
||||
const placeholderKeys = Object.keys(placeholders);
|
||||
if (placeholderKeys.length === 0) {
|
||||
return localize(`component.${flow.handler}.title`);
|
||||
return domainToName(localize, flow.handler);
|
||||
}
|
||||
const args: string[] = [];
|
||||
placeholderKeys.forEach((key) => {
|
||||
|
@ -11,6 +11,14 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export type TranslationCategory =
|
||||
| "title"
|
||||
| "state"
|
||||
| "config"
|
||||
| "options"
|
||||
| "device_automation"
|
||||
| "mfa_setup";
|
||||
|
||||
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
|
||||
fetchFrontendUserData(hass.connection, "language");
|
||||
|
||||
@ -20,6 +28,23 @@ export const saveTranslationPreferences = (
|
||||
) => saveFrontendUserData(hass.connection, "language", data);
|
||||
|
||||
export const getHassTranslations = async (
|
||||
hass: HomeAssistant,
|
||||
language: string,
|
||||
category: TranslationCategory,
|
||||
integration?: string,
|
||||
config_flow?: boolean
|
||||
): Promise<{}> => {
|
||||
const result = await hass.callWS<{ resources: {} }>({
|
||||
type: "frontend/get_translations",
|
||||
language,
|
||||
category,
|
||||
integration,
|
||||
config_flow,
|
||||
});
|
||||
return result.resources;
|
||||
};
|
||||
|
||||
export const getHassTranslationsPre109 = async (
|
||||
hass: HomeAssistant,
|
||||
language: string
|
||||
): Promise<{}> => {
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
loadDataEntryFlowDialog,
|
||||
showFlowDialog,
|
||||
} from "./show-dialog-data-entry-flow";
|
||||
import { domainToName } from "../../data/integration";
|
||||
|
||||
export const loadConfigFlowDialog = loadDataEntryFlowDialog;
|
||||
|
||||
@ -22,17 +23,31 @@ export const showConfigFlowDialog = (
|
||||
): void =>
|
||||
showFlowDialog(element, dialogParams, {
|
||||
loadDevicesAndAreas: true,
|
||||
getFlowHandlers: (hass) =>
|
||||
getConfigFlowHandlers(hass).then((handlers) =>
|
||||
handlers.sort((handlerA, handlerB) =>
|
||||
caseInsensitiveCompare(
|
||||
hass.localize(`component.${handlerA}.title`),
|
||||
hass.localize(`component.${handlerB}.title`)
|
||||
)
|
||||
getFlowHandlers: async (hass) => {
|
||||
const [handlers] = await Promise.all([
|
||||
getConfigFlowHandlers(hass),
|
||||
hass.loadBackendTranslation("title", undefined, true),
|
||||
]);
|
||||
|
||||
return handlers.sort((handlerA, handlerB) =>
|
||||
caseInsensitiveCompare(
|
||||
domainToName(hass.localize, handlerA),
|
||||
domainToName(hass.localize, handlerB)
|
||||
)
|
||||
),
|
||||
createFlow: createConfigFlow,
|
||||
fetchFlow: fetchConfigFlow,
|
||||
);
|
||||
},
|
||||
createFlow: async (hass, handler) => {
|
||||
const [step] = await Promise.all([
|
||||
createConfigFlow(hass, handler),
|
||||
hass.loadBackendTranslation("config", handler),
|
||||
]);
|
||||
return step;
|
||||
},
|
||||
fetchFlow: async (hass, flowId) => {
|
||||
const step = await fetchConfigFlow(hass, flowId);
|
||||
await hass.loadBackendTranslation("config", step.handler);
|
||||
return step;
|
||||
},
|
||||
handleFlowStep: handleConfigFlowStep,
|
||||
deleteFlow: deleteConfigFlow,
|
||||
|
||||
|
@ -25,8 +25,20 @@ export const showOptionsFlowDialog = (
|
||||
},
|
||||
{
|
||||
loadDevicesAndAreas: false,
|
||||
createFlow: createOptionsFlow,
|
||||
fetchFlow: fetchOptionsFlow,
|
||||
createFlow: async (hass, handler) => {
|
||||
const [step] = await Promise.all([
|
||||
createOptionsFlow(hass, handler),
|
||||
hass.loadBackendTranslation("options", configEntry.domain),
|
||||
]);
|
||||
return step;
|
||||
},
|
||||
fetchFlow: async (hass, flowId) => {
|
||||
const [step] = await Promise.all([
|
||||
fetchOptionsFlow(hass, flowId),
|
||||
hass.loadBackendTranslation("options", configEntry.domain),
|
||||
]);
|
||||
return step;
|
||||
},
|
||||
handleFlowStep: handleOptionsFlowStep,
|
||||
deleteFlow: deleteOptionsFlow,
|
||||
|
||||
|
@ -20,6 +20,8 @@ import "../../components/ha-icon-next";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { FlowConfig } from "./show-dialog-data-entry-flow";
|
||||
import { configFlowContentStyles } from "./styles";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import { LocalizeFunc } from "../../common/translations/localize";
|
||||
|
||||
interface HandlerObj {
|
||||
name: string;
|
||||
@ -40,31 +42,37 @@ class StepFlowPickHandler extends LitElement {
|
||||
|
||||
private _width?: number;
|
||||
|
||||
private _getHandlers = memoizeOne((h: string[], filter?: string) => {
|
||||
const handlers: HandlerObj[] = h.map((handler) => {
|
||||
return {
|
||||
name: this.hass.localize(`component.${handler}.title`) || handler,
|
||||
slug: handler,
|
||||
};
|
||||
});
|
||||
private _getHandlers = memoizeOne(
|
||||
(h: string[], filter?: string, _localize?: LocalizeFunc) => {
|
||||
const handlers: HandlerObj[] = h.map((handler) => {
|
||||
return {
|
||||
name: domainToName(this.hass.localize, handler),
|
||||
slug: handler,
|
||||
};
|
||||
});
|
||||
|
||||
if (filter) {
|
||||
const options: Fuse.FuseOptions<HandlerObj> = {
|
||||
keys: ["name", "slug"],
|
||||
caseSensitive: false,
|
||||
minMatchCharLength: 2,
|
||||
threshold: 0.2,
|
||||
};
|
||||
const fuse = new Fuse(handlers, options);
|
||||
return fuse.search(filter);
|
||||
if (filter) {
|
||||
const options: Fuse.FuseOptions<HandlerObj> = {
|
||||
keys: ["name", "slug"],
|
||||
caseSensitive: false,
|
||||
minMatchCharLength: 2,
|
||||
threshold: 0.2,
|
||||
};
|
||||
const fuse = new Fuse(handlers, options);
|
||||
return fuse.search(filter);
|
||||
}
|
||||
return handlers.sort((a, b) =>
|
||||
a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1
|
||||
);
|
||||
}
|
||||
return handlers.sort((a, b) =>
|
||||
a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1
|
||||
);
|
||||
});
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const handlers = this._getHandlers(this.handlers, this.filter);
|
||||
const handlers = this._getHandlers(
|
||||
this.handlers,
|
||||
this.filter,
|
||||
this.hass.localize
|
||||
);
|
||||
|
||||
return html`
|
||||
<h2>${this.hass.localize("ui.panel.config.integrations.new")}</h2>
|
||||
|
@ -67,6 +67,12 @@ export class HomeAssistantAppEl extends HassElement {
|
||||
}
|
||||
}
|
||||
|
||||
protected hassConnected() {
|
||||
super.hassConnected();
|
||||
// @ts-ignore
|
||||
this._loadHassTranslations(this.hass!.language, "state");
|
||||
}
|
||||
|
||||
protected hassReconnected() {
|
||||
super.hassReconnected();
|
||||
|
||||
|
89
src/onboarding/action-badge.ts
Normal file
89
src/onboarding/action-badge.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../components/ha-icon";
|
||||
|
||||
@customElement("action-badge")
|
||||
class ActionBadge extends LitElement {
|
||||
@property() public icon!: string;
|
||||
|
||||
@property() public title!: string;
|
||||
|
||||
@property() public badgeIcon?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public clickable = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="icon">
|
||||
<iron-icon .icon=${this.icon}></iron-icon>
|
||||
${this.badgeIcon
|
||||
? html` <ha-icon class="badge" .icon=${this.badgeIcon}></ha-icon> `
|
||||
: ""}
|
||||
</div>
|
||||
<div class="title">${this.title}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
:host([clickable]) {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
margin: 0 auto 8px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--secondary-text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:host([clickable]) .icon {
|
||||
border-color: var(--primary-color);
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
color: var(--primary-color);
|
||||
bottom: -5px;
|
||||
right: -5px;
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
width: 18px;
|
||||
display: block;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.title {
|
||||
min-height: 2.3em;
|
||||
word-break: break-word;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"action-badge": ActionBadge;
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ import { HassElement } from "../state/hass-element";
|
||||
import { registerServiceWorker } from "../util/register-service-worker";
|
||||
import "./onboarding-create-user";
|
||||
import "./onboarding-loading";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
interface OnboardingEvent<T extends ValidOnboardingStep> {
|
||||
type: T;
|
||||
@ -45,6 +46,8 @@ declare global {
|
||||
|
||||
@customElement("ha-onboarding")
|
||||
class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
public translationFragment = "page-onboarding";
|
||||
|
||||
@property() private _loading = false;
|
||||
@ -102,6 +105,12 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
if (changedProps.has("language")) {
|
||||
document.querySelector("html")!.setAttribute("lang", this.language!);
|
||||
}
|
||||
if (changedProps.has("hass")) {
|
||||
this.hassChanged(
|
||||
this.hass!,
|
||||
changedProps.get("hass") as HomeAssistant | undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _curStep() {
|
||||
|
@ -11,7 +11,7 @@ import "../components/ha-icon";
|
||||
|
||||
@customElement("integration-badge")
|
||||
class IntegrationBadge extends LitElement {
|
||||
@property() public icon!: string;
|
||||
@property() public domain!: string;
|
||||
|
||||
@property() public title!: string;
|
||||
|
||||
@ -22,7 +22,10 @@ class IntegrationBadge extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="icon">
|
||||
<iron-icon .icon=${this.icon}></iron-icon>
|
||||
<img
|
||||
src="https://brands.home-assistant.io/${this.domain}/icon.png"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
${this.badgeIcon
|
||||
? html` <ha-icon class="badge" .icon=${this.badgeIcon}></ha-icon> `
|
||||
: ""}
|
||||
@ -44,33 +47,35 @@ class IntegrationBadge extends LitElement {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
margin: 0 auto 8px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--secondary-text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:host([clickable]) .icon {
|
||||
border-color: var(--primary-color);
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
color: var(--primary-color);
|
||||
bottom: -5px;
|
||||
right: -5px;
|
||||
background-color: white;
|
||||
color: white;
|
||||
bottom: -7px;
|
||||
right: -10px;
|
||||
background-color: var(--label-badge-green);
|
||||
border-radius: 50%;
|
||||
width: 18px;
|
||||
display: block;
|
||||
height: 18px;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
@ -26,7 +26,9 @@ import {
|
||||
showConfigFlowDialog,
|
||||
} from "../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./action-badge";
|
||||
import "./integration-badge";
|
||||
import { domainToName } from "../data/integration";
|
||||
|
||||
@customElement("onboarding-integrations")
|
||||
class OnboardingIntegrations extends LitElement {
|
||||
@ -42,8 +44,15 @@ class OnboardingIntegrations extends LitElement {
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.hass.loadBackendTranslation("title", undefined, true);
|
||||
this._unsubEvents = subscribeConfigFlowInProgress(this.hass, (flows) => {
|
||||
this._discovered = flows;
|
||||
for (const flow of flows) {
|
||||
// To render title placeholders
|
||||
if (flow.context.title_placeholders) {
|
||||
this.hass.loadBackendTranslation("config", flow.handler);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -62,13 +71,14 @@ class OnboardingIntegrations extends LitElement {
|
||||
// Render discovered and existing entries together sorted by localized title.
|
||||
const entries: Array<[string, TemplateResult]> = this._entries.map(
|
||||
(entry) => {
|
||||
const title = this.hass.localize(`component.${entry.domain}.title`);
|
||||
const title = domainToName(this.hass.localize, entry.domain);
|
||||
return [
|
||||
title,
|
||||
html`
|
||||
<integration-badge
|
||||
.domain=${entry.domain}
|
||||
.title=${title}
|
||||
icon="hass:check"
|
||||
badgeIcon="hass:check"
|
||||
></integration-badge>
|
||||
`,
|
||||
];
|
||||
@ -83,8 +93,8 @@ class OnboardingIntegrations extends LitElement {
|
||||
<button .flowId=${flow.flow_id} @click=${this._continueFlow}>
|
||||
<integration-badge
|
||||
clickable
|
||||
.domain=${flow.handler}
|
||||
.title=${title}
|
||||
icon="hass:plus"
|
||||
></integration-badge>
|
||||
</button>
|
||||
`,
|
||||
@ -102,13 +112,13 @@ class OnboardingIntegrations extends LitElement {
|
||||
<div class="badges">
|
||||
${content}
|
||||
<button @click=${this._createFlow}>
|
||||
<integration-badge
|
||||
<action-badge
|
||||
clickable
|
||||
title=${this.onboardingLocalize(
|
||||
"ui.panel.page-onboarding.integration.more_integrations"
|
||||
)}
|
||||
icon="hass:dots-horizontal"
|
||||
></integration-badge>
|
||||
></action-badge>
|
||||
</button>
|
||||
</div>
|
||||
<div class="footer">
|
||||
@ -172,14 +182,17 @@ class OnboardingIntegrations extends LitElement {
|
||||
return css`
|
||||
.badges {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.badges > * {
|
||||
width: 24%;
|
||||
min-width: 90px;
|
||||
width: 96px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
button {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
|
@ -50,6 +50,11 @@ class HaConfigAutomation extends HassRouterPage {
|
||||
return automations;
|
||||
});
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.hass.loadBackendTranslation("device_automation");
|
||||
}
|
||||
|
||||
protected updatePageEl(pageEl, changedProps: PropertyValues) {
|
||||
pageEl.hass = this.hass;
|
||||
pageEl.narrow = this.narrow;
|
||||
|
@ -322,6 +322,7 @@ class HaPanelConfig extends HassRouterPage {
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.hass.loadBackendTranslation("title");
|
||||
if (isComponentLoaded(this.hass, "cloud")) {
|
||||
this._updateCloudStatus();
|
||||
}
|
||||
|
@ -93,6 +93,8 @@ class HaConfigIntegrations extends HassRouterPage {
|
||||
this._loadData();
|
||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||
});
|
||||
// For config entries. Also loading config flow ones for add integration
|
||||
this.hass.loadBackendTranslation("title", undefined, true);
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
@ -142,6 +144,12 @@ class HaConfigIntegrations extends HassRouterPage {
|
||||
}),
|
||||
subscribeConfigFlowInProgress(this.hass, (flowsInProgress) => {
|
||||
this._configEntriesInProgress = flowsInProgress;
|
||||
for (const flow of flowsInProgress) {
|
||||
// To render title placeholders
|
||||
if (flow.context.title_placeholders) {
|
||||
this.hass.loadBackendTranslation("config", flow.handler);
|
||||
}
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
@ -46,6 +46,11 @@ class HaConfigScript extends HassRouterPage {
|
||||
return scripts;
|
||||
});
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.hass.loadBackendTranslation("device_automation");
|
||||
}
|
||||
|
||||
protected updatePageEl(pageEl, changedProps: PropertyValues) {
|
||||
pageEl.hass = this.hass;
|
||||
pageEl.narrow = this.narrow;
|
||||
|
@ -29,6 +29,11 @@ class PanelDeveloperTools extends LitElement {
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.hass.loadBackendTranslation("title");
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const page = this._page;
|
||||
return html`
|
||||
|
@ -235,6 +235,11 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.hass.loadBackendTranslation("title");
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// We are unable to parse date because we use intl api to render date
|
||||
|
@ -170,6 +170,7 @@ class HaMfaModuleSetupFlow extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.hass.loadBackendTranslation("mfa_setup", "auth");
|
||||
this.addEventListener("keypress", (ev) => {
|
||||
if (ev.keyCode === 13) {
|
||||
this._submitStep();
|
||||
|
@ -113,6 +113,14 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
}
|
||||
return resp;
|
||||
},
|
||||
loadBackendTranslation: (category, integration?, configFlow?) =>
|
||||
// @ts-ignore
|
||||
this._loadHassTranslations(
|
||||
this.hass?.language,
|
||||
category,
|
||||
integration,
|
||||
configFlow
|
||||
),
|
||||
...getState(),
|
||||
...this._pendingHass,
|
||||
};
|
||||
|
@ -3,6 +3,8 @@ import { computeRTL } from "../common/util/compute_rtl";
|
||||
import {
|
||||
getHassTranslations,
|
||||
saveTranslationPreferences,
|
||||
TranslationCategory,
|
||||
getHassTranslationsPre109,
|
||||
} from "../data/translation";
|
||||
import { translationMetadata } from "../resources/translations-metadata";
|
||||
import { Constructor, HomeAssistant } from "../types";
|
||||
@ -13,6 +15,17 @@ import {
|
||||
getUserLanguage,
|
||||
} from "../util/hass-translation";
|
||||
import { HassBaseEl } from "./hass-base-mixin";
|
||||
import { atLeastVersion } from "../common/config/version";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
|
||||
interface LoadedTranslationCategory {
|
||||
// individual integrations loaded for this category
|
||||
integrations: string[];
|
||||
// if integrations that have been set up for this category are loaded
|
||||
setup: boolean;
|
||||
// if
|
||||
configFlow: boolean;
|
||||
}
|
||||
|
||||
/*
|
||||
* superClass needs to contain `this.hass` and `this._updateHass`.
|
||||
@ -23,6 +36,11 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
||||
// eslint-disable-next-line: variable-name
|
||||
private __coreProgress?: string;
|
||||
|
||||
private __loadedTranslations: {
|
||||
// track what things have been loaded
|
||||
[category: string]: LoadedTranslationCategory;
|
||||
} = {};
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.addEventListener("hass-language-select", (e) =>
|
||||
@ -39,11 +57,18 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
||||
this._selectLanguage(language, false);
|
||||
}
|
||||
});
|
||||
this.hass!.connection.subscribeEvents(
|
||||
debounce(() => {
|
||||
this._refetchCachedHassTranslations(false);
|
||||
}, 500),
|
||||
"component_loaded"
|
||||
);
|
||||
this._applyTranslations(this.hass!);
|
||||
}
|
||||
|
||||
protected hassReconnected() {
|
||||
super.hassReconnected();
|
||||
this._refetchCachedHassTranslations(true);
|
||||
this._applyTranslations(this.hass!);
|
||||
}
|
||||
|
||||
@ -69,18 +94,85 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
||||
saveTranslationPreferences(this.hass, { language });
|
||||
}
|
||||
this._applyTranslations(this.hass);
|
||||
this._refetchCachedHassTranslations(true);
|
||||
}
|
||||
|
||||
private _applyTranslations(hass: HomeAssistant) {
|
||||
document.querySelector("html")!.setAttribute("lang", hass.language);
|
||||
this.style.direction = computeRTL(hass) ? "rtl" : "ltr";
|
||||
this._loadCoreTranslations(hass.language);
|
||||
this._loadHassTranslations(hass.language);
|
||||
this._loadFragmentTranslations(hass.language, hass.panelUrl);
|
||||
}
|
||||
|
||||
private async _loadHassTranslations(language: string) {
|
||||
const resources = await getHassTranslations(this.hass!, language);
|
||||
private async _loadHassTranslations(
|
||||
language: string,
|
||||
category: Parameters<typeof getHassTranslations>[2],
|
||||
integration?: Parameters<typeof getHassTranslations>[3],
|
||||
configFlow?: Parameters<typeof getHassTranslations>[4],
|
||||
force = false
|
||||
) {
|
||||
if (
|
||||
__BACKWARDS_COMPAT__ &&
|
||||
!atLeastVersion(this.hass!.connection.haVersion, 0, 109)
|
||||
) {
|
||||
if (category !== "state") {
|
||||
return;
|
||||
}
|
||||
const resources = await getHassTranslationsPre109(this.hass!, language);
|
||||
|
||||
// Ignore the repsonse if user switched languages before we got response
|
||||
if (this.hass!.language !== language) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateResources(language, resources);
|
||||
return;
|
||||
}
|
||||
|
||||
let alreadyLoaded: LoadedTranslationCategory;
|
||||
|
||||
if (category in this.__loadedTranslations) {
|
||||
alreadyLoaded = this.__loadedTranslations[category];
|
||||
} else {
|
||||
alreadyLoaded = this.__loadedTranslations[category] = {
|
||||
integrations: [],
|
||||
setup: false,
|
||||
configFlow: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Check if already loaded
|
||||
if (!force) {
|
||||
if (integration) {
|
||||
if (alreadyLoaded.integrations.includes(integration)) {
|
||||
return;
|
||||
}
|
||||
} else if (
|
||||
configFlow ? alreadyLoaded.configFlow : alreadyLoaded.setup
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to cache
|
||||
if (integration) {
|
||||
if (!alreadyLoaded.integrations.includes(integration)) {
|
||||
alreadyLoaded.integrations.push(integration);
|
||||
}
|
||||
} else {
|
||||
alreadyLoaded.setup = true;
|
||||
if (configFlow) {
|
||||
alreadyLoaded.configFlow = true;
|
||||
}
|
||||
}
|
||||
|
||||
const resources = await getHassTranslations(
|
||||
this.hass!,
|
||||
language,
|
||||
category,
|
||||
integration,
|
||||
configFlow
|
||||
);
|
||||
|
||||
// Ignore the repsonse if user switched languages before we got response
|
||||
if (this.hass!.language !== language) {
|
||||
@ -134,4 +226,20 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
||||
}
|
||||
this._updateHass(changes);
|
||||
}
|
||||
|
||||
private _refetchCachedHassTranslations(includeConfigFlow: boolean) {
|
||||
for (const [category, cache] of Object.entries(
|
||||
this.__loadedTranslations
|
||||
)) {
|
||||
if (cache.setup) {
|
||||
this._loadHassTranslations(
|
||||
this.hass!.language,
|
||||
category as TranslationCategory,
|
||||
undefined,
|
||||
includeConfigFlow && cache.configFlow,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
import { LocalizeFunc } from "./common/translations/localize";
|
||||
import { CoreFrontendUserData } from "./data/frontend";
|
||||
import { ExternalMessaging } from "./external_app/external_messaging";
|
||||
import { getHassTranslations } from "./data/translation";
|
||||
|
||||
declare global {
|
||||
/* eslint-disable no-var, no-redeclare */
|
||||
@ -19,6 +20,7 @@ declare global {
|
||||
var __BUILD__: "latest" | "es5";
|
||||
var __VERSION__: string;
|
||||
var __STATIC_PATH__: string;
|
||||
var __BACKWARDS_COMPAT__: boolean;
|
||||
/* eslint-enable no-var, no-redeclare */
|
||||
|
||||
interface Window {
|
||||
@ -174,6 +176,11 @@ export interface HomeAssistant {
|
||||
fetchWithAuth(path: string, init?: { [key: string]: any }): Promise<Response>;
|
||||
sendWS(msg: MessageBase): void;
|
||||
callWS<T>(msg: MessageBase): Promise<T>;
|
||||
loadBackendTranslation(
|
||||
category: Parameters<typeof getHassTranslations>[2],
|
||||
integration?: Parameters<typeof getHassTranslations>[3],
|
||||
configFlow?: Parameters<typeof getHassTranslations>[4]
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export type LightEntity = HassEntityBase & {
|
||||
|
Loading…
x
Reference in New Issue
Block a user