Merge pull request #3119 from home-assistant/dev

20190424.0
This commit is contained in:
Paulus Schoutsen 2019-04-24 11:13:42 -07:00 committed by GitHub
commit 30471b7cfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 439 additions and 74 deletions

View File

@ -110,7 +110,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
display: block;
}
.state div {
width: 150px;
width: 180px;
display: inline-block;
}
paper-toggle-button {
@ -311,7 +311,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
id="apparmor"
icon="hassio:shield"
label="apparmor"
description="[[addon.apparmor]]"
description=""
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.auth_api]]">
@ -355,6 +355,15 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
checked="[[addon.protected]]"
></paper-toggle-button>
</div>
<template is="dom-if" if="[[addon.ingress]]">
<div class="state">
<div>Show in Sidebar</div>
<paper-toggle-button
on-change="panelToggled"
checked="[[addon.ingress_panel]]"
></paper-toggle-button>
</div>
</template>
</template>
</div>
<div class="card-actions">
@ -522,6 +531,11 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
this.set("addon.protected", !this.addon.protected);
}
panelToggled() {
const data = { ingress_panel: !this.addon.ingress_panel };
this.hass.callApi("POST", `hassio/addons/${this.addonSlug}/options`, data);
}
showMoreInfo(e) {
const id = e.target.getAttribute("id");
showHassioMarkdownDialog(this, {

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20190419.0",
version="20190424.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",

View File

@ -14,6 +14,7 @@ import {
} from "lit-element";
import { HomeAssistant } from "../../types";
import { HassEntity } from "home-assistant-js-websocket";
import { forwardHaptic } from "../../util/haptics";
const isOn = (stateObj?: HassEntity) =>
stateObj !== undefined && !STATES_OFF.includes(stateObj.state);
@ -89,6 +90,7 @@ class HaEntityToggle extends LitElement {
if (!this.hass || !this.stateObj) {
return;
}
forwardHaptic(this, "light");
const stateDomain = computeStateDomain(this.stateObj);
let serviceDomain;
let service;

View File

@ -18,6 +18,10 @@ import isComponentLoaded from "../common/config/is_component_loaded";
import { HomeAssistant, PanelInfo } from "../types";
import { fireEvent } from "../common/dom/fire_event";
import { DEFAULT_PANEL } from "../common/const";
import {
getExternalConfig,
ExternalConfig,
} from "../external_app/external_config";
const computeUrl = (urlPath) => `/${urlPath}`;
@ -69,6 +73,7 @@ class HaSidebar extends LitElement {
@property() public hass?: HomeAssistant;
@property() public _defaultPage?: string =
localStorage.defaultPage || DEFAULT_PANEL;
@property() private _externalConfig?: ExternalConfig;
protected render() {
const hass = this.hass;
@ -117,6 +122,27 @@ class HaSidebar extends LitElement {
</a>
`
)}
${this._externalConfig && this._externalConfig.hasSettingsScreen
? html`
<a
href="#external-app-configuration"
tabindex="-1"
@click=${this._handleExternalAppConfiguration}
>
<paper-icon-item>
<ha-icon
slot="item-icon"
icon="hass:cellphone-settings-variant"
></ha-icon>
<span class="item-text"
>${hass.localize(
"ui.sidebar.external_app_configuration"
)}</span
>
</paper-icon-item>
</a>
`
: ""}
${!hass.user
? html`
<paper-icon-item @click=${this._handleLogOut} class="logout">
@ -193,6 +219,9 @@ class HaSidebar extends LitElement {
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
if (changedProps.has("_externalConfig")) {
return true;
}
if (!this.hass || !changedProps.has("hass")) {
return false;
}
@ -210,10 +239,26 @@ class HaSidebar extends LitElement {
);
}
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
if (this.hass && this.hass.auth.external) {
getExternalConfig(this.hass.auth.external).then((conf) => {
this._externalConfig = conf;
});
}
}
private _handleLogOut() {
fireEvent(this, "hass-logout");
}
private _handleExternalAppConfiguration(ev: Event) {
ev.preventDefault();
this.hass!.auth.external!.fireMessage({
type: "config_screen/show",
});
}
static get styles(): CSSResult {
return css`
:host {
@ -259,7 +304,7 @@ class HaSidebar extends LitElement {
--paper-item-min-height: 40px;
}
a ha-icon {
ha-icon[slot="item-icon"] {
color: var(--sidebar-icon-color);
}

View File

@ -26,7 +26,7 @@ const isExternal = location.search.includes("external_auth=1");
const authProm = isExternal
? () =>
import(/* webpackChunkName: "external_auth" */ "../common/auth/external_auth").then(
import(/* webpackChunkName: "external_auth" */ "../external_app/external_auth").then(
(mod) => new mod.default(hassUrl)
)
: () =>

View File

@ -2,6 +2,7 @@
* Auth class that connects to a native app for authentication.
*/
import { Auth } from "home-assistant-js-websocket";
import { ExternalMessaging, InternalMessage } from "./external_messaging";
const CALLBACK_SET_TOKEN = "externalAuthSetToken";
const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken";
@ -20,6 +21,7 @@ declare global {
externalApp?: {
getExternalAuth(payload: string);
revokeExternalAuth(payload: string);
externalBus(payload: string);
};
webkit?: {
messageHandlers: {
@ -29,6 +31,9 @@ declare global {
revokeExternalAuth: {
postMessage(payload: BasePayload);
};
externalBus: {
postMessage(payload: InternalMessage);
};
};
};
}
@ -41,6 +46,8 @@ if (!window.externalApp && !window.webkit) {
}
export default class ExternalAuth extends Auth {
public external = new ExternalMessaging();
constructor(hassUrl) {
super({
hassUrl,
@ -51,19 +58,10 @@ export default class ExternalAuth extends Auth {
// This will trigger connection to do a refresh right away
expires: 0,
});
this.external.attach();
}
public async refreshAccessToken() {
const responseProm = new Promise<RefreshTokenResponse>(
(resolve, reject) => {
window[CALLBACK_SET_TOKEN] = (success, data) =>
success ? resolve(data) : reject(data);
}
);
// Allow promise to set resolve on window object.
await 0;
const callbackPayload = { callback: CALLBACK_SET_TOKEN };
if (window.externalApp) {
@ -74,21 +72,18 @@ export default class ExternalAuth extends Auth {
);
}
const tokens = await responseProm;
const tokens = await new Promise<RefreshTokenResponse>(
(resolve, reject) => {
window[CALLBACK_SET_TOKEN] = (success, data) =>
success ? resolve(data) : reject(data);
}
);
this.data.access_token = tokens.access_token;
this.data.expires = tokens.expires_in * 1000 + Date.now();
}
public async revoke() {
const responseProm = new Promise((resolve, reject) => {
window[CALLBACK_REVOKE_TOKEN] = (success, data) =>
success ? resolve(data) : reject(data);
});
// Allow promise to set resolve on window object.
await 0;
const callbackPayload = { callback: CALLBACK_REVOKE_TOKEN };
if (window.externalApp) {
@ -99,6 +94,9 @@ export default class ExternalAuth extends Auth {
);
}
await responseProm;
await new Promise((resolve, reject) => {
window[CALLBACK_REVOKE_TOKEN] = (success, data) =>
success ? resolve(data) : reject(data);
});
}
}

View File

@ -0,0 +1,16 @@
import { ExternalMessaging } from "./external_messaging";
export interface ExternalConfig {
hasSettingsScreen: boolean;
}
export const getExternalConfig = (
bus: ExternalMessaging
): Promise<ExternalConfig> => {
if (!bus.cache.cfg) {
bus.cache.cfg = bus.sendMessage<ExternalConfig>({
type: "config/get",
});
}
return bus.cache.cfg;
};

View File

@ -0,0 +1,15 @@
import { ExternalMessaging } from "./external_messaging";
export const externalForwardConnectionEvents = (bus: ExternalMessaging) => {
document.addEventListener("connection-status", (ev) =>
bus.fireMessage({
type: "connection-status",
payload: { event: ev.detail },
})
);
};
export const externalForwardHaptics = (bus: ExternalMessaging) =>
document.addEventListener("haptic", (ev) =>
bus.fireMessage({ type: "haptic", payload: { hapticType: ev.detail } })
);

View File

@ -0,0 +1,111 @@
import {
externalForwardConnectionEvents,
externalForwardHaptics,
} from "./external_events_forwarder";
const CALLBACK_EXTERNAL_BUS = "externalBus";
interface CommandInFlight {
resolve: (data: any) => void;
reject: (err: ExternalError) => void;
}
export interface InternalMessage {
id?: number;
type: string;
payload?: unknown;
}
interface ExternalError {
code: string;
message: string;
}
interface ExternalMessageResult {
id: number;
type: "result";
success: true;
result: unknown;
}
interface ExternalMessageResultError {
id: number;
type: "result";
success: false;
error: ExternalError;
}
type ExternalMessage = ExternalMessageResult | ExternalMessageResultError;
export class ExternalMessaging {
public commands: { [msgId: number]: CommandInFlight } = {};
public cache: { [key: string]: any } = {};
public msgId = 0;
public attach() {
externalForwardConnectionEvents(this);
externalForwardHaptics(this);
window[CALLBACK_EXTERNAL_BUS] = (msg) => this.receiveMessage(msg);
}
/**
* Send message to external app that expects a response.
* @param msg message to send
*/
public sendMessage<T>(msg: InternalMessage): Promise<T> {
const msgId = ++this.msgId;
msg.id = msgId;
this.fireMessage(msg);
return new Promise<T>((resolve, reject) => {
this.commands[msgId] = { resolve, reject };
});
}
/**
* Send message to external app without expecting a response.
* @param msg message to send
*/
public fireMessage(msg: InternalMessage) {
if (!msg.id) {
msg.id = ++this.msgId;
}
this._sendExternal(msg);
}
public receiveMessage(msg: ExternalMessage) {
if (__DEV__) {
// tslint:disable-next-line: no-console
console.log("Receiving message from external app", msg);
}
const pendingCmd = this.commands[msg.id];
if (!pendingCmd) {
// tslint:disable-next-line: no-console
console.warn(`Received unknown msg ID`, msg.id);
return;
}
if (msg.type === "result") {
if (msg.success) {
pendingCmd.resolve(msg.result);
} else {
pendingCmd.reject(msg.error);
}
}
}
protected _sendExternal(msg: InternalMessage) {
if (__DEV__) {
// tslint:disable-next-line: no-console
console.log("Sending message to external app", msg);
}
if (window.externalApp) {
window.externalApp.externalBus(JSON.stringify(msg));
} else {
window.webkit!.messageHandlers.externalBus.postMessage(msg);
}
}
}

View File

@ -16,6 +16,8 @@ import { getLocalLanguage } from "../../util/hass-translation";
import { fetchWithAuth } from "../../util/fetch-with-auth";
import hassCallApi from "../../util/hass-call-api";
import { subscribePanels } from "../../data/ws-panels";
import { forwardHaptic } from "../../util/haptics";
import { fireEvent } from "../../common/dom/fire_event";
export default (superClass) =>
class extends EventsMixin(LocalizeMixin(superClass)) {
@ -75,6 +77,7 @@ export default (superClass) =>
err
);
}
forwardHaptic(this, "error");
const message =
this.hass.localize(
"ui.notification_toast.service_call_failed",
@ -126,11 +129,16 @@ export default (superClass) =>
const conn = this.hass.connection;
fireEvent(document, "connection-status", "connected");
conn.addEventListener("ready", () => this.hassReconnected());
conn.addEventListener("disconnected", () => this.hassDisconnected());
// If we reconnect after losing connection and auth is no longer valid.
conn.addEventListener("reconnect-error", (_conn, err) => {
if (err === ERR_INVALID_AUTH) location.reload();
if (err === ERR_INVALID_AUTH) {
fireEvent(document, "connection-status", "auth-invalid");
location.reload();
}
});
subscribeEntities(conn, (states) => this._updateHass({ states }));
@ -142,10 +150,12 @@ export default (superClass) =>
hassReconnected() {
super.hassReconnected();
this._updateHass({ connected: true });
fireEvent(document, "connection-status", "connected");
}
hassDisconnected() {
super.hassDisconnected();
this._updateHass({ connected: false });
fireEvent(document, "connection-status", "disconnected");
}
};

View File

@ -64,7 +64,7 @@ class HaConfigManagerDashboard extends LocalizeMixin(
<template is="dom-repeat" items="[[progress]]">
<div class="config-entry-row">
<paper-item-body>
[[_computeIntegrationTitle(localize, item.handler)]]
[[_computeActiveFlowTitle(localize, item)]]
</paper-item-body>
<mwc-button on-click="_continueFlow"
>[[localize('ui.panel.config.integrations.configure')]]</mwc-button
@ -190,6 +190,23 @@ class HaConfigManagerDashboard extends LocalizeMixin(
return localize(`component.${integration}.config.title`);
}
_computeActiveFlowTitle(localize, integration) {
const placeholders = integration.context.title_placeholders || {};
const placeholderKeys = Object.keys(placeholders);
if (placeholderKeys.length === 0) {
return localize(`component.${integration.handler}.config.title`);
}
const args = [];
placeholderKeys.forEach((key) => {
args.push(key);
args.push(placeholders[key]);
});
return localize(
`component.${integration.handler}.config.flow_title`,
...args
);
}
_computeConfigEntryEntities(hass, configEntry, entities) {
if (!entities) {
return [];

View File

@ -13,6 +13,7 @@ import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-tog
import { DOMAINS_TOGGLE } from "../../../common/const";
import { turnOnOffEntities } from "../common/entity/turn-on-off-entities";
import { HomeAssistant } from "../../../types";
import { forwardHaptic } from "../../../util/haptics";
@customElement("hui-entities-toggle")
class HuiEntitiesToggle extends LitElement {
@ -65,6 +66,7 @@ class HuiEntitiesToggle extends LitElement {
}
private _callService(ev: MouseEvent): void {
forwardHaptic(this, "light");
const turnOn = (ev.target as PaperToggleButtonElement).checked;
turnOnOffEntities(this.hass!, this._toggleEntities!, turnOn!);
}

View File

@ -22,6 +22,7 @@ import { HomeAssistant } from "../../../types";
import { EntityRow, EntityConfig } from "./types";
import { setOption } from "../../../data/input-select";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { forwardHaptic } from "../../../util/haptics";
@customElement("hui-input-select-entity-row")
class HuiInputSelectEntityRow extends LitElement implements EntityRow {
@ -97,6 +98,7 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
}
private _selectedChanged(ev): void {
forwardHaptic(this, "light");
// Selected Option will transition to '' before transitioning to new value
const stateObj = this.hass!.states[this._config!.entity];
if (

View File

@ -9,6 +9,8 @@ import {
HassServices,
} from "home-assistant-js-websocket";
import { LocalizeFunc } from "./common/translations/localize";
import { ExternalMessaging } from "./external_app/external_messaging";
import { HASSDomEvent } from "./common/dom/fire_event";
declare global {
var __DEV__: boolean;
@ -16,9 +18,7 @@ declare global {
var __BUILD__: "latest" | "es5";
var __VERSION__: string;
var __STATIC_PATH__: string;
}
declare global {
interface Window {
// Custom panel entry point url
customPanelJS: string;
@ -38,9 +38,16 @@ declare global {
value: unknown;
};
change: undefined;
"connection-status": ConnectionStatus;
}
interface GlobalEventHandlersEventMap {
"connection-status": HASSDomEvent<ConnectionStatus>;
}
}
type ConnectionStatus = "connected" | "auth-invalid" | "disconnected";
export interface WebhookError {
code: number;
message: string;
@ -109,7 +116,7 @@ export interface Resources {
}
export interface HomeAssistant {
auth: Auth;
auth: Auth & { external?: ExternalMessaging };
connection: Connection;
connected: boolean;
states: HassEntities;

32
src/util/haptics.ts Normal file
View File

@ -0,0 +1,32 @@
/**
* Utility function that enables haptic feedback
*/
import { fireEvent, HASSDomEvent } from "../common/dom/fire_event";
// Allowed types are from iOS HIG.
// https://developer.apple.com/design/human-interface-guidelines/ios/user-interaction/feedback/#haptics
// Implementors on platforms other than iOS should attempt to match the patterns (shown in HIG) as closely as possible.
export type HapticType =
| "success"
| "warning"
| "failure"
| "light"
| "medium"
| "heavy"
| "selection";
declare global {
// for fire event
interface HASSDomEvents {
haptic: HapticType;
}
interface GlobalEventHandlersEventMap {
haptic: HASSDomEvent<HapticType>;
}
}
export const forwardHaptic = (el: HTMLElement, hapticType: HapticType) => {
fireEvent(el, "haptic", hapticType);
};

View File

@ -0,0 +1,77 @@
import * as assert from "assert";
import {
ExternalMessaging,
InternalMessage,
} from "../../src/external_app/external_messaging";
// @ts-ignore
global.__DEV__ = true;
class MockExternalMessaging extends ExternalMessaging {
public mockSent: InternalMessage[] = [];
protected _sendExternal(msg: InternalMessage) {
this.mockSent.push(msg);
}
}
describe("ExternalMessaging", () => {
let bus: MockExternalMessaging;
beforeEach(() => {
bus = new MockExternalMessaging();
});
it("Send success results", async () => {
const sendMessageProm = bus.sendMessage({
type: "config/get",
});
assert.equal(bus.mockSent.length, 1);
assert.deepEqual(bus.mockSent[0], {
id: 1,
type: "config/get",
});
bus.receiveMessage({
id: 1,
type: "result",
success: true,
result: {
hello: "world",
},
});
const result = await sendMessageProm;
assert.deepEqual(result, {
hello: "world",
});
});
it("Send fail results", async () => {
const sendMessageProm = bus.sendMessage({
type: "config/get",
});
bus.receiveMessage({
id: 1,
type: "result",
success: false,
error: {
code: "no_auth",
message: "There is no authentication.",
},
});
try {
await sendMessageProm;
assert.fail("Should have raised");
} catch (err) {
assert.deepEqual(err, {
code: "no_auth",
message: "There is no authentication.",
});
}
});
});

View File

@ -307,7 +307,7 @@
"ui": {
"panel": {
"shopping-list": {
"clear_completed": "Suprimir completats",
"clear_completed": "Elimina els completats",
"add_item": "Afegir article",
"microphone_tip": "Clica el micròfon a la part superior dreta i digues \"Add candy to my shopping list\""
},
@ -322,8 +322,8 @@
"mailbox": {
"empty": "No tens missatges",
"playback_title": "Reproducció de missatges",
"delete_prompt": "Vols suprimir aquest missatge?",
"delete_button": "Suprimir"
"delete_prompt": "Vols eliminar aquest missatge?",
"delete_button": "Elimina"
},
"config": {
"header": "Configuració de Home Assistant",
@ -389,8 +389,8 @@
"introduction": "Els activadors són les regles que fan que es dispari un automatisme. Pots definir més d'un activador per a cada automatisme. Una vegada s'iniciï un activador, el Home Assistant validarà les condicions (si n'hi ha) i finalment cridarà l'acció.\n\n[Més informació sobre activadors](https:\/\/home-assistant.io\/docs\/automation\/trigger\/)",
"add": "Afegir activador",
"duplicate": "Duplicar",
"delete": "Suprimir",
"delete_confirm": "Segur que vols suprimir-ho?",
"delete": "Elimina",
"delete_confirm": "Segur que vols eliminar-ho?",
"unsupported_platform": "Plataforma {platform} no suportada.",
"type_select": "Tipus d'activador",
"type": {
@ -471,8 +471,8 @@
"introduction": "Les condicions són una part opcional d'un automatisme i es poden utilitzar per permetre o evitar que es produeixi l'acció quan s'activa l'automatisme. Les condicions són similars als activadors però funcionen de forma diferent: un activador analitza els esdeveniments que ocorren en el sistema mentre que una condició només observa com està el sistema en un moment determinat, per exemple, un activador se n'adonarà de quan s'està activant un interruptor mentre que una condició només pot veure si un interruptor està activat o desactivat.\n\n[Més informació sobre les condicions](https:\/\/home-assistant.io\/docs\/scripts\/conditions\/).",
"add": "Afegir condició",
"duplicate": "Duplicar",
"delete": "Suprimir",
"delete_confirm": "Segur que vols suprimir-ho?",
"delete": "Elimina",
"delete_confirm": "Segur que vols eliminar-ho?",
"unsupported_condition": "Condició {condition} no suportada.",
"type_select": "Tipus de condició",
"type": {
@ -517,8 +517,8 @@
"introduction": "Les accions són allò que farà Home Assistant quan s'activi l'automatisme i es compleixin totes les condicions (si n'hi ha).\n\n[Més informació sobre les accions](https:\/\/home-assistant.io\/docs\/automation\/action\/).",
"add": "Afegir acció",
"duplicate": "Duplicar",
"delete": "Suprimir",
"delete_confirm": "Segur que vols suprimir-ho?",
"delete": "Elimina",
"delete_confirm": "Segur que vols eliminar-ho?",
"unsupported_action": "Acció {action} no suportada.",
"type_select": "Tipus d'acció",
"type": {
@ -595,7 +595,7 @@
"config_entry": {
"no_devices": "Aquesta integració no té dispositius.",
"no_device": "Entitats sense dispositius",
"delete_confirm": "Estas segur que vols suprimir aquesta integració?",
"delete_confirm": "Estas segur que vols eliminar aquesta integració?",
"restart_confirm": "Reinicia el Home Assistant per acabar d'eliminar aquesta integració",
"manuf": "de {manufacturer}",
"hub": "Connectat a través de",
@ -639,7 +639,7 @@
"create_area": "CREA ÀREA",
"editor": {
"default_name": "Àrea nova",
"delete": "SUPRIMIR",
"delete": "ELIMINA",
"update": "ACTUALITZAR",
"create": "CREAR"
}
@ -657,7 +657,7 @@
"editor": {
"unavailable": "Aquesta entitat no està disponible actualment.",
"default_name": "Àrea nova",
"delete": "SUPRIMIR",
"delete": "ELIMINA",
"update": "ACTUALITZAR"
}
},
@ -697,19 +697,19 @@
"description": "Cada testimoni d'autenticació d'actualització representa un inici de sessió diferent. Els testimonis d'autenticació d'actualització s'eliminaran automàticament quan tanquis la sessió. A sota hi ha una llista de testimonis d'autenticació d'actualització que estan actius actualment al teu compte.",
"token_title": "Refresca el testimoni d'autenticació del {clientId}",
"created_at": "Creat el {date}",
"confirm_delete": "Estas segur que vols suprimir el testimoni d'autenticació d'actualització per {name}?",
"delete_failed": "No s'ha pogut suprimir el testimoni d'autenticació d'actualització.",
"confirm_delete": "Estas segur que vols eliminar el testimoni d'autenticació d'actualització per {name}?",
"delete_failed": "No s'ha pogut eliminar el testimoni d'autenticació d'actualització.",
"last_used": "Darrer ús el {date} des de {location}",
"not_used": "Mai no s'ha utilitzat",
"current_token_tooltip": "No s'ha pogut suprimir el testimoni d'autenticació d'actualització."
"current_token_tooltip": "No s'ha pogut eliminar el testimoni d'autenticació d'actualització."
},
"long_lived_access_tokens": {
"header": "Testimonis d'autenticació d'accés de llarga durada",
"description": "Crea testimonis d'autenticació d'accés de llarga durada per permetre als teus programes (scripts) interactuar amb la instància de Home Assistant. Cada testimoni d'autenticació serà vàlid durant deu anys després de la seva creació. Els següents testimonis d'autenticació d'accés de llarga durada estan actius actualment.",
"learn_auth_requests": "Aprèn a fer sol·licituds autenticades.",
"created_at": "Creat el {date}",
"confirm_delete": "Estàs segur que vols suprimir el testimoni d'autenticació d'accés per {name}?",
"delete_failed": "No s'ha pogut suprimir el testimoni d'autenticació d'accés.",
"confirm_delete": "Estàs segur que vols eliminar el testimoni d'autenticació d'accés per {name}?",
"delete_failed": "No s'ha pogut eliminar el testimoni d'autenticació d'accés.",
"create": "Crea un testimoni d'autenticació",
"create_failed": "No s'ha pogut crear el testimoni d'autenticació d'accés.",
"prompt_name": "Nom?",
@ -727,7 +727,7 @@
"new_password": "Contrasenya nova",
"confirm_new_password": "Confirmar contrasenya nova",
"error_required": "Obligatori",
"submit": "Enviar"
"submit": "Envia"
},
"mfa": {
"header": "Mòduls d'autenticació amb múltiples passos",
@ -740,7 +740,7 @@
"title_success": "Èxit!",
"step_done": "Configuració feta per a {step}",
"close": "Tanca",
"submit": "Enviar"
"submit": "Envia"
}
},
"page-authorize": {
@ -878,8 +878,8 @@
"pick_card": "Tria la targeta que vols afegir.",
"add": "Afegir targeta",
"edit": "Editar",
"delete": "Suprimir",
"move": "Moure"
"delete": "Elimina",
"move": "Mou"
},
"migrate": {
"header": "Configuració incompatible",
@ -892,7 +892,7 @@
"header": "Configuració de la visualització",
"add": "Afegeix visualització",
"edit": "Editar visualització",
"delete": "Suprimir visualització"
"delete": "Eliminar visualització"
},
"save_config": {
"header": "Pren el control de la interfície d'usuari Lovelace",
@ -929,7 +929,7 @@
},
"common": {
"loading": "Carregant",
"cancel": "Cancel·lar",
"cancel": "Cancel·la",
"save": "Desar"
},
"duration": {

View File

@ -581,7 +581,8 @@
"cloud": {
"caption": "Home Assistant Cloud",
"description_login": "Přihlášen jako {email}",
"description_not_login": "Nepřihlášen"
"description_not_login": "Nepřihlášen",
"description_features": "Ovládejte vzdáleně, integrace a Alexou a Google Asistent"
},
"integrations": {
"caption": "Integrace",
@ -613,7 +614,9 @@
"remove": "Odstranit zařízení ze sítě Zigbee."
},
"device_card": {
"area_picker_label": "Oblast"
"device_name_placeholder": "Zvolené jméno",
"area_picker_label": "Oblast",
"update_name_button": "Aktualizovat název"
},
"add_device_page": {
"header": "Zigbee Home Automation - Přidat zařízení",
@ -1175,6 +1178,7 @@
},
"groups": {
"system-admin": "Správci",
"system-users": "Uživatelé"
"system-users": "Uživatelé",
"system-read-only": "Uživatelé jen pro čtení"
}
}

View File

@ -518,7 +518,7 @@
"add": "Προσθήκη ενέργειας",
"duplicate": "Διπλότυπο",
"delete": "Διαγραφή",
"delete_confirm": "Επιθυμείτε την διαγραφή σιγουρα;",
"delete_confirm": "Επιθυμείτε την διαγραφή σίγουρα;",
"unsupported_action": "Μη υποστηριζόμενη ενέργεια: {action}",
"type_select": "Τύπος ενέργειας",
"type": {
@ -609,7 +609,7 @@
"caption": "ZHA",
"description": "Διαχείριση του δικτύου ZigBee Home Automation",
"services": {
"reconfigure": "Ρυθμίστε ξανά τη συσκευή ZHA (heal συσκευή). Χρησιμοποιήστε αυτήν την επιλογή εάν αντιμετωπίζετε ζητήματα με τη συσκευή. Εάν η συγκεκριμένη συσκευή τροφοδοτείται απο μπαταρία βεβαιωθείτε ότι είναι ενεργοποιημένη και δέχεται εντολές όταν χρησιμοποιείτε αυτή την υπηρεσία.",
"reconfigure": "Ρυθμίστε ξανά τη συσκευή ZHA (heal συσκευή). Χρησιμοποιήστε αυτήν την επιλογή εάν αντιμετωπίζετε ζητήματα με τη συσκευή. Εάν η συγκεκριμένη συσκευή τροφοδοτείται από μπαταρία βεβαιωθείτε ότι είναι ενεργοποιημένη και δέχεται εντολές όταν χρησιμοποιείτε αυτή την υπηρεσία.",
"updateDeviceName": "Ορίστε ένα προσαρμοσμένο όνομα γι αυτήν τη συσκευή στο μητρώο συσκευών.",
"remove": "Καταργήστε μια συσκευή από το δίκτυο ZigBee."
},
@ -629,8 +629,8 @@
"description": "Επισκόπηση όλων των περιοχών στο σπίτι σας.",
"picker": {
"header": "Περιοχή Μητρώου",
"introduction": "Οι περιοχές χρησιμοποιούνται για την οργάνωση της τοποθεσίας των συσκευών. Αυτές οι πληροφορίες θα χρησιμοποιηθούν σε όλο το Home Assistant για να σας βοηθήσουν στην οργάνωση της διασύνδεσης, των αδειών και των ενσωματώσεών σας σε άλλα συστήματα.",
"introduction2": "Για να τοποθετήσετε συσκευές σε μια περιοχή, χρησιμοποιήστε τον παρακάτω σύνδεσμο για να μεταβείτε στη σελίδα ενοποιήσεων και στη συνέχεια κάντε κλικ στην ρυθμισμένη ενωποίηση για να μεταβείτε στις κάρτες της συσκευής.",
"introduction": "Οι περιοχές χρησιμοποιούνται για την οργάνωση της τοποθεσίας των συσκευών. Αυτές οι πληροφορίες θα χρησιμοποιηθούν σε όλο το Home Assistant για να σας βοηθήσουν στην οργάνωση της διασύνδεσης, των αδειών και των ενσωματώσεων σας σε άλλα συστήματα.",
"introduction2": "Για να τοποθετήσετε συσκευές σε μια περιοχή, χρησιμοποιήστε τον παρακάτω σύνδεσμο για να μεταβείτε στη σελίδα ενοποιήσεων και στη συνέχεια κάντε κλικ στην ρυθμισμένη ενοποίηση για να μεταβείτε στις κάρτες της συσκευής.",
"integrations_page": "Σελίδα ενσωματώσεων",
"no_areas": "Φαίνεται ότι δεν έχετε ορίσει ακόμα κάποια περιοχή!",
"create_area": "ΔΗΜΙΟΥΡΓΙΑ ΠΕΡΙΟΧΗΣ"
@ -884,7 +884,7 @@
"migrate": {
"header": "Μη συμβατή διαμόρφωση",
"para_no_id": "Αυτό το στοιχείο δεν έχει αναγνωριστικό. Προσθέστε ένα αναγνωριστικό σε αυτό το στοιχείο στο 'ui-lovelace.yaml'.",
"para_migrate": "Ο Home Assistant μπορεί να προσθέσει αυτόματα όλα τα αναγνωριστικά απο τις κάρτες και τις προβολές σας πατώντας το πλήκτρο 'Ρυθμίσεις μετεγκατάστασης'.",
"para_migrate": "Ο Home Assistant μπορεί να προσθέσει αυτόματα όλα τα αναγνωριστικά από τις κάρτες και τις προβολές σας πατώντας το πλήκτρο 'Ρυθμίσεις μετεγκατάστασης'.",
"migrate": "Ρυθμίσεις μετεγκατάστασης"
},
"header": "Επεξεργασία περιβάλλοντος χρήστη",
@ -896,7 +896,7 @@
},
"save_config": {
"header": "Πάρτε τον έλεγχο του περιβάλλοντος χρήστη στο Lovelace",
"para": "Από προεπιλογή, το Home Assistant θα διατηρήσει το περιβάλλον χρήστη που έχετε ενημερώνοντάς το όταν θα γίνονται διαθέσιμες νέες οντότητες ή στοιχεία Lovelace. Αν πάρετε τον έλεγχο, δεν θα πραγματοποιούμε πλέον αλλαγές για εσάς.",
"para": "Από προεπιλογή, το Home Assistant θα διατηρήσει το περιβάλλον χρήστη που έχετε ενημερώνοντας το όταν θα γίνονται διαθέσιμες νέες οντότητες ή στοιχεία Lovelace. Αν πάρετε τον έλεγχο, δεν θα πραγματοποιούμε πλέον αλλαγές για εσάς.",
"para_sure": "Είστε βέβαιος ότι θέλετε να πάρετε τον έλεγχο του περιβάλλοντος χρήστη;",
"cancel": "Δεν πειράζει",
"save": "Πάρτε τον έλεγχο"
@ -1088,7 +1088,7 @@
"dialogs": {
"more_info_settings": {
"save": "Αποθήκευση",
"name": "Επιβολή Oνόματος",
"name": "Επιβολή Ονόματος",
"entity_id": "Αναγνωριστικό οντότητας"
},
"more_info_control": {

View File

@ -202,7 +202,8 @@
"not_home": "Fjarverandi"
},
"default": {
"error": "Villa"
"error": "Villa",
"entity_not_found": "Eining fannst ekki"
}
},
"ui": {
@ -327,6 +328,7 @@
},
"geo_location": {
"source": "Uppruni",
"zone": "Öryggissvæði",
"event": "Viðburður:",
"enter": "Koma",
"leave": "Brottför"
@ -425,7 +427,8 @@
"cloud": {
"caption": "Home Assistant skýið",
"description_login": "Innskráð(ur) sem {email}",
"description_not_login": "Ekki skráð(ur) inn"
"description_not_login": "Ekki skráð(ur) inn",
"description_features": "Stjórna heimili að heiman með Alexa og Google Assistant."
},
"integrations": {
"caption": "Samþættingar",
@ -444,7 +447,8 @@
"hub": "Tengt gegnum",
"firmware": "Fastbúnaðarútgáfa: {version}",
"device_unavailable": "Tæki ekki tiltækt",
"entity_unavailable": "Eining ekki tiltæk"
"entity_unavailable": "Eining ekki tiltæk",
"no_area": "Ekkert svæði"
}
},
"zha": {
@ -456,6 +460,7 @@
},
"device_card": {
"device_name_placeholder": "Nafn notanda",
"area_picker_label": "Svæði",
"update_name_button": "Uppfæra nafn"
},
"add_device_page": {
@ -465,14 +470,20 @@
}
},
"area_registry": {
"caption": "Svæðaskrá",
"description": "Yfirlit yfir öll svæði á heimilinu þínu.",
"no_areas": "Það virðist sem að þú hafir engin svæði ennþá!",
"create_area": "BÚA TIL SVÆÐI",
"editor": {
"default_name": "Nýtt svæði",
"delete": "EYÐA",
"update": "UPPFÆRA",
"create": "STOFNA"
},
"picker": {
"integrations_page": "Samþættingar síða"
"integrations_page": "Samþættingar síða",
"no_areas": "Það virðist sem að þú hafir engin svæði ennþá!",
"create_area": "BÚA TIL SVÆÐI"
}
},
"entity_registry": {
@ -485,6 +496,7 @@
},
"editor": {
"unavailable": "Þessi eining er ekki tiltæk eins og er.",
"default_name": "Nýtt svæði",
"delete": "EYÐA",
"update": "UPPFÆRA"
}

View File

@ -441,7 +441,7 @@
"label": "Strefa",
"entity": "Encja z lokalizacją",
"zone": "Strefa",
"event": "Zdarzenie",
"event": "Zdarzenie:",
"enter": "Wprowadź",
"leave": "Opuść"
},
@ -1088,7 +1088,7 @@
"dialogs": {
"more_info_settings": {
"save": "Zapisz",
"name": "Nazwa",
"name": "Nadpisanie nazwy",
"entity_id": "Identyfikator encji"
},
"more_info_control": {

View File

@ -581,7 +581,8 @@
"cloud": {
"caption": "Home Assistant Cloud",
"description_login": "Inloggad som {email}",
"description_not_login": "Inte inloggad"
"description_not_login": "Inte inloggad",
"description_features": "Styra även när du inte är hemma, integrera med Alexa och Google Assistant."
},
"integrations": {
"caption": "Integrationer",
@ -860,7 +861,7 @@
"cards": {
"shopping-list": {
"checked_items": "Markerade objekt",
"clear_items": "Rensa avbockade objekt",
"clear_items": "Rensa markerade objekt",
"add_item": "Lägg till objekt"
},
"empty_state": {
@ -881,7 +882,7 @@
"move": "Flytta"
},
"migrate": {
"header": "Konfigurationen är inte giltig",
"header": "Ogiltig konfiguration",
"para_no_id": "Det här elementet har inget ID. Lägg till ett ID till det här elementet i \"ui-lovelace.yaml\".",
"para_migrate": "Home Assistant kan automatiskt lägga till ID till alla dina kort och vyer genom att du klickar på \"Migrera konfiguration\".",
"migrate": "Migrera konfigurationen"
@ -894,7 +895,7 @@
"delete": "Radera vy"
},
"save_config": {
"header": "Ta kontroll över ditt Lovelace användargränssnitt",
"header": "Ta kontroll över Lovelace UI",
"para": "Home Assistant kommer som standard att behålla ditt användargränssnitt, uppdatera det när nya enheter eller Lovelace-komponenter blir tillgängliga. Om du tar kontroll kommer vi inte längre göra ändringar automatiskt för dig.",
"para_sure": "Är du säker på att du vill ta kontroll över ditt användargränssnitt?",
"cancel": "Glöm det",