* feat(voice): unified mobile voice bar with gesture-isolated PTT row Replace separate VoiceStatusChip + VoicePttButton with a single CompactVoiceBar widget that combines both into a two-row layout: - Control row (tap): status text, mute, deafen, settings chevron - PTT row (hold): full-width hold-to-talk, shown only in PTT mode Gesture isolation prevents mis-touch between rows: the control row uses tap-only InkWell/IconButton while the PTT row uses a raw Listener for pointer-down/up events. Key changes: - Add CompactVoiceBar widget with state-colored container (normal, muted, talk-power-blocked) - Remove mute/deafen IconButtons from AppBar headerActions - Restructure voice details sheet into primary section + collapsible ExpansionTiles (audio processing, PTT capability, debug) - Optimistic state updates for mute/deafen to eliminate tap delay - Instant PTT visual feedback (no AnimatedContainer fade) - Constant geometry across all states (no layout shift on toggle) * fix(voice): preserve current PTT button format * feat(voice): move mute/deafen controls into VoiceStatusChip * fix(voice): ensure consistent chip height across mute states Remove isSelected/selectedIcon from IconButtons inside VoiceStatusChip. Material 3 toggle IconButtons (_SelectableIconButton) can vary in height when the selected state changes due to tap target sizing. Use simple conditional icons instead and set shrinkWrap tap target size with tight constraints for stable 40x40 buttons regardless of state. * fix(voice): remove leftover duplicate mute/deafen buttons in VoiceStatusChip * fix(voice): replace unsafe stereo cast with bytemuck and localise talk-power tooltip Replace the raw-pointer `&mut [(f32, f32)]` to `&mut [f32]` cast in the oboe output callback with `bytemuck::cast_slice_mut`, eliminating the unsafe block and relying on bytemuck compile-time NoUninit verification instead. Add voiceTalkPowerBlocked l10n key (en + zh) and replace the only remaining hard-coded English tooltip in VoiceStatusChip with it.
304 lines
13 KiB
Plaintext
304 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",
|
|
"voiceTalkPowerBlocked": "Insufficient talk power to speak in this channel"
|
|
}
|