diff --git a/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt b/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt index 0152f3d2f7b1..e55e512a16b0 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt @@ -126,8 +126,9 @@ class NodeForegroundService : Service() { state.connected -> nativeString("OpenClaw Node · Connected") else -> nativeString("OpenClaw Node") } + val displayStatus = gatewayConnectionStatusForDisplay(state.status) val text = - (state.server?.let { "${state.status} · $it" } ?: state.status) + + (state.server?.let { nativeString("\$status · \$server", displayStatus, it) } ?: displayStatus) + voiceNotificationSuffix( mode = state.mode, manualMicEnabled = state.capture.micEnabled, diff --git a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt index 1007fd5de298..9b26a4b550b7 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt @@ -319,6 +319,29 @@ data class GatewayConnectionDisplay( val problem: GatewayConnectionProblem?, ) +private const val GATEWAY_STATUS_OFFLINE = "Offline" +private const val GATEWAY_STATUS_CONNECTED = "Connected" +private const val GATEWAY_STATUS_NODE_OFFLINE = "Connected (node offline)" +private const val GATEWAY_STATUS_OPERATOR_OFFLINE = "Connected (operator offline)" + +private fun gatewayOperatorConnectionState(operator: String): String = "Connected (operator: $operator)" + +internal fun gatewayConnectionStatusForDisplay(statusText: String): String { + val status = statusText.trim() + return when { + status.isEmpty() || status == GATEWAY_STATUS_OFFLINE -> nativeString("Offline") + status == GATEWAY_STATUS_CONNECTED -> nativeString("Connected") + status == GATEWAY_STATUS_NODE_OFFLINE -> nativeString("Connected (node offline)") + status == GATEWAY_STATUS_OPERATOR_OFFLINE -> nativeString("Connected (operator offline)") + status.startsWith("Connected (operator: ") && status.endsWith(")") -> + nativeString( + "Connected (operator: \$operator)", + status.removePrefix("Connected (operator: ").dropLast(1), + ) + else -> status + } +} + private fun gatewayProblemAfterDisconnect( problem: GatewayConnectionProblem?, statusText: String, @@ -337,16 +360,16 @@ internal fun gatewayConnectionDisplay( val operator = operatorStatusText.trim() val node = nodeStatusText.trim() return when { - operatorConnected && nodeConnected -> GatewayConnectionDisplay(true, "Connected", null) - operatorConnected -> GatewayConnectionDisplay(true, "Connected (node offline)", nodeProblem) + operatorConnected && nodeConnected -> GatewayConnectionDisplay(true, GATEWAY_STATUS_CONNECTED, null) + operatorConnected -> GatewayConnectionDisplay(true, GATEWAY_STATUS_NODE_OFFLINE, nodeProblem) nodeConnected -> GatewayConnectionDisplay( isConnected = false, statusText = if (operator.isNotEmpty() && operator != "Offline") { - "Connected (operator: $operator)" + gatewayOperatorConnectionState(operator) } else { - "Connected (operator offline)" + GATEWAY_STATUS_OPERATOR_OFFLINE }, problem = operatorProblem, ) @@ -697,9 +720,9 @@ class NodeRuntime private constructor( private val _nodeCapabilityApproval = MutableStateFlow(GatewayNodeCapabilityApproval.Loading) val nodeCapabilityApproval: StateFlow = _nodeCapabilityApproval.asStateFlow() - private val _gatewayConnectionDisplay = MutableStateFlow(GatewayConnectionDisplay(false, "Offline", null)) + private val _gatewayConnectionDisplay = MutableStateFlow(GatewayConnectionDisplay(false, GATEWAY_STATUS_OFFLINE, null)) val gatewayConnectionDisplay: StateFlow = _gatewayConnectionDisplay.asStateFlow() - private val _statusText = MutableStateFlow("Offline") + private val _statusText = MutableStateFlow(GATEWAY_STATUS_OFFLINE) val statusText: StateFlow = _statusText.asStateFlow() private val _gatewayConnectionProblem = MutableStateFlow(null) val gatewayConnectionProblem: StateFlow = _gatewayConnectionProblem.asStateFlow() diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt index 89ab67559c1e..041e757a8ead 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt @@ -424,9 +424,9 @@ private fun gatewayManualTransportPresentation( effectiveTls = effectiveTls, helperText = when { - requiresTls -> "Secure connection is required for this host." + requiresTls -> nativeString("Secure connection is required for this host.") effectiveTls -> null - else -> "Use only on a trusted private network." + else -> nativeString("Use only on a trusted private network.") }, ) diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayDiagnostics.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayDiagnostics.kt index 358aec450cd0..faba1b777277 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayDiagnostics.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayDiagnostics.kt @@ -4,6 +4,7 @@ import ai.openclaw.app.BuildConfig import ai.openclaw.app.GatewayConnectionProblem import ai.openclaw.app.GatewayNodeApprovalState import ai.openclaw.app.GatewayNodeCapabilityApproval +import ai.openclaw.app.gatewayConnectionStatusForDisplay import ai.openclaw.app.gateway.normalizeGatewayApprovalRequestId import ai.openclaw.app.i18n.nativeString import android.content.ClipData @@ -23,7 +24,7 @@ internal fun openClawAndroidVersionLabel(): String { } /** Normalizes blank gateway status text for display and diagnostics copy. */ -internal fun gatewayStatusForDisplay(statusText: String): String = statusText.trim().ifEmpty { nativeString("Offline") } +internal fun gatewayStatusForDisplay(statusText: String): String = gatewayConnectionStatusForDisplay(statusText) /** Resolves the best non-secret endpoint label available to diagnostics surfaces. */ internal fun gatewayDiagnosticsEndpoint( @@ -38,7 +39,7 @@ internal fun gatewayDiagnosticsEndpoint( /** Detects pairing/approval status text so UI can offer pairing-specific actions. */ internal fun gatewayStatusLooksLikePairing(statusText: String): Boolean { - val lower = gatewayStatusForDisplay(statusText).lowercase() + val lower = statusText.trim().lowercase() return lower.contains("pair") || lower.contains("approve") } @@ -136,7 +137,7 @@ internal fun buildGatewayDiagnosticsReport( .orEmpty() .ifEmpty { Build.VERSION.SDK_INT.toString() } val endpoint = gatewayAddress.trim().ifEmpty { "unknown" } - val status = gatewayStatusForDisplay(statusText) + val status = statusText.trim().ifEmpty { "Offline" } return """ Help diagnose this OpenClaw Android gateway connection failure. diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt index 35f037392d0c..9a32dda7bb3e 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt @@ -532,7 +532,7 @@ fun OnboardingFlow( ) { val trimmed = code.trim() if (trimmed.isEmpty()) { - setupError = "Enter the setup code from openclaw qr." + setupError = nativeString("Enter the setup code from openclaw qr.") return } val plan = @@ -556,7 +556,7 @@ fun OnboardingFlow( setupError = endpointError?.let { gatewayEndpointValidationMessage(it, GatewayEndpointInputSource.SETUP_CODE) - } ?: "Setup code was not accepted. Generate a fresh code with openclaw qr." + } ?: nativeString("Setup code was not accepted. Generate a fresh code with openclaw qr.") return } connectGateway(plan = plan, inputSource = inputSource) @@ -574,7 +574,7 @@ fun OnboardingFlow( GatewayEndpointValidationError.IPV6_ZONE_ID_UNSUPPORTED, -> gatewayEndpointValidationMessage(scanned.error, GatewayEndpointInputSource.QR_SCAN) - else -> "That QR code is not an OpenClaw setup QR. Generate a fresh code with openclaw qr, then try again." + else -> nativeString("That QR code is not an OpenClaw setup QR. Generate a fresh code with openclaw qr, then try again.") } showSetupScanError(message) return @@ -586,7 +586,7 @@ fun OnboardingFlow( fun pairFromManualFields() { if (manualTokenLooksLikeSetupCode(token)) { - setupError = "That looks like a setup code. Go back and choose Setup Gateway, then Use setup code." + setupError = nativeString("That looks like a setup code. Go back and choose Setup Gateway, then Use setup code.") return } val transport = @@ -637,7 +637,7 @@ fun OnboardingFlow( try { InputImage.fromFilePath(context, uri) } catch (_: Exception) { - showSetupScanError("Could not read that image. Choose a clear screenshot or image of the QR from openclaw qr.") + showSetupScanError(nativeString("Could not read that image. Choose a clear screenshot or image of the QR from openclaw qr.")) return@rememberLauncherForActivityResult } setupBarcodeScanner @@ -645,12 +645,12 @@ fun OnboardingFlow( .addOnSuccessListener { barcodes -> val rawValue = barcodes.firstNotNullOfOrNull { barcode -> barcode.rawValue?.takeIf { it.isNotBlank() } } if (rawValue == null) { - showSetupScanError("No setup QR code was found in that image. Choose the QR generated by openclaw qr, or enter the setup code manually.") + showSetupScanError(nativeString("No setup QR code was found in that image. Choose the QR generated by openclaw qr, or enter the setup code manually.")) return@addOnSuccessListener } handleScannedSetupCode(rawValue, inputSource = OnboardingGatewayInputSource.SetupGallery) }.addOnFailureListener { - showSetupScanError("Could not read a QR code from that image. Choose a clearer image or enter the setup code manually.") + showSetupScanError(nativeString("Could not read a QR code from that image. Choose a clearer image or enter the setup code manually.")) } } @@ -754,7 +754,7 @@ fun OnboardingFlow( onRequestCameraPermission = { cameraPermissionLauncher.launch(Manifest.permission.CAMERA) }, onCodeScanned = { rawValue -> handleScannedSetupCode(rawValue, inputSource = OnboardingGatewayInputSource.SetupScanner) }, onCameraError = { - showSetupScanError("Could not start the camera. Choose a QR image from gallery or enter the setup code manually.") + showSetupScanError(nativeString("Could not start the camera. Choose a QR image from gallery or enter the setup code manually.")) }, onCloseScanner = { inlineQrScannerActive = false }, onChooseFromGallery = { @@ -2559,13 +2559,13 @@ private fun finishingGatewayProgressItems( /** Detects gateway-approved states where the Android node is still coming online. */ internal fun gatewayStatusLooksLikePartialConnect(statusText: String): Boolean { - val lower = gatewayStatusForDisplay(statusText).lowercase() + val lower = statusText.trim().lowercase() return lower.contains("operator offline") || lower.contains("node offline") } /** Detects explicit endpoint/auth failures surfaced as status text without structured details. */ internal fun gatewayStatusLooksLikeFailure(statusText: String): Boolean { - val lower = gatewayStatusForDisplay(statusText).lowercase() + val lower = statusText.trim().lowercase() return lower.startsWith("failed:") || lower.startsWith("error:") || lower.startsWith("gateway error:") } @@ -2616,25 +2616,27 @@ internal fun recoveryGatewayDetail( gatewayConnectionProblem: GatewayConnectionProblem?, ): String = if (ready) { - remoteAddress?.takeIf { it.isNotBlank() } ?: "Ready for chat and voice" + remoteAddress?.takeIf { it.isNotBlank() } ?: nativeString("Ready for chat and voice") } else if (nodeCapabilityApprovalNeedsUserAction(nodeCapabilityApproval)) { - "Gateway paired. Waiting for node capability approval." + nativeString("Gateway paired. Waiting for node capability approval.") } else if (gatewayConnectionProblem?.isPairingRequired == true && !gatewayConnectionProblem.canAutoRetry) { recoveryGatewayApprovalCommand(gatewayConnectionProblem) - ?.let { "Gateway approval is pending. Run this on the gateway host:" } - ?: "Gateway approval is pending. Run openclaw devices list on the gateway host, approve this phone, then retry." + ?.let { nativeString("Gateway approval is pending. Run this on the gateway host:") } + ?: nativeString( + "Gateway approval is pending. Run openclaw devices list on the gateway host, approve this phone, then retry.", + ) } else if (gatewayConnectionProblem?.isPairingRequired == true && gatewayConnectionProblem.canAutoRetry) { - "Gateway approval is in progress. OpenClaw will retry automatically." + nativeString("Gateway approval is in progress. OpenClaw will retry automatically.") } else if (gatewayConnectionProblem != null) { recoveryGatewayAuthDetail(gatewayConnectionProblem) } else if (nodeCapabilityApproval == GatewayNodeCapabilityApproval.Loading) { - "Gateway paired. Checking node capability approval." + nativeString("Gateway paired. Checking node capability approval.") } else if (statusText.contains("operator offline", ignoreCase = true)) { - "Gateway paired. Waiting for operator access." + nativeString("Gateway paired. Waiting for operator access.") } else if (gatewayStatusLooksLikePairing(statusText)) { - "Gateway approval is in progress. OpenClaw will retry automatically." + nativeString("Gateway approval is in progress. OpenClaw will retry automatically.") } else { - remoteAddress?.takeIf { it.isNotBlank() } ?: "Gateway unreachable" + remoteAddress?.takeIf { it.isNotBlank() } ?: nativeString("Gateway unreachable") } internal fun recoveryGatewayAuthDetail(gatewayConnectionProblem: GatewayConnectionProblem): String = @@ -2655,7 +2657,7 @@ internal fun recoveryGatewayAuthDetail(gatewayConnectionProblem: GatewayConnecti "update_auth_credentials" -> nativeString("Saved authentication is invalid. Re-authenticate or reset this gateway connection.") "update_auth_configuration" -> nativeString("Gateway authentication is not configured. Edit this connection and try again.") "review_auth_configuration" -> nativeString("Gateway authentication needs review. Check gateway settings, then retry.") - else -> gatewayConnectionProblem.message.takeIf { it.isNotBlank() } ?: "Gateway authentication needs attention." + else -> gatewayConnectionProblem.message.takeIf { it.isNotBlank() } ?: nativeString("Gateway authentication needs attention.") } } @@ -2666,12 +2668,12 @@ private fun recoveryGatewayProtocolMismatchDetail(gatewayConnectionProblem: Gate val summary = when { clientMax != null && expected != null && clientMax < expected -> - "This app is older than the Gateway. Update OpenClaw on this device, then retry." + nativeString("This app is older than the Gateway. Update OpenClaw on this device, then retry.") clientMin != null && expected != null && clientMin > expected -> - "The Gateway is older than this app. Update OpenClaw on the Gateway host, then retry." - else -> "The app and Gateway use incompatible protocol versions. Update OpenClaw on both, then retry." + nativeString("The Gateway is older than this app. Update OpenClaw on the Gateway host, then retry.") + else -> nativeString("The app and Gateway use incompatible protocol versions. Update OpenClaw on both, then retry.") } - return protocolMismatchVersions(clientMin, clientMax, expected)?.let { "$summary $it" } ?: summary + return protocolMismatchVersions(clientMin, clientMax, expected)?.let { nativeString("\$summary \$details", summary, it) } ?: summary } internal fun recoveryGatewayProtocolMismatchCommand( @@ -2863,9 +2865,9 @@ internal fun cameraPermissionRowStatusText( androidCameraPermissionGranted: Boolean, ): String = when { - capabilityEnabled -> "Enabled" - androidCameraPermissionGranted -> "Off" - else -> "Not allowed" + capabilityEnabled -> nativeString("Enabled") + androidCameraPermissionGranted -> nativeString("Off") + else -> nativeString("Not allowed") } internal fun cameraCapabilityAfterRowTap( diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMarkdown.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMarkdown.kt index bccf7c55b1a5..af114f1949ee 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMarkdown.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMarkdown.kt @@ -722,7 +722,7 @@ private fun InlineBase64Image( if (image != null) { Image( bitmap = image, - contentDescription = mimeType ?: "image", + contentDescription = mimeType ?: nativeString("Image"), contentScale = ContentScale.Fit, modifier = Modifier.fillMaxWidth(), ) diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt index 5fd8667b01af..95fcff65ecf5 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt @@ -433,19 +433,19 @@ fun ChatOutboxBubble( val statusColor = if (failed) mobileDanger else mobileWarning val statusLabel = when (item.status) { - ChatOutboxStatus.Queued -> "Queued — sends when reconnected" - ChatOutboxStatus.Sending -> "Sending…" - ChatOutboxStatus.Accepted -> "Sent — confirming delivery…" + ChatOutboxStatus.Queued -> nativeString("Queued — sends when reconnected") + ChatOutboxStatus.Sending -> nativeString("Sending…") + ChatOutboxStatus.Accepted -> nativeString("Sent — confirming delivery…") ChatOutboxStatus.Failed -> item.lastError ?.trim() ?.takeIf { it.isNotEmpty() } - ?.let { "Failed — $it" } ?: "Failed" + ?.let { nativeString("Failed — \$it", it) } ?: nativeString("Failed") } ChatBubbleContainer( style = bubbleStyle("user").copy(borderColor = statusColor.copy(alpha = 0.6f)), - roleLabel = "You", + roleLabel = nativeString("You"), ) { if (item.text.isNotBlank()) { ChatMarkdown(text = item.text, textColor = mobileText) @@ -566,7 +566,7 @@ internal fun ChatBase64Image( Box { Image( bitmap = image, - contentDescription = mimeType ?: "attachment", + contentDescription = mimeType ?: nativeString("Attachment"), contentScale = ContentScale.Fit, modifier = Modifier.fillMaxWidth(), ) diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt index 82048a43bf05..fd3227a4ad6f 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt @@ -309,7 +309,7 @@ fun ChatScreen( attachments.addAll(merged.attachments) shareImportNotice = if (merged.failedImageCount + merged.droppedImageCount > 0) { - "Some shared images were omitted or could not be added." + nativeString("Some shared images were omitted or could not be added.") } else { null } @@ -653,9 +653,9 @@ private fun ChatHeader( ModelPill( text = when { - pendingRunCount > 0 -> "Working" - healthOk -> "Ready" - else -> "Offline" + pendingRunCount > 0 -> nativeString("Working") + healthOk -> nativeString("Ready") + else -> nativeString("Offline") }, status = when { @@ -1039,9 +1039,9 @@ private fun ChatBubble( Text( text = when { - live -> "OpenClaw · Live" - isUser -> "You" - normalizedRole == "system" -> "System" + live -> nativeString("OpenClaw · Live") + isUser -> nativeString("You") + normalizedRole == "system" -> nativeString("System") else -> nativeString("OpenClaw") }, style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp, fontWeight = FontWeight.SemiBold), @@ -1056,7 +1056,7 @@ private fun ChatBubble( base64 = checkNotNull(part.base64), mimeType = part.mimeType, ) - else -> Text(text = part.fileName ?: "Attachment", style = ClawTheme.type.body, color = ClawTheme.colors.textMuted) + else -> Text(text = part.fileName ?: nativeString("Attachment"), style = ClawTheme.type.body, color = ClawTheme.colors.textMuted) } } if (messageId != null) { @@ -1569,7 +1569,7 @@ private fun SlashCommandRow( ) Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) { Text( - text = command.description.ifBlank { command.category ?: "Command" }, + text = command.description.ifBlank { command.category ?: nativeString("Command") }, style = ClawTheme.type.caption, color = ClawTheme.colors.textMuted, maxLines = 1, @@ -1786,7 +1786,7 @@ private fun AttachmentChip( } Text( text = - attachment.durationMs?.let { duration -> "Voice note · ${formatVoiceNoteDuration(duration)}" } + attachment.durationMs?.let { duration -> nativeString("Voice note · \${formatVoiceNoteDuration(duration)}", formatVoiceNoteDuration(duration)) } ?: attachment.fileName, style = ClawTheme.type.caption, color = ClawTheme.colors.textMuted, @@ -1817,7 +1817,7 @@ private fun chatSessionChipText( ): String { val mainKey = mainSessionKey.trim().ifEmpty { "main" } if (entry.key == mainKey || (entry.key == "main" && mainKey == "main")) return nativeString("Main") - val name = entry.displayName?.takeIf { it.isNotBlank() } ?: entry.key.takeIf { entry.updatedAtMs != null } ?: "Current" + val name = entry.displayName?.takeIf { it.isNotBlank() } ?: entry.key.takeIf { entry.updatedAtMs != null } ?: nativeString("Current") return friendlySessionName(name) } @@ -1894,9 +1894,9 @@ internal fun userFacingChatError( ): String { val lower = error.lowercase(Locale.US) return when { - lower.contains("not connected") && gatewayConnected -> "Chat is still checking Gateway health." - lower.contains("not connected") -> "Gateway is offline. Fix the connection below or copy diagnostics." - lower.contains("unauthorized") || lower.contains("auth") -> "Gateway authentication needs attention." + lower.contains("not connected") && gatewayConnected -> nativeString("Chat is still checking Gateway health.") + lower.contains("not connected") -> nativeString("Gateway is offline. Fix the connection below or copy diagnostics.") + lower.contains("unauthorized") || lower.contains("auth") -> nativeString("Gateway authentication needs attention.") else -> error } } @@ -1913,7 +1913,10 @@ internal fun contextMeterLabel( thinkingLevel: String, thinkingSupported: Boolean = true, ): String { - val contextLabel = contextMeterWidth(usage)?.let { "Context ${(it * 100).roundToInt()}%" } ?: "Context --" + val contextLabel = + contextMeterWidth(usage)?.let { + nativeString("Context \${(it * 100).roundToInt()}%", (it * 100).roundToInt()) + } ?: nativeString("Context --") return if (thinkingSupported) nativeString("\$contextLabel · \${contextMeterThinkingLabel(thinkingLevel)}", contextLabel, contextMeterThinkingLabel(thinkingLevel)) else contextLabel } diff --git a/apps/android/app/src/main/java/ai/openclaw/app/voice/MicCaptureManager.kt b/apps/android/app/src/main/java/ai/openclaw/app/voice/MicCaptureManager.kt index 9e07abea0a93..0c5a4532c0c8 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/voice/MicCaptureManager.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/voice/MicCaptureManager.kt @@ -246,7 +246,7 @@ internal class MicCaptureManager( _statusText.value = when { _micEnabled.value && _isSending.value -> nativeString("Listening · sending queued voice") - _micEnabled.value -> "Listening" + _micEnabled.value -> nativeString("Listening") _isSending.value -> nativeString("Mic off · sending…") else -> nativeString("Mic off") } @@ -602,7 +602,8 @@ internal class MicCaptureManager( sendQueuedIfIdle() } - private fun queuedWaitingStatus(): String = "${queuedMessageCount()} queued · waiting for gateway" + private fun queuedWaitingStatus(): String = + nativeString("\${queuedMessageCount()} queued · waiting for gateway", queuedMessageCount()) private fun appendConversation( role: VoiceConversationRole, @@ -783,8 +784,8 @@ internal class MicCaptureManager( private fun listeningStatus(): String = when { _isSending.value -> nativeString("Listening · sending queued voice") - hasQueuedMessages() -> "Listening · ${queuedMessageCount()} queued" - else -> "Listening" + hasQueuedMessages() -> nativeString("Listening · \${queuedMessageCount()} queued", queuedMessageCount()) + else -> nativeString("Listening") } private fun pcm16ToPcmu(pcm16: ByteArray): ByteArray { diff --git a/apps/android/app/src/main/java/ai/openclaw/app/voice/TalkModeManager.kt b/apps/android/app/src/main/java/ai/openclaw/app/voice/TalkModeManager.kt index 4c483fca7023..69268c972bdd 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/voice/TalkModeManager.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/voice/TalkModeManager.kt @@ -2798,21 +2798,21 @@ class TalkModeManager internal constructor( if (stopRequested) return _isListening.value = false if (error == SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS) { - setStatus("Microphone permission required") + setStatus(nativeString("Microphone permission required")) return } setStatus( when (error) { - SpeechRecognizer.ERROR_AUDIO -> "Audio error" - SpeechRecognizer.ERROR_CLIENT -> "Client error" - SpeechRecognizer.ERROR_NETWORK -> "Network error" - SpeechRecognizer.ERROR_NETWORK_TIMEOUT -> "Network timeout" + SpeechRecognizer.ERROR_AUDIO -> nativeString("Audio error") + SpeechRecognizer.ERROR_CLIENT -> nativeString("Client error") + SpeechRecognizer.ERROR_NETWORK -> nativeString("Network error") + SpeechRecognizer.ERROR_NETWORK_TIMEOUT -> nativeString("Network timeout") SpeechRecognizer.ERROR_NO_MATCH -> nativeString("Listening") - SpeechRecognizer.ERROR_RECOGNIZER_BUSY -> "Recognizer busy" - SpeechRecognizer.ERROR_SERVER -> "Server error" + SpeechRecognizer.ERROR_RECOGNIZER_BUSY -> nativeString("Recognizer busy") + SpeechRecognizer.ERROR_SERVER -> nativeString("Server error") SpeechRecognizer.ERROR_SPEECH_TIMEOUT -> nativeString("Listening") - else -> "Speech error ($error)" + else -> nativeString("Speech error (\$error)", error) }, ) scheduleRestart(delayMs = 600)