diff --git a/setup.py b/setup.py index b3932bd0f7..25742ee198 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20210402.1", + version="20210406.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/home-assistant-polymer", author="The Home Assistant Authors", diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 0ba0c36cb2..d4e77ce4e9 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -68,8 +68,12 @@ export const computeStateDisplay = ( } } - // `counter` and `number` domains do not have a unit of measurement but should still use `formatNumber` - if (domain === "counter" || domain === "number") { + // `counter` `number` and `input_number` domains do not have a unit of measurement but should still use `formatNumber` + if ( + domain === "counter" || + domain === "number" || + domain === "input_number" + ) { return formatNumber(compareState, locale); } diff --git a/src/common/string/filter/filter.ts b/src/common/string/filter/filter.ts index d3471433ab..ff42c20393 100644 --- a/src/common/string/filter/filter.ts +++ b/src/common/string/filter/filter.ts @@ -34,14 +34,12 @@ const _maxLen = 128; function initTable() { const table: number[][] = []; - const row: number[] = [0]; - for (let i = 1; i <= _maxLen; i++) { - row.push(-i); + const row: number[] = []; + for (let i = 0; i <= _maxLen; i++) { + row[i] = 0; } for (let i = 0; i <= _maxLen; i++) { - const thisRow = row.slice(0); - thisRow[0] = -i; - table.push(thisRow); + table.push(row.slice(0)); } return table; } @@ -50,7 +48,7 @@ function isSeparatorAtPos(value: string, index: number): boolean { if (index < 0 || index >= value.length) { return false; } - const code = value.charCodeAt(index); + const code = value.codePointAt(index); switch (code) { case CharCode.Underline: case CharCode.Dash: @@ -62,8 +60,16 @@ function isSeparatorAtPos(value: string, index: number): boolean { case CharCode.DoubleQuote: case CharCode.Colon: case CharCode.DollarSign: + case CharCode.LessThan: + case CharCode.OpenParen: + case CharCode.OpenSquareBracket: return true; + case undefined: + return false; default: + if (isEmojiImprecise(code)) { + return true; + } return false; } } @@ -92,10 +98,15 @@ function isPatternInWord( patternLen: number, wordLow: string, wordPos: number, - wordLen: number + wordLen: number, + fillMinWordPosArr = false ): boolean { while (patternPos < patternLen && wordPos < wordLen) { if (patternLow[patternPos] === wordLow[wordPos]) { + if (fillMinWordPosArr) { + // Remember the min word position for each pattern position + _minWordMatchPos[patternPos] = wordPos; + } patternPos += 1; } wordPos += 1; @@ -104,42 +115,22 @@ function isPatternInWord( } enum Arrow { - Top = 0b1, - Diag = 0b10, - Left = 0b100, + Diag = 1, + Left = 2, + LeftLeft = 3, } /** - * A tuple of three values. + * An array representating a fuzzy match. + * * 0. the score - * 1. the matches encoded as bitmask (2^53) - * 2. the offset at which matching started + * 1. the offset at which matching started + * 2. `` + * 3. `` + * 4. `` etc */ -export type FuzzyScore = [number, number, number]; - -interface FilterGlobals { - _matchesCount: number; - _topMatch2: number; - _topScore: number; - _wordStart: number; - _firstMatchCanBeWeak: boolean; - _table: number[][]; - _scores: number[][]; - _arrows: Arrow[][]; -} - -function initGlobals(): FilterGlobals { - return { - _matchesCount: 0, - _topMatch2: 0, - _topScore: 0, - _wordStart: 0, - _firstMatchCanBeWeak: false, - _table: initTable(), - _scores: initTable(), - _arrows: initTable(), - }; -} +// export type FuzzyScore = [score: number, wordStart: number, ...matches: number[]];// [number, number, number]; +export type FuzzyScore = Array; export function fuzzyScore( pattern: string, @@ -150,7 +141,6 @@ export function fuzzyScore( wordStart: number, firstMatchCanBeWeak: boolean ): FuzzyScore | undefined { - const globals = initGlobals(); const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length; const wordLen = word.length > _maxLen ? _maxLen : word.length; @@ -172,18 +162,30 @@ export function fuzzyScore( patternLen, wordLow, wordStart, - wordLen + wordLen, + true ) ) { return undefined; } + // Find the max matching word position for each pattern position + // NOTE: the min matching word position was filled in above, in the `isPatternInWord` call + _fillInMaxWordMatchPos( + patternLen, + wordLen, + patternStart, + wordStart, + patternLow, + wordLow + ); + let row = 1; let column = 1; let patternPos = patternStart; let wordPos = wordStart; - let hasStrongFirstMatch = false; + const hasStrongFirstMatch = [false]; // There will be a match, fill in tables for ( @@ -191,83 +193,146 @@ export function fuzzyScore( patternPos < patternLen; row++, patternPos++ ) { + // Reduce search space to possible matching word positions and to possible access from next row + const minWordMatchPos = _minWordMatchPos[patternPos]; + const maxWordMatchPos = _maxWordMatchPos[patternPos]; + const nextMaxWordMatchPos = + patternPos + 1 < patternLen ? _maxWordMatchPos[patternPos + 1] : wordLen; + for ( - column = 1, wordPos = wordStart; - wordPos < wordLen; + column = minWordMatchPos - wordStart + 1, wordPos = minWordMatchPos; + wordPos < nextMaxWordMatchPos; column++, wordPos++ ) { - const score = _doScore( - pattern, - patternLow, - patternPos, - patternStart, - word, - wordLow, - wordPos - ); + let score = Number.MIN_SAFE_INTEGER; + let canComeDiag = false; - if (patternPos === patternStart && score > 1) { - hasStrongFirstMatch = true; + if (wordPos <= maxWordMatchPos) { + score = _doScore( + pattern, + patternLow, + patternPos, + patternStart, + word, + wordLow, + wordPos, + wordLen, + wordStart, + _diag[row - 1][column - 1] === 0, + hasStrongFirstMatch + ); } - globals._scores[row][column] = score; + let diagScore = 0; + if (score !== Number.MAX_SAFE_INTEGER) { + canComeDiag = true; + diagScore = score + _table[row - 1][column - 1]; + } - const diag = - globals._table[row - 1][column - 1] + (score > 1 ? 1 : score); - const top = globals._table[row - 1][column] + -1; - const left = globals._table[row][column - 1] + -1; + const canComeLeft = wordPos > minWordMatchPos; + const leftScore = canComeLeft + ? _table[row][column - 1] + (_diag[row][column - 1] > 0 ? -5 : 0) + : 0; // penalty for a gap start - if (left >= top) { - // left or diag - if (left > diag) { - globals._table[row][column] = left; - globals._arrows[row][column] = Arrow.Left; - } else if (left === diag) { - globals._table[row][column] = left; - globals._arrows[row][column] = Arrow.Left || Arrow.Diag; - } else { - globals._table[row][column] = diag; - globals._arrows[row][column] = Arrow.Diag; - } - } else if (top > diag) { - globals._table[row][column] = top; - globals._arrows[row][column] = Arrow.Top; - } else if (top === diag) { - globals._table[row][column] = top; - globals._arrows[row][column] = Arrow.Top || Arrow.Diag; + const canComeLeftLeft = + wordPos > minWordMatchPos + 1 && _diag[row][column - 1] > 0; + const leftLeftScore = canComeLeftLeft + ? _table[row][column - 2] + (_diag[row][column - 2] > 0 ? -5 : 0) + : 0; // penalty for a gap start + + if ( + canComeLeftLeft && + (!canComeLeft || leftLeftScore >= leftScore) && + (!canComeDiag || leftLeftScore >= diagScore) + ) { + // always prefer choosing left left to jump over a diagonal because that means a match is earlier in the word + _table[row][column] = leftLeftScore; + _arrows[row][column] = Arrow.LeftLeft; + _diag[row][column] = 0; + } else if (canComeLeft && (!canComeDiag || leftScore >= diagScore)) { + // always prefer choosing left since that means a match is earlier in the word + _table[row][column] = leftScore; + _arrows[row][column] = Arrow.Left; + _diag[row][column] = 0; + } else if (canComeDiag) { + _table[row][column] = diagScore; + _arrows[row][column] = Arrow.Diag; + _diag[row][column] = _diag[row - 1][column - 1] + 1; } else { - globals._table[row][column] = diag; - globals._arrows[row][column] = Arrow.Diag; + throw new Error(`not possible`); } } } if (_debug) { - printTables(pattern, patternStart, word, wordStart, globals); + printTables(pattern, patternStart, word, wordStart); } - if (!hasStrongFirstMatch && !firstMatchCanBeWeak) { + if (!hasStrongFirstMatch[0] && !firstMatchCanBeWeak) { return undefined; } - globals._matchesCount = 0; - globals._topScore = -100; - globals._wordStart = wordStart; - globals._firstMatchCanBeWeak = firstMatchCanBeWeak; + row--; + column--; - _findAllMatches2( - row - 1, - column - 1, - patternLen === wordLen ? 1 : 0, - 0, - false, - globals - ); - if (globals._matchesCount === 0) { - return undefined; + const result: FuzzyScore = [_table[row][column], wordStart]; + + let backwardsDiagLength = 0; + let maxMatchColumn = 0; + + while (row >= 1) { + // Find the column where we go diagonally up + let diagColumn = column; + do { + const arrow = _arrows[row][diagColumn]; + if (arrow === Arrow.LeftLeft) { + diagColumn -= 2; + } else if (arrow === Arrow.Left) { + diagColumn -= 1; + } else { + // found the diagonal + break; + } + } while (diagColumn >= 1); + + // Overturn the "forwards" decision if keeping the "backwards" diagonal would give a better match + if ( + backwardsDiagLength > 1 && // only if we would have a contiguous match of 3 characters + patternLow[patternStart + row - 1] === wordLow[wordStart + column - 1] && // only if we can do a contiguous match diagonally + !isUpperCaseAtPos(diagColumn + wordStart - 1, word, wordLow) && // only if the forwards chose diagonal is not an uppercase + backwardsDiagLength + 1 > _diag[row][diagColumn] // only if our contiguous match would be longer than the "forwards" contiguous match + ) { + diagColumn = column; + } + + if (diagColumn === column) { + // this is a contiguous match + backwardsDiagLength++; + } else { + backwardsDiagLength = 1; + } + + if (!maxMatchColumn) { + // remember the last matched column + maxMatchColumn = diagColumn; + } + + row--; + column = diagColumn - 1; + result.push(column); } - return [globals._topScore, globals._topMatch2, wordStart]; + if (wordLen === patternLen) { + // the word matches the pattern with all characters! + // giving the score a total match boost (to come up ahead other words) + result[0] += 2; + } + + // Add 1 penalty for each skipped character in the word + const skippedCharsCount = maxMatchColumn - patternLen; + result[0] -= skippedCharsCount; + + return result; } function _doScore( @@ -277,50 +342,81 @@ function _doScore( patternStart: number, word: string, wordLow: string, - wordPos: number -) { + wordPos: number, + wordLen: number, + wordStart: number, + newMatchStart: boolean, + outFirstMatchStrong: boolean[] +): number { if (patternLow[patternPos] !== wordLow[wordPos]) { - return -1; + return Number.MIN_SAFE_INTEGER; } + + let score = 1; + let isGapLocation = false; if (wordPos === patternPos - patternStart) { // common prefix: `foobar <-> foobaz` // ^^^^^ - if (pattern[patternPos] === word[wordPos]) { - return 7; - } - return 5; - } - - if ( + score = pattern[patternPos] === word[wordPos] ? 7 : 5; + } else if ( isUpperCaseAtPos(wordPos, word, wordLow) && (wordPos === 0 || !isUpperCaseAtPos(wordPos - 1, word, wordLow)) ) { // hitting upper-case: `foo <-> forOthers` // ^^ ^ - if (pattern[patternPos] === word[wordPos]) { - return 7; - } - return 5; - } - - if ( + score = pattern[patternPos] === word[wordPos] ? 7 : 5; + isGapLocation = true; + } else if ( isSeparatorAtPos(wordLow, wordPos) && (wordPos === 0 || !isSeparatorAtPos(wordLow, wordPos - 1)) ) { // hitting a separator: `. <-> foo.bar` // ^ - return 5; - } - - if ( + score = 5; + } else if ( isSeparatorAtPos(wordLow, wordPos - 1) || isWhitespaceAtPos(wordLow, wordPos - 1) ) { // post separator: `foo <-> bar_foo` // ^^^ - return 5; + score = 5; + isGapLocation = true; } - return 1; + + if (score > 1 && patternPos === patternStart) { + outFirstMatchStrong[0] = true; + } + + if (!isGapLocation) { + isGapLocation = + isUpperCaseAtPos(wordPos, word, wordLow) || + isSeparatorAtPos(wordLow, wordPos - 1) || + isWhitespaceAtPos(wordLow, wordPos - 1); + } + + // + if (patternPos === patternStart) { + // first character in pattern + if (wordPos > wordStart) { + // the first pattern character would match a word character that is not at the word start + // so introduce a penalty to account for the gap preceding this match + score -= isGapLocation ? 3 : 5; + } + } else if (newMatchStart) { + // this would be the beginning of a new match (i.e. there would be a gap before this location) + score += isGapLocation ? 2 : 0; + } else { + // this is part of a contiguous match, so give it a slight bonus, but do so only if it would not be a prefered gap location + score += isGapLocation ? 0 : 1; + } + + if (wordPos + 1 === wordLen) { + // we always penalize gaps, but this gives unfair advantages to a match that would match the last character in the word + // so pretend there is a gap after the last character in the word to normalize things + score -= isGapLocation ? 3 : 5; + } + + return score; } function printTable( @@ -360,104 +456,96 @@ function printTables( pattern: string, patternStart: number, word: string, - wordStart: number, - globals: FilterGlobals + wordStart: number ): void { pattern = pattern.substr(patternStart); word = word.substr(wordStart); - console.log( - printTable(globals._table, pattern, pattern.length, word, word.length) - ); - console.log( - printTable(globals._arrows, pattern, pattern.length, word, word.length) - ); - console.log( - printTable(globals._scores, pattern, pattern.length, word, word.length) - ); + console.log(printTable(_table, pattern, pattern.length, word, word.length)); + console.log(printTable(_arrows, pattern, pattern.length, word, word.length)); + console.log(printTable(_diag, pattern, pattern.length, word, word.length)); } -function _findAllMatches2( - row: number, - column: number, - total: number, - matches: number, - lastMatched: boolean, - globals: FilterGlobals -): void { - if (globals._matchesCount >= 10 || total < -25) { - // stop when having already 10 results, or - // when a potential alignment as already 5 gaps - return; +const _minWordMatchPos = initArr(2 * _maxLen); // min word position for a certain pattern position +const _maxWordMatchPos = initArr(2 * _maxLen); // max word position for a certain pattern position +const _diag = initTable(); // the length of a contiguous diagonal match +const _table = initTable(); +const _arrows = initTable(); + +function initArr(maxLen: number) { + const row: number[] = []; + for (let i = 0; i <= maxLen; i++) { + row[i] = 0; } + return row; +} - let simpleMatchCount = 0; +function _fillInMaxWordMatchPos( + patternLen: number, + wordLen: number, + patternStart: number, + wordStart: number, + patternLow: string, + wordLow: string +) { + let patternPos = patternLen - 1; + let wordPos = wordLen - 1; + while (patternPos >= patternStart && wordPos >= wordStart) { + if (patternLow[patternPos] === wordLow[wordPos]) { + _maxWordMatchPos[patternPos] = wordPos; + patternPos--; + } + wordPos--; + } +} - while (row > 0 && column > 0) { - const score = globals._scores[row][column]; - const arrow = globals._arrows[row][column]; +export interface FuzzyScorer { + ( + pattern: string, + lowPattern: string, + patternPos: number, + word: string, + lowWord: string, + wordPos: number, + firstMatchCanBeWeak: boolean + ): FuzzyScore | undefined; +} - if (arrow === Arrow.Left) { - // left -> no match, skip a word character - column -= 1; - if (lastMatched) { - total -= 5; // new gap penalty - } else if (matches !== 0) { - total -= 1; // gap penalty after first match - } - lastMatched = false; - simpleMatchCount = 0; - } else if (arrow && Arrow.Diag) { - if (arrow && Arrow.Left) { - // left - _findAllMatches2( - row, - column - 1, - matches !== 0 ? total - 1 : total, // gap penalty after first match - matches, - lastMatched, - globals - ); - } - - // diag - total += score; - row -= 1; - column -= 1; - lastMatched = true; - - // match -> set a 1 at the word pos - matches += 2 ** (column + globals._wordStart); - - // count simple matches and boost a row of - // simple matches when they yield in a - // strong match. - if (score === 1) { - simpleMatchCount += 1; - - if (row === 0 && !globals._firstMatchCanBeWeak) { - // when the first match is a weak - // match we discard it - return; - } - } else { - // boost - total += 1 + simpleMatchCount * (score - 1); - simpleMatchCount = 0; - } +export function createMatches(score: undefined | FuzzyScore): Match[] { + if (typeof score === "undefined") { + return []; + } + const res: Match[] = []; + const wordPos = score[1]; + for (let i = score.length - 1; i > 1; i--) { + const pos = score[i] + wordPos; + const last = res[res.length - 1]; + if (last && last.end === pos) { + last.end = pos + 1; } else { - return; + res.push({ start: pos, end: pos + 1 }); } } - - total -= column >= 3 ? 9 : column * 3; // late start penalty - - // dynamically keep track of the current top score - // and insert the current best score at head, the rest at tail - globals._matchesCount += 1; - if (total > globals._topScore) { - globals._topScore = total; - globals._topMatch2 = matches; - } + return res; } -// #endregion +/** + * A fast function (therefore imprecise) to check if code points are emojis. + * Generated using https://github.com/alexdima/unicode-utils/blob/master/generate-emoji-test.js + */ +export function isEmojiImprecise(x: number): boolean { + return ( + (x >= 0x1f1e6 && x <= 0x1f1ff) || + x === 8986 || + x === 8987 || + x === 9200 || + x === 9203 || + (x >= 9728 && x <= 10175) || + x === 11088 || + x === 11093 || + (x >= 127744 && x <= 128591) || + (x >= 128640 && x <= 128764) || + (x >= 128992 && x <= 129003) || + (x >= 129280 && x <= 129535) || + (x >= 129648 && x <= 129750) + ); +} diff --git a/src/common/string/filter/sequence-matching.ts b/src/common/string/filter/sequence-matching.ts index 6e4a5bf2e5..aa67093497 100644 --- a/src/common/string/filter/sequence-matching.ts +++ b/src/common/string/filter/sequence-matching.ts @@ -11,7 +11,7 @@ import { fuzzyScore } from "./filter"; */ export const fuzzySequentialMatch = (filter: string, ...words: string[]) => { - let topScore = 0; + let topScore = Number.NEGATIVE_INFINITY; for (const word of words) { const scores = fuzzyScore( @@ -28,15 +28,24 @@ export const fuzzySequentialMatch = (filter: string, ...words: string[]) => { continue; } - // The VS Code implementation of filter treats a score of "0" as just barely a match - // But we will typically use this matcher in a .filter(), which interprets 0 as a failure. - // By shifting all scores up by 1, we allow "0" matches, while retaining score precedence - const score = scores[0] + 1; + // The VS Code implementation of filter returns a: + // - Negative score for a good match that starts in the middle of the string + // - Positive score if the match starts at the beginning of the string + // - 0 if the filter string is just barely a match + // - undefined for no match + // The "0" return is problematic since .filter() will remove that match, even though a 0 == good match. + // So, if we encounter a 0 return, set it to 1 so the match will be included, and still respect ordering. + const score = scores[0] === 0 ? 1 : scores[0]; if (score > topScore) { topScore = score; } } + + if (topScore === Number.NEGATIVE_INFINITY) { + return undefined; + } + return topScore; }; @@ -59,7 +68,7 @@ export const fuzzyFilterSort: FuzzyFilterSort = (filter, items) => { : fuzzySequentialMatch(filter, item.filterText); return item; }) - .filter((item) => item.score !== undefined && item.score > 0) + .filter((item) => item.score !== undefined) .sort(({ score: scoreA = 0 }, { score: scoreB = 0 }) => scoreA > scoreB ? -1 : scoreA < scoreB ? 1 : 0 ); diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index c6db3a0e54..41a4152cb3 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -100,7 +100,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { public excludeDomains?: string[]; /** - * Show only deviced with entities of these device classes. + * Show only devices with entities of these device classes. * @type {Array} * @attr include-device-classes */ diff --git a/src/components/ha-date-input.ts b/src/components/ha-date-input.ts index 76aaa8c689..e3342396c5 100644 --- a/src/components/ha-date-input.ts +++ b/src/components/ha-date-input.ts @@ -1,62 +1,61 @@ -import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker"; +import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker-light"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + PropertyValues, + query, +} from "lit-element"; +import "@polymer/paper-input/paper-input"; +import { fireEvent } from "../common/dom/fire_event"; +import { mdiCalendar } from "@mdi/js"; +import "./ha-svg-icon"; -const VaadinDatePicker = customElements.get("vaadin-date-picker"); - -const documentContainer = document.createElement("template"); -documentContainer.setAttribute("style", "display: none;"); -documentContainer.innerHTML = ` - - - -`; -document.head.appendChild(documentContainer.content); - -export class HaDateInput extends VaadinDatePicker { - constructor() { - super(); - - this.i18n.formatDate = this._formatISODate; - this.i18n.parseDate = this._parseISODate; - } - - ready() { - super.ready(); - const styleEl = document.createElement("style"); - styleEl.innerHTML = ` - :host { - width: 12ex; - margin-top: -6px; - --material-body-font-size: 16px; - --_material-text-field-input-line-background-color: var(--primary-text-color); - --_material-text-field-input-line-opacity: 1; - --material-primary-color: var(--primary-text-color); - } - `; - this.shadowRoot.appendChild(styleEl); - this._inputElement.querySelector("[part='toggle-button']").style.display = - "none"; - } - - private _formatISODate(d) { +const i18n = { + monthNames: [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ], + weekdays: [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ], + weekdaysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + firstDayOfWeek: 0, + week: "Week", + calendar: "Calendar", + clear: "Clear", + today: "Today", + cancel: "Cancel", + formatTitle: (monthName, fullYear) => { + return monthName + " " + fullYear; + }, + formatDate: (d: { day: number; month: number; year: number }) => { return [ ("0000" + String(d.year)).slice(-4), ("0" + String(d.month + 1)).slice(-2), ("0" + String(d.day)).slice(-2), ].join("-"); - } - - private _parseISODate(text) { + }, + parseDate: (text: string) => { const parts = text.split("-"); const today = new Date(); let date; @@ -80,11 +79,73 @@ export class HaDateInput extends VaadinDatePicker { return { day: date, month, year }; } return undefined; + }, +}; +@customElement("ha-date-input") +export class HaDateInput extends LitElement { + @property() public value?: string; + + @property({ type: Boolean }) public disabled = false; + + @property() public label?: string; + + @query("vaadin-date-picker-light", true) private _datePicker; + + private _inited = false; + + updated(changedProps: PropertyValues) { + if (changedProps.has("value")) { + this._datePicker.value = this.value; + this._inited = true; + } + } + + render() { + return html` + + + + `; + } + + private _valueChanged(ev: CustomEvent) { + if ( + !this.value || + (this._inited && !this._compareStringDates(ev.detail.value, this.value)) + ) { + fireEvent(this, "value-changed", { value: ev.detail.value }); + } + } + + private _compareStringDates(a: string, b: string): boolean { + const aParts = a.split("-"); + const bParts = b.split("-"); + let i = 0; + for (const aPart of aParts) { + if (Number(aPart) !== Number(bParts[i])) { + return false; + } + i++; + } + return true; + } + + static get styles(): CSSResult { + return css` + paper-input { + width: 110px; + } + ha-svg-icon { + color: var(--secondary-text-color); + } + `; } } - -customElements.define("ha-date-input", HaDateInput as any); - declare global { interface HTMLElementTagNameMap { "ha-date-input": HaDateInput; diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index 11eabcae30..d082962d9b 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -350,6 +350,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { .includeDeviceClasses=${this.includeDeviceClasses} .includeDomains=${this.includeDomains} @value-changed=${this._targetPicked} + allow-custom-entity >`; } return html``; diff --git a/src/components/trace/hat-graph-node.ts b/src/components/trace/hat-graph-node.ts index 80bba78d74..c9d6b8296f 100644 --- a/src/components/trace/hat-graph-node.ts +++ b/src/components/trace/hat-graph-node.ts @@ -10,6 +10,8 @@ export class HatGraphNode extends LitElement { @property({ reflect: true, type: Boolean }) graphstart?: boolean; + @property({ reflect: true, type: Boolean }) spacer?: boolean; + @property({ reflect: true, type: Boolean }) nofocus?: boolean; @property({ reflect: true, type: Number }) badge?: number; @@ -25,6 +27,12 @@ export class HatGraphNode extends LitElement { if (!svgEl) { return; } + if (this.spacer) { + svgEl.setAttribute("width", "10px"); + svgEl.setAttribute("height", "41px"); + svgEl.setAttribute("viewBox", "-5 -40 10 26"); + return; + } const bbox = svgEl.getBBox(); const extra_height = this.graphstart ? 2 : 1; const extra_width = SPACING; @@ -50,7 +58,11 @@ export class HatGraphNode extends LitElement { - + />` + : "" + } ${ this.badge ? svg` @@ -133,7 +149,7 @@ export class HatGraphNode extends LitElement { :host([nofocus]):host-context(.active), :host([nofocus]):host-context(:focus) { - --stroke-clr: var(--active-clr); + --circle-clr: var(--active-clr); --icon-clr: var(--default-icon-clr); } diff --git a/src/components/trace/hat-graph.ts b/src/components/trace/hat-graph.ts index fc8c3f28b2..8b7bed8b03 100644 --- a/src/components/trace/hat-graph.ts +++ b/src/components/trace/hat-graph.ts @@ -208,12 +208,6 @@ export class HatGraph extends LitElement { :host([disabled]) path.line { stroke: var(--disabled-clr); } - :host(.active) #top path.line { - stroke: var(--active-clr); - } - :host(:focus) #top path.line { - stroke: var(--active-clr); - } `; } } diff --git a/src/components/trace/hat-script-graph.ts b/src/components/trace/hat-script-graph.ts index 0fef57b294..2ed44e22c1 100644 --- a/src/components/trace/hat-script-graph.ts +++ b/src/components/trace/hat-script-graph.ts @@ -108,7 +108,7 @@ class HatScriptGraph extends LitElement { })} .track_start=${[track_path]} .track_end=${[track_path]} - tabindex=${trace === undefined ? "-1" : "0"} + tabindex=${trace ? "-1" : "0"} short > { const branch_path = `${path}/choose/${i}`; + const track_this = + trace !== undefined && trace[0].result?.choice === i; + if (track_this) { + this.trackedNodes[branch_path] = { config, path: branch_path }; + } return html` ${ensureArray(branch.sequence).map((action, j) => @@ -185,8 +193,8 @@ class HatScriptGraph extends LitElement { })} - ${this._search}` : this._search} + .value=${this._commandMode ? `>${this._search}` : this._search} @keydown=${this._handleInputKeyDown} @focus=${this._setFocusFirstListItem} > @@ -159,9 +160,23 @@ export class QuickBar extends LitElement { class="prefix" .path=${mdiConsoleLine} >` - : ""} - - ${!this._items + : html``} + ${this._search && + html` + + + + `} + + ${!items ? html` ${scroll({ - items: this._items, + items, renderItem: (item: QuickBarItem, index?: number) => this._renderItem(item, index), })} @@ -196,7 +211,6 @@ export class QuickBar extends LitElement { } private _handleOpened() { - this._setFilteredItems(); this.updateComplete.then(() => { this._done = true; }); @@ -302,11 +316,11 @@ export class QuickBar extends LitElement { private _handleInputKeyDown(ev: KeyboardEvent) { if (ev.code === "Enter") { - if (!this._items?.length) { + const firstItem = this._getItemAtIndex(0); + if (!firstItem || firstItem.style.display === "none") { return; } - - this.processItemAndCloseDialog(this._items[0], 0); + this.processItemAndCloseDialog((firstItem as any).item, 0); } else if (ev.code === "ArrowDown") { ev.preventDefault(); this._getItemAtIndex(0)?.focus(); @@ -338,16 +352,20 @@ export class QuickBar extends LitElement { this._search = newFilter; } - this._debouncedSetFilter(this._search); - if (oldCommandMode !== this._commandMode) { - this._items = undefined; this._focusSet = false; - this._initializeItemsIfNeeded(); + this._filter = this._search; + } else { + this._debouncedSetFilter(this._search); } } + private _clearSearch() { + this._search = ""; + this._filter = ""; + } + private _debouncedSetFilter = debounce((filter: string) => { this._filter = filter; }, 100); @@ -553,16 +571,10 @@ export class QuickBar extends LitElement { return this._opened ? !this._commandMode : false; } - private _setFilteredItems() { - const items = this._commandMode ? this._commandItems : this._entityItems; - this._items = this._filter - ? this._filterItems(items || [], this._filter) - : items; - } - private _filterItems = memoizeOne( - (items: QuickBarItem[], filter: string): QuickBarItem[] => - fuzzyFilterSort(filter.trimLeft(), items) + (items: QuickBarItem[], filter: string): QuickBarItem[] => { + return fuzzyFilterSort(filter.trimLeft(), items); + } ); static get styles() { @@ -598,27 +610,26 @@ export class QuickBar extends LitElement { color: var(--primary-text-color); } - span.command-category { - font-weight: bold; - padding: 3px; - display: inline-flex; - border-radius: 6px; - color: black; + paper-input mwc-icon-button { + --mdc-icon-button-size: 24px; + color: var(--primary-text-color); + } + + .command-category { + --ha-chip-icon-color: #585858; + --ha-chip-text-color: #212121; } .command-category.reload { --ha-chip-background-color: #cddc39; - --ha-chip-text-color: black; } .command-category.navigation { --ha-chip-background-color: var(--light-primary-color); - --ha-chip-text-color: black; } .command-category.server_control { --ha-chip-background-color: var(--warning-color); - --ha-chip-text-color: black; } span.command-text { diff --git a/src/layouts/hass-error-screen.ts b/src/layouts/hass-error-screen.ts index cadf6c28ed..b335218f03 100644 --- a/src/layouts/hass-error-screen.ts +++ b/src/layouts/hass-error-screen.ts @@ -28,7 +28,7 @@ class HassErrorScreen extends LitElement { return html` ${this.toolbar ? html`
- ${this.rootnav + ${this.rootnav || history.state?.root ? html` - ${this.rootnav + ${this.rootnav || history.state?.root ? html` - + ${this.mainPage || history.state?.root + ? html` + + ` + : html` + + `}
${this.header}
diff --git a/src/layouts/hass-tabs-subpage.ts b/src/layouts/hass-tabs-subpage.ts index ed4d1e57ac..fa0f873966 100644 --- a/src/layouts/hass-tabs-subpage.ts +++ b/src/layouts/hass-tabs-subpage.ts @@ -140,7 +140,7 @@ class HassTabsSubpage extends LitElement { const showTabs = tabs.length > 1 || !this.narrow; return html`
- ${this.mainPage + ${this.mainPage || history.state?.root ? html` - -
-
-
- ${this.hass.localize("ui.panel.error.supervisor.title")} -
+
  1. - ${this.hass.localize("ui.panel.error.supervisor.wait")} + ${this.hass.localize("ui.errors.supervisor.wait")}
  2. - ${this.hass.localize("ui.panel.error.supervisor.observer")} + ${this.hass.localize("ui.errors.supervisor.observer")}
  3. - ${this.hass.localize("ui.panel.error.supervisor.reboot")} + ${this.hass.localize("ui.errors.supervisor.reboot")}
  4. - ${this.hass.localize( - "ui.panel.error.supervisor.system_health" - )} + ${this.hass.localize("ui.errors.supervisor.system_health")}
  5. @@ -83,13 +74,13 @@ class SupervisorErrorScreen extends LitElement { target="_blank" rel="noreferrer" > - ${this.hass.localize("ui.panel.error.supervisor.ask")} + ${this.hass.localize("ui.errors.supervisor.ask")}
-
+ `; } @@ -125,50 +116,17 @@ class SupervisorErrorScreen extends LitElement { ); } - private _handleBack(): void { - history.back(); - } - static get styles(): CSSResultArray { return [ haStyle, css` - .toolbar { - display: flex; - align-items: center; - font-size: 20px; - height: var(--header-height); - padding: 0 16px; - pointer-events: none; - background-color: var(--app-header-background-color); - font-weight: 400; - box-sizing: border-box; - } - ha-icon-button-arrow-prev { - pointer-events: auto; - } - .content { - color: var(--primary-text-color); - display: flex; - padding: 16px; - align-items: center; - justify-content: center; - flex-direction: column; - } - .title { - font-size: 24px; - font-weight: 400; - line-height: 32px; - padding-bottom: 16px; - } - a { color: var(--mdc-theme-primary); } ha-card { width: 600px; - margin: 16px; + margin: auto; padding: 8px; } @media all and (max-width: 500px) { diff --git a/src/panels/config/automation/trace/ha-automation-trace.ts b/src/panels/config/automation/trace/ha-automation-trace.ts index 1546ad49ad..53c7131dc3 100644 --- a/src/panels/config/automation/trace/ha-automation-trace.ts +++ b/src/panels/config/automation/trace/ha-automation-trace.ts @@ -103,7 +103,6 @@ export class HaAutomationTrace extends LitElement { .hass=${this.hass} .narrow=${this.narrow} .route=${this.route} - .backCallback=${() => this._backTapped()} .tabs=${configSections.automation} > ${this.narrow @@ -388,10 +387,6 @@ export class HaAutomationTrace extends LitElement { this._trace = trace; } - private _backTapped(): void { - history.back(); - } - private _downloadTrace() { const aEl = document.createElement("a"); aEl.download = `trace ${this._entityId} ${ diff --git a/src/panels/config/automation/trace/styles.ts b/src/panels/config/automation/trace/styles.ts index 9463bd88a7..4f236499c1 100644 --- a/src/panels/config/automation/trace/styles.ts +++ b/src/panels/config/automation/trace/styles.ts @@ -22,9 +22,17 @@ export const traceTabStyles = css` border-bottom: 2px solid transparent; user-select: none; background: none; + color: var(--primary-text-color); + outline: none; + transition: background 15ms linear; } .tabs > *.active { border-bottom-color: var(--accent-color); } + + .tabs > *:focus, + .tabs > *:hover { + background: var(--secondary-background-color); + } `; diff --git a/src/panels/config/cloud/alexa/cloud-alexa.ts b/src/panels/config/cloud/alexa/cloud-alexa.ts index d9bdbd1e14..079ebdef10 100644 --- a/src/panels/config/cloud/alexa/cloud-alexa.ts +++ b/src/panels/config/cloud/alexa/cloud-alexa.ts @@ -214,9 +214,9 @@ class CloudAlexa extends LitElement { } return html` - + ${ emptyFilter ? html` diff --git a/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts b/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts index 4582ff7a1b..3ba045e6a5 100644 --- a/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts +++ b/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts @@ -239,7 +239,8 @@ class CloudGoogleAssistant extends LitElement { return html` + .header=${this.hass!.localize("ui.panel.config.cloud.google.title")} + .narrow=${this.narrow}> ${ emptyFilter ? html` diff --git a/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts b/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts index 1df1f36d08..e2ec44c628 100644 --- a/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts +++ b/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts @@ -23,6 +23,8 @@ import "./mqtt-subscribe-card"; class HaPanelDevMqtt extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; + @property({ type: Boolean }) public narrow!: boolean; + @internalProperty() private topic = ""; @internalProperty() private payload = ""; @@ -41,7 +43,7 @@ class HaPanelDevMqtt extends LitElement { protected render(): TemplateResult { return html` - +
diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts index 5d4db4aa36..33e08cb18b 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts @@ -197,7 +197,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) { // Numeric entries with a min value of 0 and max of 1 are considered boolean if ( - (item.configuration_value_type === "range" && + (item.configuration_value_type === "manual_entry" && item.metadata.min === 0 && item.metadata.max === 1) || this._isEnumeratedBool(item) @@ -217,7 +217,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) { `; } - if (item.configuration_value_type === "range") { + if (item.configuration_value_type === "manual_entry") { return html`${labelAndDescription} ${this._config!.show_state !== true && entityConf.show_state !== true ? html`
` diff --git a/src/panels/lovelace/entity-rows/hui-input-datetime-entity-row.ts b/src/panels/lovelace/entity-rows/hui-input-datetime-entity-row.ts index bd3bedfed3..fbc6ce31ff 100644 --- a/src/panels/lovelace/entity-rows/hui-input-datetime-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-input-datetime-entity-row.ts @@ -63,9 +63,9 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow { + @value-changed=${this._selectedValueChanged} + > + ${stateObj.attributes.has_time ? "," : ""} ` : ``} @@ -103,9 +103,7 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow { const date = this._dateInputEl ? this._dateInputEl.value : undefined; - if (time !== stateObj.state) { - setInputDateTimeValue(this.hass!, stateObj.entity_id, time, date); - } + setInputDateTimeValue(this.hass!, stateObj.entity_id, time, date); ev.target.blur(); } diff --git a/src/panels/lovelace/entity-rows/hui-input-number-entity-row.ts b/src/panels/lovelace/entity-rows/hui-input-number-entity-row.ts index 344d5d4d06..92583cffbc 100644 --- a/src/panels/lovelace/entity-rows/hui-input-number-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-input-number-entity-row.ts @@ -10,7 +10,7 @@ import { PropertyValues, TemplateResult, } from "lit-element"; -import { formatNumber } from "../../../common/string/format_number"; +import { computeStateDisplay } from "../../../common/entity/compute_state_display"; import { computeRTLDirection } from "../../../common/util/compute_rtl"; import "../../../components/ha-slider"; import { UNAVAILABLE_STATES } from "../../../data/entity"; @@ -89,8 +89,12 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow { id="input" > - ${formatNumber(Number(stateObj.state), this.hass.locale)} - ${stateObj.attributes.unit_of_measurement} + ${computeStateDisplay( + this.hass.localize, + stateObj, + this.hass.locale, + stateObj.state + )}
` diff --git a/src/panels/lovelace/entity-rows/hui-number-entity-row.ts b/src/panels/lovelace/entity-rows/hui-number-entity-row.ts index ac1e7edcc3..40e748ec76 100644 --- a/src/panels/lovelace/entity-rows/hui-number-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-number-entity-row.ts @@ -19,6 +19,7 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-generic-entity-row"; import { EntityConfig, LovelaceRow } from "./types"; import { createEntityNotFoundWarning } from "../components/hui-warning"; +import { computeStateDisplay } from "../../../common/entity/compute_state_display"; @customElement("hui-number-entity-row") class HuiNumberEntityRow extends LitElement implements LovelaceRow { @@ -88,8 +89,12 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow { id="input" > - ${Number(stateObj.state)} - ${stateObj.attributes.unit_of_measurement} + ${computeStateDisplay( + this.hass.localize, + stateObj, + this.hass.locale, + stateObj.state + )}
` diff --git a/src/resources/ha-date-picker-style.js b/src/resources/ha-date-picker-style.js deleted file mode 100644 index 185b29d453..0000000000 --- a/src/resources/ha-date-picker-style.js +++ /dev/null @@ -1,103 +0,0 @@ -const documentContainer = document.createElement("template"); -documentContainer.setAttribute("style", "display: none;"); - -documentContainer.innerHTML = ` - - - - - - - - - - - - -`; - -document.head.appendChild(documentContainer.content); diff --git a/src/state-summary/state-card-input_number.js b/src/state-summary/state-card-input_number.js index f01328fa88..a3c4577118 100644 --- a/src/state-summary/state-card-input_number.js +++ b/src/state-summary/state-card-input_number.js @@ -5,6 +5,7 @@ import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { computeStateDisplay } from "../common/entity/compute_state_display"; import "../components/entity/state-info"; import "../components/ha-slider"; @@ -75,7 +76,7 @@ class StateCardInputNumber extends mixinBehaviors( class="state sliderstate" hidden="[[hiddenslider]]" > - [[value]] [[stateObj.attributes.unit_of_measurement]] + [[formattedState]]
`; @@ -138,6 +139,7 @@ class StateCardInputNumber extends mixinBehaviors( }, step: Number, value: Number, + formattedState: String, mode: String, }; } @@ -159,6 +161,12 @@ class StateCardInputNumber extends mixinBehaviors( max: Number(newVal.attributes.max), step: Number(newVal.attributes.step), value: Number(newVal.state), + formattedState: computeStateDisplay( + this.hass.localize, + newVal, + this.hass.locale, + newVal.state + ), mode: String(newVal.attributes.mode), maxlength: String(newVal.attributes.max).length, hiddenbox: newVal.attributes.mode !== "box", diff --git a/src/state/url-sync-mixin.ts b/src/state/url-sync-mixin.ts index 1976a06a14..b7f44f89de 100644 --- a/src/state/url-sync-mixin.ts +++ b/src/state/url-sync-mixin.ts @@ -25,6 +25,9 @@ export const urlSyncMixin = < public connectedCallback(): void { super.connectedCallback(); + if (history.length === 1) { + history.replaceState({ ...history.state, root: true }, ""); + } window.addEventListener("popstate", this._popstateChangeListener); this.addEventListener("dialog-closed", this._dialogClosedListener); } diff --git a/src/translations/en.json b/src/translations/en.json index 3315f926f1..e187f9656d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -823,6 +823,14 @@ "key_not_expected": "Key \"{key}\" is not expected or not supported by the visual editor.", "key_wrong_type": "The provided value for \"{key}\" is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong}).", "no_template_editor_support": "Templates not supported in visual editor" + }, + "supervisor": { + "title": "Could not load the Supervisor panel!", + "wait": "If you just started, make sure you have given the Supervisor enough time to start.", + "ask": "Ask for help", + "reboot": "Try a reboot of the host", + "observer": "Check the Observer", + "system_health": "Check System Health" } }, "login-form": { @@ -3521,17 +3529,6 @@ "complete_access": "It will have access to all data in Home Assistant.", "hide_message": "Check docs for the panel_custom component to hide this message" } - }, - "error": { - "go_back": "Go back", - "supervisor": { - "title": "Could not load the Supervisor panel!", - "wait": "If you just started, make sure you have given the supervisor enough time to start.", - "ask": "Ask for help", - "reboot": "Try a reboot of the host", - "observer": "Check the Observer", - "system_health": "Check System Health" - } } } }, diff --git a/test-mocha/common/string/sequence_matching.test.ts b/test-mocha/common/string/sequence_matching.test.ts index 48400606ea..1b079cb4ba 100644 --- a/test-mocha/common/string/sequence_matching.test.ts +++ b/test-mocha/common/string/sequence_matching.test.ts @@ -20,25 +20,25 @@ describe("fuzzySequentialMatch", () => { }); const shouldMatchEntity = [ - createExpectation("automation.ticker", 138), - createExpectation("automation.ticke", 129), - createExpectation("automation.", 89), - createExpectation("au", 17), - createExpectation("automationticker", 107), - createExpectation("tion.tick", 18), - createExpectation("ticker", 1), - createExpectation("automation.r", 89), - createExpectation("tick", 1), - createExpectation("aumatick", 15), - createExpectation("aion.tck", 14), - createExpectation("ioticker", 19), - createExpectation("atmto.ikr", 1), - createExpectation("uoaintce", 1), - createExpectation("au.tce", 17), - createExpectation("tomaontkr", 9), - createExpectation("s", 7), - createExpectation("stocks", 48), - createExpectation("sks", 7), + createExpectation("automation.ticker", 131), + createExpectation("automation.ticke", 121), + createExpectation("automation.", 82), + createExpectation("au", 10), + createExpectation("automationticker", 85), + createExpectation("tion.tick", 8), + createExpectation("ticker", -4), + createExpectation("automation.r", 73), + createExpectation("tick", -8), + createExpectation("aumatick", 9), + createExpectation("aion.tck", 4), + createExpectation("ioticker", -4), + createExpectation("atmto.ikr", -34), + createExpectation("uoaintce", -39), + createExpectation("au.tce", -3), + createExpectation("tomaontkr", -19), + createExpectation("s", 1), + createExpectation("stocks", 42), + createExpectation("sks", -5), ]; const shouldNotMatchEntity = [ @@ -72,7 +72,7 @@ describe("fuzzySequentialMatch", () => { entity.entity_id, entity.friendly_name ); - assert.equal(res, 0); + assert.equal(res, undefined); }); } }); @@ -80,24 +80,45 @@ describe("fuzzySequentialMatch", () => { describe("fuzzyFilterSort", () => { const filter = "ticker"; - const item1 = { + const automationTicker = { filterText: "automation.ticker", altText: "Stocks", score: 0, }; - const item2 = { filterText: "sensor.ticker", altText: "Stocks up", score: 0 }; - const item3 = { + const ticker = { + filterText: "ticker", + altText: "Just ticker", + score: 0, + }; + const sensorTicker = { + filterText: "sensor.ticker", + altText: "Stocks up", + score: 0, + }; + const timerCheckRouter = { filterText: "automation.check_router", altText: "Timer Check Router", score: 0, }; - const itemsBeforeFilter = [item1, item2, item3]; + const badMatch = { + filterText: "light.chandelier", + altText: "Chandelier", + score: 0, + }; + const itemsBeforeFilter = [ + automationTicker, + sensorTicker, + timerCheckRouter, + ticker, + badMatch, + ]; - it(`sorts correctly`, () => { + it(`filters and sorts correctly`, () => { const expectedItemsAfterFilter = [ - { ...item2, score: 23 }, - { ...item3, score: 12 }, - { ...item1, score: 1 }, + { ...ticker, score: 44 }, + { ...sensorTicker, score: 1 }, + { ...automationTicker, score: -4 }, + { ...timerCheckRouter, score: -8 }, ]; const res = fuzzyFilterSort(filter, itemsBeforeFilter); diff --git a/translations/frontend/bg.json b/translations/frontend/bg.json index aa56a3b53d..e1a2fcb6fe 100644 --- a/translations/frontend/bg.json +++ b/translations/frontend/bg.json @@ -364,7 +364,7 @@ "change_hostname": "Промяна в името на хоста", "confirm_reboot": "Сигурни ли сте, че искате да рестартирате хоста?", "confirm_shutdown": "Сигурни ли сте, че искате да изключите хоста?", - "docker_version": "Docker версия", + "docker_version": "Версия на Docker", "failed_to_get_hardware_list": "Неуспешно получаване на списъка с хардуер", "failed_to_import_from_usb": "Неуспешно импортиране от USB", "failed_to_reboot": "Неуспешно рестартиране на хоста", @@ -640,6 +640,7 @@ "today": "Днес" }, "data-table": { + "clear": "Изчистване", "filtering_by": "Филтриране по", "hidden": "{number} скрити", "no-data": "Няма данни", @@ -750,8 +751,10 @@ "related-filter-menu": { "filter_by_area": "Филтриране по област", "filter_by_device": "Филтриране по устройство", + "filter_by_entity": "Филтриране по обект", "filtered_by_area": "област: {area_name}", - "filtered_by_device": "устройство: {device_name}" + "filtered_by_device": "устройство: {device_name}", + "filtered_by_entity": "обект: {entity_name}" }, "related-items": { "area": "Област", @@ -1024,12 +1027,14 @@ "zha_device_info": { "buttons": { "add": "Добавете устройства чрез това устройство", + "clusters": "Управление на клъстери", "remove": "Премахване на устройство", "zigbee_information": "Подпис на Zigbee устройството" }, "confirmations": { "remove": "Сигурни ли сте, че искате да премахнете устройството?" }, + "device_signature": "Подпис на Zigbee устройството", "last_seen": "Последно видян", "manuf": "от {manufacturer}", "no_area": "Без област", @@ -1492,10 +1497,12 @@ "title": "Alexa" }, "connected": "Свързан", + "connection_status": "Състояние на връзката с облака", "google": { "config_documentation": "Документация за конфигурацията", "devices_pin": "ПИН код за Устройства за защита", "enter_pin_hint": "Въведете ПИН за използване на устройства за защита", + "not_configured_text": "Преди да можете да използвате Google Assistant, трябва да активирате умението Home Assistant Cloud за Google Assistant в приложението Google Home.", "not_configured_title": "Google Assistant не е активиран", "security_devices": "Устройства за защита", "title": "Google Assistant" @@ -1507,9 +1514,11 @@ "not_connected": "Не е свързан", "remote": { "certificate_info": "Информация за сертификата", + "link_learn_how_it_works": "Научете как работи", "title": "Дистанционен контрол" }, "sign_out": "Отписване", + "thank_you_note": "Благодарим Ви, че сте част от Home Assistant Cloud. Именно заради хора като вас ние сме в състояние да направим страхотно изживяване при автоматизацията на дома за всички. Благодарим Ви!", "tts": { "default_language": "Език по подразбиране за използване", "dialog": { @@ -1632,6 +1641,9 @@ "description": "Споделяйте доклади за сривове и диагностична информация", "title": "Диагностика" }, + "statistics": { + "title": "Статистика за употребата" + }, "usage_supervisor": { "title": "Използвани интеграции и добавки" }, @@ -1732,7 +1744,7 @@ "filter": "Филтър", "hidden_devices": "{number} скрити {number, plural,\n one {устройство}\n other {устройства}\n}", "show_all": "Покажи всички", - "show_disabled": "Показване на деактивирани устройства" + "show_disabled": "Показване на деактивираните устройства" }, "search": "Търсене на устройства" }, @@ -1766,7 +1778,11 @@ }, "filter": { "filter": "Филтър", - "show_all": "Покажи всички" + "hidden_entities": "{number} скрити {number, plural,\n one {обект}\n other {обекта}\n}", + "show_all": "Покажи всички", + "show_disabled": "Показване на деактивираните обекти", + "show_readonly": "Показване на обектите само за четене", + "show_unavailable": "Показване на недостъпните обекти" }, "header": "Обекти", "headers": { @@ -1794,6 +1810,9 @@ "filtering_by": "Филтриране по", "show": "Покажи" }, + "hassio": { + "button": "Конфигуриране" + }, "header": "Конфигуриране на Home Assistant", "helpers": { "description": "Елементи, които помагат за изграждането на автоматизации", @@ -1815,6 +1834,7 @@ } }, "info": { + "built_using": "Изграден с използване на", "caption": "Информация", "description": "Версия, състояние на системата и връзки към документация", "documentation": "Документация", @@ -1858,6 +1878,7 @@ "hub": "Свързан чрез", "manuf": "от {manufacturer}", "no_area": "Без област", + "not_loaded": "Не е зареден, проверете {logs_link}", "options": "Настройки", "reload": "Презареждане", "reload_confirm": "Интеграцията беше презаредена", @@ -1887,6 +1908,7 @@ "disable": { "disabled_integrations": "{number} деактивирани", "hide_disabled": "Скриване на деактивираните интеграции", + "show": "Покажи", "show_disabled": "Показване на деактивираните интеграции" }, "discovered": "Открити", @@ -2114,6 +2136,7 @@ "header": "Скрипт: {name}", "icon": "Икона", "id_already_exists": "Това ID вече съществува", + "id_already_exists_save_error": "Не можете да запазите този скрипт, защото идентификаторът не е уникален, изберете друг идентификатор или го оставете празен, за да се генерира автоматично.", "introduction": "Използвайте скриптове за изпълнение на последователност от действия.", "link_available_actions": "Научете повече за наличните действия.", "modes": { @@ -2123,6 +2146,7 @@ "restart": "Рестарт", "single": "Еднократно (по подразбиране)" }, + "save_script": "Запазване на скрипта", "sequence": "Последователност", "sequence_sentence": "Последователността на действията на този скрипт." }, @@ -2135,6 +2159,7 @@ "name": "Име" }, "learn_more": "Научете повече за скриптовете", + "run_script": "Изпълнете скрипта", "show_info": "Показване на информация за скрипта" } }, @@ -2412,6 +2437,7 @@ "developer-tools": { "tabs": { "events": { + "available_events": "Налични събития", "count_listeners": "({count} слушатели)", "title": "Събития" }, @@ -2675,7 +2701,8 @@ "name": "Вертикална колона" }, "weather-forecast": { - "name": "Прогноза за времето" + "name": "Прогноза за времето", + "show_forecast": "Показване на прогноза" } }, "cardpicker": { @@ -2707,6 +2734,7 @@ "move_before": "Преместване на картата преди", "options": "Още опции", "pick_card": "Изберете картата, която искате да добавите.", + "pick_card_view_title": "Коя карта бихте искали да добавите към изгледа си {name}?", "search_cards": "Търсене на карти", "show_code_editor": "Редактирайте кода", "show_visual_editor": "Показване на визуален редактор", @@ -2768,6 +2796,7 @@ "raw_editor": { "confirm_remove_config_text": "Ние автоматично ще генерираме вашите изгледи на потребителския интерфейс на Lovelace с вашите области и устройства, ако премахнете вашата конфигурация на потребителския интерфейс на Lovelace.", "confirm_remove_config_title": "Наистина ли искате да премахнете вашата конфигурация на потребителския интерфейс на Lovelace?", + "confirm_unsaved_changes": "Имате незапазени промени. Наистина ли искате да излезете?", "confirm_unsaved_comments": "Вашата конфигурация може да съдържа коментар(и), те няма да бъдат запазени. Искате ли да продължите?", "error_invalid_config": "Вашата конфигурация не е валидна: {error}", "error_remove": "Конфигурацията не може да бъде премахната: {error}", @@ -3004,6 +3033,7 @@ }, "intro": "Готови ли сте да събудите дома си, да отвоювате независимостта си и да се присъедините към световна общност от хора автоматизиращи домовете си?", "restore": { + "description": "Като алтернатива можете да възстановите от предишна моментна снимка.", "in_progress": "Възстановяването е в ход" }, "user": { diff --git a/translations/frontend/ca.json b/translations/frontend/ca.json index b9fb65afab..e63990b763 100644 --- a/translations/frontend/ca.json +++ b/translations/frontend/ca.json @@ -231,7 +231,7 @@ }, "watchdog": { "description": "Això farà que s'iniciï el complement en cas de que falli", - "title": "Gos vigilant" + "title": "Gos guardià" } }, "protection_mode": { @@ -843,8 +843,10 @@ "related-filter-menu": { "filter_by_area": "Filtra per àrea", "filter_by_device": "Filtra per dispositiu", + "filter_by_entity": "Filtra per entitat", "filtered_by_area": "àrea: {area_name}", - "filtered_by_device": "dispositiu: {device_name}" + "filtered_by_device": "dispositiu: {device_name}", + "filtered_by_entity": "entitat: {entity_name}" }, "related-items": { "area": "Àrea", diff --git a/translations/frontend/cs.json b/translations/frontend/cs.json index 6d7628b2a8..fe960aad20 100644 --- a/translations/frontend/cs.json +++ b/translations/frontend/cs.json @@ -843,8 +843,10 @@ "related-filter-menu": { "filter_by_area": "Filtrovat dle oblasti", "filter_by_device": "Filtrovat dle zařízení", + "filter_by_entity": "Filtrovat dle entity", "filtered_by_area": "oblasti: {area_name}", - "filtered_by_device": "zařízení: {device_name}" + "filtered_by_device": "zařízení: {device_name}", + "filtered_by_entity": "entita: {entity_name}" }, "related-items": { "area": "Oblast", diff --git a/translations/frontend/de.json b/translations/frontend/de.json index 41ed38391e..ce9a5b068b 100644 --- a/translations/frontend/de.json +++ b/translations/frontend/de.json @@ -721,6 +721,9 @@ "today": "Heute" }, "data-table": { + "clear": "Leeren", + "filtering_by": "Filtern nach", + "hidden": "{number} ausgeblendet", "no-data": "Keine Daten", "search": "Suche" }, @@ -835,6 +838,14 @@ "label": "Bild", "unsupported_format": "Nicht unterstütztes Format, bitte wähle ein JPEG-, PNG- oder GIF-Bild." }, + "related-filter-menu": { + "filter_by_area": "Nach Bereich filtern", + "filter_by_device": "Nach Gerät filtern", + "filter_by_entity": "Nach Entität filtern", + "filtered_by_area": "Bereich: {area_name}", + "filtered_by_device": "Gerät: {device_name}", + "filtered_by_entity": "Entität: {entity_name}" + }, "related-items": { "area": "Bereich", "automation": "Teil der folgenden Automatisierungen", @@ -1094,37 +1105,37 @@ "zone": "Zonen" }, "reload": { - "automation": "Automatisierungen neu laden", - "command_line": "Komandozeilenentätien neu laden", - "core": "Positionsdaten und Anpassungen neu laden", - "filesize": "Dateigröße-Entitäten neu laden", - "filter": "Filterentitäten neu laden", - "generic": "Allgemeine IP-Kamera-Entitäten neu laden", - "generic_thermostat": "Allgemeine Thermostat-Entitäten neu laden", - "group": "Gruppen, Gruppenentitäten und Benachrichtigungsservices neu laden", - "history_stats": "Verlaufsstatistiken neu laden", - "homekit": "HomeKit neu laden", - "input_boolean": "Input-Booleans neu laden", - "input_datetime": "Input-Datum/Zeit neu laden", - "input_number": "Input-Numern neu laden", - "input_select": "Input-Selektionen neu laden", - "input_text": "Input-Texte neu laden", - "min_max": "Min/Max-Entitäten neu laden", - "mqtt": "MQTT-Entitäten neu laden", - "person": "Personen neu laden", - "ping": "Binäre-Ping-Entitäten neu laden", - "reload": "{domain} neu laden", - "rest": "REST-Entitäten und Benachrichtigunsdienste neu laden", - "rpi_gpio": "Raspberry Pi GPIO Entitäten neu laden", - "scene": "Szenen neu laden", - "script": "Skripte neu laden", - "smtp": "SMTP-Benachrichtigungsdienst neu laden", - "statistics": "Statistikentitäten neu laden", - "telegram": "Telegram-Benachrichtigungsdienst neu laden", - "template": "Template-Entitäten neu laden", - "trend": "Trend-Entitäten neu laden", - "universal": "Universelle Medien-Player-Entitäten neu laden", - "zone": "Zonen neu laden" + "automation": "Automatisierungen", + "command_line": "Kommandozeilen-Entitäten", + "core": "Positionsdaten & Anpassungen", + "filesize": "Dateigröße-Entitäten", + "filter": "Filter-Entitäten", + "generic": "Allgemeine IP-Kamera Entitäten", + "generic_thermostat": "Generische Thermostatentitäten", + "group": "Gruppen, Gruppenentitäten und Benachrichtigungsservices", + "history_stats": "Verlaufsstatistik-Entitäten", + "homekit": "HomeKit", + "input_boolean": "Eingabe-Booleans", + "input_datetime": "Eingabe-Datums- und Zeitfelder", + "input_number": "Eingabenummern", + "input_select": "Eingabe-Auswahl", + "input_text": "Eingabetexte", + "min_max": "Min/Max-Entitäten", + "mqtt": "Manuell konfigurierte MQTT-Entitäten", + "person": "Personen", + "ping": "Binäre Ping-Sensorentitäten", + "reload": "{domain}", + "rest": "Rest-Entitäten und Benachrichtigungsdienste", + "rpi_gpio": "Raspberry Pi GPIO Entitäten", + "scene": "Szenen", + "script": "Skripte", + "smtp": "SMTP-Benachrichtigungsdienste", + "statistics": "Statistik-Entitäten", + "telegram": "Telegram-Benachrichtigungsdienste", + "template": "Template-Entitäten", + "trend": "Trend-Entitäten", + "universal": "Universelle Media Player-Entitäten", + "zone": "Zonen" }, "server_control": { "perform_action": "Server {action}", @@ -1177,6 +1188,9 @@ "zha_device_card": { "device_name_placeholder": "Gerätename ändern" } + }, + "zha_reconfigure_device": { + "heading": "Gerät neu konfigurieren" } }, "duration": { @@ -1305,6 +1319,7 @@ "extra_fields": { "brightness_pct": "Helligkeit", "code": "Code", + "flash": "Blinken", "humidity": "Luftfeuchtigkeit", "message": "Nachricht", "mode": "Modus", @@ -1344,9 +1359,9 @@ "label": "Dienst ausführen" }, "wait_for_trigger": { - "continue_timeout": "Bei Zeitüberschreitung fortfahren", + "continue_timeout": "Bei Timeout fortsetzen", "label": "Auf Auslöser warten", - "timeout": "Zeitüberschreitung (optional)" + "timeout": "Timeout (optional)" }, "wait_template": { "continue_timeout": "Bei Timeout fortsetzen", @@ -1470,6 +1485,7 @@ "move_down": "Runterschieben", "move_up": "Hochschieben", "save": "Speichern", + "show_trace": "Trace anzeigen", "triggers": { "add": "Auslöser hinzufügen", "delete": "Löschen", @@ -1678,6 +1694,7 @@ "info": "Mit der Google Assistant-Integration für Home Assistant Cloud kannst du alle deine Home Assistant-Geräte über jedes Google Assistant-fähige Gerät steuern.", "info_state_reporting": "Wenn die Statusberichterstellung aktiviert wird, sendet Home Assistant alle Statusänderungen exponierter Entitäten an Google. So wird in der Google-App immer der neueste Status angezeigt.", "manage_entities": "Entitäten verwalten", + "not_configured_title": "Google Assistant ist nicht aktiviert", "security_devices": "Sicherheitsgeräte", "sync_entities": "Entitäten mit Google synchronisieren", "sync_entities_404_message": "Fehler beim Synchronisieren deiner Entitäten mit Google. Bitte Google, \"Hey Google, synchronisiere meine Geräte\", deine Entitäten zu synchronisieren.", @@ -1840,6 +1857,36 @@ "description": "Mengeneinheiten, Standort, Zeitzone & andere allgemeine Parameter", "section": { "core": { + "analytics": { + "documentation": "Bevor du dies aktivierst, stelle sicher, dass du die Statistikdokumentationsseite {link} besuchst, um zu verstehen, was gesendet und gespeichert wird.", + "header": "Statistiken", + "instance_id": "Instanz-ID: {huuid}", + "introduction": "Teile Statistiken aus deiner Instanz. Diese Daten werden öffentlich verfügbar sein unter {link}", + "learn_more": "Erfahre mehr darüber, wie deine Daten verarbeitet werden.", + "needs_base": "Du musst die Basisstatistiken aktivieren, damit diese Option verfügbar ist", + "preference": { + "base": { + "description": "Dies umfasst die Instanz-ID, die Version und den Installationstyp", + "title": "Grundlegende Statistiken" + }, + "diagnostics": { + "description": "Teile Absturzberichte und Diagnoseinformationen", + "title": "Diagnoseinformationen" + }, + "statistics": { + "description": "Dies umfasst die Anzahl von Elementen in deiner Installation. Eine vollständige Liste findest du in der Dokumentation", + "title": "Nutzungsstatistiken" + }, + "usage_supervisor": { + "description": "Dies umfasst die Namen und Funktionen deiner Integrationen und Add-ons", + "title": "Verwendete Integrationen und Add-ons" + }, + "usage": { + "description": "Dies umfasst die Namen deiner Integrationen", + "title": "Verwendete Integrationen" + } + } + }, "core_config": { "edit_requires_storage": "Editor deaktiviert, da die Konfiguration in configuration.yaml gespeichert ist.", "elevation": "Höhe", @@ -2027,6 +2074,9 @@ "filtering_by": "Filtern nach", "show": "Anzeigen" }, + "hassio": { + "button": "Konfigurieren" + }, "header": "Home Assistant konfigurieren", "helpers": { "caption": "Helfer", @@ -2113,8 +2163,10 @@ "entity_unavailable": "Entität nicht verfügbar", "firmware": "Firmware: {version}", "hub": "Verbunden über", + "logs": "Logs", "manuf": "von {manufacturer}", "no_area": "Kein Bereich", + "not_loaded": "Nicht geladen, prüfe den {logs_link}", "options": "Optionen", "reload": "Neu laden", "reload_confirm": "Die Integration wurde neu geladen", @@ -2140,6 +2192,7 @@ "finish": "Fertig", "loading_first_time": "Bitte warten, während die Integration installiert wird", "not_all_required_fields": "Nicht alle Pflichtfelder sind ausgefüllt.", + "not_loaded": "Die Integration konnte nicht geladen werden. Versuche Home Assistant neu zu starten.", "pick_flow_step": { "new_flow": "Nein, richte eine andere Instanz von {integration} ein", "title": "Wir haben diese entdeckt, willst du sie einrichten?" @@ -2154,6 +2207,7 @@ "disable": { "disabled_integrations": "{number} deaktiviert", "hide_disabled": "Deaktivierte Integrationen ausblenden", + "show": "Anzeigen", "show_disabled": "Deaktivierte Integrationen anzeigen" }, "discovered": "Entdeckt", @@ -2189,6 +2243,13 @@ "clear": "Leeren", "description": "Home Assistant Logs einsehen", "details": "Protokolldetails ({level})", + "level": { + "critical": "KRITISCH", + "debug": "DEBUG", + "error": "FEHLER", + "info": "INFO", + "warning": "WARNUNG" + }, "load_full_log": "Vollständiges Home Assistant-Protokoll laden", "loading_log": "Fehlerprotokoll wird geladen...", "multiple_messages": "Die Nachricht ist zum ersten Mal um {time} aufgetreten und erscheint {counter} mal", @@ -2279,7 +2340,7 @@ "description_publish": "Ein Paket veröffentlichen", "listening_to": "Anhören von", "message_received": "Nachricht {id} empfangen auf {topic} um {time}:", - "payload": "Payload (Vorlage erlaubt)", + "payload": "Payload (Template erlaubt)", "publish": "Veröffentlichen", "start_listening": "Anfangen zuzuhören", "stop_listening": "Aufhören zuzuhören", @@ -2533,39 +2594,39 @@ "description": "Neustarten und Stoppen des Home Assistant Servers", "section": { "reloading": { - "automation": "Automatisierungen neu laden", - "command_line": "Kommandozeilen-Entitäten neu laden", - "core": "Ort & Anpassungen neu laden", - "filesize": "Dateigröße-Entitäten neu laden", - "filter": "Filter-Entitäten neu laden", - "generic": "Allgemeine IP-Kamera Entitäten neu laden", - "generic_thermostat": "Generische Thermostatentitäten neu laden", - "group": "Gruppen, Gruppenentitäten und Benachrichtigungsdienste neu laden", + "automation": "Automatisierungen", + "command_line": "Kommandozeilen-Entitäten", + "core": "Ort & Anpassungen", + "filesize": "Dateigröße-Entitäten", + "filter": "Filter-Entitäten", + "generic": "Allgemeine IP-Kamera Entitäten", + "generic_thermostat": "Generische Thermostatentitäten", + "group": "Gruppen, Gruppenentitäten und Benachrichtigungsdienste", "heading": "Neuladen der YAML-Konfiguration", - "history_stats": "Verlaufsstatistik-Entitäten neu laden", - "homekit": "HomeKit neu laden", - "input_boolean": "Eingabe-Booleans neu laden", - "input_datetime": "Eingabe-Datums- und Zeitfelder neu laden", - "input_number": "Eingabenummern neu laden", - "input_select": "Eingabe-Auswahl neu laden", - "input_text": "Eingabetexte neu laden", + "history_stats": "Verlaufsstatistik-Entitäten", + "homekit": "HomeKit", + "input_boolean": "Eingabe-Booleans", + "input_datetime": "Eingabe-Datums- und Zeitfelder", + "input_number": "Eingabenummern", + "input_select": "Eingabe-Auswahl", + "input_text": "Eingabetexte", "introduction": "Einige Komponenten von Home Assistant können ohne einen Neustart neu geladen werden. \"Neu laden\" entlädt dabei die aktuelle Konfiguration und lädt die neue Konfiguration.", - "min_max": "Min/Max-Entitäten neu laden", - "mqtt": "Lade manuell konfigurierte MQTT-Entitäten neu", - "person": "Personen neu laden", - "ping": "Binäre Ping-Sensorentitäten neu laden", - "reload": "{domain} neu laden", - "rest": "Rest-Entitäten und Benachrichtigungsdienste neu laden", - "rpi_gpio": "Raspberry Pi GPIO Entitäten neu laden", - "scene": "Szenen neu laden", - "script": "Skripte neu laden", - "smtp": "SMTP-Benachrichtigungsdienste neu laden", - "statistics": "Statistik-Entitäten neu laden", - "telegram": "Telegram-Benachrichtigungsdienste neu laden", - "template": "Template-Entitäten neu laden", - "trend": "Trend-Entitäten neu laden", - "universal": "Universelle Media Player-Entitäten neu laden", - "zone": "Zonen neu laden" + "min_max": "Min/Max-Entitäten", + "mqtt": "Manuell konfigurierte MQTT-Entitäten", + "person": "Personen", + "ping": "Binäre Ping-Sensorentitäten", + "reload": "{domain}", + "rest": "Rest-Entitäten und Benachrichtigungsdienste", + "rpi_gpio": "Raspberry Pi GPIO Entitäten", + "scene": "Szenen", + "script": "Skripte", + "smtp": "SMTP-Benachrichtigungsdienste", + "statistics": "Statistik-Entitäten", + "telegram": "Telegram-Benachrichtigungsdienste", + "template": "Template-Entitäten", + "trend": "Trend-Entitäten", + "universal": "Universelle Media Player-Entitäten", + "zone": "Zonen" }, "server_management": { "confirm_restart": "Möchtest du Home Assistant wirklich neu starten?", @@ -2985,6 +3046,7 @@ "column_parameter": "Parameter", "description": "Mit dem Dienstentwicklungstool kannst du jeden verfügbaren Dienst in Home Assistant aufrufen.", "fill_example_data": "Mit Beispieldaten füllen", + "no_template_ui_support": "Die Benutzeroberfläche unterstützt keine Templates. Du kannst aber weiterhin den YAML-Editor verwenden.", "title": "Dienste", "ui_mode": "Zum UI Modus", "yaml_mode": "Zum YAML Modus", @@ -3012,7 +3074,7 @@ }, "templates": { "all_listeners": "Dieses Template überwacht alle Zustandsänderungsereignisse.", - "description": "Vorlagen werden durch die Jinja2-Template-Engine mit einigen für Home Assistant spezifischen Erweiterungen gerendert.", + "description": "Templates werden durch die Jinja2-Template-Engine mit einigen für Home Assistant spezifischen Erweiterungen gerendert.", "domain": "Domain", "editor": "Template-Editor", "entity": "Entität", @@ -3684,6 +3746,10 @@ } }, "page-onboarding": { + "analytics": { + "finish": "Weiter", + "intro": "Teile Statistiken aus deiner Instanz. Diese Daten werden unter {link}" + }, "core-config": { "button_detect": "Erkennen", "finish": "Weiter", @@ -3693,12 +3759,14 @@ "location_name": "Name deiner Home Assistant Installation", "location_name_default": "Home" }, + "finish": "Fertig", "integration": { "finish": "Fertig", "intro": "Geräte und Dienste werden in Home Assistant als Integrationen dargestellt. Sie können jetzt oder später über die Konfigurationsseite eingerichtet werden.", "more_integrations": "Mehr" }, "intro": "Sind Sie bereit, dein Zuhause zu wecken, Ihre Privatsphäre zurückzugewinnen und einer weltweiten Gemeinschaft von Tüftlern beizutreten?", + "next": "Weiter", "restore": { "description": "Alternativ kannst du von einem vorherigen Snapshot wiederherstellen.", "hide_log": "Vollständiges Protokoll ausblenden", @@ -3793,6 +3861,15 @@ "enable": "Aktivieren", "header": "Multi-Faktor-Authentifizierungsmodul" }, + "number_format": { + "description": "Wähle aus, wie Zahlen formatiert werden sollen.", + "dropdown_label": "Zahlenformat", + "formats": { + "language": "Auto (Spracheinstellung verwenden)", + "none": "Keine" + }, + "header": "Zahlenformat" + }, "push_notifications": { "add_device_prompt": { "input_label": "Gerätename", diff --git a/translations/frontend/en.json b/translations/frontend/en.json index ef4f9e358f..ec2c51a42a 100644 --- a/translations/frontend/en.json +++ b/translations/frontend/en.json @@ -843,8 +843,10 @@ "related-filter-menu": { "filter_by_area": "Filter by area", "filter_by_device": "Filter by device", + "filter_by_entity": "Filter by entity", "filtered_by_area": "area: {area_name}", - "filtered_by_device": "device: {device_name}" + "filtered_by_device": "device: {device_name}", + "filtered_by_entity": "entity: {entity_name}" }, "related-items": { "area": "Area", diff --git a/translations/frontend/es.json b/translations/frontend/es.json index 68db774d39..01b5b0d4c6 100644 --- a/translations/frontend/es.json +++ b/translations/frontend/es.json @@ -843,8 +843,10 @@ "related-filter-menu": { "filter_by_area": "Filtrar por área", "filter_by_device": "Filtrar por dispositivo", + "filter_by_entity": "Filtrar por entidad", "filtered_by_area": "área: {area_name}", - "filtered_by_device": "dispositivo: {device_name}" + "filtered_by_device": "dispositivo: {device_name}", + "filtered_by_entity": "entidad: {entity_name}" }, "related-items": { "area": "Área", diff --git a/translations/frontend/et.json b/translations/frontend/et.json index b2496473a8..908c51ae58 100644 --- a/translations/frontend/et.json +++ b/translations/frontend/et.json @@ -843,8 +843,10 @@ "related-filter-menu": { "filter_by_area": "Filtreeri ala järgi", "filter_by_device": "Filtreeri seadmete järgi", + "filter_by_entity": "Filtreeri olemi järgi", "filtered_by_area": "ala: {area_name}", - "filtered_by_device": "seade: {device_name}" + "filtered_by_device": "seade: {device_name}", + "filtered_by_entity": "olem: {entity_name}" }, "related-items": { "area": "Ala", diff --git a/translations/frontend/fa.json b/translations/frontend/fa.json index e7eef99e76..f661eea4a8 100644 --- a/translations/frontend/fa.json +++ b/translations/frontend/fa.json @@ -99,6 +99,11 @@ "unknown": "نامشخص" } }, + "supervisor": { + "common": { + "close": "بستن" + } + }, "ui": { "auth_store": { "ask": "ميخواي وارد سیستم بموني؟", @@ -312,6 +317,10 @@ "play": "پخش", "play-media": "پخش رسانه ها" }, + "related-filter-menu": { + "filtered_by_area": "ناحیه: {area_name}", + "filtered_by_device": "دستگاه : {device_name}" + }, "related-items": { "area": "Zona", "device": "Dispozitiv", @@ -450,6 +459,9 @@ "server_control": "کنترل های سرور", "users": "کاربران", "zone": "مناطق" + }, + "types": { + "server_control": "سرور" } } }, @@ -472,6 +484,9 @@ "zha_device_card": { "device_name_placeholder": "Prenume utilizator" } + }, + "zha_reconfigure_device": { + "heading": "پیکربندی مجدد دستگاه" } }, "duration": { @@ -891,6 +906,9 @@ "description": "سیستم واحد ، مکان ، منطقه زمانی و سایر پارامترهای کلی", "section": { "core": { + "analytics": { + "learn_more": "درباره نحوه پردازش داده های خود بیشتر بیاموزید." + }, "core_config": { "edit_requires_storage": "ویرایشگر غیرفعال شده است چون پیکربندی ذخیره شده در configuration.yaml.", "elevation": "ارتفاع", @@ -1031,6 +1049,9 @@ "clear": "پاک کردن", "filtering_by": "فیلتر کردن با" }, + "hassio": { + "button": "پیکربندی" + }, "header": "پیکربندی HOME ASSistant", "helpers": { "caption": "Ajutoare", @@ -1120,6 +1141,13 @@ }, "introduction": "در اینجا می توانید اجزای خود و صفحه اصلی دستیار را پیکربندی کنید.فعلا ویرایش همه چیز از طریق ویرایش بصری امکان پذیر نیست ، اما ما داریم روی اون کار می کنیم.", "logs": { + "level": { + "critical": "بحرانی", + "debug": "اشکال زدایی", + "error": "خطا", + "info": "اطلاعات", + "warning": "هشدار" + }, "refresh": "تازه کن" }, "lovelace": { diff --git a/translations/frontend/fi.json b/translations/frontend/fi.json index 0bbd6fbe45..b0f919f978 100644 --- a/translations/frontend/fi.json +++ b/translations/frontend/fi.json @@ -3489,12 +3489,14 @@ "location_name": "Home Assistant -asennuksen nimi", "location_name_default": "Koti" }, + "finish": "Valmis", "integration": { "finish": "Valmis", "intro": "Laitteet ja palvelut on esitetty Home Assistantissa integraatioina. Voit määrittää ne nyt tai tehdä sen myöhemmin asetus valikosta.", "more_integrations": "Lisää" }, "intro": "Oletko valmis herättämään kotisi eloon, palauttamaan yksityisyytesi ja liittymään maailmanlaajuiseen nikkarien joukkoon?", + "next": "Seuraava", "restore": { "description": "Vaihtoehtoisesti voit palauttaa aiemmasta varmuuskopiosta.", "hide_log": "Piilota koko loki", diff --git a/translations/frontend/fr.json b/translations/frontend/fr.json index f8d1f336c4..6002e035da 100644 --- a/translations/frontend/fr.json +++ b/translations/frontend/fr.json @@ -723,6 +723,9 @@ "today": "Aujourd'hui" }, "data-table": { + "clear": "Effacer", + "filtering_by": "Filtrer par", + "hidden": "{number} masqué", "no-data": "Pas de données", "search": "Chercher" }, @@ -837,6 +840,14 @@ "label": "Image", "unsupported_format": "Format non pris en charge, veuillez choisir une image JPEG, PNG ou GIF." }, + "related-filter-menu": { + "filter_by_area": "Filtrer par zone", + "filter_by_device": "Filtrer par appareil", + "filter_by_entity": "Filtrer par entité", + "filtered_by_area": "zone: {area_name}", + "filtered_by_device": "appareil: {device_name}", + "filtered_by_entity": "entité: {entity_name}" + }, "related-items": { "area": "Pièce", "automation": "Partie des automatisations suivantes", @@ -1132,6 +1143,11 @@ "perform_action": "{action} Serveur", "restart": "Redémarrer", "stop": "Arrêter" + }, + "types": { + "navigation": "Naviguer", + "reload": "Recharger", + "server_control": "Serveur" } }, "filter_placeholder": "Filtre d'entité" @@ -1174,6 +1190,9 @@ "zha_device_card": { "device_name_placeholder": "Nom personnalisé" } + }, + "zha_reconfigure_device": { + "heading": "Reconfigurer l'appareil" } }, "duration": { @@ -1468,6 +1487,7 @@ "move_down": "Déplacer vers le bas", "move_up": "Déplacer vers le haut", "save": "Sauvegarder", + "show_trace": "Afficher la trace", "triggers": { "add": "Ajouter un déclencheur", "delete": "Supprimer", @@ -1676,6 +1696,8 @@ "info": "Grâce à l'intégration de Google Assistant pour Home Assistant Cloud, vous pourrez contrôler tous vos appareils Home Assistant via n'importe quel appareil compatible avec Google Assistant.", "info_state_reporting": "Si vous activez le rapport d'état, Home Assistant envoie tous les changements d'état des entités exposées à Google. Cela vous permet de toujours voir les derniers états de l'application Google.", "manage_entities": "Gérer les entités", + "not_configured_text": "Avant de pouvoir utiliser Google Assistant, vous devez activer l'action Home Assistant Cloud pour Google Assistant dans l'application Google Home.", + "not_configured_title": "Google Assistant n'est pas activé", "security_devices": "Dispositif de sécurité", "sync_entities": "Synchroniser les entités vers Google", "sync_entities_404_message": "Échec de la synchronisation de vos entités avec Google, demandez à Google \"Hey Google, synchronise mes appareils\" de synchroniser vos entités.", @@ -1838,6 +1860,23 @@ "description": "Unités de mesure, emplacement, fuseau horaire et autres paramètres généraux", "section": { "core": { + "analytics": { + "documentation": "Avant d'activer cette fonction, prenez soin de visiter la page de documentation {link} afin de comprendre ce que vous envoyez et comment cela est conservé.", + "instance_id": "ID de l'instance: {huuid}", + "learn_more": "En appendre plus sur le traitement de vos données", + "preference": { + "diagnostics": { + "description": "Partager les rapports d'accident et les informations de diagnostic", + "title": "Diagnostiques" + }, + "statistics": { + "title": "Statistiques d'utilisation" + }, + "usage": { + "title": "Intégrations utilisées" + } + } + }, "core_config": { "edit_requires_storage": "L'éditeur est désactivé car la configuration est stockée dans configuration.yaml.", "elevation": "Élévation", @@ -2025,6 +2064,9 @@ "filtering_by": "Filtrage par", "show": "Montrer" }, + "hassio": { + "button": "Configurer" + }, "header": "Configurer Home Assistant", "helpers": { "caption": "Entrées", @@ -2111,6 +2153,7 @@ "entity_unavailable": "Entité indisponible", "firmware": "Firmware: {version}", "hub": "Connecté via", + "logs": "journaux", "manuf": "par {manufacturer}", "no_area": "Pas de zone", "options": "Options", @@ -2138,6 +2181,7 @@ "finish": "Terminer", "loading_first_time": "Veuillez patienter pendant que l'intégration est en cours d'installation", "not_all_required_fields": "Tous les champs obligatoires ne sont pas renseignés.", + "not_loaded": "Cette intégration ne peut être chargée, essayez de redémarrer Home Assistant", "pick_flow_step": { "new_flow": "Non, configurez une autre instance de {integration}", "title": "Nous les avons découverts, vous voulez les mettre en place?" @@ -2152,6 +2196,7 @@ "disable": { "disabled_integrations": "{number} désactivé", "hide_disabled": "Masquer les intégrations désactivées", + "show": "Montrer", "show_disabled": "Afficher les intégrations désactivées" }, "discovered": "Découvert", @@ -2187,6 +2232,12 @@ "clear": "Nettoyer", "description": "Afficher les journaux de Home Assistant", "details": "Détails du journal ( {level} )", + "level": { + "critical": "CRITIQUE", + "error": "ERREUR", + "info": "INFO", + "warning": "ATTENTION" + }, "load_full_log": "Charger le journal complet de Home Assistant", "loading_log": "Chargement du journal des erreurs…", "multiple_messages": "message survenu pour la première fois à {time} et apparu {counter} fois.", @@ -3682,6 +3733,9 @@ } }, "page-onboarding": { + "analytics": { + "finish": "Suivant" + }, "core-config": { "button_detect": "Détecter", "finish": "Suivant", @@ -3691,12 +3745,14 @@ "location_name": "Nom de votre installation Home Assistant", "location_name_default": "Maison" }, + "finish": "Terminer", "integration": { "finish": "Terminer", "intro": "Les appareils et les services sont représentés dans Home Assistant sous forme d’intégrations. Vous pouvez les configurer maintenant ou le faire plus tard à partir de l'écran de configuration.", "more_integrations": "Plus" }, "intro": "Êtes-vous prêt à réveiller votre maison, à récupérer votre vie privée et à rejoindre une communauté mondiale de bricoleurs?", + "next": "Suivant", "restore": { "description": "Vous pouvez également restaurer à partir d'un instantané précédent.", "hide_log": "Masquer le journal", @@ -3791,6 +3847,12 @@ "enable": "Activer", "header": "Modules d'authentification multi-facteurs" }, + "number_format": { + "description": "Définir le formatage des nombres", + "formats": { + "none": "Aucun" + } + }, "push_notifications": { "add_device_prompt": { "input_label": "Nom de l'appareil", diff --git a/translations/frontend/gl.json b/translations/frontend/gl.json index 37e9541e15..74b762b8e3 100644 --- a/translations/frontend/gl.json +++ b/translations/frontend/gl.json @@ -165,6 +165,7 @@ } }, "common": { + "cancel": "Cancelar", "close": "Pechar", "description": "Descrición", "error": { @@ -175,8 +176,10 @@ "failed_to_update_name": "Non se puido actualizar {name}", "learn_more": "Aprender máis", "new_version_available": "Nova versión dispoñible", + "newest_version": "Versión máis nova", "no": "Non", "refresh": "Actualizar", + "release_notes": "Notas de lanzamento", "reload": "Recargar", "reset_defaults": "Restablecer os valores predeterminados", "reset_options": "Restablecer as opcións", @@ -185,6 +188,7 @@ "running_version": "Actualmente estás executando a versión {version}", "save": "Gardar", "show_more": "Amosar máis información sobre isto", + "update": "Actualización", "update_available": "{count, plural,\n one {actualización pendente}\n other {{count} actualizacións pendentes}\n}", "version": "Versión", "yes": "Si" @@ -387,7 +391,11 @@ "ui": { "card": { "fan": { - "direction": "Enderezo" + "direction": "Enderezo", + "preset_mode": "Modo predefinido" + }, + "script": { + "run": "Executar" }, "weather": { "attributes": { @@ -441,7 +449,9 @@ "show_areas": "Amosar áreas" }, "data-table": { - "clear": "Elim" + "clear": "Elim", + "filtering_by": "Filtrando por", + "hidden": "{number} oculto" }, "device-picker": { "show_devices": "Amosar dispositivos" @@ -461,11 +471,23 @@ }, "show_trace": "Amosar rastro" }, + "media-browser": { + "class": { + "url": "URL" + } + }, "related-filter-menu": { "filter_by_area": "Filtrar por área", "filter_by_device": "Filtrar por dispositivo", + "filter_by_entity": "Filtrar por entidade", "filtered_by_area": "área: {area_name}", - "filtered_by_device": "dispositivo: {device_name}" + "filtered_by_device": "dispositivo: {device_name}", + "filtered_by_entity": "entidade: {entity_name}" + }, + "service-control": { + "required": "Este campo é necesario", + "service_data": "Datos do servizo", + "target": "Obxectivos" } }, "dialogs": { @@ -496,10 +518,18 @@ "view_in_visualization": "Ver en Visualización" }, "device_children": "Dispositivos Zigbee fillos" + }, + "zha_reconfigure_device": { + "heading": "Reconfiguración do dispositivo" } }, "errors": { "config": { + "edit_in_yaml_supported": "Aínda podes editar a túa configuración en YAML.", + "editor_not_available": "Non hai editor visual dispoñible para o tipo \" {type} \".", + "editor_not_supported": "O editor visual non é compatible con esta configuración", + "key_missing": "Falta a clave requirida \" {key} \".", + "key_wrong_type": "O editor visual non admite o valor proporcionado para \" {key} Admitimos ( {type_correct} ) pero recibimos ( {type_wrong} ).", "no_template_editor_support": "Os modelos non son compatibles co editor visual" } }, @@ -569,6 +599,16 @@ "google": { "not_configured_text": "Antes de usar o Asistente de Google, debes activar a skill Home Assistant Cloud para Google Assistant na aplicación Google Home.", "not_configured_title": "Google Assistant non está activado" + }, + "tts": { + "dialog": { + "example_message": "Ola {name} , podes reproducir calquera texto en calquera reprodutor multimedia compatible.", + "header": "Proba Texto a voz", + "play": "Reproducir", + "target": "Obxectivo", + "target_browser": "Navegador" + }, + "try": "Probar" } }, "forgot_password": { @@ -602,6 +642,7 @@ "title": "Estatísticas de uso" }, "usage_supervisor": { + "description": "Isto inclúe os nomes e as capacidades das súas integracións e complementos", "title": "Integracións e complementos usados" }, "usage": { @@ -614,6 +655,7 @@ } }, "devices": { + "confirm_disable_config_entry": "Non hai máis dispositivos para a entrada de configuración {entry_name} . Queres deshabilitar a entrada de configuración?", "enabled_description": "Os dispositivos desactivados non se amosarán e as entidades que pertencen ao dispositivo desactivaranse e non se engadirán ao Asistente doméstico.", "picker": { "filter": { @@ -635,15 +677,39 @@ "filtering": { "show": "Amosar" }, + "hassio": { + "button": "Configurar" + }, "integrations": { "config_entry": { + "disable_restart_confirm": "Reinicie o Home Assistant para rematar de desactivar esta integración", + "disable": { + "disable_confirm": "Seguro que queres desactivar esta entrada de configuración? Os seus dispositivos e entidades estarán desactivados.", + "disabled": "Desactivado", + "disabled_by": { + "device": "dispositivo", + "integration": "integración", + "user": "usuario" + }, + "disabled_cause": "Desactivado por {cause}" + }, + "enable_restart_confirm": "Reinicie o Home Assistant para rematar de habilitar esta integración", "logs": "rexistros", "not_loaded": "Non cargado, comproba o {logs_link}" }, "config_flow": { - "not_loaded": "Non se puido cargar a integración. Tenta reiniciar Home Assistant." + "could_not_load": "Non se puido cargar o fluxo de configuración", + "error": "Erro", + "not_loaded": "Non se puido cargar a integración. Tenta reiniciar Home Assistant.", + "pick_flow_step": { + "new_flow": "Non, configura outra instancia de {integration}", + "title": "Descubrimos estes, queres montalos?" + } }, + "confirm_new": "¿Queres configurar {integration} ?", "disable": { + "disabled_integrations": "{number} desactivado", + "hide_disabled": "Ocultar integracións desactivadas", "show": "Amosar", "show_disabled": "Amosar as integracións desactivadas" }, @@ -689,9 +755,23 @@ "show_info": "Amosar información sobre o script" } }, + "server_control": { + "section": { + "reloading": { + "filesize": "Entidades de tamaño de ficheiro", + "mqtt": "Entidades MQTT configuradas manualmente", + "ping": "Ping a entidades binarias do sensor", + "reload": "{domain}", + "rpi_gpio": "Entidades GPIO de Raspberry Pi", + "smtp": "Servizos de notificación SMTP", + "trend": "Entidades de tendencia" + } + } + }, "users": { "editor": { - "name": "Nome de visualización" + "name": "Nome de visualización", + "password_changed": "O contrasinal cambiouse correctamente" }, "picker": { "headers": { @@ -720,6 +800,12 @@ } }, "zwave": { + "migration": { + "ozw": { + "header": "Migrar a OpenZWave", + "introduction": "Este asistente axudarache a migrar da integración herdada de Z-Wave á integración OpenZWave que está actualmente en versión beta." + } + }, "ozw_log": { "introduction": "Ver o rexistro. 0 é o mínimo (carga todo o rexistro) e 1000 é o máximo. Load amosará un rexistro estático e a cola actualizarase automaticamente co último número de liñas especificado no rexistro." } @@ -728,9 +814,18 @@ "developer-tools": { "tabs": { "services": { - "no_template_ui_support": "A interface de usuario non admite modelos, máis podes usar o editor YAML." + "accepts_target": "Este servizo acepta un destino, por exemplo: `entity_id: light.bed_light`", + "all_parameters": "Todos os parámetros dispoñibles", + "no_template_ui_support": "A interface de usuario non admite modelos, máis podes usar o editor YAML.", + "ui_mode": "Ir ao modo de interface de usuario", + "yaml_mode": "Ir ao modo YAML", + "yaml_parameters": "Os parámetros só están dispoñibles no modo YAML" + }, + "states": { + "copy_id": "Copia a identificación ao portapapeis" }, "templates": { + "no_listeners": "Este modelo non escoita ningún evento e non se actualizará automaticamente.", "unknown_error_template": "Erro descoñecido ao amosar o modelo" } } @@ -886,6 +981,10 @@ "show_visual_editor": "Amosar editor visual", "typed_header": "Configuración da tarxeta {type}" }, + "menu": { + "manage_dashboards": "Xestionar paneis", + "manage_resources": "Xestionar recursos" + }, "migrate": { "para_migrate": "Home Assistant pode engadir identificacións a todas as súas tarxetas e vistas automaticamente premendo o botón \"Migrar configuración\"." }, @@ -917,12 +1016,15 @@ "my": { "component_not_loaded": "Esta redirección non é compatible coa túa instancia de Home Assistant. Necesitas a integración {integration} para usar esta redirección.", "documentation": "documentación", + "error": "Produciuse un erro descoñecido", + "faq_link": "Preguntas máis frecuentes sobre o meu Home Assistant", "no_supervisor": "Esta redirección non é compatible coa instalación do teu Home Assistant. Precísase o sistema operativo Home Assistant ou o método de instalación do Home Assistant supervisado. Para obter máis información, consulte o {docs_link} .", "not_supported": "Esta redirección non é compatible coa túa instancia de Home Assistant. Comprobe a {link} para coñecer as redireccións compatibles e a versión na que se introduciron." }, "page-onboarding": { "analytics": { - "finish": "Seguinte" + "finish": "Seguinte", + "intro": "Comparte análises desde a túa instancia. Estes datos estarán dispoñibles publicamente en {link}" }, "core-config": { "intro_location": "Gustaríanos saber onde vives. Esta información axudará a amosar información e configurar automatismos baseados no sol. Estes datos nunca se comparten fóra da túa rede." diff --git a/translations/frontend/it.json b/translations/frontend/it.json index fd10898af9..217bb31b80 100644 --- a/translations/frontend/it.json +++ b/translations/frontend/it.json @@ -245,7 +245,7 @@ "start": "avvia", "stop": "arresta", "uninstall": "disinstalla", - "visit_addon_page": "Visita la pagina con {name} per maggiori dettagli" + "visit_addon_page": "Visita la pagina di {name} per maggiori dettagli" }, "documentation": { "get_documentation": "Impossibile ottenere la documentazione del componente aggiuntivo, {error}" @@ -843,8 +843,10 @@ "related-filter-menu": { "filter_by_area": "Filtra per area", "filter_by_device": "Filtra per dispositivo", + "filter_by_entity": "Filtra per entità", "filtered_by_area": "area: {area_name}", - "filtered_by_device": "dispositivo: {device_name}" + "filtered_by_device": "dispositivo: {device_name}", + "filtered_by_entity": "entità: {entity_name}" }, "related-items": { "area": "Area", @@ -1293,7 +1295,7 @@ "delete_confirm": "Sei sicuro di voler eliminare questo?", "duplicate": "Duplica", "header": "Azioni", - "introduction": "Le Azioni sono ciò che Home Assistant eseguirà quando un'attivazione (o trigger) avvia un'automazione.", + "introduction": "Le Azioni sono ciò che Home Assistant eseguirà quando un'Attivazione (o Trigger) avvia un'Automazione.", "learn_more": "Per saperne di più sulle azioni", "name": "Azione", "type_select": "Tipo di azione", @@ -1385,7 +1387,7 @@ "delete_confirm": "Sei sicuro di voler eliminare questo?", "duplicate": "Duplica", "header": "Condizioni", - "introduction": "Le Condizioni sono facoltative e impediranno l'esecuzione dell'automazione a meno che non siano soddisfatte tutte.", + "introduction": "Le Condizioni sono facoltative e impediranno l'esecuzione dell'Automazione a meno che non siano soddisfatte tutte.", "learn_more": "Per saperne di più sulle condizioni", "name": "Condizione", "type_select": "Tipo di condizione", @@ -1475,7 +1477,7 @@ }, "modes": { "description": "La Modalità controlla cosa succede quando l'automazione viene attivata mentre le azioni sono ancora in esecuzione da una attivazione precedente. Controllare la {documentation_link} per maggiori informazioni.", - "documentation": "documentazione di automazione", + "documentation": "documentazione sull'automazione", "label": "Modalità", "parallel": "Parallelo", "queued": "In coda", @@ -1563,12 +1565,12 @@ }, "time_pattern": { "hours": "Ore", - "label": "Pattern temporale", + "label": "Ricorrenza temporale", "minutes": "Minuti", "seconds": "Secondi" }, "time": { - "at": "Al tempo", + "at": "Al momento", "label": "Ora", "type_input": "Valore di un aiutante data/ora", "type_value": "Tempo fisso" @@ -1592,7 +1594,7 @@ }, "picker": { "add_automation": "Aggiungi Automazione", - "delete_automation": "Cancellare l'automazione", + "delete_automation": "Elimina automazione", "delete_confirm": "Sei sicuro di voler eliminare questa automazione?", "dev_automation": "Debug dell'automazione", "dev_only_editable": "È possibile eseguire il debug solo delle automazioni definite in automations.yaml.", @@ -1622,7 +1624,7 @@ "error_unsupported": "Non siamo riusciti a creare un'automazione per questo (ancora?).", "for_example": "Per esempio:", "header": "Crea una nuova automazione", - "introduction": "Digitare sotto ciò che questa automazione dovrebbe fare, e proveremo a convertirlo in un'automazione Home Assistant.", + "introduction": "Scrivi qui sotto cosa dovrebbe fare questa automazione, e noi cercheremo di convertirla in un'automazione di Home Assistant.", "language_note": "Nota: per il momento è supportato solo l'inglese." } } @@ -1946,7 +1948,7 @@ "unknown_condition": "Condizione sconosciuta" }, "create": "Crea l'automazione con il dispositivo", - "create_disable": "Non è possibile creare automazione con un dispositivo disabilitato", + "create_disable": "Non è possibile creare una automazione con un dispositivo disabilitato", "no_automations": "Nessuna automazione", "no_device_automations": "Non ci sono Automazioni disponibili per questo dispositivo.", "triggers": { @@ -2837,7 +2839,7 @@ "edit_home_zone_narrow": "Il raggio della zona Casa non può ancora essere modificato dal frontend. La posizione può essere modificata dalla configurazione generale.", "go_to_core_config": "Passare alla configurazione generale?", "home_zone_core_config": "La posizione della zona Casa è modificabile dalla pagina di configurazione generale. Il raggio della zona iniziale non può ancora essere modificato dal frontend. Vuoi andare alla configurazione generale?", - "introduction": "Le Zone consentono di specificare determinate regioni sulla Terra. Quando una persona si trova all'interno di una Zona, nel suo Stato ci sarà il nome dalla Zona. Le Zone possono anche essere utilizzate come Attivazioni (o Trigger) o Condizioni all'interno delle impostazioni di Automazione.", + "introduction": "Le Zone consentono di specificare determinate regioni sulla Terra. Quando una persona si trova all'interno di una Zona, nel suo stato apparirà il nome dalla Zona. Le Zone possono anche essere utilizzate come Attivazioni (o Trigger) o Condizioni all'interno delle impostazioni di una Automazione.", "no_zones_created_yet": "Sembra che tu non abbia ancora creato nessuna zona." }, "zwave_js": { diff --git a/translations/frontend/ko.json b/translations/frontend/ko.json index e40e5a31ea..49431e7819 100644 --- a/translations/frontend/ko.json +++ b/translations/frontend/ko.json @@ -843,8 +843,10 @@ "related-filter-menu": { "filter_by_area": "영역별 필터", "filter_by_device": "기기별 필터", + "filter_by_entity": "구성요소별 필터", "filtered_by_area": "영역: {area_name}", - "filtered_by_device": "기기: {device_name}" + "filtered_by_device": "기기: {device_name}", + "filtered_by_entity": "구성요소: {entity_name}" }, "related-items": { "area": "영역", diff --git a/translations/frontend/nl.json b/translations/frontend/nl.json index 1125c519cb..85312b2964 100644 --- a/translations/frontend/nl.json +++ b/translations/frontend/nl.json @@ -843,8 +843,10 @@ "related-filter-menu": { "filter_by_area": "Filter op gebied", "filter_by_device": "Filter op apparaat", + "filter_by_entity": "Filter op entiteit", "filtered_by_area": "gebied: {area_name}", - "filtered_by_device": "apparaat: {device_name}" + "filtered_by_device": "apparaat: {device_name}", + "filtered_by_entity": "entiteit: {entity_name}" }, "related-items": { "area": "Gebied", @@ -1105,37 +1107,37 @@ "zone": "Zones" }, "reload": { - "automation": "Herlaad automatiseringen", - "command_line": "Herlaad commandline entiteiten", - "core": "Herlaad locatie & aanpassingen", - "filesize": "Herlaad bestandsgroote entiteiten", - "filter": "Herlaad filter entiteiten", - "generic": "Herlaad", - "generic_thermostat": "Herlaad", - "group": "Herlaad groepen, groepsentiteiten en notificatieservices", - "history_stats": "Herlaad historische statistieken entiteiten", - "homekit": "Herlaad HomeKit", - "input_boolean": "Herlaad input booleans", - "input_datetime": "Herlaad input datatijd", - "input_number": "Herlaad input nummers", - "input_select": "Herlaad input selecties", - "input_text": "Herlaad input teksten", + "automation": "Automatiseringen", + "command_line": "Opdrachtregel-entiteiten", + "core": "Locatie en aanpassingen", + "filesize": "Bestandsgrootte entiteiten", + "filter": "Filter-entiteiten", + "generic": "Generieke IP camera entiteiten", + "generic_thermostat": "Generieke thermostaat entiteiten", + "group": "Groepen, groepsentiteiten en notify services", + "history_stats": "Historische statistieken entiteiten", + "homekit": "HomeKit", + "input_boolean": "Input booleans", + "input_datetime": "Input date times", + "input_number": "Input numbers", + "input_select": "Input selects", + "input_text": "Input texts", "min_max": "Laad min/max entiteiten opnieuw", - "mqtt": "Herlaad mqtt entiteiten", - "person": "Herlaad personen", - "ping": "Herlaad binaire ping-sensorentiteiten", - "reload": "Herlaad {domain}", - "rest": "Herlaad rest entiteiten en notificatieservices", - "rpi_gpio": "Herlaad Raspberry PI GPIO entiteiten", + "mqtt": "Handmatig geconfigureerde MQTT-entiteiten", + "person": "Personen", + "ping": "Ping binaire sensor entiteiten", + "reload": "{domain}", + "rest": "Rest-entiteiten en notify-services", + "rpi_gpio": "Raspberry Pi GPIO-entiteiten", "scene": "Scenes", - "script": "Herlaad scripts", - "smtp": "Herlaad smtp notificatie services", - "statistics": "Herlaad statistieken entiteiten", - "telegram": "Herlaad telegram notificatie services", - "template": "Herlaad template entiteiten", - "trend": "Herlaad trend entiteiten", - "universal": "Herlaad universele mediaplayer entiteiten", - "zone": "Herlaad zones" + "script": "Scripts", + "smtp": "SMTP notify services", + "statistics": "Statistische entiteiten", + "telegram": "Telegram notify services", + "template": "Template entiteiten", + "trend": "Trend-entiteiten", + "universal": "Universele mediaspeler entiteiten", + "zone": "Zones" }, "server_control": { "perform_action": "{action} Server", @@ -2613,17 +2615,17 @@ "input_text": "Input texts", "introduction": "Sommige delen van Home Assistant kunnen opnieuw worden geladen zonder dat een herstart vereist is. Als je herladen gebruikt, wordt de huidige configuratie leeggemaakt en wordt de nieuwe geladen.", "min_max": "Min/max entiteiten", - "mqtt": "Herlaad handmatig geconfigureerde MQTT-entiteiten", + "mqtt": "Handmatig geconfigureerde MQTT-entiteiten", "person": "Personen", "ping": "Ping binaire sensor entiteiten", - "reload": "Herlaad {domain}", + "reload": "{domain}", "rest": "Rest-entiteiten en notify-services", - "rpi_gpio": "Herlaad Raspberry Pi GPIO-entiteiten", + "rpi_gpio": "Raspberry Pi GPIO-entiteiten", "scene": "Scenes", "script": "Scripts", - "smtp": "Herlaad de SMTP notify services", + "smtp": "SMTP notify services", "statistics": "Statistische entiteiten", - "telegram": "Herlaad telegram notify services", + "telegram": "Telegram notify services", "template": "Template entiteiten", "trend": "Trend-entiteiten", "universal": "Universele mediaspeler entiteiten", diff --git a/translations/frontend/pl.json b/translations/frontend/pl.json index 64d024db0d..e208dd04a2 100644 --- a/translations/frontend/pl.json +++ b/translations/frontend/pl.json @@ -457,7 +457,7 @@ "leave_beta_action": "Opuść kanał beta", "leave_beta_description": "Pobieraj stabilne aktualizacje dla Home Assistanta, Supervisora i hosta", "ram_usage": "Zużycie pamięci przez Supervisora", - "reload_supervisor": "Wczytaj ponownie Supervisora", + "reload_supervisor": "Wczytaj ponownie Supervisor", "share_diagnostics": "Udostępnij dane diagnostyczne", "share_diagnostics_description": "Udostępniaj raporty o awariach i informacje diagnostyczne.", "share_diagonstics_description": "Czy chcesz automatycznie udostępniać raporty o awariach i informacje diagnostyczne, gdy Supervisor napotka nieoczekiwane błędy? {line_break} Pozwoli nam to rozwiązać problemy, informacje są dostępne tylko dla głównego zespołu Home Assistant Core i nie będą udostępniane innym. {line_break} Dane nie zawierają żadnych prywatnych/wrażliwych informacji i możesz to wyłączyć w ustawieniach w dowolnym momencie.", @@ -2071,6 +2071,9 @@ "filtering_by": "Filtrowanie przez", "show": "Pokaż" }, + "hassio": { + "button": "Konfiguruj" + }, "header": "Konfiguruj Home Assistanta", "helpers": { "caption": "Pomocnicy", diff --git a/translations/frontend/ru.json b/translations/frontend/ru.json index de7a2f5826..b80637f77e 100644 --- a/translations/frontend/ru.json +++ b/translations/frontend/ru.json @@ -279,7 +279,7 @@ "failed_to_update_name": "Не удалось обновить {name}", "learn_more": "Узнать больше", "new_version_available": "Доступна новая версия", - "newest_version": "Последняя версия", + "newest_version": "Доступная версия", "no": "Нет", "refresh": "Обновить", "release_notes": "Список изменений", @@ -843,8 +843,10 @@ "related-filter-menu": { "filter_by_area": "Фильтр по помещениям", "filter_by_device": "Фильтр по устройствам", + "filter_by_entity": "Фильтр по объектам", "filtered_by_area": "помещению: {area_name}", - "filtered_by_device": "устройству: {device_name}" + "filtered_by_device": "устройству: {device_name}", + "filtered_by_entity": "объекту: {entity_name}" }, "related-items": { "area": "Помещение", diff --git a/translations/frontend/zh-Hans.json b/translations/frontend/zh-Hans.json index 07ea9b0377..b1effc5b06 100644 --- a/translations/frontend/zh-Hans.json +++ b/translations/frontend/zh-Hans.json @@ -840,6 +840,14 @@ "label": "图片", "unsupported_format": "格式不受支持,请选择 JPEG、PNG 或 GIF 图像。" }, + "related-filter-menu": { + "filter_by_area": "按区域", + "filter_by_device": "按设备", + "filter_by_entity": "按实体", + "filtered_by_area": "区域: {area_name}", + "filtered_by_device": "设备: {device_name}", + "filtered_by_entity": "实体: {entity_name}" + }, "related-items": { "area": "区域", "automation": "以下自动化的一部分", @@ -1182,6 +1190,9 @@ "zha_device_card": { "device_name_placeholder": "更改设备名称" } + }, + "zha_reconfigure_device": { + "heading": "重新配置设备" } }, "duration": { @@ -1685,6 +1696,7 @@ "info": "通过 Home Assistant Cloud 的 Google Assistant 集成,您将可以通过任何启用了 Google Assistant 的设备来控制所有 Home Assistant 设备。", "info_state_reporting": "如果启用状态报告,则 Home Assistant 会将公开实体的所有状态变化发送给 Google。这样您就可以始终在 Google 应用中看到最新状态。", "manage_entities": "管理实体", + "not_configured_text": "在使用 Google Assistant 之前,需要在 Google Home 应用中激活 Google Assistant 的 Home Assistant Cloud 技能。", "not_configured_title": "Google Assistant 未激活", "security_devices": "安全设备", "sync_entities": "同步实体到 Google", @@ -1849,9 +1861,11 @@ "section": { "core": { "analytics": { + "documentation": "在启用此功能之前,请确保您已阅读了分析文档页面 {link},以了解将发送的内容及其存储方式。", "header": "分析", "instance_id": "实例ID: {huuid}", "introduction": "共享实例的分析。此数据将在 {link} 公开。", + "learn_more": "详细了解您的数据将被如何处理。", "needs_base": "需要启用基础分析才能启用此选项", "preference": { "base": { @@ -1863,6 +1877,7 @@ "title": "诊断" }, "statistics": { + "description": "包括安装的元素数量,完整列表请参阅文档", "title": "使用情况统计" }, "usage_supervisor": { @@ -2062,6 +2077,9 @@ "filtering_by": "正在筛选:", "show": "显示" }, + "hassio": { + "button": "配置" + }, "header": "配置 Home Assistant", "helpers": { "caption": "辅助元素", @@ -2148,8 +2166,10 @@ "entity_unavailable": "实体不可用", "firmware": "固件:{version}", "hub": "连接于", + "logs": "日志", "manuf": "by {manufacturer}", "no_area": "没有区域", + "not_loaded": "未加载,请检查{logs_link}", "options": "选项", "reload": "重载", "reload_confirm": "集成已重新加载", @@ -2175,6 +2195,7 @@ "finish": "完成", "loading_first_time": "正在安装集成,请稍候", "not_all_required_fields": "请填写所有必填字段", + "not_loaded": "集成未能加载,请尝试重启 Home Assistant。", "pick_flow_step": { "new_flow": "否,设置另一个 {integration} 实例", "title": "已发现以下集成,要设置它们吗?" @@ -2225,10 +2246,17 @@ "clear": "清除", "description": "查看 Home Assistant 日志", "details": "日志详细信息( {level} )", + "level": { + "critical": "CRITICAL", + "debug": "DEBUG", + "error": "ERROR", + "info": "INFO", + "warning": "WARNING" + }, "load_full_log": "加载完整 Home Assistant 日志", "loading_log": "正在加载错误日志...", "multiple_messages": "消息首次出现在 {time},显示了 {counter} 次", - "no_errors": "未报告任何错误。", + "no_errors": "未报告任何错误", "no_issues": "没有新问题!", "refresh": "刷新" }, @@ -3721,6 +3749,10 @@ } }, "page-onboarding": { + "analytics": { + "finish": "下一步", + "intro": "共享实例的分析。此数据将在 {link} 公开。" + }, "core-config": { "button_detect": "自动检测", "finish": "下一步", @@ -3730,12 +3762,14 @@ "location_name": "Home Assistant 安装的名称", "location_name_default": "我的家" }, + "finish": "完成", "integration": { "finish": "完成", "intro": "设备和服务在 Home Assistant 中表示为集成。您可以立即设置它们,也可以稍后在配置屏幕进行设置。", "more_integrations": "更多" }, "intro": "准备好唤醒你的家、找回你的隐私,并加入世界级的极客社区了吗?", + "next": "下一步", "restore": { "description": "或者,您也可以从以前的快照还原。", "hide_log": "隐藏完整日志", diff --git a/translations/frontend/zh-Hant.json b/translations/frontend/zh-Hant.json index 7356aea22a..f67f03898d 100644 --- a/translations/frontend/zh-Hant.json +++ b/translations/frontend/zh-Hant.json @@ -843,8 +843,10 @@ "related-filter-menu": { "filter_by_area": "依區域篩選", "filter_by_device": "依裝置篩選", + "filter_by_entity": "依實體篩選", "filtered_by_area": "區域:{area_name}", - "filtered_by_device": "裝置:{device_name}" + "filtered_by_device": "裝置:{device_name}", + "filtered_by_entity": "實體:{entity_name}" }, "related-items": { "area": "分區",