Files
Edison Jwa 29afbb5e97 fix: address post-event-driven issues and client info parity (#16)
* fix(ui): restore speaking status indicators

Speaking state (isSpeaking) is computed from voice activity timestamps
in the protocol layer and cannot be represented as a discrete delta.
The event-driven refactor removed periodic snapshot refreshes, causing
speaking indicators to go stale.

Adds a 750ms periodic snapshot refresh (matching SPEAKING_ACTIVITY_WINDOW)
while the audio stats timer is active (in-channel only). Structural
changes (moves, joins, leaves) are still handled by instant deltas.

* fix(ui): hide server query clients from delta joins

When a ServerQuery client sends a message, a ClientJoined delta fires.
Before PR#15 the periodic snapshot rebuild would include the SQ client
but the snapshot_view filter hid it. With deltas, the client persisted
in the local snapshot. Now ClientJoined deltas skip SQ clients entirely.

* fix(proto): log getconnectioninfo errors instead of silently discarding

Ping and packet loss showing 'Unknown' in the client info sheet is
caused by getconnectioninfo failures being silently swallowed. Now
logs the error with the client_id so the root cause can be diagnosed
(e.g. missing b_client_connectioninfo_view permission on the server).

Also logs clientgetvariables failures.

* fix(proto): refresh non-self client profiles before mapping

* feat(protocol): add ping deviation to client profiles

* chore(ui): regenerate Flutter bridge bindings for ping deviation

* fix(l10n): add ping deviation labels to client info

* feat(ui): show ping deviation in client info sheet
2026-06-03 18:26:29 +09:00

303 lines
13 KiB
Plaintext

{
"@@locale": "en",
"@@x-source-of-truth": "DEC-015 (register v0.9.5). Template ARB for English. Server-provided content is NOT translated (ADR-008 / DEC-015).",
"appTitle": "Chanora",
"homeNotProductionReadyBanner": "Beta build — voice in/out wired; not production ready.",
"fieldServerHost": "Server address",
"fieldNickname": "Nickname",
"fieldServerPassword": "Server password",
"fieldServerPasswordHelp": "Leave blank if the server does not require one.",
"fieldPassword": "Password",
"fieldDisplayName": "Display name",
"connectAction": "Connect",
"disconnectAction": "Leave server",
"disconnectConfirmTitle": "Leave server?",
"disconnectConfirmBody": "You will disconnect from the current server.",
"disconnectConfirmCancel": "Stay",
"disconnectConfirmAction": "Leave",
"refreshAction": "Refresh",
"diagnosticsAction": "Diagnostics",
"diagnosticsSaveAction": "Save export",
"diagnosticsLiveUpdating": "Live updating",
"shareAction": "Share",
"diagnosticsSaved": "Diagnostic export saved to {path}",
"@diagnosticsSaved": {
"placeholders": { "path": { "type": "String" } }
},
"aboutAction": "About",
"aboutVersion": "Version {version}",
"@aboutVersion": {
"placeholders": { "version": { "type": "String" } }
},
"aboutAuthor": "Author: Edison Jwa",
"aboutNonAffiliation": "Chanora is independent and is not affiliated with, endorsed by, sponsored by, or officially associated with TeamSpeak.",
"aboutLicenseHeading": "License",
"aboutLicenseBody": "Chanora is dual-licensed under the Apache License, Version 2.0 or the MIT License, at your option. The full license texts ship as LICENSE-APACHE and LICENSE-MIT at the repository root.",
"aboutThirdPartyHeading": "Third-party software",
"aboutThirdPartyBody": "Chanora is built on tsclientlib, flutter_rust_bridge, platform-native audio backends, rusqlite, and the Flutter framework, among others. See the NOTICE file at the repository root for the current attribution list.",
"copyAction": "Copy",
"closeAction": "Close",
"openAction": "Open",
"cancelAction": "Cancel",
"retryAction": "Retry",
"chatAction": "Chat",
"chatCloseAction": "Close chat",
"chatNewPrivateAction": "New private chat",
"chatSearchClientsHint": "Search clients...",
"chatDirectMessageAction": "Private message",
"chatPokeAction": "Poke",
"clientInfoAction": "Info",
"startAudioAction": "Start audio",
"pttHoldToTalk": "Hold to talk",
"pttTransmitting": "Transmitting…",
"pttHoldToTalkSemanticsHint": "Press and hold to transmit voice; release to stop.",
"pttCapabilityBadge": "PTT: {level} ({backend})",
"@pttCapabilityBadge": {
"placeholders": {
"level": { "type": "String" },
"backend": { "type": "String" }
}
},
"pttCapabilityBoundKey": "Key: {key}",
"@pttCapabilityBoundKey": {
"placeholders": {
"key": { "type": "String" }
}
},
"pttCapabilityExplainTitle": "Push-to-Talk capability",
"pttCapabilityExplainFocusedHeading": "Focused PTT",
"pttCapabilityExplainFocusedBody": "Chanora is currently using Focused Push-to-Talk: the binding only fires while the Chanora window is focused. This is the universal fallback used on every platform when a global capture path is not available.",
"pttCapabilityExplainGoGlobalWindows": "On Windows, Global PTT is engaged automatically once you bind a key. No additional permission is required.",
"pttCapabilityExplainGoGlobalMacos": "On macOS, Global PTT requires Input Monitoring permission. Open System Settings → Privacy & Security → Input Monitoring, allow Chanora, then re-bind the key.",
"pttCapabilityExplainGoGlobalLinux": "On Linux, Global PTT requires a GNOME-Wayland desktop with the GlobalShortcuts portal. Re-bind the key and accept the desktop's shortcut dialog when it appears.",
"pttCapabilityExplainGoGlobalIos": "On iOS, Apple does not expose global hotkeys to apps. Chanora uses the on-screen Push-to-Talk button and only transmits while the app is in the foreground.",
"pttCapabilityExplainGoGlobalGeneric": "Global PTT is not available in this environment. Focused PTT will keep working while the Chanora window has focus.",
"pttConfigureAction": "Configure",
"pttConfigureTitle": "Configure Push-to-Talk binding",
"pttConfigurePrompt": "Press the key or mouse side button you want to use for Push-to-Talk.",
"pttConfigureWaiting": "(waiting for input…)",
"pttConfigureCaptured": "Captured",
"pttConfigurePrivacyNote": "Chanora never logs the actual key value. Only the input class (keyboard / mouse-side-button) and a platform-neutral label leave this dialog.",
"pttConfigureSaveAction": "Save",
"pttConfigurePortalRedirect": "Your desktop environment will open its own shortcut dialog. Pick the key you want to use for Push-to-Talk.",
"inputMuteAction": "Mute mic",
"inputUnmuteAction": "Unmute mic",
"outputMuteAction": "Mute speaker",
"outputUnmuteAction": "Unmute speaker",
"joinChannelAction": "Join channel",
"leaveChannelAction": "Leave channel",
"channelPasswordTitle": "Channel password",
"bookmarksHeading": "Bookmarks",
"bookmarksEmpty": "No bookmarks yet. Enter a server above and tap \"Save bookmark\".",
"bookmarkAddAction": "Save bookmark",
"bookmarkAddTitle": "Save bookmark",
"bookmarkDeleteAction": "Delete bookmark",
"statusIdle": "Not connected",
"statusConnecting": "Connecting…",
"statusConnected": "Connected to {server}",
"@statusConnected": {
"placeholders": { "server": { "type": "String" } }
},
"statusError": "Error: {message}",
"@statusError": {
"placeholders": { "message": { "type": "String" } }
},
"statusReconnecting": "Reconnecting… attempt {attempt}, in {delay}s",
"@statusReconnecting": {
"placeholders": {
"attempt": { "type": "int" },
"delay": { "type": "int" }
}
},
"statusConnectionLost": "Connection lost: {reason}",
"@statusConnectionLost": {
"placeholders": { "reason": { "type": "String" } }
},
"audioStatsLine": "TX {sent} frames • RX {received} frames • Mic {ptt}",
"@audioStatsLine": {
"placeholders": {
"sent": { "type": "int" },
"received": { "type": "int" },
"ptt": { "type": "String" }
}
},
"channelsHeading": "Channels",
"clientsHeading": "Online clients",
"serverWelcomeHeading": "Server welcome message",
"countChannelsAndClients": "{channels} channels • {clients} online",
"@countChannelsAndClients": {
"placeholders": {
"channels": { "type": "int" },
"clients": { "type": "int" }
}
},
"voiceModePtt": "PTT",
"voiceModeContinuous": "Continuous",
"voiceModeVoiceActivity": "Voice activity",
"voiceModeComingSoon": "Coming soon",
"voiceModeLabel": "Transmit mode",
"voiceReleaseTailLabel": "Release tail",
"voiceReleaseTailHint": "ms",
"voiceLeaveAction": "Leave voice",
"voiceHardMuteLabel": "Mute microphone",
"voiceOutputMuteLabel": "Mute speakers",
"voiceSettingsTitle": "Voice settings",
"voiceBindKeyAction": "Bind PTT key",
"voiceBoundKeyLabel": "Bound key",
"voicePttHoldHint": "Hold the button",
"voiceMicOn": "on",
"voiceMicOff": "off",
"voiceSheetTitle": "Voice",
"audioOutputLabel": "Audio output",
"audioRouteSpeaker": "Speaker",
"audioRouteReceiver": "iPhone receiver",
"audioRouteBluetooth": "Bluetooth",
"audioRouteWiredHeadset": "Wired headset",
"audioRouteCarAudio": "Car audio",
"audioRouteAirplay": "AirPlay",
"audioRouteUnknown": "Unknown",
"channelJoinAlreadyIn": "Already in this channel.",
"channelJoinFailedPermission": "Insufficient permission to join this channel.",
"channelJoinFailedPassword": "Wrong channel password.",
"channelJoinFailedFull": "Channel is full.",
"channelJoinFailedFamilyFull": "Channel family limit reached.",
"channelJoinFailedPrivate": "This channel is private.",
"channelJoinFailedTimeout": "Could not join channel: the server didn't respond in time.",
"channelJoinFailedFlooding": "Too many channel switches. Please wait a moment.",
"channelJoinFailedGeneric": "Could not join channel: {message}",
"@channelJoinFailedGeneric": {
"placeholders": {
"message": { "type": "String" }
}
},
"networkPermissionTitle": "Network Permission Required",
"networkPermissionBody": "Chanora needs permission to access the network. On macOS, go to System Settings → Privacy & Security → Local Network and enable Chanora, then try again.",
"networkPermissionOpenSettings": "Open System Settings",
"microphonePermissionTitle": "Microphone Permission Required",
"microphonePermissionBody": "Chanora needs permission to access the microphone. On macOS, go to System Settings → Privacy & Security → Microphone and enable Chanora.",
"microphonePermissionRequiredForVoice": "Microphone permission is required for voice transmission.",
"permissionGrantAction": "Grant",
"startupPermissionsTitle": "Permissions",
"startupPermissionsBody": "Chanora requests microphone, Bluetooth headset, and notification permissions at startup so voice, headset routing, and the foreground session work correctly.",
"startupPermissionsNotNow": "Not now",
"startupPermissionsAllow": "Allow",
"audioRouteSystemDefault": "System default",
"audioRouteEarpiece": "Earpiece",
"audioRouteUsbHeadset": "USB headset",
"audioRouteOtherDevice": "Other device",
"audioRouteRefreshDevices": "Refresh audio devices",
"audioRouteCannotSelect": "This output cannot be selected.",
"audioRouteChangeFailed": "Could not change audio output.",
"iosAudioInterrupted": "Audio interrupted by system (phone call)",
"iosAudioResuming": "Audio resuming",
"linkTrustTitle": "Open external link?",
"linkTrustBody": "You are about to open a link to:\n\n{domain}",
"@linkTrustBody": {
"placeholders": {
"domain": { "type": "String" }
}
},
"linkTrustRememberDomain": "Trust all links from this domain",
"clientInfoFetchingProfile": "Fetching TeamSpeak profile",
"clientInfoLoadingProfile": "Loading profile...",
"clientInfoProfileUnavailable": "Profile unavailable",
"clientInfoProfileUnavailableBody": "The server did not return profile details for this client.",
"clientInfoIdentitySection": "Identity",
"clientInfoMembershipSection": "Membership",
"clientInfoConnectionSection": "Connection",
"clientInfoHistorySection": "History",
"clientInfoTransferSection": "Transfer",
"clientInfoClientId": "Client ID",
"clientInfoDatabaseId": "Database ID",
"clientInfoUniqueId": "Unique ID",
"clientInfoDescription": "Description",
"clientInfoAvatar": "Avatar",
"clientInfoServerGroups": "Server groups",
"clientInfoChannelGroup": "Channel group",
"clientInfoChannelId": "Channel ID",
"clientInfoOnline": "Online",
"clientInfoIdle": "Idle",
"clientInfoPing": "Ping",
"clientInfoPingDeviation": "Ping deviation",
"clientInfoAddress": "Address",
"clientInfoPacketLossClientToServer": "Packet loss C->S",
"clientInfoPacketLossServerToClient": "Packet loss S->C",
"clientInfoFirstConnected": "First connected",
"clientInfoLastConnected": "Last connected",
"clientInfoConnections": "Connections",
"clientInfoDownloadedMonth": "Downloaded this month",
"clientInfoUploadedMonth": "Uploaded this month",
"clientInfoDownloadedTotal": "Downloaded total",
"clientInfoUploadedTotal": "Uploaded total",
"clientInfoUnknown": "Unknown",
"clientInfoHidden": "Hidden",
"clientInfoNone": "None",
"pokeSnackBarClearAction": "Clear",
"pokeSnackBarMoreIndicator": "...",
"pokeSnackBarIncomingNoMessage": "{sender} pokes you",
"@pokeSnackBarIncomingNoMessage": {
"placeholders": {
"sender": { "type": "String" }
}
},
"pokeSnackBarIncomingWithMessage": "{sender} pokes you: {message}",
"@pokeSnackBarIncomingWithMessage": {
"placeholders": {
"sender": { "type": "String" },
"message": { "type": "String" }
}
},
"pokeHistorySelfNoMessage": "<{time}> You poked \"{target}\".",
"@pokeHistorySelfNoMessage": {
"placeholders": {
"time": { "type": "String" },
"target": { "type": "String" }
}
},
"pokeHistorySelfWithMessage": "<{time}> You poked \"{target}\" with message: {message}",
"@pokeHistorySelfWithMessage": {
"placeholders": {
"time": { "type": "String" },
"target": { "type": "String" },
"message": { "type": "String" }
}
},
"pokeHistoryIncomingNoMessage": "<{time}> \"{sender}\" pokes you",
"@pokeHistoryIncomingNoMessage": {
"placeholders": {
"time": { "type": "String" },
"sender": { "type": "String" }
}
},
"pokeHistoryIncomingWithMessage": "<{time}> \"{sender}\" pokes you: {message}",
"@pokeHistoryIncomingWithMessage": {
"placeholders": {
"time": { "type": "String" },
"sender": { "type": "String" },
"message": { "type": "String" }
}
},
"clientVolumeAction": "User Volume",
"clientVolumeTitle": "{name} — volume",
"@clientVolumeTitle": {
"placeholders": { "name": { "type": "String" } }
},
"clientVolumeLabel": "{percent}%",
"@clientVolumeLabel": {
"placeholders": { "percent": { "type": "int" } }
},
"clientVolumeMuteAction": "Mute user",
"clientVolumeUnmuteAction": "Unmute user",
"clientVolumeResetAction": "Reset to default",
"permissionDenied": "Permission Denied"
}