From bbb8ea3eea4036d9e39cffd529d09dcc338c5159 Mon Sep 17 00:00:00 2001 From: Joey Stanford Date: Mon, 3 Aug 2026 10:27:14 -0600 Subject: [PATCH 1/5] chore: update deps and dedupe --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f0359a4f..c5ad60554 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1676,8 +1676,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.11.10: - resolution: {integrity: sha512-35JEvJ5/KKlbCHjMCsONI2w6HE88STjVdHk+C7d8LtcFxUjZR1KeLP9izofn2qs0KUxX5r4z73bwH/rd+JHacw==} + baseline-browser-mapping@2.11.11: + resolution: {integrity: sha512-/yImnXwyTvgMkhgekLHok/Rx5vO6E0BmStWlSqKWMVm2a2ITuZ1Tn+9bgLS+gZRdZmWtd8nxuhHpdmCUOWsTQQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -6206,7 +6206,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.11.10: {} + baseline-browser-mapping@2.11.11: {} bidi-js@1.0.3: dependencies: @@ -6241,7 +6241,7 @@ snapshots: browserslist@4.28.7: dependencies: - baseline-browser-mapping: 2.11.10 + baseline-browser-mapping: 2.11.11 caniuse-lite: 1.0.30001806 electron-to-chromium: 1.5.399 node-releases: 2.0.51 From 02ed8887508dfe3fdb4a20df0d61bce637554631 Mon Sep 17 00:00:00 2001 From: Joey Stanford Date: Mon, 3 Aug 2026 10:42:41 -0600 Subject: [PATCH 2/5] fix(meshtastic): suppress MeshCore BLE ghost hears on all node paths Wire MAC-based ghost suppression through PacketRouter and node side effects, and only store parseable BLE MACs so Linux Web BT UUIDs cannot pretend to be radios for Meshtastic last_heard bumps. --- .../lib/connectedMeshcoreBleMac.test.ts | 50 ++++++- src/renderer/lib/connectedMeshcoreBleMac.ts | 43 +++++- src/renderer/lib/drivers/PacketRouter.test.ts | 46 ++++++ src/renderer/lib/drivers/PacketRouter.ts | 23 +++ .../meshtasticNodeSideEffects.test.ts | 131 +++++++++++++++++- .../meshtastic/meshtasticNodeSideEffects.ts | 12 ++ .../useMeshcoreRuntime.reconnect.test.ts | 8 +- src/renderer/runtime/useMeshcoreRuntime.ts | 19 ++- 8 files changed, 317 insertions(+), 15 deletions(-) diff --git a/src/renderer/lib/connectedMeshcoreBleMac.test.ts b/src/renderer/lib/connectedMeshcoreBleMac.test.ts index e6ee7d40c..c9854210d 100644 --- a/src/renderer/lib/connectedMeshcoreBleMac.test.ts +++ b/src/renderer/lib/connectedMeshcoreBleMac.test.ts @@ -1,9 +1,14 @@ -import { describe, expect, it } from 'vitest'; +import { afterEach, describe, expect, it } from 'vitest'; import { + getConnectedMeshcoreBleMac, readMeshcoreWebBluetoothDeviceId, + resetConnectedMeshcoreBleMacForTests, resolveConnectedMeshcoreBleIdentity, + resolveConnectedMeshcoreBleMacForSuppression, + setConnectedMeshcoreBleMac, } from './connectedMeshcoreBleMac'; +import { shouldSuppressMeshtasticNodeHear } from './meshcoreBleMacMeshtasticNodeId'; describe('resolveConnectedMeshcoreBleIdentity', () => { it('prefers explicit blePeripheralId over Web Bluetooth and last-id fallbacks', () => { @@ -16,7 +21,7 @@ describe('resolveConnectedMeshcoreBleIdentity', () => { ).toBe('aa:bb:cc:dd:ee:ff'); }); - it('uses Web Bluetooth device id when peripheral id is missing (Linux)', () => { + it('uses Web Bluetooth device id when peripheral id is missing (Linux reconnect identity)', () => { expect( resolveConnectedMeshcoreBleIdentity({ blePeripheralId: undefined, @@ -47,6 +52,47 @@ describe('resolveConnectedMeshcoreBleIdentity', () => { }); }); +describe('resolveConnectedMeshcoreBleMacForSuppression', () => { + it('skips opaque Web Bluetooth UUIDs and prefers a parseable MAC fallback', () => { + expect( + resolveConnectedMeshcoreBleMacForSuppression({ + blePeripheralId: undefined, + webBluetoothDeviceId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + fallbackLastBlePeripheralId: 'cc:2e:e3:da:2e:2f', + }), + ).toBe('cc:2e:e3:da:2e:2f'); + }); + + it('returns null when only opaque Linux Web Bluetooth ids are available', () => { + expect( + resolveConnectedMeshcoreBleMacForSuppression({ + blePeripheralId: undefined, + webBluetoothDeviceId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + fallbackLastBlePeripheralId: 'stored-opaque-uuid', + }), + ).toBeNull(); + }); +}); + +describe('setConnectedMeshcoreBleMac', () => { + afterEach(() => { + resetConnectedMeshcoreBleMacForTests(); + }); + + it('stores parseable Noble MACs', () => { + setConnectedMeshcoreBleMac('cc:2e:e3:da:2e:2f'); + expect(getConnectedMeshcoreBleMac()).toBe('cc:2e:e3:da:2e:2f'); + expect(shouldSuppressMeshtasticNodeHear(0xe3da2e2f, getConnectedMeshcoreBleMac())).toBe(true); + }); + + it('clears instead of storing opaque Web Bluetooth UUIDs', () => { + setConnectedMeshcoreBleMac('cc:2e:e3:da:2e:2f'); + setConnectedMeshcoreBleMac('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'); + expect(getConnectedMeshcoreBleMac()).toBeNull(); + expect(shouldSuppressMeshtasticNodeHear(0xe3da2e2f, getConnectedMeshcoreBleMac())).toBe(false); + }); +}); + describe('readMeshcoreWebBluetoothDeviceId', () => { it('reads getWebBluetoothDeviceId from duck-typed connections', () => { expect( diff --git a/src/renderer/lib/connectedMeshcoreBleMac.ts b/src/renderer/lib/connectedMeshcoreBleMac.ts index dec4baef2..bf5aefe48 100644 --- a/src/renderer/lib/connectedMeshcoreBleMac.ts +++ b/src/renderer/lib/connectedMeshcoreBleMac.ts @@ -1,10 +1,25 @@ -/** BLE MAC / peripheral id of the live MeshCore RF link (null when not BLE-connected). */ +import { meshcoreBleMacToMeshtasticNodeId } from './meshcoreBleMacMeshtasticNodeId'; + +/** BLE MAC of the live MeshCore RF link (null when not BLE-connected or id is not a MAC). */ let connectedMeshcoreBleMac: string | null = null; -/** Called from MeshCore runtime when a BLE session connects or disconnects. */ +/** + * Called from MeshCore runtime when a BLE session connects or disconnects. + * Only stores ids that parse as a 12-hex BLE MAC (Noble peripheral id). Linux + * Web Bluetooth device ids are opaque UUIDs — storing them would never match a + * Meshtastic nodeNum, so we clear instead of pretending they are MACs. + */ export function setConnectedMeshcoreBleMac(mac: string | null): void { const trimmed = mac?.trim() ?? ''; - connectedMeshcoreBleMac = trimmed.length > 0 ? trimmed : null; + if (trimmed.length === 0) { + connectedMeshcoreBleMac = null; + return; + } + if (meshcoreBleMacToMeshtasticNodeId(trimmed) == null) { + connectedMeshcoreBleMac = null; + return; + } + connectedMeshcoreBleMac = trimmed; } export function getConnectedMeshcoreBleMac(): string | null { @@ -14,6 +29,7 @@ export function getConnectedMeshcoreBleMac(): string | null { /** * Prefer an explicit Noble peripheral id, then a live Web Bluetooth device id, * then a remembered last-BLE id (Linux chooser may omit blePeripheralId on connect). + * Used for reconnect identity — may return opaque Web BT UUIDs on Linux. */ export function resolveConnectedMeshcoreBleIdentity(opts: { blePeripheralId?: string | null; @@ -31,6 +47,27 @@ export function resolveConnectedMeshcoreBleIdentity(opts: { return null; } +/** + * First candidate that parses as a BLE MAC for Meshtastic ghost suppression. + * Skips opaque Linux Web Bluetooth device ids. + */ +export function resolveConnectedMeshcoreBleMacForSuppression(opts: { + blePeripheralId?: string | null; + webBluetoothDeviceId?: string | null; + fallbackLastBlePeripheralId?: string | null; +}): string | null { + for (const candidate of [ + opts.blePeripheralId, + opts.webBluetoothDeviceId, + opts.fallbackLastBlePeripheralId, + ]) { + const trimmed = candidate?.trim() ?? ''; + if (trimmed.length === 0) continue; + if (meshcoreBleMacToMeshtasticNodeId(trimmed) != null) return trimmed; + } + return null; +} + /** Duck-typed read of MeshcoreWebBluetoothConnection.getWebBluetoothDeviceId(). */ export function readMeshcoreWebBluetoothDeviceId(conn: unknown): string | null { if (!conn || typeof conn !== 'object') return null; diff --git a/src/renderer/lib/drivers/PacketRouter.test.ts b/src/renderer/lib/drivers/PacketRouter.test.ts index 87a05d22c..093e9c106 100644 --- a/src/renderer/lib/drivers/PacketRouter.test.ts +++ b/src/renderer/lib/drivers/PacketRouter.test.ts @@ -407,4 +407,50 @@ describe('PacketRouter', () => { packetRouter.dispatch({ type: 'nonexistent' } as unknown as DomainEvent, ID); }).not.toThrow(); }); + + it('skips Meshtastic node_info store writes for MeshCore BLE MAC ghost nodes', async () => { + const { setConnectedMeshcoreBleMac, resetConnectedMeshcoreBleMacForTests } = + await import('../connectedMeshcoreBleMac'); + addIdentity({ + id: ID_MT, + protocol: meshtasticProtocol, + signature: 'sig-mt-ghost', + transports: [], + createdAt: 1, + lastSeenAt: 1, + }); + const ghostId = 0xe3da2e2f; + const priorHeard = 1_700_000_000_000; + useNodeStore.setState({ + nodes: { + [ID_MT]: { + [ghostId]: { + nodeId: ghostId, + longName: 'Blue', + shortName: 'BLUE', + lastHeardAt: priorHeard, + }, + }, + }, + }); + setConnectedMeshcoreBleMac('cc:2e:e3:da:2e:2f'); + const listener = vi.fn(); + const detach = packetRouter.addListener(listener); + packetRouter.dispatch( + { + type: 'node_info', + payload: { + nodeId: ghostId, + longName: 'Blue', + shortName: 'BLUE', + lastHeardAt: Date.now(), + }, + }, + ID_MT, + ); + expect(useNodeStore.getState().nodes[ID_MT][ghostId].lastHeardAt).toBe(priorHeard); + expect(listener).not.toHaveBeenCalled(); + detach(); + resetConnectedMeshcoreBleMacForTests(); + }); }); diff --git a/src/renderer/lib/drivers/PacketRouter.ts b/src/renderer/lib/drivers/PacketRouter.ts index 9e0aa974e..c6fabb1a3 100644 --- a/src/renderer/lib/drivers/PacketRouter.ts +++ b/src/renderer/lib/drivers/PacketRouter.ts @@ -31,7 +31,9 @@ import { upsertWaypoint, useNodeStore, } from '../../stores/nodeStore'; +import { getConnectedMeshcoreBleMac } from '../connectedMeshcoreBleMac'; import { errLikeToLogString } from '../errLikeToLogString'; +import { shouldSuppressMeshtasticNodeHear } from '../meshcoreBleMacMeshtasticNodeId'; import { ensureMeshtasticChatSenderInNodeStore } from '../meshtastic/meshtasticChatSenderNode'; import { shouldSuppressMeshtasticLocalConfigWrite } from '../meshtastic/meshtasticConfigIngressGuard'; import { meshtasticTracerouteLastHeardNodeIds } from '../meshtasticLastHeard'; @@ -43,6 +45,15 @@ import { } from '../timeConstants'; import type { IdentityId } from '../types'; +function shouldSuppressMeshtasticGhostNodeHear( + identityId: IdentityId, + nodeId: number | undefined, +): boolean { + if (nodeId == null || nodeId === 0) return false; + if (getIdentity(identityId)?.protocol.type !== 'meshtastic') return false; + return shouldSuppressMeshtasticNodeHear(nodeId, getConnectedMeshcoreBleMac()); +} + function resolveMeshtasticSenderName(identityId: IdentityId, from: number): string | undefined { if (from <= 0) return undefined; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Identity bucket may be absent at runtime. @@ -277,12 +288,24 @@ class PacketRouter { break; } case 'node_info': + if (shouldSuppressMeshtasticGhostNodeHear(identityId, event.payload.nodeId)) { + skipListeners = true; + break; + } upsertNode(identityId, event.payload); break; case 'position': + if (shouldSuppressMeshtasticGhostNodeHear(identityId, event.payload.nodeId)) { + skipListeners = true; + break; + } updatePosition(identityId, event.payload); break; case 'telemetry': + if (shouldSuppressMeshtasticGhostNodeHear(identityId, event.payload.nodeId)) { + skipListeners = true; + break; + } updateTelemetry(identityId, event.payload); break; case 'trace_route': { diff --git a/src/renderer/lib/meshtastic/meshtasticNodeSideEffects.test.ts b/src/renderer/lib/meshtastic/meshtasticNodeSideEffects.test.ts index f80dca43c..d6e3be055 100644 --- a/src/renderer/lib/meshtastic/meshtasticNodeSideEffects.test.ts +++ b/src/renderer/lib/meshtastic/meshtasticNodeSideEffects.test.ts @@ -2,10 +2,17 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { useDiagnosticsStore } from '../../stores/diagnosticsStore'; -import { useNodeStore } from '../../stores/nodeStore'; +import { addIdentity, useIdentityStore } from '../../stores/identityStore'; +import { upsertNodeRecord, useNodeStore } from '../../stores/nodeStore'; +import { + resetConnectedMeshcoreBleMacForTests, + setConnectedMeshcoreBleMac, +} from '../connectedMeshcoreBleMac'; import { packetRouter } from '../drivers/PacketRouter'; import { getIdentityNode } from '../identityStoreReads'; +import { meshtasticProtocol } from '../protocols/MeshtasticProtocol'; import { MESH_PROTOCOL_STORAGE_KEY } from '../storedMeshProtocol'; +import { meshNodeToNodeRecord } from '../storeRecordAdapters'; import type { MeshNode } from '../types'; import { attachMeshtasticNodeSideEffects, @@ -15,6 +22,9 @@ import { const IDENTITY = 'id-node-se'; const MY_NODE = 1; const PEER = 42; +/** Nathan Blue MAC suffix — MeshCore BLE ghost nodeNum. */ +const GHOST_NODE = 0xe3da2e2f; +const MESHCORE_BLE_MAC = 'cc:2e:e3:da:2e:2f'; function emptyNode(nodeId: number): MeshNode { return { @@ -56,7 +66,17 @@ function makeDeps(overrides: Partial = {}) { describe('attachMeshtasticNodeSideEffects', () => { beforeEach(() => { useNodeStore.setState({ nodes: {} }); + useIdentityStore.setState({ identities: {}, activeIdentityId: null }); + addIdentity({ + id: IDENTITY, + protocol: meshtasticProtocol, + signature: 'sig-node-se', + transports: [], + createdAt: 1, + lastSeenAt: 1, + }); localStorage.setItem(MESH_PROTOCOL_STORAGE_KEY, 'meshtastic'); + resetConnectedMeshcoreBleMacForTests(); window.electronAPI = { db: { saveNode: vi.fn().mockResolvedValue(undefined) }, } as unknown as typeof window.electronAPI; @@ -65,6 +85,8 @@ describe('attachMeshtasticNodeSideEffects', () => { afterEach(() => { vi.restoreAllMocks(); localStorage.clear(); + resetConnectedMeshcoreBleMacForTests(); + useIdentityStore.setState({ identities: {}, activeIdentityId: null }); }); it('applies UserPacket node_info into the hook map and marks RF-heard', () => { @@ -217,4 +239,111 @@ describe('attachMeshtasticNodeSideEffects', () => { expect(deps.touchLastData).not.toHaveBeenCalled(); detach(); }); + + describe('MeshCore BLE ghost suppression', () => { + const priorHeard = 1_700_000_000_000; + + beforeEach(() => { + setConnectedMeshcoreBleMac(MESHCORE_BLE_MAC); + upsertNodeRecord( + IDENTITY, + meshNodeToNodeRecord({ + ...emptyNode(GHOST_NODE), + long_name: 'Blue', + short_name: 'BLUE', + last_heard: priorHeard, + }), + ); + vi.mocked(window.electronAPI.db.saveNode).mockClear(); + }); + + it('does not bump last_heard on UserPacket node_info for the MAC ghost', () => { + const { deps } = makeDeps(); + const detach = attachMeshtasticNodeSideEffects(IDENTITY, deps); + packetRouter.dispatch( + { + type: 'node_info', + payload: { + nodeId: GHOST_NODE, + longName: 'Blue', + shortName: 'BLUE', + fromUserPacket: true, + lastHeardAt: Date.now(), + }, + }, + IDENTITY, + ); + expect(getIdentityNode(IDENTITY, GHOST_NODE)?.last_heard).toBe(priorHeard); + expect(window.electronAPI.db.saveNode).not.toHaveBeenCalled(); + expect(deps.rfHeardNodeIds.current.has(GHOST_NODE)).toBe(false); + detach(); + }); + + it('does not bump last_heard on NodeDB node_info for the MAC ghost', () => { + const { deps } = makeDeps(); + const detach = attachMeshtasticNodeSideEffects(IDENTITY, deps); + packetRouter.dispatch( + { + type: 'node_info', + payload: { + nodeId: GHOST_NODE, + longName: 'Blue', + shortName: 'BLUE', + snr: 9, + hopsAway: 0, + lastHeardAt: Date.now(), + fromUserPacket: false, + }, + }, + IDENTITY, + ); + expect(getIdentityNode(IDENTITY, GHOST_NODE)?.last_heard).toBe(priorHeard); + expect(getIdentityNode(IDENTITY, GHOST_NODE)?.snr).toBe(0); + expect(window.electronAPI.db.saveNode).not.toHaveBeenCalled(); + detach(); + }); + + it('does not bump last_heard on position for the MAC ghost', () => { + const { deps } = makeDeps(); + const detach = attachMeshtasticNodeSideEffects(IDENTITY, deps); + packetRouter.dispatch( + { + type: 'position', + payload: { + nodeId: GHOST_NODE, + latitude: 39.7392, + longitude: -104.9903, + timestamp: Date.now(), + }, + }, + IDENTITY, + ); + expect(getIdentityNode(IDENTITY, GHOST_NODE)?.last_heard).toBe(priorHeard); + expect(getIdentityNode(IDENTITY, GHOST_NODE)?.latitude).toBeNull(); + expect(window.electronAPI.db.saveNode).not.toHaveBeenCalled(); + detach(); + }); + + it('does not bump last_heard on deviceMetrics telemetry for the MAC ghost', () => { + const { deps } = makeDeps(); + const detach = attachMeshtasticNodeSideEffects(IDENTITY, deps); + packetRouter.dispatch( + { + type: 'telemetry', + payload: { + nodeId: GHOST_NODE, + timestamp: Date.now(), + batteryLevel: 50, + variantCase: 'deviceMetrics', + }, + }, + IDENTITY, + ); + expect(getIdentityNode(IDENTITY, GHOST_NODE)?.last_heard).toBe(priorHeard); + expect(getIdentityNode(IDENTITY, GHOST_NODE)?.battery).toBe(0); + expect(deps.setTelemetry).not.toHaveBeenCalled(); + expect(window.electronAPI.db.saveNode).not.toHaveBeenCalled(); + detach(); + }); + }); }); diff --git a/src/renderer/lib/meshtastic/meshtasticNodeSideEffects.ts b/src/renderer/lib/meshtastic/meshtasticNodeSideEffects.ts index 8aeb5552d..d39c086bb 100644 --- a/src/renderer/lib/meshtastic/meshtasticNodeSideEffects.ts +++ b/src/renderer/lib/meshtastic/meshtasticNodeSideEffects.ts @@ -24,11 +24,13 @@ import { } from '../../../shared/nodeNameUtils'; import { upsertNodeRecord } from '../../stores/nodeStore'; import { usePositionHistoryStore } from '../../stores/positionHistoryStore'; +import { getConnectedMeshcoreBleMac } from '../connectedMeshcoreBleMac'; import { validateCoords } from '../coordUtils'; import { persistDbWrite } from '../dbPersistRetry'; import { attachTypedPacketListeners } from '../drivers/attachTypedPacketListener'; import { shouldPreserveStaticGpsForSelfNode } from '../gpsSource'; import { getIdentityNode } from '../identityStoreReads'; +import { shouldSuppressMeshtasticNodeHear } from '../meshcoreBleMacMeshtasticNodeId'; import { computeNodeInfoLastHeardMs, mergeMeshtasticLivePacketLastHeard, @@ -49,6 +51,11 @@ import type { import { processMeshtasticNodeDiagnostics } from './meshtasticProcessNodeDiagnostics'; import { cacheTransportDisplayName } from './transportDisplayNameCache'; +/** Skip hear bumps for MeshCore BLE MAC-derived Meshtastic ghost nodes. */ +function shouldSuppressGhostNodeHear(nodeNum: number): boolean { + return shouldSuppressMeshtasticNodeHear(nodeNum, getConnectedMeshcoreBleMac()); +} + const ROLE_CLIENT_MUTE = 1; const BLE_DEVICE_NAMES_KEY = 'mesh-client:bleDeviceNames'; const SERIAL_PORT_NODE_NAMES_KEY = 'mesh-client:serialPortNodeNames'; @@ -140,6 +147,7 @@ function handleUserPacketNodeInfo( deps: MeshtasticNodeSideEffectsDeps, ): void { const nodeNum = info.nodeId; + if (shouldSuppressGhostNodeHear(nodeNum)) return; deps.rfHeardNodeIds.current.add(nodeNum); const existing = getIdentityNode(identityId, nodeNum) ?? deps.emptyNode(nodeNum); const long_name = preferNonEmptyTrimmedString(info.longName, existing.long_name, { @@ -210,6 +218,7 @@ function handleNodeDbNodeInfo( deps: MeshtasticNodeSideEffectsDeps, ): void { const nodeNum = info.nodeId; + if (shouldSuppressGhostNodeHear(nodeNum)) return; const myNodeNum = deps.getMyNodeNum(); const isSelf = nodeNum === myNodeNum; deps.rfHeardNodeIds.current.add(nodeNum); @@ -316,6 +325,7 @@ function handlePosition( const nodeNum = position.nodeId; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Runtime guard protects external or callback-mutated state. if (position.latitude == null || position.longitude == null) return; + if (shouldSuppressGhostNodeHear(nodeNum)) return; const myNodeNum = deps.getMyNodeNum(); if (nodeNum !== 0) { deps.rfHeardNodeIds.current.add(nodeNum); @@ -370,6 +380,7 @@ function handleEnvironmentTelemetry( deps: MeshtasticNodeSideEffectsDeps, ): void { const nodeNum = telemetry.nodeId; + if (shouldSuppressGhostNodeHear(nodeNum)) return; const point: EnvironmentTelemetryPoint = { timestamp: Date.now(), nodeNum, @@ -442,6 +453,7 @@ function handleDeviceMetricsTelemetry( deps: MeshtasticNodeSideEffectsDeps, ): void { const nodeNum = telemetry.nodeId; + if (shouldSuppressGhostNodeHear(nodeNum)) return; const myNodeNum = deps.getMyNodeNum(); const point: TelemetryPoint = { timestamp: Date.now(), diff --git a/src/renderer/runtime/useMeshcoreRuntime.reconnect.test.ts b/src/renderer/runtime/useMeshcoreRuntime.reconnect.test.ts index 3608073a1..91f26fe1d 100644 --- a/src/renderer/runtime/useMeshcoreRuntime.reconnect.test.ts +++ b/src/renderer/runtime/useMeshcoreRuntime.reconnect.test.ts @@ -150,8 +150,11 @@ describe('useMeshcoreRuntime auto-reconnect (regression)', () => { expect(lostBody).toContain('setConnectedMeshcoreBleMac(null)'); const reconnectBody = extractUseCallbackBody(RUNTIME_SOURCE, 'attemptMeshcoreReconnect'); expect(reconnectBody).toContain('resolveConnectedMeshcoreBleIdentity'); + expect(reconnectBody).toContain('resolveConnectedMeshcoreBleMacForSuppression'); expect(reconnectBody).toContain('readMeshcoreWebBluetoothDeviceId'); - expect(reconnectBody).toMatch(/Reconnect succeeded[\s\S]*?setConnectedMeshcoreBleMac\(bleId\)/); + expect(reconnectBody).toMatch( + /Reconnect succeeded[\s\S]*?setConnectedMeshcoreBleMac\(resolveConnectedMeshcoreBleMacForSuppression\(bleIdentityOpts\)\)/, + ); }); it('clears MeshCore BLE MAC on prepareRfConnect replacement and BLE connect failure', () => { @@ -166,11 +169,12 @@ describe('useMeshcoreRuntime auto-reconnect (regression)', () => { it('sets MeshCore BLE identity after connect attach including Linux Web Bluetooth without blePeripheralId', () => { const connectBody = extractUseCallbackBody(RUNTIME_SOURCE, 'connect'); expect(connectBody).toContain('resolveConnectedMeshcoreBleIdentity'); + expect(connectBody).toContain('resolveConnectedMeshcoreBleMacForSuppression'); expect(connectBody).toContain('readMeshcoreWebBluetoothDeviceId(opened.conn)'); expect(connectBody).toContain( "fallbackLastBlePeripheralId: resolveLastBlePeripheralId('meshcore')", ); - expect(connectBody).toContain("setConnectedMeshcoreBleMac(type === 'ble' ? bleId : null)"); + expect(connectBody).toContain('bleIdentityOpts ? resolveConnectedMeshcoreBleMacForSuppression'); }); it('flushes deferred reconnects after reconnect attempts settle', () => { diff --git a/src/renderer/runtime/useMeshcoreRuntime.ts b/src/renderer/runtime/useMeshcoreRuntime.ts index f0a46cfb0..142d0b5cb 100644 --- a/src/renderer/runtime/useMeshcoreRuntime.ts +++ b/src/renderer/runtime/useMeshcoreRuntime.ts @@ -9,6 +9,7 @@ import { requestChatOutboxDrain } from '@/renderer/lib/chatOutboxDrain'; import { readMeshcoreWebBluetoothDeviceId, resolveConnectedMeshcoreBleIdentity, + resolveConnectedMeshcoreBleMacForSuppression, setConnectedMeshcoreBleMac, } from '@/renderer/lib/connectedMeshcoreBleMac'; import { errLikeToLogString } from '@/renderer/lib/errLikeToLogString'; @@ -2848,15 +2849,16 @@ export function useMeshcoreRuntime() { `[useMeshcoreRuntime] Reconnect succeeded on attempt ${meshcoreReconnectAttemptRef.current}`, ); if (params.rfType === 'ble') { - const bleId = resolveConnectedMeshcoreBleIdentity({ + const bleIdentityOpts = { blePeripheralId: params.blePeripheralId, webBluetoothDeviceId: readMeshcoreWebBluetoothDeviceId(opened.conn), fallbackLastBlePeripheralId: resolveLastBlePeripheralId('meshcore') ?? null, - }); + }; + const bleId = resolveConnectedMeshcoreBleIdentity(bleIdentityOpts); if (bleId) { params.blePeripheralId = bleId; } - setConnectedMeshcoreBleMac(bleId); + setConnectedMeshcoreBleMac(resolveConnectedMeshcoreBleMacForSuppression(bleIdentityOpts)); } else { setConnectedMeshcoreBleMac(null); } @@ -3084,14 +3086,15 @@ export function useMeshcoreRuntime() { ? await withNobleBleConnectMutex('meshcore', openTransport) : await openTransport(); await attachRfSession(opened.driverIdentityId, type); - const bleId = + const bleIdentityOpts = type === 'ble' - ? resolveConnectedMeshcoreBleIdentity({ + ? { blePeripheralId, webBluetoothDeviceId: readMeshcoreWebBluetoothDeviceId(opened.conn), fallbackLastBlePeripheralId: resolveLastBlePeripheralId('meshcore') ?? null, - }) + } : null; + const bleId = bleIdentityOpts ? resolveConnectedMeshcoreBleIdentity(bleIdentityOpts) : null; meshcoreConnectionParamsRef.current = { rfType: type, httpAddress: type === 'tcp' ? tcpHost : undefined, @@ -3099,7 +3102,9 @@ export function useMeshcoreRuntime() { serialPortId: type === 'serial' ? localStorage.getItem(LAST_SERIAL_PORT_KEY) : undefined, serialPort: null, }; - setConnectedMeshcoreBleMac(type === 'ble' ? bleId : null); + setConnectedMeshcoreBleMac( + bleIdentityOpts ? resolveConnectedMeshcoreBleMacForSuppression(bleIdentityOpts) : null, + ); meshcoreExplicitDisconnectRef.current = false; meshcoreReconnectAttemptRef.current = 0; meshcoreIsReconnectingRef.current = false; From 51b543bfc930377c584b6a41f12a151051c8b3be Mon Sep 17 00:00:00 2001 From: Joey Stanford Date: Mon, 3 Aug 2026 10:43:36 -0600 Subject: [PATCH 3/5] fix: hydrate use24HourTime from SQLite on App mount AppPanel is lazy, so Chat and other panels could keep a stale localStorage clock until the App tab was opened. Reconcile early from app_settings like locale. --- src/renderer/App.tsx | 22 +++++++++++++++++++++ src/renderer/stores/timeFormatStore.test.ts | 22 +++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 85924b514..808398a49 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -263,6 +263,7 @@ import { useReticulumIdentityStore } from './stores/reticulumIdentityStore'; import { useReticulumPeerStore } from './stores/reticulumPeerStore'; import { useRncpTransferStore } from './stores/rncpTransferStore'; import { useRrcSessionStore } from './stores/rrcSessionStore'; +import { useTimeFormatStore } from './stores/timeFormatStore'; // Tabs capability filtering lives in appTabMappings.ts (computeTabMappings). @@ -498,6 +499,27 @@ function AppContent() { window.removeEventListener('mesh-client:rncp-offer', onOffer); }; }, [addToast, t]); + + // Reconcile 24h clock from SQLite early — AppPanel is lazy and Chat reads the store first. + useEffect(() => { + let cancelled = false; + void window.electronAPI.appSettings + .getAll() + .then((raw) => { + if (cancelled) return; + const use24 = raw?.use24HourTime; + if (use24 === 'true' || use24 === 'false') { + useTimeFormatStore.getState().hydrateFromSqlite(use24 === 'true'); + } + }) + .catch((err: unknown) => { + console.warn('[App] use24HourTime hydrate failed ' + errLikeToLogString(err)); + }); + return () => { + cancelled = true; + }; + }, []); + const runtimes = useAllRuntimes(); const meshtasticRuntime = runtimes.meshtastic as unknown as MeshtasticRuntime; const meshcoreRuntime = runtimes.meshcore as unknown as MeshcoreRuntime; diff --git a/src/renderer/stores/timeFormatStore.test.ts b/src/renderer/stores/timeFormatStore.test.ts index 9eb5d7b61..6eb522afd 100644 --- a/src/renderer/stores/timeFormatStore.test.ts +++ b/src/renderer/stores/timeFormatStore.test.ts @@ -24,4 +24,26 @@ describe('timeFormatStore', () => { 'timeFormatStore setUse24HourTime', ); }); + + it('hydrateFromSqlite wins over stale localStorage-backed state', () => { + useTimeFormatStore.setState({ use24HourTime: false }); + useTimeFormatStore.getState().hydrateFromSqlite(true); + expect(useTimeFormatStore.getState().use24HourTime).toBe(true); + expect(mergeAppSetting).toHaveBeenCalledWith( + 'use24HourTime', + true, + 'timeFormatStore hydrateFromSqlite', + ); + }); + + it('hydrateFromSqlite can clear a stale true from localStorage', () => { + useTimeFormatStore.setState({ use24HourTime: true }); + useTimeFormatStore.getState().hydrateFromSqlite(false); + expect(useTimeFormatStore.getState().use24HourTime).toBe(false); + expect(mergeAppSetting).toHaveBeenCalledWith( + 'use24HourTime', + false, + 'timeFormatStore hydrateFromSqlite', + ); + }); }); From 2837cddecd23b6cf6cef1cabadd350a3048fbe33 Mon Sep 17 00:00:00 2001 From: Joey Stanford Date: Mon, 3 Aug 2026 10:44:25 -0600 Subject: [PATCH 4/5] fix(i18n): correct post-5.25.0 false friends and add quality guards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align hub→backbone picker/CTA copy, Nomad countdown “left”, anatomy backbone labels, zh tabAll, ja I2P spacing, and related path/guidance strings; gate regressions in check-i18n-quality. --- scripts/check-i18n-quality.mjs | 82 +++++++++++++++++++++ scripts/check-i18n-quality.test.mjs | 40 ++++++++++ src/renderer/locales/cs/translation.json | 10 +-- src/renderer/locales/de/translation.json | 10 +-- src/renderer/locales/es/translation.json | 28 +++---- src/renderer/locales/fr/translation.json | 20 ++--- src/renderer/locales/id/translation.json | 14 ++-- src/renderer/locales/it/translation.json | 20 ++--- src/renderer/locales/ja/translation.json | 16 ++-- src/renderer/locales/ko/translation.json | 12 +-- src/renderer/locales/nl/translation.json | 16 ++-- src/renderer/locales/pl/translation.json | 16 ++-- src/renderer/locales/pt-BR/translation.json | 14 ++-- src/renderer/locales/ru/translation.json | 12 +-- src/renderer/locales/tr/translation.json | 14 ++-- src/renderer/locales/uk/translation.json | 10 +-- src/renderer/locales/zh/translation.json | 18 ++--- 17 files changed, 237 insertions(+), 115 deletions(-) diff --git a/scripts/check-i18n-quality.mjs b/scripts/check-i18n-quality.mjs index 46c01a1f5..cc83250b4 100644 --- a/scripts/check-i18n-quality.mjs +++ b/scripts/check-i18n-quality.mjs @@ -3755,6 +3755,87 @@ function checkReticulumMapIssues(ctx) { return issues; } +/** + * Post-v5.25.0 false friends: backbone anatomy, Nomad “left”, hub→backbone picker, + * zh recipes tabAll, ja spaced I2P. + * @param {LocaleQualityCtx} ctx + * @returns {string[]} + */ +function checkPreReleaseLocaleAccuracyIssues(ctx) { + const { locale, flatKey, val, enVal } = ctx; + const issues = []; + if (locale === 'en') return issues; + + const BACKBONE_ANATOMY = [ + { re: /colonne vertébrale/i, hint: 'use network backbone, not spine/anatomy' }, + { re: /spina dorsale/i, hint: 'use network backbone (dorsale), not spine anatomy' }, + { re: /tulang punggung/i, hint: 'use backbone (network), not spine anatomy' }, + { re: /\bkręgosłup/i, hint: 'use magistrala/sieć szkieletowa, not kręgosłup' }, + ]; + + if ( + flatKey === 'connectionPanel.reticulumInterfaces.defaultHubRegion.primary_global' || + flatKey === 'connectionPanel.reticulumInterfaces.addDefaultHubs' || + flatKey === 'connectionPanel.reticulumInterfaces.defaultHubsPickerTitle' + ) { + for (const { re, hint } of BACKBONE_ANATOMY) { + if (re.test(val)) { + issues.push(`${flatKey} false friend: ${hint}`); + } + } + } + + if ( + flatKey === 'connectionPanel.reticulumInterfaces.defaultHubsPickerTitle' && + /network hub|netzwerk-hub|concentrator|集线器|ネットワークハブ|hub jaringan|ağ hub/i.test(val) + ) { + issues.push( + 'defaultHubsPickerTitle must use backbone wording aligned with addDefaultHubs, not network hubs', + ); + } + + if ( + (flatKey === 'nomadNetwork.pageLoadingCountdown' || + flatKey === 'nomadNetwork.pageLoadingRetryCountdown' || + flatKey === 'nomadNetwork.pageLoadingTimeLeft') && + enVal.includes('left') + ) { + if ( + /\bgauche\b|\bizquierda\b|\bsinistra\b|\besquerda\b|(?:^|[^\p{L}])左(?:$|[^\p{L}])|\bліворуч\b/iu.test( + val, + ) + ) { + issues.push( + `${flatKey}: translate "left" as time remaining, not direction (gauche/izquierda/左/…)`, + ); + } + } + + if (flatKey === 'nodeListPanel.tabAll' && locale === 'zh' && /食谱/.test(val)) { + issues.push('nodeListPanel.tabAll must mean all nodes, not recipes (食谱)'); + } + + if ( + (flatKey === 'connectionPanel.reticulumInterfaces.purpose.i2p' || + flatKey === 'connectionPanel.reticulumInterfaces.backboneEnableGuidanceBody' || + flatKey === 'networkPanel.reticulumStackSettings.pathMediumPreferenceHint') && + locale === 'ja' && + /I\s+2\s+P/.test(val) + ) { + issues.push(`${flatKey}: keep I2P as contiguous token (not "I 2 P")`); + } + + if ( + flatKey === 'networkPanel.reticulumStackSettings.pathMediumPreference' && + locale === 'es' && + /\bPath\b/.test(val) + ) { + issues.push('pathMediumPreference must not leave English "Path" in Spanish'); + } + + return issues; +} + /** RRC chat rooms — not hotel rooms; slash commands must stay wire tokens. */ export const RRC_PREFIX = 'rrc.'; @@ -3929,6 +4010,7 @@ const LOCALE_STRING_QUALITY_CHECKS = [ checkReticulumDefaultHubKeyIssues, checkRepeatersCliIssues, checkReticulumMapIssues, + checkPreReleaseLocaleAccuracyIssues, checkHopAwayVerbFalseFriendIssues, checkAirTimeFalseFriendIssues, checkWireTokenLiteralPreservedIssues, diff --git a/scripts/check-i18n-quality.test.mjs b/scripts/check-i18n-quality.test.mjs index eaafbc018..d012b8cc4 100644 --- a/scripts/check-i18n-quality.test.mjs +++ b/scripts/check-i18n-quality.test.mjs @@ -2067,4 +2067,44 @@ describe('sniffer tab and MQTT channel PSK i18n quality', () => { }); expectIssue(issues, 'rrc false friend'); }); + + it('flags Nomad countdown "left" translated as direction', () => { + const issues = localeStringQualityIssues({ + locale: 'es', + flatKey: 'nomadNetwork.pageLoadingCountdown', + enVal: 'Loading page… {{time}} left', + val: 'Cargando página... {{time}} izquierda', + }); + expectIssue(issues, 'time remaining'); + }); + + it('flags zh nodeListPanel.tabAll recipes false friend', () => { + const issues = localeStringQualityIssues({ + locale: 'zh', + flatKey: 'nodeListPanel.tabAll', + enVal: 'All', + val: '所有食谱', + }); + expectIssue(issues, 'recipes'); + }); + + it('flags anatomy backbone on primary_global', () => { + const issues = localeStringQualityIssues({ + locale: 'fr', + flatKey: 'connectionPanel.reticulumInterfaces.defaultHubRegion.primary_global', + enVal: 'Primary & Global Backbone', + val: 'Colonne vertébrale principale et mondiale', + }); + expectIssue(issues, 'spine/anatomy'); + }); + + it('flags ja spaced I2P token', () => { + const issues = localeStringQualityIssues({ + locale: 'ja', + flatKey: 'connectionPanel.reticulumInterfaces.purpose.i2p', + enVal: 'I2P backbone via a router on this machine.', + val: 'I 2 P バックボーン', + }); + expectIssue(issues, 'I2P'); + }); }); diff --git a/src/renderer/locales/cs/translation.json b/src/renderer/locales/cs/translation.json index 2cc3b8aac..fef1b5eba 100644 --- a/src/renderer/locales/cs/translation.json +++ b/src/renderer/locales/cs/translation.json @@ -1122,9 +1122,9 @@ "advancedAria": "Pokročilé možnosti klíč-hodnota rozhraní", "advancedKeyReserved": "Klíč „{{key}}“ je známé pole a byl ignorován — použijte výše uvedený formulář.", "pickerDeviceAriaWithRssi": "{{name}} {{address}}, {{rssi}} dBm", - "decommissionedHubEnableBlocked": "Tento hub je vyřazen z provozu a nemůže zůstat zapnutý — mesh-client jej vypne při každém spuštění zásobníku. Použijte Přidat výchozí síťové huby nebo páteřní adresář pro živé koncové body.", + "decommissionedHubEnableBlocked": "Tento hub je vyřazen a nemůže zůstat zapnutý — mesh-client ho při každém startu zásobníku vypne. Použijte «Přidat výchozí páteř» nebo adresář páteří pro živé koncové body.", "decommissionedBadge": "vyřazeno z provozu", - "defaultHubsPickerTitle": "Přidat výchozí síťová rozbočovače", + "defaultHubsPickerTitle": "Přidat výchozí páteř", "defaultHubsPickerConfirm": "Přidat {{count}} rozbočovače", "defaultHubsPickerConfirmSync": "Synchronizace vybrána", "defaultHubsPickerConfirmAria": "Přidat nebo synchronizovat {{count}} vybraná výchozí rozbočovače", @@ -1161,7 +1161,7 @@ "addDefaultHubsFailed": "Přidání výchozích páteří se nezdařilo.", "backboneEnableGuidanceLead": "Povolit maximálně 1 až 3 páteřní brány", "backboneEnableGuidanceBody": "—2 je ideální místo pro redundanci bez zbytečného nafukování šířky pásma. Zaměřte se na 1 globální rozbočovač a 1 regionální bránu a povolte speciální rozhraní (jako je I2P nebo Yggdrasil) přesně podle potřeby. Lokálně připojené RNodes a místní LAN rozhraní se do tohoto limitu páteřní sítě nezapočítávají.", - "defaultHubsPickerHint": "Začněte s Primárním a Globálním plus vaším regionem. Povolte maximálně 1 až 3 páteřní brány (2 jsou sladké místo). Místní RNode a LAN rozhraní se nepočítají. Nové položky jsou přidány zakázány, takže je můžete před povolením zkontrolovat. Synchronizace také deaktivuje zbývající vyřazené oficiální rozbočovače testnetu (Amsterdam)." + "defaultHubsPickerHint": "Začněte primární a globální páteří plus svým regionem. Zapněte nejvýše 1 až 3 páteřní brány (2 je optimum). Místní RNode a LAN rozhraní se nepočítají. Nové položky se přidávají vypnuté ke kontrole. Sync také vypne zbývající vyřazené oficiální testnet huby (Amsterdam)." }, "reticulumPeers": { "hops": "Hops", @@ -1650,7 +1650,7 @@ "announceBusPressureTipBoundaryHubs": "Povolené hraniční / páteřní rozbočovače: {{hubs}}.", "announceBusPressureTipTxSaturated": "Fronta TX klesá na: {{names}} — toto rozhraní může být nasycené.", "tooManyDefaultBackbones": "Máte zapnutou výchozí páteř {{count}}. Zvažte vypnutí doplňků a ponechání asi 2 pro nadbytečnost.", - "decommissionedHubEnabled": "Vyřazený rozbočovač „{{name}}“ je stále povolen — mesh-client jej vypne při každém spuštění zásobníku. Použijte Přidat výchozí páteř nebo páteřní adresář pro živé koncové body." + "decommissionedHubEnabled": "Vyřazený hub „{{name}}“ je stále zapnutý — mesh-client ho při každém startu zásobníku vypne. Použijte «Přidat výchozí páteř» nebo adresář páteří pro živé koncové body." } }, "routingPort": { @@ -3215,7 +3215,7 @@ "pathsGlobalPreference": "Globální: {{preference}}", "pathsListAria": "Hodnocené sloty cesty", "pathsActiveBadge": "Aktivní", - "pathsBackupBadge": "Záloha", + "pathsBackupBadge": "Záložní", "pathsMedium": "Médium", "pathsExpired": "Platnost vypršela", "pathsEmpty": "Zatím žádné sloty pro cestu. Požádejte o cestu nebo počkejte na oznámení.", diff --git a/src/renderer/locales/de/translation.json b/src/renderer/locales/de/translation.json index 29e0e4916..d921db6cd 100644 --- a/src/renderer/locales/de/translation.json +++ b/src/renderer/locales/de/translation.json @@ -1119,9 +1119,9 @@ "advancedAria": "Erweiterte Schnittstellen-Key-Value-Optionen", "advancedKeyReserved": "Der Schlüssel „{{key}}“ ist ein bekanntes Feld und wurde ignoriert — verwende das obige Formular.", "pickerDeviceAriaWithRssi": "{{name}} {{address}}, {{rssi}} dBm", - "decommissionedHubEnableBlocked": "Dieser Hub ist außer Betrieb genommen und kann nicht aktiviert bleiben — der Mesh-Client schaltet ihn bei jedem Stapelstart aus. Verwenden Sie Standard-Netzwerk-Hubs oder das Backbone-Verzeichnis für Live-Endpunkte hinzufügen.", + "decommissionedHubEnableBlocked": "Dieser Hub ist außer Betrieb und kann nicht aktiviert bleiben — mesh-client schaltet ihn bei jedem Stack-Start aus. Nutzen Sie „Standard-Backbones hinzufügen“ oder das Backbone-Verzeichnis für aktuelle Endpunkte.", "decommissionedBadge": "stillgelegt", - "defaultHubsPickerTitle": "Standard-Netzwerk-Hubs hinzufügen", + "defaultHubsPickerTitle": "Standard-Backbones hinzufügen", "defaultHubsPickerConfirm": "{{count}} Hubs hinzufügen", "defaultHubsPickerConfirmSync": "Ausgewählte synchronisieren", "defaultHubsPickerConfirmAria": "Hinzufügen oder Synchronisieren von {{count}} ausgewählten Standard-Hubs", @@ -1157,8 +1157,8 @@ "addDefaultHubsAllPresent": "Ausgewählte Backbones sind bereits konfiguriert; keine außer Betrieb genommenen Testnet-Hubs aktiviert.", "addDefaultHubsFailed": "Standard-Backbones konnten nicht hinzugefügt werden.", "backboneEnableGuidanceLead": "Aktivieren Sie höchstens 1 bis 3 Backbone-Gateways", - "backboneEnableGuidanceBody": "—2 ist der Sweet Spot für Redundanz ohne unnötige Bandbreitenaufblähung. Zielen Sie auf 1 globalen Hub und 1 regionalen Gateway ab und aktivieren Sie spezielle Schnittstellen (wie I2P oder Yggdrasil) streng nach Bedarf. Lokal angeschlossene RNodes und lokale LAN-Schnittstellen zählen nicht zu diesem Backbone-Limit.", - "defaultHubsPickerHint": "Beginnen Sie mit Primary & Global plus Ihrer Region. Aktivieren Sie höchstens 1 bis 3 Backbone-Gateways (2 ist der Sweet Spot). Lokale RNodes und LAN-Schnittstellen zählen nicht. Neue Einträge werden deaktiviert hinzugefügt, damit Sie sie überprüfen können, bevor Sie sie aktivieren. Sync deaktiviert auch die verbleibenden außer Betrieb genommenen offiziellen Testnet-Hubs (Amsterdam)." + "backboneEnableGuidanceBody": "—2 ist optimal für Redundanz ohne unnötigen Bandbreitenverbrauch. Streben Sie 1 globalen Hub und 1 regionales Gateway an, und aktivieren Sie Spezialschnittstellen (wie I2P oder Yggdrasil) nur bei Bedarf. Lokal verbundene RNodes und lokale LAN-Schnittstellen zählen nicht zu diesem Backbone-Limit.", + "defaultHubsPickerHint": "Beginnen Sie mit Primär & Global plus Ihrer Region. Aktivieren Sie höchstens 1 bis 3 Backbone-Gateways (2 ist optimal). Lokale RNodes und LAN-Schnittstellen zählen nicht. Neue Einträge werden deaktiviert hinzugefügt, damit Sie sie prüfen können. Sync deaktiviert außerdem verbleibende außer Betrieb genommene offizielle Testnet-Hubs (Amsterdam)." }, "reticulumPeers": { "hops": "Hops", @@ -1648,7 +1648,7 @@ "announceBusPressureTipBoundaryHubs": "Aktivierte Grenz- / Backbone-Hubs: {{hubs}}.", "announceBusPressureTipTxSaturated": "TX-Warteschlange fällt auf: {{names}} — diese Schnittstelle kann gesättigt sein.", "tooManyDefaultBackbones": "Du hast {{count}} Standard-Backbones aktiviert. Erwägen Sie, Extras zu deaktivieren und etwa 2 für die Redundanz aufzubewahren.", - "decommissionedHubEnabled": "Der stillgelegte Hub „{{name}}“ ist immer noch aktiviert — der Mesh-Client schaltet ihn bei jedem Stapelstart aus. Verwenden Sie Standard-Backbones oder das Backbone-Verzeichnis für Live-Endpunkte hinzufügen." + "decommissionedHubEnabled": "Außer Betrieb genommener Hub „{{name}}“ ist noch aktiviert — mesh-client schaltet ihn bei jedem Stack-Start aus. Nutzen Sie „Standard-Backbones hinzufügen“ oder das Backbone-Verzeichnis für aktuelle Endpunkte." } }, "routingPort": { diff --git a/src/renderer/locales/es/translation.json b/src/renderer/locales/es/translation.json index 3fe12c1e8..105330ed1 100644 --- a/src/renderer/locales/es/translation.json +++ b/src/renderer/locales/es/translation.json @@ -1121,16 +1121,16 @@ "advancedAria": "Opciones avanzadas de interfaz clave-valor", "advancedKeyReserved": "La clave «{{key}}» es un campo conocido y se ignoró. Utilice el formulario anterior.", "pickerDeviceAriaWithRssi": "{{name}} {{address}}, {{rssi}} dBm", - "decommissionedHubEnableBlocked": "Este hub está fuera de servicio y no puede permanecer habilitado — mesh-client lo apaga en cada inicio de la pila. Use Agregar hubs de red predeterminados o el directorio backbone para endpoints en vivo.", + "decommissionedHubEnableBlocked": "Este hub está fuera de servicio y no puede permanecer habilitado: mesh-client lo desactiva en cada arranque del stack. Use «Añadir backbones predeterminados» o el directorio de backbones para endpoints activos.", "decommissionedBadge": "fuera de servicio", - "defaultHubsPickerTitle": "Añadir concentradores de red predeterminados", + "defaultHubsPickerTitle": "Añadir backbones predeterminados", "defaultHubsPickerConfirm": "Añadir {{count}} hubs", "defaultHubsPickerConfirmSync": "Sincronización seleccionada", "defaultHubsPickerConfirmAria": "Añadir o sincronizar {{count}} concentradores predeterminados seleccionados", "defaultHubAlreadyAdded": "ya añadido(s)", "defaultHubPresetAria": "{{name}}, {{endpoint}}", "defaultHubRegion": { - "primary_global": "Red troncal primaria y global", + "primary_global": "Backbone primario y global", "north_america": "Norteamérica", "europe": "Europa", "asia_oceania": "Asia Oceanía", @@ -1160,7 +1160,7 @@ "addDefaultHubsFailed": "No se han podido añadir los backbones predeterminados.", "backboneEnableGuidanceLead": "Habilite de 1 a 3 puertas de enlace troncales como máximo", "backboneEnableGuidanceBody": "—2 es el punto óptimo para la redundancia sin una saturación innecesaria del ancho de banda. Apunte a 1 centro global y 1 puerta de enlace regional, y habilite interfaces especializadas (como I2P o Yggdrasil) estrictamente según sea necesario. Los RNodes conectados localmente y las interfaces LAN locales no cuentan para este límite de red troncal.", - "defaultHubsPickerHint": "Comience con Primary & Global más su región. Habilite de 1 a 3 puertas de enlace troncales como máximo (2 es el punto óptimo). Los RNodes locales y las interfaces LAN no cuentan. Se han añadido nuevas entradas deshabilitadas para que puedas revisarlas antes de habilitarlas. Sync también desactiva los hubs de testnet oficiales desmantelados restantes (Ámsterdam)." + "defaultHubsPickerHint": "Empiece con Primario y global más su región. Active como máximo 1 a 3 puertas de enlace de backbone (2 es el punto óptimo). Los RNodes locales y las interfaces LAN no cuentan. Las entradas nuevas se añaden desactivadas para que pueda revisarlas. Sync también desactiva hubs de testnet oficiales fuera de servicio restantes (Ámsterdam)." }, "reticulumPeers": { "hops": "Hops", @@ -1648,7 +1648,7 @@ "announceBusPressureTipBoundaryHubs": "Hubs de límite / backbone habilitados: {{hubs}}.", "announceBusPressureTipTxSaturated": "La cola TX cae en: {{names}} — esa interfaz puede estar saturada.", "tooManyDefaultBackbones": "Tienes {{count}} backbones predeterminados habilitados. Considere deshabilitar los extras y mantener alrededor de 2 por redundancia.", - "decommissionedHubEnabled": "El concentrador desmantelado \"{{name}}\" todavía está habilitado: mesh-client lo apaga en cada inicio de pila. Utilice Agregar backbones predeterminados o el directorio de backbone para puntos finales en vivo." + "decommissionedHubEnabled": "El hub fuera de servicio «{{name}}» sigue habilitado: mesh-client lo desactiva en cada arranque del stack. Use «Añadir backbones predeterminados» o el directorio de backbones para endpoints activos." } }, "routingPort": { @@ -2551,12 +2551,12 @@ "logLevel": "Nivel de registro", "save": "Guardar configuración de pila", "saveFailed": "No se pudo guardar la configuración de la pila.", - "pathMediumPreference": "Medio Path preferido", - "pathMediumPreferenceHint": "Cuando se puede acceder a un par a través de RF y red (TCP/I2P/etc.), elija qué medio gana de forma predeterminada. La ruta más baja se clasifica solo por recuento de saltos. Los pines Per-peer en la pestaña Peers pueden anular esto.", + "pathMediumPreference": "Medio de ruta preferido", + "pathMediumPreferenceHint": "Cuando un par es alcanzable por RF y red (TCP/I2P/etc.), elija qué medio gana por defecto. La ruta más baja se ordena solo por saltos. Los anclajes por par en la pestaña Pares pueden anular esto.", "pathMediumLowest": "Ruta más baja (conteo de saltos)", "pathMediumNetwork": "Red (no RF)", "pathMediumRf": "RF (RNode)", - "pathMediumPreferenceAria": "Medio Path preferido global", + "pathMediumPreferenceAria": "Medio de ruta preferido global", "pathMediumPreferenceSaved": "Ruta de preferencia media guardada.", "pathMediumPreferenceSaveFailed": "Error al guardar la preferencia del medio de ruta." }, @@ -2975,7 +2975,7 @@ "connectedViaRfAndMqttAria": "Conectado a través de RF y MQTT", "echoesConnectionHealthTooltip": "{{score}}% estado de la conexión", "tabAll": "Todos", - "tabHistory": "Historia", + "tabHistory": "Historial", "emptyHistory": "Aún no hay mensajes directos: envíe o reciba un DM para ver a los peers aquí." }, "nomadNetwork": { @@ -3074,16 +3074,16 @@ "sortByHopsDesc": "Ordenar por saltos, el más lejano primero", "sortByNameAsc": "Ordenar por nombre A->Z", "sortByNameDesc": "Ordenar por nombre Z->A", - "pageLoadingCountdown": "Cargando página... {{time}} izquierda", + "pageLoadingCountdown": "Cargando página… Quedan {{time}}", "pageLoadingCountdownOverdue": "Cargando página… sigue funcionando", "pageReadyToast": "Nomad — Página nómada lista: {{name}}", "staleLastSeenHint": "Escuchado por última vez {{time}}: este nodo puede estar fuera de línea incluso si figura como en línea.", - "pageLoadingRetryCountdown": "Se agotó el tiempo de espera del primer intento: actualizar la ruta y volver a intentarlo… quedan {{time}}", + "pageLoadingRetryCountdown": "El primer intento agotó el tiempo — actualizando ruta y reintentando… Quedan {{time}}", "pageLoadingRetryOverdue": "Actualizando ruta y reintentando... sigue funcionando", "enterUrlHint": "Pegue una dirección Nomad o seleccione un nodo de la lista.", "enterUrlPlaceholder": "hash:/page/index.mu", "goToUrl": "Avanzar", - "pageLoadingTimeLeft": "{{time}} se fue en este intento", + "pageLoadingTimeLeft": "Quedan {{time}} en este intento", "pageLoadingStillWorking": "Aún trabajando", "pageProgressLinking": "Conectando a través de {{iface}} ({{hops}} hops)…", "pageProgressLinkingIface": "Conectando a través de {{iface}}...", @@ -3213,13 +3213,13 @@ "pathsGlobalPreference": "Preferencia global: {{preference}}", "pathsListAria": "Ranuras de ruta clasificadas", "pathsActiveBadge": "Activo", - "pathsBackupBadge": "Respaldo", + "pathsBackupBadge": "Reserva", "pathsMedium": "Medio", "pathsExpired": "Caducada", "pathsEmpty": "Aún no hay ranuras de ruta. Solicita una ruta o espera un anuncio.", "pathsLoadFailed": "No se han podido cargar las ranuras de ruta.", "pathsPinFailed": "Error al actualizar la preferencia del medio para este compañero.", - "tabHistory": "Historia", + "tabHistory": "Historial", "emptyHistory": "Aún no hay historial de mensajes: envíe o reciba un mensaje LXMF para ver a los peers aquí.", "emptyContacts": "Aún no hay contactos guardados: abra un peer y elija Guardar como contacto." }, diff --git a/src/renderer/locales/fr/translation.json b/src/renderer/locales/fr/translation.json index 4ef1eb1de..d14762241 100644 --- a/src/renderer/locales/fr/translation.json +++ b/src/renderer/locales/fr/translation.json @@ -1120,16 +1120,16 @@ "advancedAria": "Options avancées de valeur de clé d'interface", "advancedKeyReserved": "La clé « {{key}} » est un champ connu et a été ignoré — utilisez le formulaire ci-dessus.", "pickerDeviceAriaWithRssi": "{{name}} {{address}}, {{rssi}} dBm", - "decommissionedHubEnableBlocked": "Ce hub est hors service et ne peut pas rester activé — mesh-client l’éteint à chaque démarrage de la pile. Utilisez Ajouter des hubs réseau par défaut ou le répertoire backbone pour les points de terminaison actifs.", + "decommissionedHubEnableBlocked": "Ce hub est hors service et ne peut pas rester activé — mesh-client le désactive à chaque démarrage de la pile. Utilisez « Ajouter des backbones par défaut » ou le répertoire des backbones pour des points de terminaison actifs.", "decommissionedBadge": "inactif", - "defaultHubsPickerTitle": "Ajouter des concentrateurs réseau par défaut", + "defaultHubsPickerTitle": "Ajouter des backbones par défaut", "defaultHubsPickerConfirm": "Ajouter des concentrateurs {{count}}", "defaultHubsPickerConfirmSync": "Synchroniser la sélection", "defaultHubsPickerConfirmAria": "Ajouter ou synchroniser {{count}} concentrateurs par défaut sélectionnés", "defaultHubAlreadyAdded": "déjà ajouté", "defaultHubPresetAria": "{{name}}, {{endpoint}}", "defaultHubRegion": { - "primary_global": "Colonne vertébrale principale et mondiale", + "primary_global": "Backbone primaire et mondial", "north_america": "Amérique du Nord", "europe": "Europe", "asia_oceania": "Asie & Océanie", @@ -1158,8 +1158,8 @@ "addDefaultHubsAllPresent": "Les backbones sélectionnés sont déjà configurés ; aucun hub de testnet désactivé n'est activé.", "addDefaultHubsFailed": "Échec de l'ajout des backbones par défaut.", "backboneEnableGuidanceLead": "Activer 1 à 3 passerelles dorsales au maximum", - "backboneEnableGuidanceBody": "—2 est le point idéal pour la redondance sans gonflement inutile de la bande passante. Visez 1 hub mondial et 1 passerelle régionale, et activez les interfaces spécialisées (comme I2P ou Yggdrasil) strictement selon les besoins. Les nœuds RNodes connectés localement et les interfaces LAN locales ne sont pas pris en compte dans cette limite dorsale.", - "defaultHubsPickerHint": "Commencez avec Primary & Global et votre région. Activez 1 à 3 passerelles dorsales au maximum (2 est le point idéal). Les RNodes locaux et les interfaces LAN ne comptent pas. Les nouvelles entrées sont ajoutées et désactivées afin que vous puissiez les consulter avant de les activer. Sync désactive également les hubs de testnet officiels déclassés restants (Amsterdam)." + "backboneEnableGuidanceBody": "—2 est l’optimum pour la redondance sans gaspiller la bande passante. Visez 1 hub mondial et 1 passerelle régionale, et activez les interfaces spécialisées (comme I2P ou Yggdrasil) seulement si besoin. Les RNodes connectés localement et les interfaces LAN locales ne comptent pas dans cette limite de backbone.", + "defaultHubsPickerHint": "Commencez par Primaire et mondial plus votre région. Activez au plus 1 à 3 passerelles backbone (2 est l’optimum). Les RNodes locaux et les interfaces LAN ne comptent pas. Les nouvelles entrées sont ajoutées désactivées pour révision. Sync désactive aussi les hubs de testnet officiels hors service restants (Amsterdam)." }, "reticulumPeers": { "hops": "Hops", @@ -1648,7 +1648,7 @@ "announceBusPressureTipBoundaryHubs": "Hubs de frontière / dorsale activés : {{hubs}}.", "announceBusPressureTipTxSaturated": "La file d'attente TX tombe sur : {{names}} — cette interface peut être saturée.", "tooManyDefaultBackbones": "Vous avez activé les backbones par défaut {{count}}. Envisagez de désactiver les extras et de conserver environ 2 pour la redondance.", - "decommissionedHubEnabled": "Le hub « {{name}} » mis hors service est toujours activé — mesh-client l'éteint à chaque démarrage de la pile. Utilisez Ajouter des backbones par défaut ou le répertoire de backbone pour les points de terminaison en direct." + "decommissionedHubEnabled": "Le hub hors service « {{name}} » est encore activé — mesh-client le désactive à chaque démarrage de la pile. Utilisez « Ajouter des backbones par défaut » ou le répertoire des backbones pour des points de terminaison actifs." } }, "routingPort": { @@ -3074,16 +3074,16 @@ "sortByHopsDesc": "Trier par sauts, le plus éloigné en premier", "sortByNameAsc": "Tri par nom : A à Z", "sortByNameDesc": "Tri par nom : Z à A", - "pageLoadingCountdown": "Chargement de la page… {{time}} gauche", + "pageLoadingCountdown": "Chargement de la page… {{time}} restantes", "pageLoadingCountdownOverdue": "Chargement de la page… fonctionne toujours", "pageReadyToast": "Nomad — Page nomade prête : {{name}}", "staleLastSeenHint": "Dernière écoute {{time}} — ce nœud peut être hors ligne même s'il est répertorié comme en ligne.", - "pageLoadingRetryCountdown": "Première tentative expirée — actualisation du chemin et nouvelle tentative… {{time}} restante", + "pageLoadingRetryCountdown": "Premier essai expiré — actualisation du chemin et nouvel essai… {{time}} restantes", "pageLoadingRetryOverdue": "Actualiser le chemin et réessayer… toujours en cours", "enterUrlHint": "Collez une adresse Nomad ou sélectionnez un nœud dans la liste.", "enterUrlPlaceholder": "hash:/page/index.mu", "goToUrl": "OK", - "pageLoadingTimeLeft": "{{time}} restante lors de cette tentative", + "pageLoadingTimeLeft": "{{time}} restantes pour cette tentative", "pageLoadingStillWorking": "Travail en cours...", "pageProgressLinking": "Connexion via {{iface}} ({{hops}} hops)…", "pageProgressLinkingIface": "Connexion via {{iface}}…", @@ -3213,7 +3213,7 @@ "pathsGlobalPreference": "Global : {{preference}}", "pathsListAria": "Emplacements de chemin classés", "pathsActiveBadge": "Actif", - "pathsBackupBadge": "Sauvegarde", + "pathsBackupBadge": "Secours", "pathsMedium": "Support", "pathsExpired": "Expiré", "pathsEmpty": "Aucun emplacement de chemin pour le moment. Demandez un chemin ou attendez une annonce.", diff --git a/src/renderer/locales/id/translation.json b/src/renderer/locales/id/translation.json index 69bbd28c8..d5faaf92a 100644 --- a/src/renderer/locales/id/translation.json +++ b/src/renderer/locales/id/translation.json @@ -1121,16 +1121,16 @@ "advancedAria": "Opsi nilai kunci antarmuka tingkat lanjut", "advancedKeyReserved": "Kunci \"{{key}}\" adalah bidang yang diketahui dan diabaikan — gunakan formulir di atas.", "pickerDeviceAriaWithRssi": "{{name}} {{address}}, {{rssi}} dBm", - "decommissionedHubEnableBlocked": "Hub ini dinonaktifkan dan tidak dapat tetap diaktifkan — mesh-client mematikannya pada setiap awal stack. Gunakan Tambahkan hub jaringan default atau direktori backbone untuk endpoint langsung.", + "decommissionedHubEnableBlocked": "Hub ini sudah dinonaktifkan dan tidak dapat tetap diaktifkan — mesh-client mematikannya setiap kali stack dimulai. Gunakan «Tambahkan backbone default» atau direktori backbone untuk endpoint aktif.", "decommissionedBadge": "dinonaktifkan", - "defaultHubsPickerTitle": "Tambahkan hub jaringan default", + "defaultHubsPickerTitle": "Tambahkan backbone default", "defaultHubsPickerConfirm": "Tambahkan hub {{count}}", "defaultHubsPickerConfirmSync": "Sinkronkan Dipilih", "defaultHubsPickerConfirmAria": "Tambahkan atau sinkronkan {{count}} hub default yang dipilih", "defaultHubAlreadyAdded": "Item sudah ditambahkan", "defaultHubPresetAria": "{{name}}, {{endpoint}}", "defaultHubRegion": { - "primary_global": "Tulang Punggung Utama & Global", + "primary_global": "Backbone Utama & Global", "north_america": "Amerika Utara", "europe": "Eropa", "asia_oceania": "Asia & Oseania", @@ -1154,13 +1154,13 @@ "enableAria": "Aktifkan antarmuka {{name}}", "disableAria": "Nonaktifkan antarmuka {{name}}", "defaultHubsLabel": "Tulang punggung default", - "addDefaultHubs": "Tambahkan tulang punggung default", + "addDefaultHubs": "Tambahkan backbone default", "addDefaultHubsAria": "Pilih tulang punggung Reticulum regional untuk ditambahkan, lalu sinkronkan preset yang dipilih", "addDefaultHubsAllPresent": "Tulang punggung yang dipilih sudah dikonfigurasi; tidak ada hub testnet yang dinonaktifkan yang diaktifkan.", "addDefaultHubsFailed": "Gagal menambahkan tulang punggung default.", "backboneEnableGuidanceLead": "Aktifkan 1 hingga 3 gateway backbone paling banyak", - "backboneEnableGuidanceBody": "—2 adalah sweet spot untuk redundansi tanpa mengasapi bandwidth yang tidak perlu. Bidik 1 hub global dan 1 gateway regional, dan aktifkan antarmuka khusus (seperti I2P atau Yggdrasil) secara ketat sesuai kebutuhan. RNode yang terhubung secara lokal dan antarmuka Lan lokal tidak dihitung dalam batas tulang punggung ini.", - "defaultHubsPickerHint": "Mulailah dengan Primary & Global plus wilayah Anda. Aktifkan 1 hingga 3 gerbang backbone paling banyak (2 adalah sweet spot). Antarmuka RNode dan Lan lokal tidak dihitung. Entri baru ditambahkan dinonaktifkan sehingga Anda dapat meninjau sebelum mengaktifkan. Sinkronisasi juga menonaktifkan sisa hub testnet resmi yang dinonaktifkan (Amsterdam)." + "backboneEnableGuidanceBody": "—2 adalah titik ideal untuk redundansi tanpa pemborosan bandwidth. Bidik 1 hub global dan 1 gateway regional, dan aktifkan antarmuka khusus (seperti I2P atau Yggdrasil) hanya jika diperlukan. RNode yang terhubung lokal dan antarmuka LAN lokal tidak dihitung dalam batas backbone ini.", + "defaultHubsPickerHint": "Mulai dengan Utama & Global plus wilayah Anda. Aktifkan paling banyak 1 hingga 3 gateway backbone (2 adalah titik ideal). RNode lokal dan antarmuka LAN tidak dihitung. Entri baru ditambahkan dalam keadaan nonaktif agar bisa ditinjau. Sync juga menonaktifkan sisa hub testnet resmi yang sudah dinonaktifkan (Amsterdam)." }, "reticulumPeers": { "hops": "Hops", @@ -1648,7 +1648,7 @@ "announceBusPressureTipBoundaryHubs": "Mengaktifkan hub batas / tulang punggung: {{hubs}}.", "announceBusPressureTipTxSaturated": "Antrean TX turun pada: {{names}} — antarmuka itu mungkin jenuh.", "tooManyDefaultBackbones": "Anda telah mengaktifkan {{count}} tulang punggung default. Pertimbangkan untuk menonaktifkan ekstra dan menyimpan sekitar 2 untuk redundansi.", - "decommissionedHubEnabled": "Hub yang dinonaktifkan \"{{name}}\" masih diaktifkan — mesh-client mematikannya di setiap awal tumpukan. Gunakan Tambahkan tulang punggung default atau direktori tulang punggung untuk titik akhir langsung." + "decommissionedHubEnabled": "Hub yang dinonaktifkan «{{name}}» masih aktif — mesh-client mematikannya setiap kali stack dimulai. Gunakan «Tambahkan backbone default» atau direktori backbone untuk endpoint aktif." } }, "routingPort": { diff --git a/src/renderer/locales/it/translation.json b/src/renderer/locales/it/translation.json index b699fb016..935d74093 100644 --- a/src/renderer/locales/it/translation.json +++ b/src/renderer/locales/it/translation.json @@ -1119,16 +1119,16 @@ "advancedAria": "Opzioni chiave-valore interfaccia avanzata", "advancedKeyReserved": "Il tasto \"{{key}}\" è un campo noto ed è stato ignorato. Utilizza il modulo qui sopra.", "pickerDeviceAriaWithRssi": "{{name}} {{address}}, {{rssi}} dBm", - "decommissionedHubEnableBlocked": "Questo hub è disattivato e non può rimanere abilitato — mesh-client lo disattiva a ogni avvio dello stack. Utilizzare Aggiungi hub di rete predefiniti o la directory backbone per gli endpoint live.", + "decommissionedHubEnableBlocked": "Questo hub è fuori servizio e non può restare abilitato — mesh-client lo spegne a ogni avvio dello stack. Usa «Aggiungi dorsali predefinite» o la directory delle dorsali per endpoint attivi.", "decommissionedBadge": "Dismesso", - "defaultHubsPickerTitle": "Aggiungi hub di rete predefiniti", + "defaultHubsPickerTitle": "Aggiungi dorsali predefinite", "defaultHubsPickerConfirm": "Aggiungi hub {{count}}", "defaultHubsPickerConfirmSync": "Sincronizza selezionati", "defaultHubsPickerConfirmAria": "Aggiungi o sincronizza {{count}} hub predefiniti selezionati", "defaultHubAlreadyAdded": "Già aggiunto", "defaultHubPresetAria": "{{name}}, {{endpoint}}", "defaultHubRegion": { - "primary_global": "Spina dorsale primaria e globale", + "primary_global": "Dorsale primaria e globale", "north_america": "America del Nord", "europe": "Europa", "asia_oceania": "Asia + Oceania", @@ -1157,8 +1157,8 @@ "addDefaultHubsAllPresent": "Le dorsali selezionate sono già configurate; nessun hub testnet disattivato abilitato.", "addDefaultHubsFailed": "Impossibile aggiungere dorsali predefinite.", "backboneEnableGuidanceLead": "Abilita da 1 a 3 gateway backbone al massimo", - "backboneEnableGuidanceBody": "—2 è il punto ideale per la ridondanza senza inutili ingrossamenti della larghezza di banda. Mira a 1 hub globale e 1 gateway regionale e abilita le interfacce speciali (come I2P o Yggdrasil) rigorosamente secondo necessità. Gli RNode connessi localmente e le interfacce LAN locali non contano ai fini di questo limite di backbone.", - "defaultHubsPickerHint": "Inizia con Primary & Global più la tua regione. Abilita da 1 a 3 gateway backbone al massimo (2 è il punto debole). I RNode locali e le interfacce LAN non contano. Le nuove voci vengono aggiunte disabilitate in modo da poterle rivedere prima dell'abilitazione. Sync disabilita anche i restanti hub testnet ufficiali disattivati (Amsterdam)." + "backboneEnableGuidanceBody": "—2 è l’optimum per la ridondanza senza spreco di banda. Punta a 1 hub globale e 1 gateway regionale, e abilita interfacce speciali (come I2P o Yggdrasil) solo se necessario. Gli RNode collegati in locale e le interfacce LAN locali non contano verso questo limite di dorsale.", + "defaultHubsPickerHint": "Inizia con Primaria e globale più la tua regione. Abilita al massimo da 1 a 3 gateway di dorsale (2 è l’optimum). Gli RNode locali e le interfacce LAN non contano. Le nuove voci vengono aggiunte disabilitate per la revisione. Sync disabilita anche gli hub testnet ufficiali fuori servizio rimanenti (Amsterdam)." }, "reticulumPeers": { "hops": "Hops", @@ -1648,7 +1648,7 @@ "announceBusPressureTipBoundaryHubs": "Hub di confine / backbone abilitati: {{hubs}}.", "announceBusPressureTipTxSaturated": "La coda TX cade su: {{names}} — l'interfaccia potrebbe essere satura.", "tooManyDefaultBackbones": "Hai {{count}} dorsali predefinite abilitate. Prendi in considerazione la possibilità di disabilitare gli extra e di tenerne circa 2 per ridondanza.", - "decommissionedHubEnabled": "L'hub dismesso \"{{name}}\" è ancora abilitato: mesh-client lo disattiva a ogni avvio dello stack. Utilizzare Aggiungi backbone predefiniti o la directory backbone per gli endpoint live." + "decommissionedHubEnabled": "L’hub fuori servizio «{{name}}» è ancora abilitato — mesh-client lo spegne a ogni avvio dello stack. Usa «Aggiungi dorsali predefinite» o la directory delle dorsali per endpoint attivi." } }, "routingPort": { @@ -2974,7 +2974,7 @@ "connectedViaRfAndMqttTooltip": "Connesso via RF e MQTT", "connectedViaRfAndMqttAria": "Connesso via RF e MQTT", "echoesConnectionHealthTooltip": "{{score}}% integrità della connessione", - "tabAll": "Tutte", + "tabAll": "Tutti", "tabHistory": "Cronologia", "emptyHistory": "Ancora nessun messaggio diretto: invia o ricevi un DM per vedere i peer qui." }, @@ -3074,16 +3074,16 @@ "sortByHopsDesc": "Ordina per salti, prima il più lontano", "sortByNameAsc": "Ordina per nome - dalla A alla Z", "sortByNameDesc": "Ordina per nome - dalla Z alla A", - "pageLoadingCountdown": "Caricamento pagina… {{time}} sinistra", + "pageLoadingCountdown": "Caricamento pagina… {{time}} rimanenti", "pageLoadingCountdownOverdue": "Caricamento della pagina... ancora funzionante", "pageReadyToast": "Pagina Nomad pronta: {{name}}", "staleLastSeenHint": "Ultimo ascolto {{time}}: questo nodo potrebbe essere offline anche se elencato come online.", - "pageLoadingRetryCountdown": "Timeout del primo tentativo: aggiornamento del percorso e nuovo tentativo... {{time}} rimasti", + "pageLoadingRetryCountdown": "Primo tentativo scaduto — aggiornamento percorso e nuovo tentativo… {{time}} rimanenti", "pageLoadingRetryOverdue": "Aggiornamento del percorso e nuovo tentativo... ancora in corso", "enterUrlHint": "Incolla un indirizzo Nomad o seleziona un nodo dall'elenco.", "enterUrlPlaceholder": "hash:/page/index.mu", "goToUrl": "Vai", - "pageLoadingTimeLeft": "{{time}} rimasti in questo tentativo", + "pageLoadingTimeLeft": "{{time}} rimanenti in questo tentativo", "pageLoadingStillWorking": "Funziona ancora...", "pageProgressLinking": "Connessione tramite {{iface}} ({{hops}} Hops)...", "pageProgressLinkingIface": "Connessione tramite {{iface}}...", diff --git a/src/renderer/locales/ja/translation.json b/src/renderer/locales/ja/translation.json index a93233a96..eaefc4ae6 100644 --- a/src/renderer/locales/ja/translation.json +++ b/src/renderer/locales/ja/translation.json @@ -1043,7 +1043,7 @@ "blePeer": "シード ピア アドレスを使用する BLE ピア メッシュ。", "generic": "Reticulumネットワークインターフェース。", "sharedInstanceClient": "Attached as a client of another app’s shared Reticulum instance — local TCP hubs from this config are not started here.", - "i2p": "このマシンのルーターを介したI 2 Pバックボーン。HTTP/HTTPS I 2 PTunnelプロキシ( 4444/4445 )ではなく、SAMアプリケーションブリッジ( 127.0.0.1 : 7656 )を有効にします。ブリッジがリッスンするようにSAMを有効にした後、I 2 Pを再起動し、このインターフェースがダウンしたままになっている場合は、Reticulumスタックを再起動します。" + "i2p": "このマシン上のルーター経由の I2P バックボーン。SAM アプリケーションブリッジ(127.0.0.1:7656)を有効にしてください — HTTP/HTTPS の I2PTunnel プロキシ(4444/4445)ではありません。SAM を有効にしたあと I2P を再起動してブリッジが待ち受けるようにし、このインターフェースがダウンのままなら Reticulum スタックも再起動してください。" }, "rfProfile": { "coordinated": "地域連携", @@ -1121,9 +1121,9 @@ "advancedAria": "高度なインターフェースキーバリューオプション", "advancedKeyReserved": "キー「{{key}}」は既知のフィールドであり、無視されました。上記のフォームを使用してください。", "pickerDeviceAriaWithRssi": "{{name}} {{address}}、{{rssi}} dBm", - "decommissionedHubEnableBlocked": "このハブは廃止されており有効のままにできません — mesh-client はスタック開始のたびにオフにします。ライブエンドポイントには「デフォルトネットワークハブを追加」またはバックボーンディレクトリを使用してください。", + "decommissionedHubEnableBlocked": "このハブは廃止されており、有効のままにできません — mesh-client はスタック起動のたびにオフにします。稼働中のエンドポイントには「デフォルトのバックボーンを追加」またはバックボーン一覧を使ってください。", "decommissionedBadge": "廃止済み", - "defaultHubsPickerTitle": "デフォルトのネットワークハブを追加する", + "defaultHubsPickerTitle": "デフォルトのバックボーンを追加", "defaultHubsPickerConfirm": "{{count}}ハブを追加", "defaultHubsPickerConfirmSync": "同期が選択されました", "defaultHubsPickerConfirmAria": "選択したデフォルトハブを{{count}}追加または同期", @@ -1159,8 +1159,8 @@ "addDefaultHubsAllPresent": "選択したバックボーンはすでに設定されています。無効化されたテストネットハブは有効化されていません。", "addDefaultHubsFailed": "デフォルトのバックボーンを追加できませんでした。", "backboneEnableGuidanceLead": "最大1〜3つのバックボーンゲートウェイを有効にする", - "backboneEnableGuidanceBody": "—2は、不要な帯域幅の膨張なしに冗長性を確保するためのスイートスポットです。1つのグローバルハブと1つのリージョナルゲートウェイを目指し、必要に応じて特別なインターフェース( I 2 PやYggdrasilなど)を厳密に有効にします。ローカルに接続されたRNodeとローカルLANインターフェイスは、このバックボーン制限にはカウントされません。", - "defaultHubsPickerHint": "プライマリ、グローバル、お住まいの地域から始めましょう。最大1〜3つのバックボーンゲートウェイを有効にします( 2がスイートスポットです)。ローカルRNodeとLANインターフェイスはカウントされません。新しいエントリは無効に追加されているため、有効にする前に確認できます。同期はまた、残りの廃止された公式テストネットハブ(アムステルダム)を無効にします。" + "backboneEnableGuidanceBody": "—冗長性を保ちつつ不要な帯域を増やさない最適は 2 です。グローバルハブ 1 つと地域ゲートウェイ 1 つを目安にし、I2P や Yggdrasil などの特殊インターフェースは必要なときだけ有効にしてください。ローカル接続の RNode とローカル LAN インターフェースはこのバックボーン上限に数えません。", + "defaultHubsPickerHint": "プライマリおよびグローバルに加え、自分の地域から始めてください。バックボーンゲートウェイは最大 1〜3(最適は 2)まで有効にします。ローカル RNode と LAN インターフェースは数えません。新規エントリは確認しやすいよう無効で追加されます。Sync は残っている廃止済み公式テストネットハブ(アムステルダム)も無効化します。" }, "reticulumPeers": { "hops": "Hops", @@ -1648,7 +1648,7 @@ "announceBusPressureTipBoundaryHubs": "境界/バックボーンハブを有効にしました: {{hubs}}。", "announceBusPressureTipTxSaturated": "TXキューは次の日付にドロップします: {{names}} —そのインターフェイスが飽和している可能性があります。", "tooManyDefaultBackbones": "{{count}}デフォルトのバックボーンが有効になっています。余分なものを無効にし、冗長性を保つために約2つを保管することを検討してください。", - "decommissionedHubEnabled": "廃止されたハブ「{{name}}」は引き続き有効です—mesh-clientはスタックの開始ごとにオフにします。ライブエンドポイントのデフォルトのバックボーンまたはバックボーンディレクトリの追加を使用します。" + "decommissionedHubEnabled": "廃止されたハブ「{{name}}」がまだ有効です — mesh-client はスタック起動のたびにオフにします。稼働中のエンドポイントには「デフォルトのバックボーンを追加」またはバックボーン一覧を使ってください。" } }, "routingPort": { @@ -2552,7 +2552,7 @@ "save": "スタック設定の保存", "saveFailed": "スタック設定の保存に失敗しました。", "pathMediumPreference": "好ましいパス媒体", - "pathMediumPreferenceHint": "ピアがRFおよびネットワーク( TCP/I 2 Pなど)を介して到達可能な場合は、デフォルトでどのメディアが勝つかを選択します。最低パスはホップカウントのみでランク付けされます。[ピア]タブのピアごとのピンは、これを上書きできます。", + "pathMediumPreferenceHint": "ピアが RF とネットワーク(TCP/I2P など)の両方で到達可能なとき、既定でどちらを優先するか選びます。最短パスはホップ数のみで順位付けします。Peers タブのピアごとの固定設定で上書きできます。", "pathMediumLowest": "最低パス(ホップカウント)", "pathMediumNetwork": "ネットワーク(非RF )", "pathMediumRf": "RF (RNode)", @@ -3213,7 +3213,7 @@ "pathsGlobalPreference": "グローバル: {{preference}}", "pathsListAria": "ランクパススロット", "pathsActiveBadge": "アクティブ", - "pathsBackupBadge": "バックアップ", + "pathsBackupBadge": "予備", "pathsMedium": "媒体", "pathsExpired": "有効期限切れ", "pathsEmpty": "パススロットはまだありません。パスをリクエストするか、アナウンスを待ちます。", diff --git a/src/renderer/locales/ko/translation.json b/src/renderer/locales/ko/translation.json index 3a2c33ef6..b33f2d1e0 100644 --- a/src/renderer/locales/ko/translation.json +++ b/src/renderer/locales/ko/translation.json @@ -1121,9 +1121,9 @@ "advancedAria": "고급 인터페이스 키-값 옵션", "advancedKeyReserved": "키 \"{{key}}\" 은 (는) 알려진 필드이며 무시되었습니다. 위의 양식을 사용하십시오.", "pickerDeviceAriaWithRssi": "{{name}} {{address}}, {{rssi}} dBm", - "decommissionedHubEnableBlocked": "이 허브는 서비스 종료되어 활성 상태를 유지할 수 없습니다 — mesh-client는 스택을 시작할 때마다 끕니다. 라이브 엔드포인트에는 기본 네트워크 허브 추가 또는 백본 디렉터리를 사용하세요.", + "decommissionedHubEnableBlocked": "이 허브는 폐기되어 계속 사용할 수 없습니다 — mesh-client는 스택을 시작할 때마다 끕니다. 활성 엔드포인트는 «기본 백본 추가» 또는 백본 목록을 사용하세요.", "decommissionedBadge": "해체됨", - "defaultHubsPickerTitle": "기본 네트워크 허브 추가", + "defaultHubsPickerTitle": "기본 백본 추가", "defaultHubsPickerConfirm": "{{count}} 허브 추가", "defaultHubsPickerConfirmSync": "동기화 선택됨", "defaultHubsPickerConfirmAria": "선택한 기본 허브 {{count}} 개 추가 또는 동기화", @@ -1160,7 +1160,7 @@ "addDefaultHubsFailed": "기본 백본을 추가하지 못했습니다.", "backboneEnableGuidanceLead": "최대 1 ~ 3개의 백본 게이트웨이 활성화", "backboneEnableGuidanceBody": "-2는 불필요한 대역폭 부풀림이 없는 이중화를 위한 스윗 스팟입니다. 글로벌 허브 1개와 지역 게이트웨이 1개를 목표로 하고 필요에 따라 특수 인터페이스 (예: I2P 또는 Yggdrasil) 를 엄격하게 활성화합니다. 로컬로 연결된 RNode 및 로컬 LAN 인터페이스는 이 백본 제한에 포함되지 않습니다.", - "defaultHubsPickerHint": "Primary & Global + 지역으로 시작하세요. 최대 1 ~ 3 개의 백본 게이트웨이를 활성화하십시오 (2 개가 가장 좋은 지점입니다). 로컬 RNode 및 LAN 인터페이스는 포함되지 않습니다. 새 항목이 비활성화되어 활성화하기 전에 검토할 수 있습니다. 또한 SYNC는 해체된 나머지 공식 테스트넷 허브 (암스테르담) 를 비활성화합니다." + "defaultHubsPickerHint": "기본 및 글로벌 백본에 지역을 더해 시작하세요. 백본 게이트웨이는 최대 1~3개(이상적은 2개)만 사용하세요. 로컬 RNode와 LAN 인터페이스는 포함되지 않습니다. 새 항목은 검토할 수 있도록 비활성으로 추가됩니다. Sync는 남은 폐기된 공식 테스트넷 허브(암스테르담)도 비활성화합니다." }, "reticulumPeers": { "hops": "Hops", @@ -1648,7 +1648,7 @@ "announceBusPressureTipBoundaryHubs": "활성화된 경계/백본 허브: {{hubs}}.", "announceBusPressureTipTxSaturated": "TX 대기열 드롭: {{names}} — 해당 인터페이스가 포화 상태일 수 있습니다.", "tooManyDefaultBackbones": "기본 백본이 {{count}} 개 활성화되어 있습니다. 엑스트라를 비활성화하고 이중화를 위해 약 2 개를 유지하는 것이 좋습니다.", - "decommissionedHubEnabled": "해체된 허브 \"{{name}}\" 가 여전히 활성화되어 있습니다 — mesh-client는 모든 스택 시작 시 이를 끕니다. 라이브 엔드포인트에 기본 백본 추가 또는 백본 디렉토리를 사용합니다." + "decommissionedHubEnabled": "폐기된 허브 «{{name}}»가 아직 사용 중입니다 — mesh-client는 스택을 시작할 때마다 끕니다. 활성 엔드포인트는 «기본 백본 추가» 또는 백본 목록을 사용하세요." } }, "routingPort": { @@ -2974,7 +2974,7 @@ "connectedViaRfAndMqttTooltip": "RF 및 MQTT를 통해 연결됨", "connectedViaRfAndMqttAria": "RF 및 MQTT를 통해 연결됨", "echoesConnectionHealthTooltip": "{{score}} % 연결 상태", - "tabAll": "All", + "tabAll": "전체", "tabHistory": "기록", "emptyHistory": "아직 다이렉트 메시지가 없습니다. DM을 보내거나 받아 피어를 확인하세요." }, @@ -3213,7 +3213,7 @@ "pathsGlobalPreference": "전역: {{preference}}", "pathsListAria": "순위가 지정된 경로 슬롯", "pathsActiveBadge": "활성화", - "pathsBackupBadge": "백업", + "pathsBackupBadge": "예비", "pathsMedium": "매체", "pathsExpired": "만료됨", "pathsEmpty": "아직 경로 슬롯이 없습니다. 경로를 요청하거나 발표를 기다리세요.", diff --git a/src/renderer/locales/nl/translation.json b/src/renderer/locales/nl/translation.json index 0298f5d9b..11aec5e0f 100644 --- a/src/renderer/locales/nl/translation.json +++ b/src/renderer/locales/nl/translation.json @@ -1120,9 +1120,9 @@ "advancedAria": "Geavanceerde interface key-value opties", "advancedKeyReserved": "Sleutel \"{{key}}\" is een bekend veld en werd genegeerd — gebruik het bovenstaande formulier.", "pickerDeviceAriaWithRssi": "{{name}} {{address}}, {{rssi}} dBm", - "decommissionedHubEnableBlocked": "Deze hub wordt buiten gebruik gesteld en kan niet ingeschakeld blijven — mesh-client schakelt het uit bij elke start van de stapel. Gebruik Add default network hubs of de backbone directory for live endpoints.", + "decommissionedHubEnableBlocked": "Deze hub is buiten gebruik en kan niet ingeschakeld blijven — mesh-client zet hem bij elke stackstart uit. Gebruik «Standaard backbones toevoegen» of de backbone-directory voor actieve eindpunten.", "decommissionedBadge": "ontmanteld", - "defaultHubsPickerTitle": "Standaard netwerkhubs toevoegen", + "defaultHubsPickerTitle": "Standaard backbones toevoegen", "defaultHubsPickerConfirm": "Voeg {{count}} hubs toe", "defaultHubsPickerConfirmSync": "Geselecteerde synchronisatie", "defaultHubsPickerConfirmAria": "{{count}} geselecteerde standaard hubs toevoegen of synchroniseren", @@ -1158,8 +1158,8 @@ "addDefaultHubsAllPresent": "Geselecteerde backbones zijn al geconfigureerd; er zijn geen ontmantelde testnet-hubs ingeschakeld.", "addDefaultHubsFailed": "Kan standaard backbones niet toevoegen.", "backboneEnableGuidanceLead": "Maximaal 1 tot 3 backbone-gateways inschakelen", - "backboneEnableGuidanceBody": "—2 is de sweet spot voor redundantie zonder onnodig opzwellen van bandbreedte. Streef naar 1 wereldwijde hub en 1 regionale gateway en schakel zo nodig strikt speciale interfaces in (zoals I2P of Yggdrasil). Lokaal verbonden RNodes en lokale LAN-interfaces tellen niet mee voor deze backbone-limiet.", - "defaultHubsPickerHint": "Begin met Primair & Globaal plus uw regio. Schakel maximaal 1 tot 3 backbone-gateways in (2 is de sweet spot). Lokale RNodes en LAN-interfaces tellen niet mee. Nieuwe vermeldingen zijn uitgeschakeld toegevoegd, zodat u ze kunt bekijken voordat u ze inschakelt. Sync schakelt ook de resterende buiten gebruik gestelde officiële testnet-hubs (Amsterdam) uit." + "backboneEnableGuidanceBody": "—2 is ideaal voor redundantie zonder onnodige bandbreedte. Streef naar 1 globale hub en 1 regionale gateway, en schakel speciale interfaces (zoals I2P of Yggdrasil) alleen in als nodig. Lokaal verbonden RNodes en lokale LAN-interfaces tellen niet mee voor deze backbone-limiet.", + "defaultHubsPickerHint": "Begin met Primair & globaal plus uw regio. Schakel hoogstens 1 tot 3 backbone-gateways in (2 is ideaal). Lokale RNodes en LAN-interfaces tellen niet mee. Nieuwe items worden uitgeschakeld toegevoegd zodat u ze kunt beoordelen. Sync schakelt ook resterende buiten gebruik gestelde officiële testnet-hubs uit (Amsterdam)." }, "reticulumPeers": { "hops": "Hops", @@ -1648,7 +1648,7 @@ "announceBusPressureTipBoundaryHubs": "Ingeschakelde grens- / ruggengraathubs: {{hubs}}.", "announceBusPressureTipTxSaturated": "TX-wachtrij valt op: {{names}} — die interface kan verzadigd zijn.", "tooManyDefaultBackbones": "Je hebt {{count}} standaard backbones ingeschakeld. Overweeg om extra's uit te schakelen en er ongeveer 2 te houden voor redundantie.", - "decommissionedHubEnabled": "De ontmantelde hub \"{{name}}\" is nog steeds ingeschakeld — mesh-client schakelt deze uit bij elke start van de stapel. Gebruik Standaard backbones toevoegen of de backbone-directory voor live eindpunten." + "decommissionedHubEnabled": "Buiten gebruik gestelde hub „{{name}}” staat nog aan — mesh-client zet hem bij elke stackstart uit. Gebruik «Standaard backbones toevoegen» of de backbone-directory voor actieve eindpunten." } }, "routingPort": { @@ -2552,7 +2552,7 @@ "save": "Bewaar stapelinstellingen", "saveFailed": "Kan stapelinstellingen niet opslaan.", "pathMediumPreference": "Voorkeurspad medium", - "pathMediumPreferenceHint": "Wanneer een peer bereikbaar is via RF en netwerk (TCP/I2P/etc.), kies dan standaard welk medium wint. Het laagste pad wordt alleen gerangschikt op hoptelling. Per-peer pins op het tabblad Peers kunnen dit overschrijven.", + "pathMediumPreferenceHint": "Als een peer bereikbaar is via RF en netwerk (TCP/I2P/enz.), kies welk medium standaard wint. Laagste pad rangschikt alleen op hop-aantal. Per-peer vastzettingen op het tabblad Peers kunnen dit overschrijven.", "pathMediumLowest": "Laagste pad (hoptelling)", "pathMediumNetwork": "Netwerk (niet-RF)", "pathMediumRf": "RF (RNode)", @@ -2974,7 +2974,7 @@ "connectedViaRfAndMqttTooltip": "Verbonden via RF en MQTT", "connectedViaRfAndMqttAria": "Verbonden via RF en MQTT", "echoesConnectionHealthTooltip": "{{score}}% verbindingsstatus", - "tabAll": "All", + "tabAll": "Alles", "tabHistory": "Historie", "emptyHistory": "Nog geen directe berichten — stuur of ontvang een DM om peers hier te zien." }, @@ -3213,7 +3213,7 @@ "pathsGlobalPreference": "Globaal: {{preference}}", "pathsListAria": "Gerangschikte pad-sleuven", "pathsActiveBadge": "Actief", - "pathsBackupBadge": "Back-up", + "pathsBackupBadge": "Reserve", "pathsMedium": "Medium", "pathsExpired": "Verlopen", "pathsEmpty": "Nog geen padvakken. Vraag een pad aan of wacht op een aankondiging.", diff --git a/src/renderer/locales/pl/translation.json b/src/renderer/locales/pl/translation.json index c10fcb876..45c5c82b3 100644 --- a/src/renderer/locales/pl/translation.json +++ b/src/renderer/locales/pl/translation.json @@ -1123,9 +1123,9 @@ "advancedAria": "Zaawansowane opcje wartości klawiszy interfejsu", "advancedKeyReserved": "Klucz „{{key}}” jest znanym polem i został zignorowany — użyj powyższego formularza.", "pickerDeviceAriaWithRssi": "{{name}} {{address}}, {{rssi}} dBm", - "decommissionedHubEnableBlocked": "Ten hub jest wycofany z eksploatacji i nie może pozostać włączony — mesh-client wyłącza go przy każdym uruchomieniu stosu. Użyj Dodaj domyślne huby sieciowe lub katalogu backbone dla aktywnych punktów końcowych.", + "decommissionedHubEnableBlocked": "Ten hub jest wycofany i nie może pozostać włączony — mesh-client wyłącza go przy każdym starcie stosu. Użyj «Dodaj domyślne magistrale» lub katalogu magistrali dla aktywnych punktów końcowych.", "decommissionedBadge": "wycofany z eksploatacji", - "defaultHubsPickerTitle": "Dodaj domyślne koncentratory sieciowe", + "defaultHubsPickerTitle": "Dodaj domyślne magistrale", "defaultHubsPickerConfirm": "Dodaj koncentratory {{count}}", "defaultHubsPickerConfirmSync": "Wybrano synchronizację", "defaultHubsPickerConfirmAria": "Dodaj lub zsynchronizuj wybrane domyślne koncentratory {{count}}", @@ -1156,13 +1156,13 @@ "enableAria": "Włącz interfejs {{name}}", "disableAria": "Wyłącz interfejs {{name}}", "defaultHubsLabel": "Domyślne kręgosłupy", - "addDefaultHubs": "Dodaj domyślne kręgosłupy", + "addDefaultHubs": "Dodaj domyślne magistrale", "addDefaultHubsAria": "Wybierz regionalne backbone’y Reticulum do dodania, a następnie zsynchronizuj wybrane ustawienia wstępne", "addDefaultHubsAllPresent": "Wybrane kręgosłupy są już skonfigurowane; nie włączono wycofanych koncentratorów sieci testowej.", "addDefaultHubsFailed": "Nie udało się dodać domyślnych kości kręgosłupa.", "backboneEnableGuidanceLead": "Włącz maksymalnie 1 do 3 bramek szkieletowych", - "backboneEnableGuidanceBody": "—2 to słodkie miejsce na redundancję bez zbędnego wzdęcia przepustowości. Celuj w 1 globalny hub i 1 bramę regionalną oraz włączaj specjalistyczne interfejsy (takie jak I2P lub Yggdrasil) ściśle w razie potrzeby. Lokalnie podłączone RNodes i lokalne interfejsy LAN nie wliczają się do tego limitu sieci szkieletowej.", - "defaultHubsPickerHint": "Zacznij od Primary & Global plus Twój region. Włącz maksymalnie od 1 do 3 bramek szkieletowych (2 to słodki punkt). Lokalne interfejsy RNodes i LAN nie są uwzględniane. Nowe wpisy są dodawane wyłączone, więc możesz je przejrzeć przed włączeniem. SYNC wyłącza również pozostałe zlikwidowane oficjalne węzły testowe (Amsterdam)." + "backboneEnableGuidanceBody": "—2 to optimum dla nadmiarowości bez zbędnego zużycia pasma. Celuj w 1 globalny hub i 1 regionalną bramę, a specjalne interfejsy (jak I2P lub Yggdrasil) włączaj tylko gdy trzeba. Lokalnie podłączone RNode i lokalne interfejsy LAN nie wliczają się do tego limitu magistrali.", + "defaultHubsPickerHint": "Zacznij od Podstawowej i globalnej magistrali oraz swojego regionu. Włącz co najwyżej 1–3 bramy magistrali (2 to optimum). Lokalne RNode i interfejsy LAN się nie liczą. Nowe wpisy są dodawane wyłączone, by je przejrzeć. Sync wyłącza też pozostałe wycofane oficjalne huby testnetu (Amsterdam)." }, "reticulumPeers": { "hops": "Hops", @@ -1652,7 +1652,7 @@ "announceBusPressureTipBoundaryHubs": "Włączone koncentratory brzegowe / szkieletowe: {{hubs}}.", "announceBusPressureTipTxSaturated": "Kolejka TX spada w dniu: {{names}} — ten interfejs może być nasycony.", "tooManyDefaultBackbones": "Masz włączone {{count}} domyślne kręgosłupy. Rozważ wyłączenie dodatków i zachowanie około 2 na potrzeby redundancji.", - "decommissionedHubEnabled": "Wycofany koncentrator „{{name}}” jest nadal włączony — mesh-client wyłącza go przy każdym uruchomieniu stosu. Użyj opcji Dodaj domyślne kręgosłupy lub katalogu kręgosłupa dla punktów końcowych na żywo." + "decommissionedHubEnabled": "Wycofany hub „{{name}}” jest nadal włączony — mesh-client wyłącza go przy każdym starcie stosu. Użyj «Dodaj domyślne magistrale» lub katalogu magistrali dla aktywnych punktów końcowych." } }, "routingPort": { @@ -2979,7 +2979,7 @@ "connectedViaRfAndMqttAria": "Połączono przez RF i MQTT", "echoesConnectionHealthTooltip": "{{score}}% stan połączenia", "tabAll": "Wszystko", - "tabHistory": "Historia", + "tabHistory": "Historia rozmów", "emptyHistory": "Nie ma jeszcze bezpośrednich wiadomości — wyślij lub otrzymaj DM, aby zobaczyć peerów tutaj." }, "nomadNetwork": { @@ -3223,7 +3223,7 @@ "pathsEmpty": "Nie ma jeszcze slotów ścieżek. Poproś o ścieżkę lub poczekaj na ogłoszenie.", "pathsLoadFailed": "Nie udało się załadować slotów ścieżki.", "pathsPinFailed": "Nie udało się zaktualizować preferencji medium dla tego partnera.", - "tabHistory": "Historia", + "tabHistory": "Historia rozmów", "emptyHistory": "Brak historii wiadomości — wyślij lub otrzymaj wiadomość LXMF, aby zobaczyć peerów tutaj.", "emptyContacts": "Brak zapisanych kontaktów — otwórz peera i wybierz Zapisz jako kontakt." }, diff --git a/src/renderer/locales/pt-BR/translation.json b/src/renderer/locales/pt-BR/translation.json index af8c2ca70..99643eaa4 100644 --- a/src/renderer/locales/pt-BR/translation.json +++ b/src/renderer/locales/pt-BR/translation.json @@ -1121,9 +1121,9 @@ "advancedAria": "Opções avançadas de chave-valor da interface", "advancedKeyReserved": "A chave \"{{key}}\" é um campo conhecido e foi ignorado — use o formulário acima.", "pickerDeviceAriaWithRssi": "{{name}} {{address}}, {{rssi}} dBm", - "decommissionedHubEnableBlocked": "Este hub está desativado e não pode permanecer ativado — o mesh-client o desliga em todas as partidas de pilha. Use Adicionar hubs de rede padrão ou o diretório de backbone para pontos de extremidade ativos.", + "decommissionedHubEnableBlocked": "Este hub está desativado e não pode permanecer habilitado — o mesh-client o desliga a cada início do stack. Use «Adicionar backbones padrão» ou o diretório de backbones para endpoints ativos.", "decommissionedBadge": "DESCOMISSIONADO", - "defaultHubsPickerTitle": "Adicionar hubs de rede padrão", + "defaultHubsPickerTitle": "Adicionar backbones padrão", "defaultHubsPickerConfirm": "Adicionar centros {{count}}", "defaultHubsPickerConfirmSync": "Sincronizar selecionados", "defaultHubsPickerConfirmAria": "Adicionar ou sincronizar {{count}} hubs padrão selecionados", @@ -1160,7 +1160,7 @@ "addDefaultHubsFailed": "Falha ao adicionar backbones padrão.", "backboneEnableGuidanceLead": "Habilite de 1 a 3 gateways de backbone no máximo", "backboneEnableGuidanceBody": "—2 é o ponto ideal para redundância sem inchaço desnecessário da largura de banda. Aponte para 1 hub global e 1 gateway regional e habilite interfaces especializadas (como I2P ou Yggdrasil) estritamente conforme necessário. Os RNodes conectados localmente e as interfaces de LAN locais não contam para esse limite de backbone.", - "defaultHubsPickerHint": "Comece com Primário e Global mais sua região. Ative de 1 a 3 gateways de backbone no máximo (2 é o ponto ideal). Os RNodes locais e as interfaces LAN não contam. Novas entradas são adicionadas desativadas para que você possa revisar antes de ativar. O SYNC também desativa os hubs de rede de teste oficiais desativados restantes (Amsterdã)." + "defaultHubsPickerHint": "Comece com Primário e global mais sua região. Ative no máximo 1 a 3 gateways de backbone (2 é o ponto ideal). RNodes locais e interfaces LAN não contam. Novas entradas são adicionadas desativadas para revisão. Sync também desativa hubs de testnet oficiais desativados restantes (Amsterdã)." }, "reticulumPeers": { "hops": "Hops", @@ -1648,7 +1648,7 @@ "announceBusPressureTipBoundaryHubs": "Hubs de limite / backbone ativados: {{hubs}}.", "announceBusPressureTipTxSaturated": "A fila TX cai em: {{names}} — essa interface pode estar saturada.", "tooManyDefaultBackbones": "Você tem {{count}} backbones padrão ativados. Considere desativar extras e manter cerca de 2 para redundância.", - "decommissionedHubEnabled": "O hub desativado \"{{name}}\" ainda está ativado — o mesh-client o desliga em todas as partidas de pilha. Use Adicionar backbones padrão ou o diretório de backbone para pontos de extremidade ativos." + "decommissionedHubEnabled": "O hub desativado «{{name}}» ainda está habilitado — o mesh-client o desliga a cada início do stack. Use «Adicionar backbones padrão» ou o diretório de backbones para endpoints ativos." } }, "routingPort": { @@ -3074,16 +3074,16 @@ "sortByHopsDesc": "Ordenar por saltos, o mais distante primeiro", "sortByNameAsc": "Ordenar por nome de A a Z", "sortByNameDesc": "Ordenar por nome: z à a", - "pageLoadingCountdown": "Carregando página… {{time}} esquerda", + "pageLoadingCountdown": "Carregando página… {{time}} restantes", "pageLoadingCountdownOverdue": "Carregando página… ainda funcionando", "pageReadyToast": "Página Nomad pronta: {{name}}", "staleLastSeenHint": "Ouvido pela última vez em {{time}} — este nó pode estar offline mesmo se estiver listado como online.", - "pageLoadingRetryCountdown": "A primeira tentativa expirou — atualizando o caminho e tentando novamente… {{time}} restante", + "pageLoadingRetryCountdown": "Primeira tentativa expirou — atualizando caminho e tentando de novo… {{time}} restantes", "pageLoadingRetryOverdue": "Atualizando o caminho e tentando novamente... ainda funcionando", "enterUrlHint": "Cole um endereço Nomad ou selecione um nó da lista.", "enterUrlPlaceholder": "hash:/page/index.mu", "goToUrl": "Ir", - "pageLoadingTimeLeft": "{{time}} restante nesta tentativa", + "pageLoadingTimeLeft": "{{time}} restantes nesta tentativa", "pageLoadingStillWorking": "Ainda trabalhando?", "pageProgressLinking": "Conectando-se via {{iface}} ({{hops}} hops)…", "pageProgressLinkingIface": "Conectando-se via {{iface}}…", diff --git a/src/renderer/locales/ru/translation.json b/src/renderer/locales/ru/translation.json index a05af55c1..ff30fc024 100644 --- a/src/renderer/locales/ru/translation.json +++ b/src/renderer/locales/ru/translation.json @@ -1123,9 +1123,9 @@ "advancedAria": "Расширенные параметры ключ-значение интерфейса", "advancedKeyReserved": "Ключ \"{{key}}\" является известным полем и был проигнорирован — используйте форму выше.", "pickerDeviceAriaWithRssi": "{{name}} {{address}}, {{rssi}} дБм", - "decommissionedHubEnableBlocked": "Этот концентратор выведен из эксплуатации и не может оставаться включенным — mesh-client отключает его при каждом запуске стека. Используйте Add default network hubs (Добавить сетевые концентраторы по умолчанию) или backbone directory (Магистральный каталог) для активных конечных точек.", + "decommissionedHubEnableBlocked": "Этот хаб выведен из эксплуатации и не может оставаться включённым — mesh-client отключает его при каждом запуске стека. Используйте «Добавить магистрали по умолчанию» или каталог магистралей для рабочих конечных точек.", "decommissionedBadge": "выведена из эксплуатации", - "defaultHubsPickerTitle": "Добавить сетевые концентраторы по умолчанию", + "defaultHubsPickerTitle": "Добавить магистрали по умолчанию", "defaultHubsPickerConfirm": "Добавить {{count}} концентраторов", "defaultHubsPickerConfirmSync": "Синхронизация выбрана", "defaultHubsPickerConfirmAria": "Добавление или синхронизация {{count}} выбранных концентраторов по умолчанию", @@ -1161,8 +1161,8 @@ "addDefaultHubsAllPresent": "Выбранные магистрали уже настроены; выведенные из эксплуатации концентраторы тестовой сети не включены.", "addDefaultHubsFailed": "Не удалось добавить магистрали по умолчанию.", "backboneEnableGuidanceLead": "Включить не более 1-3 магистральных шлюзов", - "backboneEnableGuidanceBody": "—2 - сладкая точка для избыточности без ненужного раздувания полосы пропускания. Стремитесь к 1 глобальному хабу и 1 региональному шлюзу и включайте специализированные интерфейсы (например, I2P или Yggdrasil) строго по мере необходимости. Локально подключенные RNode и локальные LAN-интерфейсы не учитываются при определении этого предела магистрали.", - "defaultHubsPickerHint": "Начните с Primary & Global и вашего региона. Включите максимум от 1 до 3 магистральных шлюзов (2 - сладкая точка). Локальные RNodes и LAN-интерфейсы не учитываются. Новые записи отключены, поэтому вы можете просмотреть их перед включением. Sync также отключает оставшиеся выведенные из эксплуатации официальные концентраторы тестовой сети (Амстердам)." + "backboneEnableGuidanceBody": "—2 — оптимум для избыточности без лишней нагрузки на канал. Цель: 1 глобальный хаб и 1 региональный шлюз; специальные интерфейсы (I2P или Yggdrasil) включайте только по необходимости. Локально подключённые RNode и локальные LAN-интерфейсы не входят в этот лимит магистрали.", + "defaultHubsPickerHint": "Начните с первичной и глобальной магистрали плюс ваш регион. Включайте не более 1–3 магистральных шлюзов (2 — оптимум). Локальные RNode и LAN-интерфейсы не учитываются. Новые записи добавляются выключенными для проверки. Sync также отключает оставшиеся выведенные из эксплуатации официальные тестовые хабы (Амстердам)." }, "reticulumPeers": { "hops": "Hops", @@ -1650,7 +1650,7 @@ "announceBusPressureTipBoundaryHubs": "Включенные граничные / магистральные концентраторы: {{hubs}}.", "announceBusPressureTipTxSaturated": "Очередь TX падает на: {{names}} — этот интерфейс может быть насыщенным.", "tooManyDefaultBackbones": "У вас включена {{count}} магистраль по умолчанию. Подумайте о том, чтобы отключить дополнительные услуги и оставить около 2 для резервирования.", - "decommissionedHubEnabled": "Выведенный из эксплуатации концентратор \"{{name}}\" по-прежнему включен — mesh-client отключает его при каждом запуске стека. Используйте Add default backbones (Добавить магистрали по умолчанию) или backbone directory (Каталог магистрали) для активных конечных точек." + "decommissionedHubEnabled": "Выведенный из эксплуатации хаб «{{name}}» всё ещё включён — mesh-client отключает его при каждом запуске стека. Используйте «Добавить магистрали по умолчанию» или каталог магистралей для рабочих конечных точек." } }, "routingPort": { @@ -2554,7 +2554,7 @@ "save": "Сохранить настройки стека", "saveFailed": "Не удалось сохранить настройки стека.", "pathMediumPreference": "Предпочтительная среда пути", - "pathMediumPreferenceHint": "Когда одноранговый узел доступен по радиочастоте и сети (TCP/I2P/и т. д.), выберите, какой носитель выигрывает по умолчанию. Самый низкий путь ранжируется только по количеству прыжков. Одноранговые контакты на вкладке Одноранговые узлы могут переопределить это.", + "pathMediumPreferenceHint": "Когда узел доступен по RF и сети (TCP/I2P и т. д.), выберите, какая среда побеждает по умолчанию. Самый короткий путь ранжируется только по числу прыжков. Закрепления для отдельных узлов на вкладке «Узлы» могут это переопределить.", "pathMediumLowest": "Наименьший путь (количество прыжков)", "pathMediumNetwork": "Сеть (не-RF)", "pathMediumRf": "RF (RNode)", diff --git a/src/renderer/locales/tr/translation.json b/src/renderer/locales/tr/translation.json index d55b286c7..2b0a739ed 100644 --- a/src/renderer/locales/tr/translation.json +++ b/src/renderer/locales/tr/translation.json @@ -1121,16 +1121,16 @@ "advancedAria": "Gelişmiş arayüz anahtar - değer seçenekleri", "advancedKeyReserved": "\"{{key}}\" anahtarı bilinen bir alandır ve yok sayılmıştır — yukarıdaki formu kullanın.", "pickerDeviceAriaWithRssi": "{{name}} {{address}}, {{rssi}} dBm", - "decommissionedHubEnableBlocked": "Bu hub kullanımdan kaldırılmıştır ve etkin kalamaz — mesh-client her yığın başlangıcında onu kapatır. Canlı uç noktalar için varsayılan ağ hub’ları ekle veya omurga dizinini kullanın.", + "decommissionedHubEnableBlocked": "Bu hub kullanım dışı bırakıldı ve açık kalamaz — mesh-client her yığın başlangıcında kapatır. Canlı uç noktalar için «Varsayılan omurga bağlantıları ekle» veya omurga dizinini kullanın.", "decommissionedBadge": "Kullanımdan kaldırılmış", - "defaultHubsPickerTitle": "Varsayılan ağ hub'ları ekle", + "defaultHubsPickerTitle": "Varsayılan omurga bağlantıları ekle", "defaultHubsPickerConfirm": "{{count}} hub ekleyin", "defaultHubsPickerConfirmSync": "Seçilenleri senkronize et", "defaultHubsPickerConfirmAria": "Seçilen varsayılan hub'ları {{count}} ekleyin veya senkronize edin", "defaultHubAlreadyAdded": "ÇOKTAN EKLENMİŞ", "defaultHubPresetAria": "{{name}}, {{endpoint}}", "defaultHubRegion": { - "primary_global": "Birincil ve Küresel Omurga", + "primary_global": "Birincil ve küresel omurga", "north_america": "Kuzey Amerika", "europe": "Avrupa", "asia_oceania": "Asya ve Okyanusya", @@ -1154,13 +1154,13 @@ "enableAria": "Arabirimi etkinleştir {{name}}", "disableAria": "Arabirimi devre dışı bırak {{name}}", "defaultHubsLabel": "Varsayılan omurgalar", - "addDefaultHubs": "Varsayılan omurgaları ekle", + "addDefaultHubs": "Varsayılan omurga bağlantıları ekle", "addDefaultHubsAria": "Eklenecek bölgesel Reticulum omurgalarını seçin, ardından seçilen ön ayarları senkronize edin", "addDefaultHubsAllPresent": "Seçilen omurgalar zaten yapılandırılmış; devre dışı bırakılmış testnet hub'ları etkin değil.", "addDefaultHubsFailed": "Varsayılan omurgalar eklenemedi.", "backboneEnableGuidanceLead": "En fazla 1 ila 3 omurga ağ geçidini etkinleştirin", "backboneEnableGuidanceBody": "—2 gereksiz bant genişliği şişmesi olmadan fazlalık için en uygun noktadır. 1 küresel merkez ve 1 bölgesel ağ geçidini hedefleyin ve kesinlikle gerektiğinde özel arayüzleri (I2P veya Yggdrasil gibi) etkinleştirin. Yerel olarak bağlı RNode'lar ve yerel lan arayüzleri bu omurga sınırına dahil edilmez.", - "defaultHubsPickerHint": "Birincil ve Küresel artı bölgenizle başlayın. En fazla 1 ila 3 omurga ağ geçidini etkinleştirin (2 tatlı noktadır). Yerel RNode'lar ve lan arayüzleri sayılmaz. Etkinleştirmeden önce gözden geçirebilmeniz için yeni girişler devre dışı bırakıldı. SYNC ayrıca hizmet dışı kalan resmi testnet hub'larını (Amsterdam) devre dışı bırakır." + "defaultHubsPickerHint": "Birincil ve küresel omurga ile bölgenizden başlayın. En fazla 1–3 omurga ağ geçidi etkinleştirin (2 idealdir). Yerel RNode ve LAN arayüzleri sayılmaz. Yeni girdiler inceleme için kapalı eklenir. Sync ayrıca kalan kullanım dışı resmi testnet hub’larını da kapatır (Amsterdam)." }, "reticulumPeers": { "hops": "Hops", @@ -1648,7 +1648,7 @@ "announceBusPressureTipBoundaryHubs": "Etkin sınır / omurga göbekleri: {{hubs}}.", "announceBusPressureTipTxSaturated": "TX kuyruğu şu tarihte düşer: {{names}} — bu arayüz doymuş olabilir.", "tooManyDefaultBackbones": "{{count}} varsayılan omurganız etkin. Ekstraları devre dışı bırakmayı ve fazlalık için yaklaşık 2 tane tutmayı düşünün.", - "decommissionedHubEnabled": "Devre dışı bırakılan hub \"{{name}}\" hala etkindir — mesh-client her yığın başlangıcında onu kapatır. Canlı uç noktalar için Varsayılan omurga ekle veya omurga dizinini kullanın." + "decommissionedHubEnabled": "Kullanım dışı hub «{{name}}» hâlâ açık — mesh-client her yığın başlangıcında kapatır. Canlı uç noktalar için «Varsayılan omurga bağlantıları ekle» veya omurga dizinini kullanın." } }, "routingPort": { @@ -3213,7 +3213,7 @@ "pathsGlobalPreference": "Küresel: {{preference}}", "pathsListAria": "Dereceli yol yuvaları", "pathsActiveBadge": "Aktif", - "pathsBackupBadge": "Yedekleme", + "pathsBackupBadge": "Yedek", "pathsMedium": "Ortam", "pathsExpired": "Süresi doldu", "pathsEmpty": "Henüz yol aralığı yok. Bir yol isteyin veya bir duyuru bekleyin.", diff --git a/src/renderer/locales/uk/translation.json b/src/renderer/locales/uk/translation.json index 71e445717..21d9bbdb9 100644 --- a/src/renderer/locales/uk/translation.json +++ b/src/renderer/locales/uk/translation.json @@ -1123,9 +1123,9 @@ "advancedAria": "Розширені параметри ключа-значення інтерфейсу", "advancedKeyReserved": "Ключ \"{{key}}\" є відомим полем і був проігнорований — скористайтеся формою вище.", "pickerDeviceAriaWithRssi": "{{name}} {{address}}, {{rssi}} дБм", - "decommissionedHubEnableBlocked": "Цей хаб виведений з експлуатації і не може залишатися ввімкненим — mesh-client вимикає його при кожному запуску стека. Використовуйте «Додати концентратори мережі за замовчуванням» або каталог backbone для робочих кінцевих точок.", + "decommissionedHubEnableBlocked": "Цей хаб виведений з експлуатації і не може залишатися ввімкненим — mesh-client вимикає його при кожному запуску стека. Використовуйте «Додати магістралі за замовчуванням» або каталог магістралей для робочих кінцевих точок.", "decommissionedBadge": "виведені з експлуатації", - "defaultHubsPickerTitle": "Додати концентратори мережі за замовчуванням", + "defaultHubsPickerTitle": "Додати магістралі за замовчуванням", "defaultHubsPickerConfirm": "Додати {{count}} концентраторів", "defaultHubsPickerConfirmSync": "Вибрано синхронізацію", "defaultHubsPickerConfirmAria": "Додавання або синхронізація {{count}} вибраних концентраторів за замовчуванням", @@ -1162,7 +1162,7 @@ "addDefaultHubsFailed": "Не вдалося додати магістралі за замовчуванням.", "backboneEnableGuidanceLead": "Увімкнути щонайбільше від 1 до 3 магістральних шлюзів", "backboneEnableGuidanceBody": "—2 - це солодка пляма для надлишковості без зайвого роздування пропускної здатності. Створіть 1 глобальний хаб та 1 регіональний шлюз, а також увімкніть спеціалізовані інтерфейси (такі як I2P або Yggdrasil) строго за потреби. Місцеві з'єднані RNodes та локальні інтерфейси локальної мережі не враховуються до цієї межі магістралі.", - "defaultHubsPickerHint": "Почніть з Primary & Global плюс ваш регіон. Увімкніть щонайбільше від 1 до 3 магістральних шлюзів (2 - найкраще місце). Локальні RNodes та інтерфейси локальної мережі не враховуються. Нові записи вимкнено, щоб ви могли переглянути їх перед увімкненням. Sync також вимикає інші виведені з експлуатації офіційні концентратори тестової мережі (Амстердам)." + "defaultHubsPickerHint": "Почніть з первинної та глобальної магістралі плюс ваш регіон. Увімкніть не більше 1–3 магістральних шлюзів (2 — оптимум). Локальні RNode та LAN-інтерфейси не враховуються. Нові записи додаються вимкненими для перевірки. Sync також вимикає залишкові виведені з експлуатації офіційні тестові хаби (Амстердам)." }, "reticulumPeers": { "hops": "Hops", @@ -1650,7 +1650,7 @@ "announceBusPressureTipBoundaryHubs": "Увімкнені граничні / магістральні концентратори: {{hubs}}.", "announceBusPressureTipTxSaturated": "Черга TX починається: {{names}} — цей інтерфейс може бути насиченим.", "tooManyDefaultBackbones": "У вас увімкнено магістралі за замовчуванням {{count}}. Розгляньте можливість вимкнення додаткових послуг і збереження близько 2 для надмірності.", - "decommissionedHubEnabled": "Вимкнений концентратор \"{{name}}\" все ще ввімкнено — mesh-client вимикає його при кожному запуску стека. Використовуйте Add default backbone (Додати магістралі за замовчуванням) або backbone directory (каталог магістралей) для живих кінцевих точок." + "decommissionedHubEnabled": "Виведений з експлуатації хаб «{{name}}» досі ввімкнений — mesh-client вимикає його при кожному запуску стека. Використовуйте «Додати магістралі за замовчуванням» або каталог магістралей для робочих кінцевих точок." } }, "routingPort": { @@ -3080,7 +3080,7 @@ "pageLoadingCountdownOverdue": "Завантаження сторінки… все ще працює", "pageReadyToast": "Сторінка Nomad готова: {{name}}", "staleLastSeenHint": "Востаннє почуто {{time}} — цей вузол може бути офлайн, навіть якщо вказано як онлайн.", - "pageLoadingRetryCountdown": "Тайм-аут першої спроби — оновлення шляху та повторна спроба… {{time}} ліворуч", + "pageLoadingRetryCountdown": "Перша спроба завершилася тайм-аутом — оновлення шляху й повтор… Залишилося {{time}}", "pageLoadingRetryOverdue": "Освіження шляху та повторні спроби… все ще працюють", "enterUrlHint": "Вставте адресу Nomad або виберіть вузол зі списку.", "enterUrlPlaceholder": "hash:/page/index.mu", diff --git a/src/renderer/locales/zh/translation.json b/src/renderer/locales/zh/translation.json index b1de738fc..2a8c1d9a6 100644 --- a/src/renderer/locales/zh/translation.json +++ b/src/renderer/locales/zh/translation.json @@ -1121,9 +1121,9 @@ "advancedAria": "高级界面键值选项", "advancedKeyReserved": "键“{{key}}”是已知字段,已被忽略—请使用上面的表单。", "pickerDeviceAriaWithRssi": "{{name}} {{address}} , {{rssi}} dBm", - "decommissionedHubEnableBlocked": "此集线器已停用且无法保持启用 — mesh-client 会在每次堆栈启动时将其关闭。请使用“添加默认网络集线器”或骨干目录获取可用端点。", + "decommissionedHubEnableBlocked": "此枢纽已停用,无法保持启用 — mesh-client 会在每次启动协议栈时关闭它。请使用“添加默认骨干网”或骨干网目录获取可用端点。", "decommissionedBadge": "退役", - "defaultHubsPickerTitle": "添加默认网络集线器", + "defaultHubsPickerTitle": "添加默认骨干网", "defaultHubsPickerConfirm": "添加{{count}}集线器", "defaultHubsPickerConfirmSync": "同步所选", "defaultHubsPickerConfirmAria": "添加或同步{{count}}所选默认集线器", @@ -1160,7 +1160,7 @@ "addDefaultHubsFailed": "添加默认骨干网失败。", "backboneEnableGuidanceLead": "最多启用1至3个骨干网关", "backboneEnableGuidanceBody": "—2是冗余的最佳选择,不会造成不必要的带宽膨胀。瞄准1个全球集线器和1个区域网关,并严格根据需要启用专业接口(如I2P或Yggdrasil )。本地连接的RNode和本地LAN接口不计入此骨干限制。", - "defaultHubsPickerHint": "从主要和全球以及您所在的地区开始。最多启用1至3个骨干网关( 2个是最佳选择)。本地RNode和LAN接口不计算在内。新条目被禁用,因此您可以在启用前查看。SYNC还会禁用剩余的退役官方测试网中心(阿姆斯特丹)。" + "defaultHubsPickerHint": "从“主要和全球骨干网”加上您的区域开始。最多启用 1 到 3 个骨干网关(2 个较理想)。本地 RNode 和局域网接口不计入。新条目默认禁用,便于您审阅后再启用。同步还会禁用剩余已停用的官方测试网枢纽(阿姆斯特丹)。" }, "reticulumPeers": { "hops": "Hops", @@ -1648,7 +1648,7 @@ "announceBusPressureTipBoundaryHubs": "已启用边界/骨干集线器: {{hubs}}。", "announceBusPressureTipTxSaturated": "TX队列丢弃时间: {{names}} —该接口可能已饱和。", "tooManyDefaultBackbones": "您已启用{{count}}默认骨干网。请考虑禁用额外功能,并保留约2个以备不时之需。", - "decommissionedHubEnabled": "停用的集线器“{{name}}”仍处于启用状态—mesh-client在每次堆栈启动时将其关闭。使用添加默认主干或实时终结点的主干目录。" + "decommissionedHubEnabled": "已停用的枢纽“{{name}}”仍处于启用状态 — mesh-client 会在每次启动协议栈时关闭它。请使用“添加默认骨干网”或骨干网目录获取可用端点。" } }, "routingPort": { @@ -2552,7 +2552,7 @@ "save": "保存堆栈设置", "saveFailed": "无法保存堆栈设置。", "pathMediumPreference": "首选路径介质", - "pathMediumPreferenceHint": "当可以通过射频和网络( TCP/I2P等)访问对等节点时,默认情况下选择哪种介质获胜。最低路径仅按跳数排名。“同行”选项卡上的每点引脚可以覆盖此设置。", + "pathMediumPreferenceHint": "当对等节点可通过射频和网络(TCP/I2P 等)到达时,选择默认优先使用哪种介质。最低路径仅按跳数排序。对等节点选项卡上的按节点固定设置可覆盖此项。", "pathMediumLowest": "最低路径(跳数)", "pathMediumNetwork": "网络(非RF )", "pathMediumRf": "RF (RNode)", @@ -2974,7 +2974,7 @@ "connectedViaRfAndMqttTooltip": "通过射频和MQTT连接", "connectedViaRfAndMqttAria": "通过射频和MQTT连接", "echoesConnectionHealthTooltip": "{{score}} %连接健康", - "tabAll": "所有食谱", + "tabAll": "全部", "tabHistory": "历史", "emptyHistory": "还没有私信—发送或接收 DM 以在此处查看对等节点。" }, @@ -3074,16 +3074,16 @@ "sortByHopsDesc": "按跃点排序,最远的在前", "sortByNameAsc": "按名称排序 - A 到 Z", "sortByNameDesc": "按名称排序 - Z 到 A", - "pageLoadingCountdown": "正在加载页面... {{time}} 左", + "pageLoadingCountdown": "正在加载页面… 剩余 {{time}}", "pageLoadingCountdownOverdue": "正在加载页面...仍在工作", "pageReadyToast": "Nomad 页面就绪:{{name}}", "staleLastSeenHint": "最后听说 {{time}} — 即使列为在线,该节点也可能处于离线状态。", - "pageLoadingRetryCountdown": "首次尝试超时—刷新路径并重试……剩余{{time}}", + "pageLoadingRetryCountdown": "首次尝试超时 — 正在刷新路径并重试… 剩余 {{time}}", "pageLoadingRetryOverdue": "正在刷新路径并重试……仍在工作", "enterUrlHint": "粘贴 Nomad 地址,或从列表中选择一个节点。", "enterUrlPlaceholder": "hash:/page/index.mu", "goToUrl": "前往", - "pageLoadingTimeLeft": "本次尝试剩余{{time}}", + "pageLoadingTimeLeft": "本次尝试剩余 {{time}}", "pageLoadingStillWorking": "仍在工作", "pageProgressLinking": "正在通过{{iface}} ( {{hops}}跳)连接……", "pageProgressLinkingIface": "正在通过{{iface}}连接……", From e852760d37cb14b75f1c20fb010157177135c01c Mon Sep 17 00:00:00 2001 From: Joey Stanford Date: Mon, 3 Aug 2026 10:58:57 -0600 Subject: [PATCH 5/5] test: cover Spanish Path false friend and harden PacketRouter MAC reset Add an independent i18n-quality fixture for es pathMediumPreference with English "Path", and move MeshCore BLE MAC test cleanup into afterEach so it still runs when assertions fail. --- scripts/check-i18n-quality.test.mjs | 10 ++++++++++ src/renderer/lib/drivers/PacketRouter.test.ts | 10 ++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/scripts/check-i18n-quality.test.mjs b/scripts/check-i18n-quality.test.mjs index d012b8cc4..8c8a57613 100644 --- a/scripts/check-i18n-quality.test.mjs +++ b/scripts/check-i18n-quality.test.mjs @@ -2107,4 +2107,14 @@ describe('sniffer tab and MQTT channel PSK i18n quality', () => { }); expectIssue(issues, 'I2P'); }); + + it('flags Spanish pathMediumPreference leaving English Path', () => { + const issues = localeStringQualityIssues({ + locale: 'es', + flatKey: 'networkPanel.reticulumStackSettings.pathMediumPreference', + enVal: 'Preferred path medium', + val: 'Medio Path preferido', + }); + expectIssue(issues, 'English "Path"'); + }); }); diff --git a/src/renderer/lib/drivers/PacketRouter.test.ts b/src/renderer/lib/drivers/PacketRouter.test.ts index 093e9c106..4497bf3e9 100644 --- a/src/renderer/lib/drivers/PacketRouter.test.ts +++ b/src/renderer/lib/drivers/PacketRouter.test.ts @@ -5,6 +5,10 @@ import { getDevice, useDeviceStore } from '../../stores/deviceStore'; import { addIdentity, useIdentityStore } from '../../stores/identityStore'; import { useMessageStore } from '../../stores/messageStore'; import { useNodeStore } from '../../stores/nodeStore'; +import { + resetConnectedMeshcoreBleMacForTests, + setConnectedMeshcoreBleMac, +} from '../connectedMeshcoreBleMac'; import { clearMeshtasticConfigIngressGuardsForTests, setMeshtasticRemoteConfigTarget, @@ -29,6 +33,7 @@ describe('PacketRouter', () => { useIdentityStore.setState({ identities: {}, activeIdentityId: null }); useDeviceStore.setState({ devices: {} }); clearMeshtasticConfigIngressGuardsForTests(); + resetConnectedMeshcoreBleMacForTests(); }); const cases: { event: DomainEvent; assert: () => void }[] = [ @@ -408,9 +413,7 @@ describe('PacketRouter', () => { }).not.toThrow(); }); - it('skips Meshtastic node_info store writes for MeshCore BLE MAC ghost nodes', async () => { - const { setConnectedMeshcoreBleMac, resetConnectedMeshcoreBleMacForTests } = - await import('../connectedMeshcoreBleMac'); + it('skips Meshtastic node_info store writes for MeshCore BLE MAC ghost nodes', () => { addIdentity({ id: ID_MT, protocol: meshtasticProtocol, @@ -451,6 +454,5 @@ describe('PacketRouter', () => { expect(useNodeStore.getState().nodes[ID_MT][ghostId].lastHeardAt).toBe(priorHeard); expect(listener).not.toHaveBeenCalled(); detach(); - resetConnectedMeshcoreBleMacForTests(); }); });