From c5f94adef40b3e67c32111e681e2571fd5fa74df Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 12 Jul 2026 11:02:07 +0200 Subject: [PATCH] fix(android): localize settings and thinking presentation --- .../java/ai/openclaw/app/ui/ShellScreen.kt | 30 ++++++++++++------- .../ai/openclaw/app/ui/chat/ChatScreen.kt | 11 ++++++- .../java/ai/openclaw/app/AppLanguageTest.kt | 3 ++ .../openclaw/app/ui/ShellScreenLogicTest.kt | 6 ++++ .../app/ui/chat/ChatContextMeterTest.kt | 10 +++---- 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt index 1b18111097e6..b78c99a4d7d9 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt @@ -1311,12 +1311,17 @@ private fun SectionLabel( title: String, action: (@Composable () -> Unit)? = null, ) { + val localizedTitle = nativeString(title) Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, ) { - Text(text = nativeString(title).uppercase(), style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted) + Text( + text = localizedUppercase(localizedTitle, currentAppLanguage().languageTag), + style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), + color = ClawTheme.colors.textMuted, + ) action?.invoke() } } @@ -1326,6 +1331,7 @@ private fun ModuleListRow( row: ModuleRow, onClick: () -> Unit, ) { + val localizedTitle = nativeString(row.title) Surface(color = Color.Transparent, contentColor = ClawTheme.colors.text) { Row( modifier = @@ -1341,7 +1347,7 @@ private fun ModuleListRow( Icon(imageVector = row.icon, contentDescription = null, modifier = Modifier.size(20.dp), tint = ClawTheme.colors.text) Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) { Text( - text = nativeString(row.title), + text = localizedTitle, style = ClawTheme.type.body, color = ClawTheme.colors.text, maxLines = 1, @@ -1353,7 +1359,7 @@ private fun ModuleListRow( } Icon( imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = nativeString("Open \${row.title}", row.title), + contentDescription = settingsRowDisclosureDescription(localizedTitle, opensRoute = true), modifier = Modifier.size(17.dp), tint = ClawTheme.colors.textMuted, ) @@ -1898,8 +1904,9 @@ internal fun settingsSectionTitleForRoute(route: SettingsRoute): String = @Composable private fun SettingsSectionTitle(title: String) { + val localizedTitle = nativeString(title) Text( - text = nativeString(title).uppercase(), + text = localizedUppercase(localizedTitle, currentAppLanguage().languageTag), style = ClawTheme.type.caption.copy(fontSize = 12.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, ) @@ -1984,6 +1991,7 @@ private fun SettingsListRow( showDisclosure: Boolean, onClick: () -> Unit, ) { + val localizedTitle = nativeString(row.title) Row( modifier = Modifier @@ -1996,7 +2004,7 @@ private fun SettingsListRow( horizontalArrangement = Arrangement.spacedBy(10.dp), ) { Icon(imageVector = row.icon, contentDescription = null, modifier = Modifier.size(20.dp), tint = ClawTheme.colors.text) - Text(text = nativeString(row.title), style = ClawTheme.type.body, color = ClawTheme.colors.text, modifier = Modifier.weight(1f), maxLines = 1) + Text(text = localizedTitle, style = ClawTheme.type.body, color = ClawTheme.colors.text, modifier = Modifier.weight(1f), maxLines = 1) Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(5.dp)) { if (row.value.isNotBlank()) { Text(text = row.value, style = ClawTheme.type.caption.copy(fontSize = 13.sp, lineHeight = 17.sp), color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis) @@ -2007,12 +2015,7 @@ private fun SettingsListRow( if (showDisclosure) { Icon( imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = - if (row.route != null) { - nativeString("Open \${row.title}", row.title) - } else { - nativeString(row.title) - }, + contentDescription = settingsRowDisclosureDescription(localizedTitle, opensRoute = row.route != null), modifier = Modifier.size(17.dp), tint = ClawTheme.colors.text, ) @@ -2021,6 +2024,11 @@ private fun SettingsListRow( } } +internal fun settingsRowDisclosureDescription( + localizedTitle: String, + opensRoute: Boolean, +): String = if (opensRoute) nativeString("Open \${row.title}", localizedTitle) else localizedTitle + private fun relativeSessionTime(updatedAtMs: Long): String { val deltaMs = (System.currentTimeMillis() - updatedAtMs).coerceAtLeast(0L) val minutes = deltaMs / 60_000L 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 fd3227a4ad6f..5cb654f71579 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 @@ -1920,7 +1920,16 @@ internal fun contextMeterLabel( return if (thinkingSupported) nativeString("\$contextLabel · \${contextMeterThinkingLabel(thinkingLevel)}", contextLabel, contextMeterThinkingLabel(thinkingLevel)) else contextLabel } -internal fun contextMeterThinkingLabel(value: String): String = value.trim().lowercase(Locale.US).ifEmpty { "off" } +internal fun contextMeterThinkingLabel(value: String): String { + val normalized = value.trim().lowercase(Locale.US).ifEmpty { "off" } + return when (normalized) { + "off" -> nativeString("Off") + "low" -> nativeString("Low") + "medium" -> nativeString("Medium") + "high" -> nativeString("High") + else -> normalized + } +} internal fun chatThinkingSupported( selection: ChatThinkingLevelSelection, diff --git a/apps/android/app/src/test/java/ai/openclaw/app/AppLanguageTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/AppLanguageTest.kt index 1175b2c58802..114bc830ea9b 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/AppLanguageTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/AppLanguageTest.kt @@ -5,6 +5,7 @@ import ai.openclaw.app.i18n.joinedNativeText import ai.openclaw.app.i18n.nativeText import ai.openclaw.app.i18n.resolveNativeText import ai.openclaw.app.i18n.verbatimText +import ai.openclaw.app.ui.chat.contextMeterThinkingLabel import androidx.appcompat.app.AppCompatActivity import androidx.core.os.LocaleListCompat import kotlinx.coroutines.flow.MutableStateFlow @@ -137,6 +138,8 @@ class AppLanguageTest { setAppLanguage(AppLanguage.French) assertEquals("Micro désactivé", retained.value) assertEquals("Micro désactivé · raw", retainedComposite.value) + assertEquals("Élevé", contextMeterThinkingLabel("high")) + assertEquals("adaptive", contextMeterThinkingLabel("adaptive")) assertEquals("Connexion…", gatewayConnectionStatusForDisplay("Connecting…")) assertEquals( "Impossible de charger les approbations.", diff --git a/apps/android/app/src/test/java/ai/openclaw/app/ui/ShellScreenLogicTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/ui/ShellScreenLogicTest.kt index ebb8ee3db667..732c723b3c1c 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/ui/ShellScreenLogicTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/ui/ShellScreenLogicTest.kt @@ -37,6 +37,12 @@ class ShellScreenLogicTest { assertEquals("İLETİŞİM", localizedUppercase("iletişim", languageTag = "tr", fallbackLocale = Locale.US)) } + @Test + fun settingsDisclosureUsesTheLocalizedTitle() { + assertEquals("Open Nœuds et appareils", settingsRowDisclosureDescription("Nœuds et appareils", opensRoute = true)) + assertEquals("Nœuds et appareils", settingsRowDisclosureDescription("Nœuds et appareils", opensRoute = false)) + } + @Test fun appearanceThemeModeDefaultsToDarkForExistingInstalls() { assertEquals(AppearanceThemeMode.Dark, AppearanceThemeMode.fromRawValue(null)) diff --git a/apps/android/app/src/test/java/ai/openclaw/app/ui/chat/ChatContextMeterTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/ui/chat/ChatContextMeterTest.kt index 7ef0c0f840fb..4b18d71ea613 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/ui/chat/ChatContextMeterTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/ui/chat/ChatContextMeterTest.kt @@ -34,7 +34,7 @@ class ChatContextMeterTest { assertEquals(ChatContextUsage(totalTokens = 1_250L, totalTokensFresh = true, contextTokens = 5_000L), usage) assertEquals(0.25f, contextMeterWidth(usage)) - assertEquals("Context 25% · high", contextMeterLabel(usage, "high")) + assertEquals("Context 25% · High", contextMeterLabel(usage, "high")) } @Test @@ -59,7 +59,7 @@ class ChatContextMeterTest { ) assertEquals(ChatContextUsage(totalTokens = 41_000L, totalTokensFresh = true, contextTokens = 100_000L), usage) - assertEquals("Context 41% · off", contextMeterLabel(usage, "off")) + assertEquals("Context 41% · Off", contextMeterLabel(usage, "off")) } @Test @@ -67,7 +67,7 @@ class ChatContextMeterTest { val usage = ChatContextUsage(totalTokens = 8_200L, totalTokensFresh = true, contextTokens = null) assertNull(contextMeterWidth(usage)) - assertEquals("Context -- · medium", contextMeterLabel(usage, "medium")) + assertEquals("Context -- · Medium", contextMeterLabel(usage, "medium")) } @Test @@ -75,7 +75,7 @@ class ChatContextMeterTest { val usage = ChatContextUsage(totalTokens = 150_000L, totalTokensFresh = true, contextTokens = 100_000L) assertEquals(1.0f, contextMeterWidth(usage)) - assertEquals("Context 100% · low", contextMeterLabel(usage, "low")) + assertEquals("Context 100% · Low", contextMeterLabel(usage, "low")) } @Test @@ -83,7 +83,7 @@ class ChatContextMeterTest { val usage = ChatContextUsage(totalTokens = 82_000L, totalTokensFresh = false, contextTokens = 100_000L) assertNull(contextMeterWidth(usage)) - assertEquals("Context -- · high", contextMeterLabel(usage, "high")) + assertEquals("Context -- · High", contextMeterLabel(usage, "high")) } @Test