iobroker.homewizard 0.6.6 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # ioBroker.homewizard
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/iobroker.homewizard)](https://www.npmjs.com/package/iobroker.homewizard)
4
- ![Node](https://img.shields.io/badge/node-%3E%3D20-brightgreen)
4
+ ![Node](https://img.shields.io/badge/node-%3E%3D22-brightgreen)
5
5
  ![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)
6
6
  [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
7
7
  [![npm downloads](https://img.shields.io/npm/dt/iobroker.homewizard)](https://www.npmjs.com/package/iobroker.homewizard)
@@ -24,14 +24,15 @@ Real-time energy monitoring from [HomeWizard](https://www.homewizard.com) Energy
24
24
  - **Battery control** — manage HomeWizard Plug-In Batteries (mode, permissions)
25
25
  - **System control** — LED brightness, cloud toggle, reboot, identify
26
26
  - **REST fallback** — automatic polling when WebSocket is unavailable
27
+ - **Multi-language UI** — state names, descriptions and dropdown labels follow the ioBroker system language (11 languages)
27
28
 
28
29
  ---
29
30
 
30
31
  ## Requirements
31
32
 
32
- - **Node.js >= 20**
33
- - **ioBroker js-controller >= 6.0.11**
34
- - **ioBroker Admin >= 7.6.20**
33
+ - **Node.js >= 22**
34
+ - **ioBroker js-controller >= 7.0.7**
35
+ - **ioBroker Admin >= 7.8.23**
35
36
  - **HomeWizard device with API v2 support** (firmware 4.x+ with local API enabled)
36
37
 
37
38
  ---
@@ -144,6 +145,8 @@ homewizard.0.
144
145
 
145
146
  > States are created dynamically based on what the device reports. Not all devices have all states. kWh meters additionally provide apparent/reactive current, apparent/reactive power, and power factor states.
146
147
 
148
+ > State names, descriptions and dropdown labels (e.g. `tariff`, `battery.mode`) appear in the ioBroker system language. The adapter reads `system.config.language` at startup — change it under *Settings → Base settings → Language* and restart the adapter.
149
+
147
150
  ---
148
151
 
149
152
  ## Troubleshooting
@@ -164,37 +167,32 @@ homewizard.0.
164
167
  ---
165
168
 
166
169
  ## Changelog
170
+ ### 0.7.0 (2026-05-06)
171
+ - Multi-language: state names, descriptions, dropdown labels and user-facing logs follow the ioBroker system language across 11 languages (EN, DE, RU, PT, NL, FR, IT, ES, PL, UK, ZH-CN).
172
+ - `tariff` and `battery.mode` show translated dropdown labels in admin/vis instead of raw values; power-quality and Belgian capacity-tariff states now show inline descriptions (admin tooltips).
173
+ - Baseline bumped to Node 22 and ioBroker Admin 7.8.23 (May-2026 platform requirement).
174
+ - Robustness: `errText` helper consolidates 5 inline catch-site duplicates across `main.ts`.
175
+ - `battery.permissions` parses JSON in a guarded path with explicit array check — malformed input now becomes a clean warning instead of a cryptic stack trace.
176
+ - `battery.mode` whitelist-validates before the API call (`zero` / `to_full` / `standby`); other values are warned and dropped.
177
+ - `handleAuthFailure` helper extracted; duplicate auth-stop branch in WS-disconnect and REST-fallback paths consolidated.
178
+ - Deploy workflow runs on Node 24 (Node 22 + npm@latest installer triggers `MODULE_NOT_FOUND` for `promise-retry`).
179
+ - New `coerce.test.ts` with 22 tests covering `errText`, `validateBatteryMode`, `parseBatteryPermissions` and the existing coerce helpers.
180
+
181
+ ### 0.6.7 (2026-05-01)
182
+ - Internal cleanup. No user-facing changes.
183
+ - Documentation: rewrote release notes for v0.6.0–v0.6.6 in user-friendly style across all languages.
184
+
167
185
  ### 0.6.6 (2026-04-28)
168
- - Audit cleanup against the upstream `ioBroker.example/TypeScript` full standard:
169
- - Test setup migrated: tests now live next to source as `src/**/*.test.ts` and run directly via `ts-node/register`. Removed `tsconfig.test.json` + `build-test/`, added `test/mocharc.custom.json` + `test/mocha.setup.js` + `test/tsconfig.json` + `test/.eslintrc.json`
170
- - `@types/node` rolled back from `^25.6.0` to `^20.19.24` so type defs match `engines.node: ">=20"`
171
- - Dependabot now ignores major bumps for `@types/node`, `typescript`, `eslint`, `actions/checkout`, `actions/setup-node`
172
- - `nyc` config + `coverage` script added
173
- - `prettier.config.mjs` made explicit with project-style overrides (Spaces 2-wide, double quotes)
174
- - Orphan `.github/auto-merge.yml` removed (active workflow is `automerge-dependabot.yml` using `gh pr merge`)
186
+ - Internal cleanup. No user-facing changes.
175
187
 
176
188
  ### 0.6.5 (2026-04-26)
177
- - Process-level `unhandledRejection` / `uncaughtException` handlers added as last-line-of-defence against fire-and-forget rejections.
178
- - Stop shipping the `manual-review` release-script plugin adapter-only consequence.
179
- - Audit-driven boilerplate sync with the other krobi adapters (`.vscode` json5 schemas, `tsconfig.test` looser test rules).
180
- - Min js-controller correction: was `>=7.0.0`, restored to repochecker-recommended `>=6.0.11` (Source: `ioBroker.repochecker/lib/M1000_IOPackageJson.js`).
181
- - `@types/iobroker` bumped to `^7.1.1`.
189
+ - Crash defense: process-level error handlers.
190
+ - Min `js-controller` restored to `>=6.0.11` (was incorrectly `>=7.0.0`).
182
191
 
183
192
  ### 0.6.4 (2026-04-23)
184
- - Separate test-build output (`build-test/`) from production `build/`, so `npm test` no longer risks leaving duplicated `build/src` + `build/test` trees in the published package.
185
- - Wrap async `onReady` and `onStateChange` handlers with `.catch()` to prevent unhandled promise rejections from SIGKILLing the adapter.
186
- - Declare `pairingIp` as an instance object (11-language name) instead of creating it dynamically in `onReady`.
187
-
188
- ### 0.6.3 (2026-04-18)
189
- - Harden WebSocket and REST input handling against unexpected API responses
190
- - Stop endless reconnect when the device token is invalid (fires once after 3 failed auth attempts)
191
- - Avoid creating an empty `external/` channel when a device reports no external meters
192
-
193
- ### 0.6.2 (2026-04-13)
194
- - Fix hanging promise when response stream errors mid-transfer (`res.on("error")`)
195
- - Fix onUnload: wrap in try/finally so callback always fires (prevents adapter hang on shutdown)
196
- - Optimize state creation hot path: use `setObjectNotExistsAsync` instead of `extendObjectAsync` (~50 fewer object writes per second per device)
197
- - Remove unnecessary `removeDeviceFromObject` wrapper (DRY)
193
+ - Internal hardening. No user-facing changes.
194
+
195
+ Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
198
196
 
199
197
  ## Support
200
198
 
@@ -18,10 +18,14 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var coerce_exports = {};
20
20
  __export(coerce_exports, {
21
+ BATTERY_MODES: () => BATTERY_MODES,
21
22
  coerceBoolean: () => coerceBoolean,
22
23
  coerceFiniteNumber: () => coerceFiniteNumber,
23
24
  coerceString: () => coerceString,
24
- isPlainObject: () => isPlainObject
25
+ errText: () => errText,
26
+ isPlainObject: () => isPlainObject,
27
+ parseBatteryPermissions: () => parseBatteryPermissions,
28
+ validateBatteryMode: () => validateBatteryMode
25
29
  });
26
30
  module.exports = __toCommonJS(coerce_exports);
27
31
  function coerceFiniteNumber(value) {
@@ -49,11 +53,64 @@ function coerceBoolean(value) {
49
53
  function isPlainObject(value) {
50
54
  return typeof value === "object" && value !== null && !Array.isArray(value);
51
55
  }
56
+ const BATTERY_MODES = ["zero", "to_full", "standby"];
57
+ function validateBatteryMode(value) {
58
+ return typeof value === "string" && BATTERY_MODES.includes(value) ? value : null;
59
+ }
60
+ function parseBatteryPermissions(raw) {
61
+ let parsed;
62
+ try {
63
+ parsed = JSON.parse(raw);
64
+ } catch (err) {
65
+ return { ok: false, reason: errText(err), sample: raw.slice(0, 200) };
66
+ }
67
+ if (!Array.isArray(parsed)) {
68
+ return { ok: false, reason: "expected JSON array", sample: raw.slice(0, 200) };
69
+ }
70
+ const perms = [];
71
+ for (const item of parsed) {
72
+ if (typeof item !== "string") {
73
+ return {
74
+ ok: false,
75
+ reason: `non-string entry: ${typeof item}`,
76
+ sample: raw.slice(0, 200)
77
+ };
78
+ }
79
+ perms.push(item);
80
+ }
81
+ return { ok: true, perms };
82
+ }
83
+ function errText(err) {
84
+ if (err instanceof Error) {
85
+ return err.message;
86
+ }
87
+ if (err === null) {
88
+ return "null";
89
+ }
90
+ if (err === void 0) {
91
+ return "undefined";
92
+ }
93
+ if (typeof err === "string") {
94
+ return err;
95
+ }
96
+ if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") {
97
+ return String(err);
98
+ }
99
+ try {
100
+ return JSON.stringify(err);
101
+ } catch {
102
+ return Object.prototype.toString.call(err);
103
+ }
104
+ }
52
105
  // Annotate the CommonJS export names for ESM import in node:
53
106
  0 && (module.exports = {
107
+ BATTERY_MODES,
54
108
  coerceBoolean,
55
109
  coerceFiniteNumber,
56
110
  coerceString,
57
- isPlainObject
111
+ errText,
112
+ isPlainObject,
113
+ parseBatteryPermissions,
114
+ validateBatteryMode
58
115
  });
59
116
  //# sourceMappingURL=coerce.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/coerce.ts"],
4
- "sourcesContent": ["/**\n * Boundary coercion helpers for external API data (REST + WebSocket).\n * HomeWizard API v2 is well-documented but field types still drift in practice\n * (firmware bugs, future additions, null values). These helpers guard against\n * NaN/Infinity/non-string values reaching ioBroker states.\n */\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses numeric strings; rejects NaN/Infinity/other.\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && value.length > 0) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce to a non-empty string, or null.\n *\n * @param value Unknown external value\n */\nexport function coerceString(value: unknown): string | null {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n return null;\n}\n\n/**\n * Coerce to a boolean (only `true`/`false` accepted \u2014 no truthy/falsy JS rules).\n *\n * @param value Unknown external value\n */\nexport function coerceBoolean(value: unknown): boolean | null {\n if (typeof value === \"boolean\") {\n return value;\n }\n return null;\n}\n\n/**\n * Guard for plain objects (not arrays, not null).\n *\n * @param value Unknown external value\n */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaO,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAOO,SAAS,aAAa,OAA+B;AAC1D,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cAAc,OAAgC;AAC5D,MAAI,OAAO,UAAU,WAAW;AAC9B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cAAc,OAAkD;AAC9E,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;",
4
+ "sourcesContent": ["/**\n * Boundary coercion helpers for external API data (REST + WebSocket).\n * HomeWizard API v2 is well-documented but field types still drift in practice\n * (firmware bugs, future additions, null values). These helpers guard against\n * NaN/Infinity/non-string values reaching ioBroker states.\n */\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses numeric strings; rejects NaN/Infinity/other.\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && value.length > 0) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce to a non-empty string, or null.\n *\n * @param value Unknown external value\n */\nexport function coerceString(value: unknown): string | null {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n return null;\n}\n\n/**\n * Coerce to a boolean (only `true`/`false` accepted \u2014 no truthy/falsy JS rules).\n *\n * @param value Unknown external value\n */\nexport function coerceBoolean(value: unknown): boolean | null {\n if (typeof value === \"boolean\") {\n return value;\n }\n return null;\n}\n\n/**\n * Guard for plain objects (not arrays, not null).\n *\n * @param value Unknown external value\n */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Allowed values for `battery.mode` per HomeWizard API v2. */\nexport const BATTERY_MODES = [\"zero\", \"to_full\", \"standby\"] as const;\nexport type BatteryMode = (typeof BATTERY_MODES)[number];\n\n/**\n * Validate user input for `battery.mode` against the API-allowed enum. Returns\n * the typed mode on success, or `null` if the input is not in the whitelist.\n *\n * @param value Raw user input (`String(state.val)`).\n */\nexport function validateBatteryMode(value: unknown): BatteryMode | null {\n return typeof value === \"string\" && (BATTERY_MODES as readonly string[]).includes(value)\n ? (value as BatteryMode)\n : null;\n}\n\n/** Outcome of {@link parseBatteryPermissions} \u2014 either a parsed array or a diagnostic. */\nexport type BatteryPermissionsResult = { ok: true; perms: string[] } | { ok: false; reason: string; sample: string };\n\n/**\n * Parse a JSON string for `battery.permissions`. Expected shape: `string[]`.\n * Wraps `JSON.parse` so a malformed user input becomes a typed warning instead\n * of a thrown exception, and rejects non-array results explicitly.\n *\n * @param raw Raw user input (`String(state.val)`).\n */\nexport function parseBatteryPermissions(raw: string): BatteryPermissionsResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n return { ok: false, reason: errText(err), sample: raw.slice(0, 200) };\n }\n if (!Array.isArray(parsed)) {\n return { ok: false, reason: \"expected JSON array\", sample: raw.slice(0, 200) };\n }\n // permissions are documented as string array \u2014 coerce defensively.\n const perms: string[] = [];\n for (const item of parsed) {\n if (typeof item !== \"string\") {\n return {\n ok: false,\n reason: `non-string entry: ${typeof item}`,\n sample: raw.slice(0, 200),\n };\n }\n perms.push(item);\n }\n return { ok: true, perms };\n}\n\n/**\n * Extract a log-friendly message from a thrown / rejected value. Centralizes the\n * `err instanceof Error ? err.message : String(err)` pattern that otherwise\n * gets repeated at every catch-site. Plain objects are JSON-stringified so a\n * `[object Object]` log is avoided when adapters throw bag-of-fields.\n *\n * @param err Caught value of unknown shape (Error, string, undefined, ...).\n */\nexport function errText(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n if (err === null) {\n return \"null\";\n }\n if (err === undefined) {\n return \"undefined\";\n }\n if (typeof err === \"string\") {\n return err;\n }\n if (typeof err === \"number\" || typeof err === \"boolean\" || typeof err === \"bigint\") {\n return String(err);\n }\n // Plain objects + symbols would otherwise stringify to \"[object Object]\" / fail.\n // Prefer JSON for the common case so the log is at least diagnosable.\n try {\n return JSON.stringify(err);\n } catch {\n return Object.prototype.toString.call(err);\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaO,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAOO,SAAS,aAAa,OAA+B;AAC1D,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cAAc,OAAgC;AAC5D,MAAI,OAAO,UAAU,WAAW;AAC9B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cAAc,OAAkD;AAC9E,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAGO,MAAM,gBAAgB,CAAC,QAAQ,WAAW,SAAS;AASnD,SAAS,oBAAoB,OAAoC;AACtE,SAAO,OAAO,UAAU,YAAa,cAAoC,SAAS,KAAK,IAClF,QACD;AACN;AAYO,SAAS,wBAAwB,KAAuC;AAC7E,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,KAAK;AACZ,WAAO,EAAE,IAAI,OAAO,QAAQ,QAAQ,GAAG,GAAG,QAAQ,IAAI,MAAM,GAAG,GAAG,EAAE;AAAA,EACtE;AACA,MAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,WAAO,EAAE,IAAI,OAAO,QAAQ,uBAAuB,QAAQ,IAAI,MAAM,GAAG,GAAG,EAAE;AAAA,EAC/E;AAEA,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,QAAQ;AACzB,QAAI,OAAO,SAAS,UAAU;AAC5B,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,QAAQ,qBAAqB,OAAO,IAAI;AAAA,QACxC,QAAQ,IAAI,MAAM,GAAG,GAAG;AAAA,MAC1B;AAAA,IACF;AACA,UAAM,KAAK,IAAI;AAAA,EACjB;AACA,SAAO,EAAE,IAAI,MAAM,MAAM;AAC3B;AAUO,SAAS,QAAQ,KAAsB;AAC5C,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI;AAAA,EACb;AACA,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,aAAa,OAAO,QAAQ,UAAU;AAClF,WAAO,OAAO,GAAG;AAAA,EACnB;AAGA,MAAI;AACF,WAAO,KAAK,UAAU,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAC3C;AACF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,384 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var i18n_logs_exports = {};
20
+ __export(i18n_logs_exports, {
21
+ LOG_STRINGS: () => LOG_STRINGS,
22
+ tLog: () => tLog
23
+ });
24
+ module.exports = __toCommonJS(i18n_logs_exports);
25
+ const SUPPORTED_LANGS = ["en", "de", "ru", "pt", "nl", "fr", "it", "es", "pl", "uk", "zh-cn"];
26
+ function fmt(template, params) {
27
+ if (!params) {
28
+ return template;
29
+ }
30
+ return template.replace(/\{(\w+)\}/g, (_, key) => {
31
+ const v = params[key];
32
+ if (v === null) {
33
+ return "(none)";
34
+ }
35
+ if (v === void 0) {
36
+ return `{${key}}`;
37
+ }
38
+ return String(v);
39
+ });
40
+ }
41
+ const LOG_STRINGS = {
42
+ // ──────── Adapter lifecycle / crash defense ────────
43
+ onReadyFailed: {
44
+ en: "onReady failed: {error}",
45
+ de: "onReady fehlgeschlagen: {error}",
46
+ ru: "onReady \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439: {error}",
47
+ pt: "onReady falhou: {error}",
48
+ nl: "onReady is mislukt: {error}",
49
+ fr: "onReady a \xE9chou\xE9 : {error}",
50
+ it: "onReady non riuscito: {error}",
51
+ es: "onReady fall\xF3: {error}",
52
+ pl: "onReady nie powi\xF3d\u0142 si\u0119: {error}",
53
+ uk: "onReady \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0432\u0441\u044F \u0437 \u043F\u043E\u043C\u0438\u043B\u043A\u043E\u044E: {error}",
54
+ "zh-cn": "onReady \u5931\u8D25\uFF1A{error}"
55
+ },
56
+ stateChangeFailed: {
57
+ en: "stateChange failed: {error}",
58
+ de: "stateChange fehlgeschlagen: {error}",
59
+ ru: "stateChange \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439: {error}",
60
+ pt: "stateChange falhou: {error}",
61
+ nl: "stateChange is mislukt: {error}",
62
+ fr: "stateChange a \xE9chou\xE9 : {error}",
63
+ it: "stateChange non riuscito: {error}",
64
+ es: "stateChange fall\xF3: {error}",
65
+ pl: "stateChange nie powi\xF3d\u0142 si\u0119: {error}",
66
+ uk: "stateChange \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0432\u0441\u044F \u0437 \u043F\u043E\u043C\u0438\u043B\u043A\u043E\u044E: {error}",
67
+ "zh-cn": "stateChange \u5931\u8D25\uFF1A{error}"
68
+ },
69
+ unhandledRejection: {
70
+ en: "Unhandled rejection: {error}",
71
+ de: "Unbehandelte Promise-Rejection: {error}",
72
+ ru: "\u041D\u0435\u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u043D\u044B\u0439 rejection: {error}",
73
+ pt: "Rejei\xE7\xE3o n\xE3o tratada: {error}",
74
+ nl: "Onafgehandelde rejection: {error}",
75
+ fr: "Rejet non g\xE9r\xE9 : {error}",
76
+ it: "Rejection non gestita: {error}",
77
+ es: "Rechazo no manejado: {error}",
78
+ pl: "Nieobs\u0142u\u017Cone odrzucenie: {error}",
79
+ uk: "\u041D\u0435\u043E\u0431\u0440\u043E\u0431\u043B\u0435\u043D\u0438\u0439 rejection: {error}",
80
+ "zh-cn": "\u672A\u5904\u7406\u7684 rejection\uFF1A{error}"
81
+ },
82
+ uncaughtException: {
83
+ en: "Uncaught exception: {error}",
84
+ de: "Nicht abgefangene Exception: {error}",
85
+ ru: "\u041D\u0435\u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u043D\u043E\u0435 \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435: {error}",
86
+ pt: "Exce\xE7\xE3o n\xE3o capturada: {error}",
87
+ nl: "Niet-opgevangen exception: {error}",
88
+ fr: "Exception non captur\xE9e : {error}",
89
+ it: "Eccezione non catturata: {error}",
90
+ es: "Excepci\xF3n no capturada: {error}",
91
+ pl: "Nieprzechwycony wyj\u0105tek: {error}",
92
+ uk: "\u041D\u0435\u043F\u0435\u0440\u0435\u0445\u043E\u043F\u043B\u0435\u043D\u0435 \u0432\u0438\u043A\u043B\u044E\u0447\u0435\u043D\u043D\u044F: {error}",
93
+ "zh-cn": "\u672A\u6355\u83B7\u7684\u5F02\u5E38\uFF1A{error}"
94
+ },
95
+ // ──────── Pairing flow ────────
96
+ noDevicesConfigured: {
97
+ en: "No devices configured \u2014 set 'startPairing' to true to add a device",
98
+ de: "Keine Ger\xE4te konfiguriert \u2014 'startPairing' auf true setzen, um ein Ger\xE4t hinzuzuf\xFCgen",
99
+ ru: "\u041D\u0435\u0442 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0445 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432 \u2014 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 'startPairing' \u0432 true, \u0447\u0442\u043E\u0431\u044B \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E",
100
+ pt: "Nenhum dispositivo configurado \u2014 defina 'startPairing' como true para adicionar um dispositivo",
101
+ nl: "Geen apparaten geconfigureerd \u2014 zet 'startPairing' op true om een apparaat toe te voegen",
102
+ fr: "Aucun appareil configur\xE9 \u2014 d\xE9finissez 'startPairing' sur true pour ajouter un appareil",
103
+ it: "Nessun dispositivo configurato \u2014 imposta 'startPairing' su true per aggiungere un dispositivo",
104
+ es: "No hay dispositivos configurados \u2014 establece 'startPairing' en true para a\xF1adir uno",
105
+ pl: "Brak skonfigurowanych urz\u0105dze\u0144 \u2014 ustaw 'startPairing' na true, aby doda\u0107 urz\u0105dzenie",
106
+ uk: "\u041D\u0435\u043C\u0430\u0454 \u043D\u0430\u043B\u0430\u0448\u0442\u043E\u0432\u0430\u043D\u0438\u0445 \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u0457\u0432 \u2014 \u0432\u0441\u0442\u0430\u043D\u043E\u0432\u0456\u0442\u044C 'startPairing' \u043D\u0430 true, \u0449\u043E\u0431 \u0434\u043E\u0434\u0430\u0442\u0438 \u043F\u0440\u0438\u0441\u0442\u0440\u0456\u0439",
107
+ "zh-cn": "\u672A\u914D\u7F6E\u8BBE\u5907 \u2014 \u5C06 'startPairing' \u8BBE\u7F6E\u4E3A true \u4EE5\u6DFB\u52A0\u8BBE\u5907"
108
+ },
109
+ deviceFound: {
110
+ en: "Found {name} ({type}) at {ip} \u2014 press the button on the device to pair",
111
+ de: "Gefunden: {name} ({type}) unter {ip} \u2014 Knopf am Ger\xE4t dr\xFCcken, um zu koppeln",
112
+ ru: "\u041D\u0430\u0439\u0434\u0435\u043D\u043E {name} ({type}) \u043F\u043E \u0430\u0434\u0440\u0435\u0441\u0443 {ip} \u2014 \u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0435 \u0434\u043B\u044F \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u044F",
113
+ pt: "Encontrado {name} ({type}) em {ip} \u2014 pressione o bot\xE3o do dispositivo para emparelhar",
114
+ nl: "Gevonden: {name} ({type}) op {ip} \u2014 druk op de knop op het apparaat om te koppelen",
115
+ fr: "Trouv\xE9 : {name} ({type}) \xE0 {ip} \u2014 appuyez sur le bouton de l'appareil pour l'appairer",
116
+ it: "Trovato {name} ({type}) a {ip} \u2014 premi il pulsante sul dispositivo per accoppiare",
117
+ es: "Encontrado {name} ({type}) en {ip} \u2014 pulsa el bot\xF3n del dispositivo para emparejar",
118
+ pl: "Znaleziono {name} ({type}) pod {ip} \u2014 naci\u015Bnij przycisk na urz\u0105dzeniu, aby sparowa\u0107",
119
+ uk: "\u0417\u043D\u0430\u0439\u0434\u0435\u043D\u043E {name} ({type}) \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043E\u044E {ip} \u2014 \u043D\u0430\u0442\u0438\u0441\u043D\u0456\u0442\u044C \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u0457 \u0434\u043B\u044F \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F",
120
+ "zh-cn": "\u5DF2\u627E\u5230 {name} ({type}) \u4F4D\u4E8E {ip} \u2014 \u6309\u8BBE\u5907\u4E0A\u7684\u6309\u94AE\u914D\u5BF9"
121
+ },
122
+ pairingEnabledManual: {
123
+ en: "Pairing mode enabled for {ip} \u2014 press the button on your HomeWizard device now (60 seconds timeout)",
124
+ de: "Pairing-Modus aktiv f\xFCr {ip} \u2014 jetzt Knopf am HomeWizard-Ger\xE4t dr\xFCcken (60 Sekunden Timeout)",
125
+ ru: "\u0420\u0435\u0436\u0438\u043C \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u044F \u0432\u043A\u043B\u044E\u0447\u0451\u043D \u0434\u043B\u044F {ip} \u2014 \u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0435 HomeWizard (\u0442\u0430\u0439\u043C-\u0430\u0443\u0442 60 \u0441\u0435\u043A\u0443\u043D\u0434)",
126
+ pt: "Modo de emparelhamento ativo para {ip} \u2014 pressione o bot\xE3o do dispositivo HomeWizard agora (60 segundos)",
127
+ nl: "Koppelmodus actief voor {ip} \u2014 druk nu op de knop van uw HomeWizard-apparaat (60 seconden time-out)",
128
+ fr: "Mode d'appairage activ\xE9 pour {ip} \u2014 appuyez sur le bouton de l'appareil HomeWizard (timeout 60 secondes)",
129
+ it: "Modalit\xE0 di accoppiamento attiva per {ip} \u2014 premi ora il pulsante sul dispositivo HomeWizard (timeout 60 secondi)",
130
+ es: "Modo de emparejamiento activo para {ip} \u2014 pulsa ahora el bot\xF3n del dispositivo HomeWizard (60 segundos)",
131
+ pl: "Tryb parowania aktywny dla {ip} \u2014 naci\u015Bnij teraz przycisk na urz\u0105dzeniu HomeWizard (timeout 60 sekund)",
132
+ uk: "\u0420\u0435\u0436\u0438\u043C \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u0430\u043A\u0442\u0438\u0432\u043D\u0438\u0439 \u0434\u043B\u044F {ip} \u2014 \u043D\u0430\u0442\u0438\u0441\u043D\u0456\u0442\u044C \u0437\u0430\u0440\u0430\u0437 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u0457 HomeWizard (\u0442\u0430\u0439\u043C-\u0430\u0443\u0442 60 \u0441\u0435\u043A\u0443\u043D\u0434)",
133
+ "zh-cn": "\u5DF2\u4E3A {ip} \u542F\u7528\u914D\u5BF9\u6A21\u5F0F \u2014 \u73B0\u5728\u8BF7\u6309\u4E0B HomeWizard \u8BBE\u5907\u4E0A\u7684\u6309\u94AE\uFF0860 \u79D2\u8D85\u65F6\uFF09"
134
+ },
135
+ pairingEnabledMdns: {
136
+ en: "Pairing mode enabled \u2014 searching for devices via mDNS, press the button on your HomeWizard device now (60 seconds timeout)",
137
+ de: "Pairing-Modus aktiv \u2014 Ger\xE4te-Suche via mDNS, jetzt Knopf am HomeWizard-Ger\xE4t dr\xFCcken (60 Sekunden Timeout)",
138
+ ru: "\u0420\u0435\u0436\u0438\u043C \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u044F \u0432\u043A\u043B\u044E\u0447\u0451\u043D \u2014 \u043F\u043E\u0438\u0441\u043A \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432 \u0447\u0435\u0440\u0435\u0437 mDNS, \u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0430 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0435 HomeWizard (60 \u0441\u0435\u043A\u0443\u043D\u0434)",
139
+ pt: "Modo de emparelhamento ativo \u2014 procurando dispositivos via mDNS, pressione o bot\xE3o agora (60 segundos)",
140
+ nl: "Koppelmodus actief \u2014 apparaten zoeken via mDNS, druk nu op de knop van uw HomeWizard-apparaat (60 seconden)",
141
+ fr: "Mode d'appairage activ\xE9 \u2014 recherche d'appareils via mDNS, appuyez maintenant sur le bouton (timeout 60 secondes)",
142
+ it: "Modalit\xE0 di accoppiamento attiva \u2014 ricerca dispositivi tramite mDNS, premi ora il pulsante (timeout 60 secondi)",
143
+ es: "Modo de emparejamiento activo \u2014 buscando dispositivos v\xEDa mDNS, pulsa ahora el bot\xF3n (60 segundos)",
144
+ pl: "Tryb parowania aktywny \u2014 wyszukiwanie urz\u0105dze\u0144 przez mDNS, naci\u015Bnij teraz przycisk (timeout 60 sekund)",
145
+ uk: "\u0420\u0435\u0436\u0438\u043C \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u0430\u043A\u0442\u0438\u0432\u043D\u0438\u0439 \u2014 \u043F\u043E\u0448\u0443\u043A \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u0457\u0432 \u0447\u0435\u0440\u0435\u0437 mDNS, \u043D\u0430\u0442\u0438\u0441\u043D\u0456\u0442\u044C \u0437\u0430\u0440\u0430\u0437 \u043A\u043D\u043E\u043F\u043A\u0443 (\u0442\u0430\u0439\u043C-\u0430\u0443\u0442 60 \u0441\u0435\u043A\u0443\u043D\u0434)",
146
+ "zh-cn": "\u5DF2\u542F\u7528\u914D\u5BF9\u6A21\u5F0F \u2014 \u901A\u8FC7 mDNS \u641C\u7D22\u8BBE\u5907\uFF0C\u73B0\u5728\u8BF7\u6309\u4E0B\u8BBE\u5907\u4E0A\u7684\u6309\u94AE\uFF0860 \u79D2\u8D85\u65F6\uFF09"
147
+ },
148
+ pairingTimeout: {
149
+ en: "Pairing mode automatically disabled after 60 seconds timeout",
150
+ de: "Pairing-Modus nach 60 Sekunden automatisch beendet",
151
+ ru: "\u0420\u0435\u0436\u0438\u043C \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u044F \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u043E\u0442\u043A\u043B\u044E\u0447\u0451\u043D \u043F\u043E\u0441\u043B\u0435 \u0442\u0430\u0439\u043C-\u0430\u0443\u0442\u0430 60 \u0441\u0435\u043A\u0443\u043D\u0434",
152
+ pt: "Modo de emparelhamento desativado automaticamente ap\xF3s timeout de 60 segundos",
153
+ nl: "Koppelmodus automatisch uitgeschakeld na time-out van 60 seconden",
154
+ fr: "Mode d'appairage d\xE9sactiv\xE9 automatiquement apr\xE8s timeout de 60 secondes",
155
+ it: "Modalit\xE0 di accoppiamento disattivata automaticamente dopo timeout di 60 secondi",
156
+ es: "Modo de emparejamiento desactivado autom\xE1ticamente tras 60 segundos",
157
+ pl: "Tryb parowania automatycznie wy\u0142\u0105czony po 60 sekundach",
158
+ uk: "\u0420\u0435\u0436\u0438\u043C \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u043D\u043E \u0432\u0438\u043C\u043A\u043D\u0435\u043D\u043E \u043F\u0456\u0441\u043B\u044F \u0442\u0430\u0439\u043C-\u0430\u0443\u0442\u0443 60 \u0441\u0435\u043A\u0443\u043D\u0434",
159
+ "zh-cn": "60 \u79D2\u8D85\u65F6\u540E\u5DF2\u81EA\u52A8\u7981\u7528\u914D\u5BF9\u6A21\u5F0F"
160
+ },
161
+ pairingSuccess: {
162
+ en: "Successfully paired with {name} ({type}) at {ip} \u2014 connecting...",
163
+ de: "Erfolgreich gekoppelt mit {name} ({type}) unter {ip} \u2014 verbinde...",
164
+ ru: "\u0423\u0441\u043F\u0435\u0448\u043D\u043E\u0435 \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u0435 \u0441 {name} ({type}) \u043F\u043E \u0430\u0434\u0440\u0435\u0441\u0443 {ip} \u2014 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435...",
165
+ pt: "Emparelhamento bem-sucedido com {name} ({type}) em {ip} \u2014 conectando...",
166
+ nl: "Succesvol gekoppeld met {name} ({type}) op {ip} \u2014 verbinden...",
167
+ fr: "Appairage r\xE9ussi avec {name} ({type}) \xE0 {ip} \u2014 connexion...",
168
+ it: "Accoppiamento riuscito con {name} ({type}) a {ip} \u2014 connessione in corso...",
169
+ es: "Emparejamiento exitoso con {name} ({type}) en {ip} \u2014 conectando...",
170
+ pl: "Pomy\u015Blnie sparowano z {name} ({type}) pod {ip} \u2014 \u0142\u0105czenie...",
171
+ uk: "\u0423\u0441\u043F\u0456\u0448\u043D\u0435 \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u0437 {name} ({type}) \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043E\u044E {ip} \u2014 \u043F\u0456\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u043D\u044F...",
172
+ "zh-cn": "\u5DF2\u6210\u529F\u4E0E {name} ({type}) \u914D\u5BF9\uFF0C\u5730\u5740 {ip} \u2014 \u6B63\u5728\u8FDE\u63A5..."
173
+ },
174
+ // ──────── State writes ────────
175
+ rebootingDevice: {
176
+ en: "Rebooting {name} ({ip})",
177
+ de: "Starte {name} ({ip}) neu",
178
+ ru: "\u041F\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0430 {name} ({ip})",
179
+ pt: "Reiniciando {name} ({ip})",
180
+ nl: "Opnieuw opstarten van {name} ({ip})",
181
+ fr: "Red\xE9marrage de {name} ({ip})",
182
+ it: "Riavvio di {name} ({ip})",
183
+ es: "Reiniciando {name} ({ip})",
184
+ pl: "Restartowanie {name} ({ip})",
185
+ uk: "\u041F\u0435\u0440\u0435\u0437\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F {name} ({ip})",
186
+ "zh-cn": "\u6B63\u5728\u91CD\u542F {name} ({ip})"
187
+ },
188
+ failedToSetState: {
189
+ en: "Failed to set {id}: {error}",
190
+ de: "Setzen von {id} fehlgeschlagen: {error}",
191
+ ru: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C {id}: {error}",
192
+ pt: "Falha ao definir {id}: {error}",
193
+ nl: "Instellen van {id} is mislukt: {error}",
194
+ fr: "\xC9chec de la d\xE9finition de {id} : {error}",
195
+ it: "Impossibile impostare {id}: {error}",
196
+ es: "Error al establecer {id}: {error}",
197
+ pl: "Nie uda\u0142o si\u0119 ustawi\u0107 {id}: {error}",
198
+ uk: "\u041D\u0435 \u0432\u0434\u0430\u043B\u043E\u0441\u044F \u0432\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0438 {id}: {error}",
199
+ "zh-cn": "\u65E0\u6CD5\u8BBE\u7F6E {id}\uFF1A{error}"
200
+ },
201
+ invalidPermissionsJson: {
202
+ en: "Invalid JSON for battery.permissions: {error} \u2014 expected array, got: {value}",
203
+ de: "Ung\xFCltiges JSON f\xFCr battery.permissions: {error} \u2014 Array erwartet, erhalten: {value}",
204
+ ru: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 JSON \u0434\u043B\u044F battery.permissions: {error} \u2014 \u043E\u0436\u0438\u0434\u0430\u043B\u0441\u044F \u043C\u0430\u0441\u0441\u0438\u0432, \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: {value}",
205
+ pt: "JSON inv\xE1lido para battery.permissions: {error} \u2014 esperado array, recebido: {value}",
206
+ nl: "Ongeldige JSON voor battery.permissions: {error} \u2014 array verwacht, ontvangen: {value}",
207
+ fr: "JSON invalide pour battery.permissions : {error} \u2014 tableau attendu, re\xE7u : {value}",
208
+ it: "JSON non valido per battery.permissions: {error} \u2014 atteso array, ricevuto: {value}",
209
+ es: "JSON inv\xE1lido para battery.permissions: {error} \u2014 se esperaba array, recibido: {value}",
210
+ pl: "Nieprawid\u0142owy JSON dla battery.permissions: {error} \u2014 oczekiwano tablicy, otrzymano: {value}",
211
+ uk: "\u041D\u0435\u0432\u0456\u0440\u043D\u0438\u0439 JSON \u0434\u043B\u044F battery.permissions: {error} \u2014 \u043E\u0447\u0456\u043A\u0443\u0432\u0430\u0432\u0441\u044F \u043C\u0430\u0441\u0438\u0432, \u043E\u0442\u0440\u0438\u043C\u0430\u043D\u043E: {value}",
212
+ "zh-cn": "battery.permissions \u7684 JSON \u65E0\u6548\uFF1A{error} \u2014 \u671F\u671B\u6570\u7EC4\uFF0C\u6536\u5230\uFF1A{value}"
213
+ },
214
+ invalidBatteryMode: {
215
+ en: "Invalid battery.mode value: '{value}' \u2014 expected one of: zero, to_full, standby",
216
+ de: "Ung\xFCltiger battery.mode-Wert: '{value}' \u2014 erwartet: zero, to_full oder standby",
217
+ ru: "\u041D\u0435\u0432\u0435\u0440\u043D\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 battery.mode: '{value}' \u2014 \u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F: zero, to_full \u0438\u043B\u0438 standby",
218
+ pt: "Valor inv\xE1lido para battery.mode: '{value}' \u2014 esperado: zero, to_full ou standby",
219
+ nl: "Ongeldige battery.mode-waarde: '{value}' \u2014 verwacht: zero, to_full of standby",
220
+ fr: "Valeur battery.mode invalide : '{value}' \u2014 attendu : zero, to_full ou standby",
221
+ it: "Valore battery.mode non valido: '{value}' \u2014 atteso: zero, to_full o standby",
222
+ es: "Valor battery.mode inv\xE1lido: '{value}' \u2014 esperado: zero, to_full o standby",
223
+ pl: "Nieprawid\u0142owa warto\u015B\u0107 battery.mode: '{value}' \u2014 oczekiwano: zero, to_full lub standby",
224
+ uk: "\u041D\u0435\u0432\u0456\u0440\u043D\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u043D\u044F battery.mode: '{value}' \u2014 \u043E\u0447\u0456\u043A\u0443\u0454\u0442\u044C\u0441\u044F: zero, to_full \u0430\u0431\u043E standby",
225
+ "zh-cn": "battery.mode \u503C\u65E0\u6548\uFF1A'{value}' \u2014 \u671F\u671B\uFF1Azero\u3001to_full \u6216 standby"
226
+ },
227
+ removingDevice: {
228
+ en: "Removing device {name} ({serial})",
229
+ de: "Entferne Ger\xE4t {name} ({serial})",
230
+ ru: "\u0423\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0430 {name} ({serial})",
231
+ pt: "Removendo dispositivo {name} ({serial})",
232
+ nl: "Apparaat {name} ({serial}) verwijderen",
233
+ fr: "Suppression de l'appareil {name} ({serial})",
234
+ it: "Rimozione del dispositivo {name} ({serial})",
235
+ es: "Eliminando dispositivo {name} ({serial})",
236
+ pl: "Usuwanie urz\u0105dzenia {name} ({serial})",
237
+ uk: "\u0412\u0438\u0434\u0430\u043B\u0435\u043D\u043D\u044F \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u044E {name} ({serial})",
238
+ "zh-cn": "\u6B63\u5728\u79FB\u9664\u8BBE\u5907 {name} ({serial})"
239
+ },
240
+ // ──────── Connection lifecycle ────────
241
+ searchingNewIp: {
242
+ en: "Device unreachable \u2014 searching for new IP via mDNS",
243
+ de: "Ger\xE4t nicht erreichbar \u2014 suche neue IP via mDNS",
244
+ ru: "\u0423\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u2014 \u043F\u043E\u0438\u0441\u043A \u043D\u043E\u0432\u043E\u0433\u043E IP \u0447\u0435\u0440\u0435\u0437 mDNS",
245
+ pt: "Dispositivo inacess\xEDvel \u2014 procurando novo IP via mDNS",
246
+ nl: "Apparaat onbereikbaar \u2014 nieuwe IP zoeken via mDNS",
247
+ fr: "Appareil injoignable \u2014 recherche d'une nouvelle IP via mDNS",
248
+ it: "Dispositivo non raggiungibile \u2014 ricerca nuovo IP tramite mDNS",
249
+ es: "Dispositivo inaccesible \u2014 buscando nueva IP v\xEDa mDNS",
250
+ pl: "Urz\u0105dzenie nieosi\u0105galne \u2014 wyszukiwanie nowego IP przez mDNS",
251
+ uk: "\u041F\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u2014 \u043F\u043E\u0448\u0443\u043A \u043D\u043E\u0432\u043E\u0457 IP \u0447\u0435\u0440\u0435\u0437 mDNS",
252
+ "zh-cn": "\u8BBE\u5907\u4E0D\u53EF\u8FBE \u2014 \u901A\u8FC7 mDNS \u641C\u7D22\u65B0 IP"
253
+ },
254
+ foundAtNewIp: {
255
+ en: "{name}: found at new IP {newIp} (was {oldIp})",
256
+ de: "{name}: unter neuer IP {newIp} gefunden (zuvor {oldIp})",
257
+ ru: "{name}: \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043F\u043E \u043D\u043E\u0432\u043E\u043C\u0443 IP {newIp} (\u0431\u044B\u043B\u043E {oldIp})",
258
+ pt: "{name}: encontrado em novo IP {newIp} (era {oldIp})",
259
+ nl: "{name}: gevonden op nieuwe IP {newIp} (was {oldIp})",
260
+ fr: "{name} : trouv\xE9 \xE0 la nouvelle IP {newIp} (auparavant {oldIp})",
261
+ it: "{name}: trovato al nuovo IP {newIp} (era {oldIp})",
262
+ es: "{name}: encontrado en nueva IP {newIp} (antes {oldIp})",
263
+ pl: "{name}: znaleziono pod nowym IP {newIp} (by\u0142o {oldIp})",
264
+ uk: "{name}: \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E \u0437\u0430 \u043D\u043E\u0432\u043E\u044E IP {newIp} (\u0431\u0443\u043B\u043E {oldIp})",
265
+ "zh-cn": "{name}\uFF1A\u5DF2\u5728\u65B0 IP {newIp} \u627E\u5230\uFF08\u4E4B\u524D\u4E3A {oldIp}\uFF09"
266
+ },
267
+ deviceOfflineRetrying: {
268
+ en: "{name}: device offline \u2014 will keep retrying every {seconds}s",
269
+ de: "{name}: Ger\xE4t offline \u2014 Versuche werden alle {seconds}s wiederholt",
270
+ ru: "{name}: \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043E\u0444\u043B\u0430\u0439\u043D \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 \u043A\u0430\u0436\u0434\u044B\u0435 {seconds}\u0441",
271
+ pt: "{name}: dispositivo offline \u2014 tentando novamente a cada {seconds}s",
272
+ nl: "{name}: apparaat offline \u2014 opnieuw proberen elke {seconds}s",
273
+ fr: "{name} : appareil hors-ligne \u2014 nouvelle tentative toutes les {seconds}s",
274
+ it: "{name}: dispositivo offline \u2014 nuovi tentativi ogni {seconds}s",
275
+ es: "{name}: dispositivo desconectado \u2014 reintentando cada {seconds}s",
276
+ pl: "{name}: urz\u0105dzenie offline \u2014 ponawianie co {seconds}s",
277
+ uk: "{name}: \u043F\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043E\u0444\u043B\u0430\u0439\u043D \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u0456 \u0441\u043F\u0440\u043E\u0431\u0438 \u043A\u043E\u0436\u043D\u0456 {seconds}\u0441",
278
+ "zh-cn": "{name}\uFF1A\u8BBE\u5907\u79BB\u7EBF \u2014 \u5C06\u6BCF {seconds} \u79D2\u91CD\u8BD5\u4E00\u6B21"
279
+ },
280
+ deviceUnreachable: {
281
+ en: "{name}: device unreachable \u2014 will keep retrying",
282
+ de: "{name}: Ger\xE4t nicht erreichbar \u2014 Versuche werden fortgesetzt",
283
+ ru: "{name}: \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u2014 \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0431\u0443\u0434\u0443\u0442 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u044B",
284
+ pt: "{name}: dispositivo inacess\xEDvel \u2014 tentativas continuar\xE3o",
285
+ nl: "{name}: apparaat onbereikbaar \u2014 pogingen worden voortgezet",
286
+ fr: "{name} : appareil injoignable \u2014 les tentatives continuent",
287
+ it: "{name}: dispositivo non raggiungibile \u2014 i tentativi continueranno",
288
+ es: "{name}: dispositivo inaccesible \u2014 se seguir\xE1n intentando",
289
+ pl: "{name}: urz\u0105dzenie nieosi\u0105galne \u2014 pr\xF3by b\u0119d\u0105 kontynuowane",
290
+ uk: "{name}: \u043F\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u2014 \u0441\u043F\u0440\u043E\u0431\u0438 \u0442\u0440\u0438\u0432\u0430\u0442\u0438\u043C\u0443\u0442\u044C",
291
+ "zh-cn": "{name}\uFF1A\u8BBE\u5907\u4E0D\u53EF\u8FBE \u2014 \u5C06\u7EE7\u7EED\u91CD\u8BD5"
292
+ },
293
+ deviceErrorContext: {
294
+ en: "{name} {context}: {error}",
295
+ de: "{name} {context}: {error}",
296
+ ru: "{name} {context}: {error}",
297
+ pt: "{name} {context}: {error}",
298
+ nl: "{name} {context}: {error}",
299
+ fr: "{name} {context} : {error}",
300
+ it: "{name} {context}: {error}",
301
+ es: "{name} {context}: {error}",
302
+ pl: "{name} {context}: {error}",
303
+ uk: "{name} {context}: {error}",
304
+ "zh-cn": "{name} {context}\uFF1A{error}"
305
+ },
306
+ connectionRestored: {
307
+ en: "{name}: connection restored",
308
+ de: "{name}: Verbindung wiederhergestellt",
309
+ ru: "{name}: \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E",
310
+ pt: "{name}: conex\xE3o restabelecida",
311
+ nl: "{name}: verbinding hersteld",
312
+ fr: "{name} : connexion r\xE9tablie",
313
+ it: "{name}: connessione ripristinata",
314
+ es: "{name}: conexi\xF3n restablecida",
315
+ pl: "{name}: po\u0142\u0105czenie przywr\xF3cone",
316
+ uk: "{name}: \u0437'\u0454\u0434\u043D\u0430\u043D\u043D\u044F \u0432\u0456\u0434\u043D\u043E\u0432\u043B\u0435\u043D\u043E",
317
+ "zh-cn": "{name}\uFF1A\u8FDE\u63A5\u5DF2\u6062\u590D"
318
+ },
319
+ connectionRestoredUnstable: {
320
+ en: "{name}: connection restored (unstable mode)",
321
+ de: "{name}: Verbindung wiederhergestellt (Unstable-Modus)",
322
+ ru: "{name}: \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E (\u043D\u0435\u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C)",
323
+ pt: "{name}: conex\xE3o restabelecida (modo inst\xE1vel)",
324
+ nl: "{name}: verbinding hersteld (instabiele modus)",
325
+ fr: "{name} : connexion r\xE9tablie (mode instable)",
326
+ it: "{name}: connessione ripristinata (modalit\xE0 instabile)",
327
+ es: "{name}: conexi\xF3n restablecida (modo inestable)",
328
+ pl: "{name}: po\u0142\u0105czenie przywr\xF3cone (tryb niestabilny)",
329
+ uk: "{name}: \u0437'\u0454\u0434\u043D\u0430\u043D\u043D\u044F \u0432\u0456\u0434\u043D\u043E\u0432\u043B\u0435\u043D\u043E (\u043D\u0435\u0441\u0442\u0430\u0431\u0456\u043B\u044C\u043D\u0438\u0439 \u0440\u0435\u0436\u0438\u043C)",
330
+ "zh-cn": "{name}\uFF1A\u8FDE\u63A5\u5DF2\u6062\u590D\uFF08\u4E0D\u7A33\u5B9A\u6A21\u5F0F\uFF09"
331
+ },
332
+ unstableDetected: {
333
+ en: "{name}: unstable connection detected \u2014 using faster reconnect",
334
+ de: "{name}: instabile Verbindung erkannt \u2014 schnellerer Reconnect aktiv",
335
+ ru: "{name}: \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u043E \u043D\u0435\u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E\u0435 \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u2014 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0441\u044F \u0431\u044B\u0441\u0442\u0440\u043E\u0435 \u043F\u0435\u0440\u0435\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435",
336
+ pt: "{name}: conex\xE3o inst\xE1vel detectada \u2014 usando reconex\xE3o mais r\xE1pida",
337
+ nl: "{name}: instabiele verbinding gedetecteerd \u2014 sneller opnieuw verbinden",
338
+ fr: "{name} : connexion instable d\xE9tect\xE9e \u2014 reconnexion plus rapide activ\xE9e",
339
+ it: "{name}: rilevata connessione instabile \u2014 riconnessione pi\xF9 veloce",
340
+ es: "{name}: conexi\xF3n inestable detectada \u2014 reconexi\xF3n m\xE1s r\xE1pida activada",
341
+ pl: "{name}: wykryto niestabilne po\u0142\u0105czenie \u2014 szybsze ponowne \u0142\u0105czenie",
342
+ uk: "{name}: \u0432\u0438\u044F\u0432\u043B\u0435\u043D\u043E \u043D\u0435\u0441\u0442\u0430\u0431\u0456\u043B\u044C\u043D\u0435 \u0437'\u0454\u0434\u043D\u0430\u043D\u043D\u044F \u2014 \u0432\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0454\u0442\u044C\u0441\u044F \u0448\u0432\u0438\u0434\u0448\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u0435 \u043F\u0456\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u043D\u044F",
343
+ "zh-cn": "{name}\uFF1A\u68C0\u6D4B\u5230\u4E0D\u7A33\u5B9A\u8FDE\u63A5 \u2014 \u4F7F\u7528\u66F4\u5FEB\u7684\u91CD\u8FDE"
344
+ },
345
+ connectionStabilized: {
346
+ en: "{name}: connection stabilized \u2014 using normal reconnect",
347
+ de: "{name}: Verbindung stabilisiert \u2014 normaler Reconnect aktiv",
348
+ ru: "{name}: \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0441\u0442\u0430\u0431\u0438\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043E \u2014 \u043E\u0431\u044B\u0447\u043D\u043E\u0435 \u043F\u0435\u0440\u0435\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435",
349
+ pt: "{name}: conex\xE3o estabilizada \u2014 usando reconex\xE3o normal",
350
+ nl: "{name}: verbinding gestabiliseerd \u2014 normale reconnect actief",
351
+ fr: "{name} : connexion stabilis\xE9e \u2014 reconnexion normale active",
352
+ it: "{name}: connessione stabilizzata \u2014 riconnessione normale",
353
+ es: "{name}: conexi\xF3n estabilizada \u2014 reconexi\xF3n normal activa",
354
+ pl: "{name}: po\u0142\u0105czenie ustabilizowane \u2014 normalne ponowne \u0142\u0105czenie",
355
+ uk: "{name}: \u0437'\u0454\u0434\u043D\u0430\u043D\u043D\u044F \u0441\u0442\u0430\u0431\u0456\u043B\u0456\u0437\u043E\u0432\u0430\u043D\u043E \u2014 \u0437\u0432\u0438\u0447\u0430\u0439\u043D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u0435 \u043F\u0456\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u043D\u044F",
356
+ "zh-cn": "{name}\uFF1A\u8FDE\u63A5\u5DF2\u7A33\u5B9A \u2014 \u4F7F\u7528\u5E38\u89C4\u91CD\u8FDE"
357
+ },
358
+ tokenInvalid: {
359
+ en: "{name}: token invalid \u2014 re-pair device to fix",
360
+ de: "{name}: Token ung\xFCltig \u2014 Ger\xE4t neu koppeln, um den Fehler zu beheben",
361
+ ru: "{name}: \u0442\u043E\u043A\u0435\u043D \u043D\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u0435\u043D \u2014 \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u043E\u0435 \u0441\u043E\u043F\u0440\u044F\u0436\u0435\u043D\u0438\u0435 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0430",
362
+ pt: "{name}: token inv\xE1lido \u2014 emparelhe o dispositivo novamente para corrigir",
363
+ nl: "{name}: token ongeldig \u2014 apparaat opnieuw koppelen om dit op te lossen",
364
+ fr: "{name} : token invalide \u2014 r\xE9appairez l'appareil pour corriger",
365
+ it: "{name}: token non valido \u2014 riaccoppia il dispositivo per risolvere",
366
+ es: "{name}: token inv\xE1lido \u2014 vuelve a emparejar el dispositivo para arreglarlo",
367
+ pl: "{name}: token nieprawid\u0142owy \u2014 sparuj urz\u0105dzenie ponownie, aby naprawi\u0107",
368
+ uk: "{name}: \u0442\u043E\u043A\u0435\u043D \u043D\u0435\u0434\u0456\u0439\u0441\u043D\u0438\u0439 \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u0456\u0442\u044C \u0441\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044F \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u044E",
369
+ "zh-cn": "{name}\uFF1Atoken \u65E0\u6548 \u2014 \u8BF7\u91CD\u65B0\u914D\u5BF9\u8BBE\u5907\u4EE5\u4FEE\u590D"
370
+ }
371
+ };
372
+ function tLog(lang, key, params) {
373
+ var _a;
374
+ const langKey = SUPPORTED_LANGS.includes(lang) ? lang : "en";
375
+ const bundle = LOG_STRINGS[key];
376
+ const template = (_a = bundle[langKey]) != null ? _a : bundle.en;
377
+ return fmt(template, params);
378
+ }
379
+ // Annotate the CommonJS export names for ESM import in node:
380
+ 0 && (module.exports = {
381
+ LOG_STRINGS,
382
+ tLog
383
+ });
384
+ //# sourceMappingURL=i18n-logs.js.map