Files
Toju/toju-app/public/i18n/catalog/voice.json
T
myxelium 92c2f578e2 fix(voice): route media on evidence and switch devices without dropping the call
Outgoing voice was gated on the observer's roster copy of the remote user's
voice state, which is signaling gossip. The signal server broadcasts
`user_left` for any socket it declares dead, so a suspended laptop or a flaky
hop wiped that copy and the observer detached its microphone from a peer that
never left the channel - a silent member with no way back through the UI.

`decideVoicePathRouting` now closes a path only on positive evidence: we left
voice, the peer itself reported another channel or none, or the connection is
gone. Missing gossip holds an established path instead. Opening still needs
confirmation, so a guess never starts sending; the same rule gates playback,
camera video, and the microphone a new connection puts in its first offer.
Peers are also asked for their voice state when a connection or data channel
comes up, so a rebuilt path re-confirms itself.

Alongside it, the microphone can be switched mid-call: capture moves to a
device service and rules, the live track is swapped with `replaceTrack` so
the session is never renegotiated, and the speaking indicator follows the new
stream.
2026-08-14 03:19:29 +02:00

92 lines
3.9 KiB
JSON

{
"voice": {
"controls": {
"connectionError": "Connection error",
"connectionErrorStatus": "Connection Error",
"connected": "Connected",
"retry": "Retry",
"failedConnect": "Failed to connect voice session."
},
"devices": {
"inputFellBack": "Your microphone was disconnected. Switched to the system default.",
"outputFellBack": "Your speaker was disconnected. Switched to the system default."
},
"floating": {
"backToServer": "Back to {{server}}",
"voiceFallback": "Voice",
"toggleMute": "Toggle Mute",
"toggleDeafen": "Toggle Deafen",
"toggleScreenShare": "Toggle Screen Share",
"disconnect": "Disconnect"
},
"workspace": {
"streams": "Streams",
"inVoice": "{{count}} in voice",
"liveStreams": "{{count}} live streams",
"liveStream": "{{count}} live stream",
"localPreview": "Local preview",
"focusedStream": "Focused stream",
"yourCamera": "Your camera",
"yourScreen": "Your screen",
"focusedStreamFallback": "Focused stream",
"unmuteStreamAudio": "Unmute stream audio",
"muteStreamAudio": "Mute stream audio",
"showAllStreams": "Show all streams",
"allStreams": "All streams",
"minimizeWorkspace": "Minimize stream workspace",
"returnToChat": "Return to chat",
"otherLiveStreams": "Other live streams",
"noLiveStreams": "No live streams yet",
"noLiveStreamsDescription": "Turn on your camera, click Screen Share below, or wait for someone in {{channel}} to go live.",
"participantsReady": "{{count}} participants ready",
"startScreenShare": "Start screen sharing",
"expand": "Expand",
"close": "Close",
"waitingForStream": "Waiting for a live stream",
"connectedTo": "Connected to {{server}}",
"miniWindowHint": "{{count}} live streams · double-click to expand",
"miniWindowHintSingle": "{{count}} live stream · double-click to expand",
"voiceLounge": "Voice Lounge",
"voiceServer": "Voice server",
"voiceWorkspace": "Voice workspace"
},
"streamTile": {
"cameraLive": "Camera live",
"screenShareLive": "Screen share live",
"focusAria": "Focus {{name}} {{badge}}",
"openAria": "Open {{name}} {{badge}} in widescreen mode",
"exitFullscreenTitle": "Double-click to exit fullscreen",
"enterFullscreenTitle": "Double-click for fullscreen",
"localCameraFullscreen": "Local camera preview in fullscreen",
"localPreviewFullscreen": "Local preview in fullscreen",
"cameraFullscreen": "Fullscreen camera view",
"streamFullscreen": "Fullscreen stream view",
"localCameraPreview": "Your camera preview never captures audio.",
"localPreviewMuted": "Your preview stays muted locally to avoid audio feedback.",
"streamVolume": "Stream volume",
"screenShareVolume": "Screen share volume",
"rotateLandscape": "Rotate to landscape",
"exitFullscreen": "Exit fullscreen",
"fullscreen": "Fullscreen",
"noScreenAudio": "No screen audio",
"viewingWidescreen": "Viewing in widescreen",
"viewInWidescreen": "View in widescreen",
"streamAudio": "Stream audio",
"audioPercent": "{{volume}}% audio"
}
},
"network": {
"signaling": {
"connectTimeout": "Timed out connecting to signaling server",
"connectionFailed": "Connection to signaling server failed",
"disconnected": "Disconnected from signaling server",
"healthCheckFailed": "Signaling server health check failed",
"instanceChanged": "Signaling server instance changed; refreshing websocket",
"recovered": "Signaling server recovered; refreshing websocket",
"keepaliveTimeout": "Signaling keepalive acknowledgement timed out",
"keepaliveSendFailed": "Failed to send signaling keepalive",
"payloadSendFailed": "Failed to send signaling payload"
}
}
}