diff --git a/apps/.i18n/native-source.json b/apps/.i18n/native-source.json index 721a3c4bd80..b407225c1cf 100644 --- a/apps/.i18n/native-source.json +++ b/apps/.i18n/native-source.json @@ -1,6 +1,22 @@ { "version": 1, "entries": [ + { + "kind": "conditional-branch", + "line": 89, + "path": "apps/android/app/src/main/java/ai/openclaw/app/AppLanguage.kt", + "source": "OpenClaw translations · $languageTag", + "surface": "android", + "id": "native.android.85cc8de945e3929c" + }, + { + "kind": "conditional-branch", + "line": 90, + "path": "apps/android/app/src/main/java/ai/openclaw/app/AppLanguage.kt", + "source": "Follow Android · $systemLanguageTag", + "surface": "android", + "id": "native.android.914725056e04bd7a" + }, { "kind": "conditional-branch", "line": 217, @@ -131,7 +147,7 @@ }, { "kind": "ui-named-argument", - "line": 247, + "line": 277, "path": "apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt", "source": "OPENCLAW", "surface": "android", @@ -139,7 +155,7 @@ }, { "kind": "ui-state-text", - "line": 143, + "line": 155, "path": "apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt", "source": "Searching…", "surface": "android", @@ -147,7 +163,7 @@ }, { "kind": "ui-state-text", - "line": 243, + "line": 255, "path": "apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt", "source": "Mic off", "surface": "android", @@ -155,7 +171,7 @@ }, { "kind": "ui-state-text", - "line": 253, + "line": 265, "path": "apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt", "source": "Off", "surface": "android", @@ -2571,7 +2587,7 @@ }, { "kind": "ui-named-argument", - "line": 222, + "line": 228, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Provider limits and quota health.", "surface": "android", @@ -2579,7 +2595,7 @@ }, { "kind": "ui-named-argument", - "line": 222, + "line": 228, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Usage", "surface": "android", @@ -2587,7 +2603,7 @@ }, { "kind": "ui-named-argument", - "line": 242, + "line": 248, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Connect the gateway to load usage.", "surface": "android", @@ -2595,7 +2611,7 @@ }, { "kind": "ui-named-argument", - "line": 247, + "line": 253, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "No usage data yet.", "surface": "android", @@ -2603,7 +2619,7 @@ }, { "kind": "ui-named-argument", - "line": 248, + "line": 254, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Provider limits will appear here when your gateway reports them.", "surface": "android", @@ -2611,7 +2627,7 @@ }, { "kind": "ui-named-argument", - "line": 283, + "line": 289, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Cron Jobs", "surface": "android", @@ -2619,7 +2635,7 @@ }, { "kind": "ui-named-argument", - "line": 283, + "line": 289, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Scheduled OpenClaw work from your gateway.", "surface": "android", @@ -2627,7 +2643,7 @@ }, { "kind": "ui-named-argument", - "line": 294, + "line": 300, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Android shows scheduled work status. Create and edit schedules from the desktop app.", "surface": "android", @@ -2635,7 +2651,7 @@ }, { "kind": "ui-named-argument", - "line": 304, + "line": 310, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Connect the gateway to load cron jobs.", "surface": "android", @@ -2643,7 +2659,7 @@ }, { "kind": "ui-named-argument", - "line": 309, + "line": 315, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "No scheduled jobs.", "surface": "android", @@ -2651,7 +2667,7 @@ }, { "kind": "ui-named-argument", - "line": 310, + "line": 316, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Create recurring OpenClaw work from the desktop app.", "surface": "android", @@ -2659,7 +2675,7 @@ }, { "kind": "ui-named-argument", - "line": 345, + "line": 351, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Inspect scheduled gateway work.", "surface": "android", @@ -2667,7 +2683,7 @@ }, { "kind": "ui-named-argument", - "line": 359, + "line": 365, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Connect the gateway to inspect cron jobs.", "surface": "android", @@ -2675,7 +2691,7 @@ }, { "kind": "conditional-branch", - "line": 367, + "line": 373, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Cron job not loaded.", "surface": "android", @@ -2683,7 +2699,7 @@ }, { "kind": "conditional-branch", - "line": 367, + "line": 373, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Loading cron job…", "surface": "android", @@ -2691,7 +2707,7 @@ }, { "kind": "ui-named-argument", - "line": 389, + "line": 395, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Agents", "surface": "android", @@ -2699,7 +2715,7 @@ }, { "kind": "ui-named-argument", - "line": 389, + "line": 395, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Choose and inspect the assistants available on this gateway.", "surface": "android", @@ -2707,7 +2723,7 @@ }, { "kind": "ui-named-argument", - "line": 400, + "line": 406, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Connect the gateway to load agents.", "surface": "android", @@ -2715,7 +2731,7 @@ }, { "kind": "ui-named-argument", - "line": 404, + "line": 410, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "No agents loaded yet.", "surface": "android", @@ -2723,7 +2739,7 @@ }, { "kind": "ui-named-argument", - "line": 430, + "line": 436, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Approvals", "surface": "android", @@ -2731,7 +2747,7 @@ }, { "kind": "ui-named-argument", - "line": 430, + "line": 436, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Review actions that need your attention.", "surface": "android", @@ -2739,7 +2755,7 @@ }, { "kind": "conditional-branch", - "line": 441, + "line": 447, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Refresh", "surface": "android", @@ -2747,7 +2763,7 @@ }, { "kind": "conditional-branch", - "line": 441, + "line": 447, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Refreshing", "surface": "android", @@ -2755,7 +2771,7 @@ }, { "kind": "ui-named-argument", - "line": 454, + "line": 460, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Gateway disconnected.", "surface": "android", @@ -2763,7 +2779,7 @@ }, { "kind": "ui-named-argument", - "line": 455, + "line": 461, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Connect the gateway to load approval requests in the app.", "surface": "android", @@ -2771,7 +2787,7 @@ }, { "kind": "ui-named-argument", - "line": 461, + "line": 467, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "No gateway approvals.", "surface": "android", @@ -2779,7 +2795,7 @@ }, { "kind": "ui-named-argument", - "line": 462, + "line": 468, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Exec approval requests will appear here while this phone is connected.", "surface": "android", @@ -2787,7 +2803,7 @@ }, { "kind": "ui-named-argument", - "line": 469, + "line": 475, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Session activity", "surface": "android", @@ -2795,7 +2811,7 @@ }, { "kind": "ui-named-argument", - "line": 470, + "line": 476, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Chat tool calls waiting in the active session remain visible here.", "surface": "android", @@ -2803,7 +2819,7 @@ }, { "kind": "ui-named-argument", - "line": 484, + "line": 490, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "How this phone appears to OpenClaw.", "surface": "android", @@ -2811,7 +2827,7 @@ }, { "kind": "ui-named-argument", - "line": 484, + "line": 490, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Profile", "surface": "android", @@ -2819,7 +2835,7 @@ }, { "kind": "ui-named-argument", - "line": 487, + "line": 493, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Device name", "surface": "android", @@ -2827,7 +2843,7 @@ }, { "kind": "ui-named-argument", - "line": 488, + "line": 494, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Save Profile", "surface": "android", @@ -2835,7 +2851,7 @@ }, { "kind": "ui-named-argument", - "line": 507, + "line": 513, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Configure voice, transport, and playback.", "surface": "android", @@ -2843,7 +2859,7 @@ }, { "kind": "ui-named-argument", - "line": 507, + "line": 513, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Talk Provider Setup", "surface": "android", @@ -2851,7 +2867,7 @@ }, { "kind": "ui-named-argument", - "line": 510, + "line": 516, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Audio Test", "surface": "android", @@ -2859,7 +2875,7 @@ }, { "kind": "ui-named-argument", - "line": 511, + "line": 517, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Check that OpenClaw can speak clearly on this phone.", "surface": "android", @@ -2867,7 +2883,7 @@ }, { "kind": "conditional-branch", - "line": 514, + "line": 520, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Enable speaker", "surface": "android", @@ -2875,7 +2891,7 @@ }, { "kind": "conditional-branch", - "line": 514, + "line": 520, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Mute speaker", "surface": "android", @@ -2883,7 +2899,7 @@ }, { "kind": "conditional-branch", - "line": 515, + "line": 521, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Assistant speech muted", "surface": "android", @@ -2891,7 +2907,7 @@ }, { "kind": "conditional-branch", - "line": 515, + "line": 521, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Replies play aloud", "surface": "android", @@ -2899,7 +2915,7 @@ }, { "kind": "conditional-branch", - "line": 517, + "line": 523, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Muted", "surface": "android", @@ -2907,7 +2923,7 @@ }, { "kind": "conditional-branch", - "line": 517, + "line": 523, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "On", "surface": "android", @@ -2915,7 +2931,7 @@ }, { "kind": "ui-named-argument", - "line": 521, + "line": 527, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Done", "surface": "android", @@ -2923,7 +2939,7 @@ }, { "kind": "ui-named-argument", - "line": 531, + "line": 537, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Realtime Talk", "surface": "android", @@ -2931,7 +2947,7 @@ }, { "kind": "ui-named-argument", - "line": 532, + "line": 538, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Dictation", "surface": "android", @@ -2939,7 +2955,7 @@ }, { "kind": "conditional-branch", - "line": 660, + "line": 666, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Allowlist", "surface": "android", @@ -2947,7 +2963,7 @@ }, { "kind": "conditional-branch", - "line": 660, + "line": 666, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Blocklist", "surface": "android", @@ -2955,7 +2971,7 @@ }, { "kind": "ui-named-argument", - "line": 693, + "line": 699, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Choose what reaches OpenClaw.", "surface": "android", @@ -2963,7 +2979,7 @@ }, { "kind": "ui-named-argument", - "line": 693, + "line": 699, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Notifications", "surface": "android", @@ -2971,7 +2987,7 @@ }, { "kind": "conditional-branch", - "line": 697, + "line": 703, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Alerts stay on this phone.", "surface": "android", @@ -2979,7 +2995,7 @@ }, { "kind": "conditional-branch", - "line": 697, + "line": 703, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "OpenClaw can receive selected alerts.", "surface": "android", @@ -2987,7 +3003,7 @@ }, { "kind": "conditional-branch", - "line": 709, + "line": 715, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Granted", "surface": "android", @@ -2995,7 +3011,7 @@ }, { "kind": "conditional-branch", - "line": 709, + "line": 715, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Setup", "surface": "android", @@ -3003,7 +3019,7 @@ }, { "kind": "conditional-branch", - "line": 714, + "line": 720, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Check Access", "surface": "android", @@ -3011,7 +3027,7 @@ }, { "kind": "conditional-branch", - "line": 714, + "line": 720, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Open System Access", "surface": "android", @@ -3019,7 +3035,7 @@ }, { "kind": "ui-named-argument", - "line": 724, + "line": 730, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Forwarding Mode", "surface": "android", @@ -3027,7 +3043,7 @@ }, { "kind": "ui-named-argument", - "line": 773, + "line": 779, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "App Filter", "surface": "android", @@ -3035,7 +3051,7 @@ }, { "kind": "conditional-branch", - "line": 780, + "line": 786, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Close App Picker", "surface": "android", @@ -3043,7 +3059,7 @@ }, { "kind": "conditional-branch", - "line": 780, + "line": 786, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Open App Picker", "surface": "android", @@ -3051,7 +3067,7 @@ }, { "kind": "ui-named-argument", - "line": 785, + "line": 791, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Search apps", "surface": "android", @@ -3059,7 +3075,7 @@ }, { "kind": "ui-named-argument", - "line": 788, + "line": 794, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Show System Apps", "surface": "android", @@ -3067,7 +3083,7 @@ }, { "kind": "ui-named-argument", - "line": 789, + "line": 795, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Include Android and background packages.", "surface": "android", @@ -3075,7 +3091,7 @@ }, { "kind": "ui-named-argument", - "line": 796, + "line": 802, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "No matching apps.", "surface": "android", @@ -3083,7 +3099,7 @@ }, { "kind": "ui-named-argument", - "line": 807, + "line": 813, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Showing ${visibleApps.size} of ${apps.size}. Refine search for more.", "surface": "android", @@ -3091,7 +3107,7 @@ }, { "kind": "ui-named-argument", - "line": 1046, + "line": 1052, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Choose what this phone can share.", "surface": "android", @@ -3099,7 +3115,7 @@ }, { "kind": "ui-named-argument", - "line": 1046, + "line": 1052, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Phone Capabilities", "surface": "android", @@ -3107,7 +3123,7 @@ }, { "kind": "conditional-branch", - "line": 1055, + "line": 1061, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Allow photo library access.", "surface": "android", @@ -3115,7 +3131,7 @@ }, { "kind": "conditional-branch", - "line": 1055, + "line": 1061, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Selected or full photo access granted.", "surface": "android", @@ -3123,7 +3139,7 @@ }, { "kind": "conditional-branch", - "line": 1065, + "line": 1071, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "App list stays on this phone.", "surface": "android", @@ -3131,7 +3147,7 @@ }, { "kind": "conditional-branch", - "line": 1065, + "line": 1071, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "OpenClaw can list launcher-visible apps.", "surface": "android", @@ -3139,7 +3155,7 @@ }, { "kind": "ui-named-argument", - "line": 1076, + "line": 1082, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Location", "surface": "android", @@ -3147,7 +3163,7 @@ }, { "kind": "ui-named-argument", - "line": 1084, + "line": 1090, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Always allows requested location checks while OpenClaw is in the background; Android shows this in the persistent node notification.", "surface": "android", @@ -3155,7 +3171,7 @@ }, { "kind": "ui-call", - "line": 1109, + "line": 1115, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Allow background location?", "surface": "android", @@ -3163,7 +3179,7 @@ }, { "kind": "ui-call", - "line": 1111, + "line": 1117, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "OpenClaw only checks location when your paired Gateway requests it. ", "surface": "android", @@ -3171,7 +3187,7 @@ }, { "kind": "ui-call", - "line": 1124, + "line": 1130, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Open Settings", "surface": "android", @@ -3179,7 +3195,7 @@ }, { "kind": "ui-call", - "line": 1129, + "line": 1135, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Not Now", "surface": "android", @@ -3187,7 +3203,7 @@ }, { "kind": "ui-call", - "line": 1177, + "line": 1183, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Replace gateway setup?", "surface": "android", @@ -3195,7 +3211,7 @@ }, { "kind": "ui-call", - "line": 1192, + "line": 1198, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Replace setup", "surface": "android", @@ -3203,7 +3219,7 @@ }, { "kind": "ui-call", - "line": 1208, + "line": 1214, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Forget gateway?", "surface": "android", @@ -3211,7 +3227,7 @@ }, { "kind": "ui-call", - "line": 1221, + "line": 1227, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Cancel", "surface": "android", @@ -3219,7 +3235,7 @@ }, { "kind": "ui-named-argument", - "line": 1227, + "line": 1233, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Connection between this phone and OpenClaw.", "surface": "android", @@ -3227,7 +3243,7 @@ }, { "kind": "conditional-branch", - "line": 1231, + "line": 1237, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Connected", "surface": "android", @@ -3235,7 +3251,7 @@ }, { "kind": "conditional-branch", - "line": 1231, + "line": 1237, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Offline", "surface": "android", @@ -3243,7 +3259,7 @@ }, { "kind": "conditional-branch", - "line": 1232, + "line": 1238, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Not paired", "surface": "android", @@ -3251,7 +3267,7 @@ }, { "kind": "conditional-branch", - "line": 1232, + "line": 1238, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Online", "surface": "android", @@ -3259,7 +3275,7 @@ }, { "kind": "ui-named-argument", - "line": 1242, + "line": 1248, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Reconnect", "surface": "android", @@ -3267,7 +3283,7 @@ }, { "kind": "ui-named-argument", - "line": 1243, + "line": 1249, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Disconnect", "surface": "android", @@ -3275,7 +3291,7 @@ }, { "kind": "ui-named-argument", - "line": 1247, + "line": 1253, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Gateways", "surface": "android", @@ -3283,7 +3299,7 @@ }, { "kind": "ui-named-argument", - "line": 1249, + "line": 1255, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "No paired gateways.", "surface": "android", @@ -3291,7 +3307,7 @@ }, { "kind": "ui-call", - "line": 1269, + "line": 1275, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Forget", "surface": "android", @@ -3299,7 +3315,7 @@ }, { "kind": "ui-named-argument", - "line": 1291, + "line": 1297, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Gateway setup", "surface": "android", @@ -3307,7 +3323,7 @@ }, { "kind": "ui-named-argument", - "line": 1293, + "line": 1299, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Scan or paste a setup code to add another gateway.", "surface": "android", @@ -3315,7 +3331,7 @@ }, { "kind": "ui-named-argument", - "line": 1300, + "line": 1306, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Add gateway", "surface": "android", @@ -3323,7 +3339,7 @@ }, { "kind": "ui-named-argument", - "line": 1301, + "line": 1307, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Setup Code", "surface": "android", @@ -3331,7 +3347,7 @@ }, { "kind": "ui-named-argument", - "line": 1305, + "line": 1311, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Android can scan or paste an existing setup code, but this gateway does not expose setup-code generation to the app yet. Generate the QR/code on the gateway host with openclaw qr, then scan it here or paste the setup code below.", "surface": "android", @@ -3339,7 +3355,7 @@ }, { "kind": "ui-named-argument", - "line": 1314, + "line": 1320, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Connection Setup", "surface": "android", @@ -3347,7 +3363,7 @@ }, { "kind": "ui-named-argument", - "line": 1315, + "line": 1321, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Setup code", "surface": "android", @@ -3355,7 +3371,7 @@ }, { "kind": "ui-named-argument", - "line": 1317, + "line": 1323, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Host", "surface": "android", @@ -3363,7 +3379,7 @@ }, { "kind": "ui-named-argument", - "line": 1318, + "line": 1324, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Port", "surface": "android", @@ -3371,7 +3387,7 @@ }, { "kind": "ui-named-argument", - "line": 1320, + "line": 1326, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Connection security", "surface": "android", @@ -3379,7 +3395,7 @@ }, { "kind": "conditional-branch", - "line": 1324, + "line": 1330, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Secure (TLS)", "surface": "android", @@ -3387,7 +3403,7 @@ }, { "kind": "conditional-branch", - "line": 1324, + "line": 1330, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Unencrypted", "surface": "android", @@ -3395,7 +3411,7 @@ }, { "kind": "ui-named-argument", - "line": 1341, + "line": 1347, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Token", "surface": "android", @@ -3403,7 +3419,7 @@ }, { "kind": "ui-named-argument", - "line": 1342, + "line": 1348, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Bootstrap", "surface": "android", @@ -3411,7 +3427,7 @@ }, { "kind": "ui-named-argument", - "line": 1344, + "line": 1350, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Password", "surface": "android", @@ -3419,7 +3435,7 @@ }, { "kind": "ui-named-argument", - "line": 1349, + "line": 1355, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Save & Connect", "surface": "android", @@ -3427,7 +3443,7 @@ }, { "kind": "ui-named-argument", - "line": 1366, + "line": 1372, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Enter a valid setup code or gateway address.", "surface": "android", @@ -3435,15 +3451,7 @@ }, { "kind": "ui-named-argument", - "line": 1389, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", - "source": "A calm, high-contrast OpenClaw interface.", - "surface": "android", - "id": "native.android.6f19c75742af5126" - }, - { - "kind": "ui-named-argument", - "line": 1389, + "line": 1398, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Appearance", "surface": "android", @@ -3451,15 +3459,47 @@ }, { "kind": "ui-named-argument", - "line": 1400, + "line": 1398, + "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", + "source": "Theme and translated Android text.", + "surface": "android", + "id": "native.android.15d4461b5fbdc203" + }, + { + "kind": "ui-named-argument", + "line": 1410, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Theme", "surface": "android", "id": "native.android.0e5de6796bc8d8f9" }, + { + "kind": "ui-named-argument", + "line": 1420, + "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", + "source": "App language", + "surface": "android", + "id": "native.android.bd997c088145929f" + }, + { + "kind": "ui-named-argument", + "line": 1422, + "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", + "source": "Changes Android text that OpenClaw has translated. Screens with English-only copy stay unchanged.", + "surface": "android", + "id": "native.android.1fc742aceae3f137" + }, + { + "kind": "ui-named-argument", + "line": 1459, + "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", + "source": "Selected", + "surface": "android", + "id": "native.android.3d640706b2b6b93a" + }, { "kind": "conditional-branch", - "line": 1445, + "line": 1505, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Ready", "surface": "android", @@ -3467,7 +3507,7 @@ }, { "kind": "ui-named-argument", - "line": 1474, + "line": 1534, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "About", "surface": "android", @@ -3475,7 +3515,7 @@ }, { "kind": "ui-named-argument", - "line": 1474, + "line": 1534, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "OpenClaw for Android.", "surface": "android", @@ -3483,7 +3523,7 @@ }, { "kind": "ui-named-argument", - "line": 1487, + "line": 1547, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Gateway", "surface": "android", @@ -3491,7 +3531,7 @@ }, { "kind": "ui-named-argument", - "line": 1489, + "line": 1549, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Runtime", "surface": "android", @@ -3499,7 +3539,7 @@ }, { "kind": "ui-named-argument", - "line": 1492, + "line": 1552, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Update", "surface": "android", @@ -3507,7 +3547,7 @@ }, { "kind": "ui-named-argument", - "line": 1503, + "line": 1563, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "© 2026 OpenClaw Foundation — MIT License.", "surface": "android", @@ -3515,7 +3555,7 @@ }, { "kind": "ui-named-argument", - "line": 1520, + "line": 1580, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "OpenClaw logo", "surface": "android", @@ -3523,7 +3563,7 @@ }, { "kind": "ui-named-argument", - "line": 1522, + "line": 1582, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "OpenClaw", "surface": "android", @@ -3531,7 +3571,7 @@ }, { "kind": "ui-named-argument", - "line": 1523, + "line": 1583, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Personal AI on your devices", "surface": "android", @@ -3539,7 +3579,7 @@ }, { "kind": "ui-named-argument", - "line": 1582, + "line": 1642, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Licenses", "surface": "android", @@ -3547,7 +3587,7 @@ }, { "kind": "conditional-branch", - "line": 1583, + "line": 1643, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "OpenClaw appreciates its partners in the open-source community.", "surface": "android", @@ -3555,7 +3595,7 @@ }, { "kind": "ui-named-argument", - "line": 1592, + "line": 1652, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "No license notices are packaged in this build.", "surface": "android", @@ -3563,7 +3603,7 @@ }, { "kind": "ui-named-argument", - "line": 1618, + "line": 1678, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Open ${license.title}", "surface": "android", @@ -3571,7 +3611,7 @@ }, { "kind": "conditional-branch", - "line": 1653, + "line": 1713, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Check", "surface": "android", @@ -3579,7 +3619,7 @@ }, { "kind": "ui-named-argument", - "line": 1686, + "line": 1746, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Back", "surface": "android", @@ -3587,7 +3627,7 @@ }, { "kind": "conditional-branch", - "line": 1760, + "line": 1820, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Sending", "surface": "android", @@ -3595,7 +3635,7 @@ }, { "kind": "conditional-branch", - "line": 1769, + "line": 1829, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Allow Once", "surface": "android", @@ -3603,7 +3643,7 @@ }, { "kind": "conditional-branch", - "line": 1769, + "line": 1829, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Allowing", "surface": "android", @@ -3611,7 +3651,7 @@ }, { "kind": "conditional-branch", - "line": 1777, + "line": 1837, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Always", "surface": "android", @@ -3619,7 +3659,7 @@ }, { "kind": "conditional-branch", - "line": 1777, + "line": 1837, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Saving", "surface": "android", @@ -3627,7 +3667,7 @@ }, { "kind": "conditional-branch", - "line": 1785, + "line": 1845, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Deny", "surface": "android", @@ -3635,7 +3675,7 @@ }, { "kind": "conditional-branch", - "line": 1785, + "line": 1845, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Denying", "surface": "android", @@ -3643,7 +3683,7 @@ }, { "kind": "conditional-branch", - "line": 1810, + "line": 1870, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Review", "surface": "android", @@ -3651,7 +3691,7 @@ }, { "kind": "conditional-branch", - "line": 1838, + "line": 1898, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Issue", "surface": "android", @@ -3659,7 +3699,7 @@ }, { "kind": "conditional-branch", - "line": 1868, + "line": 1928, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Enabled", "surface": "android", @@ -3667,7 +3707,7 @@ }, { "kind": "conditional-branch", - "line": 1882, + "line": 1942, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "No", "surface": "android", @@ -3675,7 +3715,7 @@ }, { "kind": "conditional-branch", - "line": 1882, + "line": 1942, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Yes", "surface": "android", @@ -3683,7 +3723,7 @@ }, { "kind": "ui-named-argument", - "line": 1900, + "line": 1960, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Last Error", "surface": "android", @@ -3691,7 +3731,7 @@ }, { "kind": "ui-named-argument", - "line": 1903, + "line": 1963, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Delivery Error", "surface": "android", @@ -3699,7 +3739,7 @@ }, { "kind": "ui-named-argument", - "line": 1942, + "line": 2002, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Tap to copy", "surface": "android", @@ -3707,7 +3747,7 @@ }, { "kind": "ui-toast", - "line": 1957, + "line": 2017, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "$title copied", "surface": "android", @@ -3715,7 +3755,7 @@ }, { "kind": "conditional-branch", - "line": 1995, + "line": 2055, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Default assistant", "surface": "android", @@ -3723,7 +3763,7 @@ }, { "kind": "conditional-branch", - "line": 1997, + "line": 2057, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Default", "surface": "android", @@ -3731,7 +3771,7 @@ }, { "kind": "conditional-branch", - "line": 2053, + "line": 2113, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Needs attention", "surface": "android", @@ -3739,7 +3779,7 @@ }, { "kind": "conditional-branch", - "line": 2056, + "line": 2116, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Waiting ${minutes}m", "surface": "android", @@ -3747,7 +3787,7 @@ }, { "kind": "conditional-branch", - "line": 2056, + "line": 2116, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Waiting for review", "surface": "android", @@ -3755,7 +3795,7 @@ }, { "kind": "conditional-branch", - "line": 2084, + "line": 2144, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "${job.scheduleLabel} · ${formatCronWake(job.nextRunAtMs)} · ${job.promptPreview}", "surface": "android", @@ -3763,7 +3803,7 @@ }, { "kind": "conditional-branch", - "line": 2091, + "line": 2151, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "No limits reported", "surface": "android", @@ -3771,7 +3811,7 @@ }, { "kind": "conditional-branch", - "line": 2122, + "line": 2182, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Off", "surface": "android", @@ -3779,7 +3819,7 @@ }, { "kind": "conditional-branch", - "line": 2143, + "line": 2203, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "System Event Text", "surface": "android", @@ -3787,7 +3827,7 @@ }, { "kind": "conditional-branch", - "line": 2144, + "line": 2204, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Agent Prompt", "surface": "android", @@ -3795,7 +3835,7 @@ }, { "kind": "conditional-branch", - "line": 2145, + "line": 2205, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Command", "surface": "android", @@ -3803,7 +3843,7 @@ }, { "kind": "conditional-branch", - "line": 2146, + "line": 2206, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt", "source": "Payload Text", "surface": "android", @@ -3811,7 +3851,7 @@ }, { "kind": "ui-named-argument", - "line": 437, + "line": 438, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Overview", "surface": "android", @@ -3819,7 +3859,7 @@ }, { "kind": "ui-named-argument", - "line": 482, + "line": 483, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "No recent sessions", "surface": "android", @@ -3827,7 +3867,7 @@ }, { "kind": "ui-named-argument", - "line": 483, + "line": 484, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Start a chat and your active OpenClaw conversations will appear here.", "surface": "android", @@ -3835,7 +3875,7 @@ }, { "kind": "ui-named-argument", - "line": 484, + "line": 485, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Start Chat", "surface": "android", @@ -3843,7 +3883,7 @@ }, { "kind": "ui-named-argument", - "line": 530, + "line": 531, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "OpenClaw", "surface": "android", @@ -3851,7 +3891,7 @@ }, { "kind": "ui-named-argument", - "line": 538, + "line": 539, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Search", "surface": "android", @@ -3859,7 +3899,7 @@ }, { "kind": "ui-named-argument", - "line": 590, + "line": 591, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "ACTIVE AGENT", "surface": "android", @@ -3867,7 +3907,7 @@ }, { "kind": "ui-named-argument", - "line": 599, + "line": 600, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "View", "surface": "android", @@ -3875,7 +3915,7 @@ }, { "kind": "conditional-branch", - "line": 602, + "line": 603, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "$pendingRunCount active", "surface": "android", @@ -3883,7 +3923,7 @@ }, { "kind": "conditional-branch", - "line": 602, + "line": 603, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Idle", "surface": "android", @@ -3891,7 +3931,7 @@ }, { "kind": "ui-named-argument", - "line": 602, + "line": 603, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Runs", "surface": "android", @@ -3899,7 +3939,7 @@ }, { "kind": "conditional-branch", - "line": 603, + "line": 604, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "$sessionCount recent", "surface": "android", @@ -3907,7 +3947,7 @@ }, { "kind": "ui-named-argument", - "line": 603, + "line": 604, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Sessions", "surface": "android", @@ -3915,7 +3955,7 @@ }, { "kind": "ui-named-argument", - "line": 604, + "line": 605, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Cron", "surface": "android", @@ -3923,7 +3963,7 @@ }, { "kind": "ui-named-argument", - "line": 607, + "line": 608, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Chat", "surface": "android", @@ -3931,7 +3971,7 @@ }, { "kind": "ui-named-argument", - "line": 611, + "line": 612, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Reconnect gateway", "surface": "android", @@ -3939,7 +3979,7 @@ }, { "kind": "ui-named-argument", - "line": 759, + "line": 760, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Open ${card.title}", "surface": "android", @@ -3947,7 +3987,7 @@ }, { "kind": "ui-named-argument", - "line": 827, + "line": 828, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Talk", "surface": "android", @@ -3955,7 +3995,7 @@ }, { "kind": "ui-named-argument", - "line": 828, + "line": 829, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Open Talk", "surface": "android", @@ -3963,7 +4003,7 @@ }, { "kind": "ui-named-argument", - "line": 830, + "line": 831, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Talk settings", "surface": "android", @@ -3971,7 +4011,7 @@ }, { "kind": "ui-named-argument", - "line": 838, + "line": 839, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Recent Sessions", "surface": "android", @@ -3979,7 +4019,7 @@ }, { "kind": "ui-named-argument", - "line": 848, + "line": 849, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "View all", "surface": "android", @@ -3987,7 +4027,7 @@ }, { "kind": "conditional-branch", - "line": 987, + "line": 988, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "$onlineNodes/$nodeCount", "surface": "android", @@ -3995,7 +4035,7 @@ }, { "kind": "conditional-branch", - "line": 987, + "line": 988, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "None", "surface": "android", @@ -4003,7 +4043,7 @@ }, { "kind": "conditional-branch", - "line": 1019, + "line": 1020, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Recent conversations", "surface": "android", @@ -4011,7 +4051,7 @@ }, { "kind": "conditional-branch", - "line": 1026, + "line": 1027, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Browse", "surface": "android", @@ -4019,7 +4059,7 @@ }, { "kind": "conditional-branch", - "line": 1026, + "line": 1027, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Offline", "surface": "android", @@ -4027,7 +4067,7 @@ }, { "kind": "conditional-branch", - "line": 1078, + "line": 1079, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Working · $pendingRunCount active ${pluralize(\"run\", pendingRunCount)}", "surface": "android", @@ -4035,7 +4075,7 @@ }, { "kind": "ui-named-argument", - "line": 1199, + "line": 1200, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Needs attention", "surface": "android", @@ -4043,7 +4083,7 @@ }, { "kind": "ui-named-argument", - "line": 1264, + "line": 1265, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Open ${row.title}", "surface": "android", @@ -4051,7 +4091,7 @@ }, { "kind": "ui-named-argument", - "line": 1372, + "line": 1373, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Open session", "surface": "android", @@ -4059,7 +4099,7 @@ }, { "kind": "ui-named-argument", - "line": 1486, + "line": 1488, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Back", "surface": "android", @@ -4067,7 +4107,7 @@ }, { "kind": "ui-named-argument", - "line": 1489, + "line": 1491, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Settings", "surface": "android", @@ -4075,7 +4115,7 @@ }, { "kind": "ui-named-argument", - "line": 1492, + "line": 1494, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Search settings", "surface": "android", @@ -4083,7 +4123,7 @@ }, { "kind": "conditional-branch", - "line": 1516, + "line": 1518, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "$readyProviderCount ready", "surface": "android", @@ -4091,7 +4131,7 @@ }, { "kind": "conditional-branch", - "line": 1516, + "line": 1518, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Review readiness", "surface": "android", @@ -4099,7 +4139,7 @@ }, { "kind": "conditional-branch", - "line": 1534, + "line": 1536, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Speaker muted", "surface": "android", @@ -4107,7 +4147,7 @@ }, { "kind": "conditional-branch", - "line": 1534, + "line": 1536, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Speaker on", "surface": "android", @@ -4115,7 +4155,7 @@ }, { "kind": "conditional-branch", - "line": 1536, + "line": 1538, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Off", "surface": "android", @@ -4123,7 +4163,7 @@ }, { "kind": "conditional-branch", - "line": 1536, + "line": 1538, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Smart delivery", "surface": "android", @@ -4131,7 +4171,7 @@ }, { "kind": "conditional-branch", - "line": 1537, + "line": 1539, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Camera enabled", "surface": "android", @@ -4139,7 +4179,7 @@ }, { "kind": "conditional-branch", - "line": 1537, + "line": 1539, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Locked", "surface": "android", @@ -4147,7 +4187,7 @@ }, { "kind": "ui-named-argument", - "line": 1579, + "line": 1586, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "OpenClaw ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})", "surface": "android", @@ -4155,7 +4195,7 @@ }, { "kind": "conditional-branch", - "line": 1582, + "line": 1589, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "All systems operational", "surface": "android", @@ -4163,7 +4203,7 @@ }, { "kind": "conditional-branch", - "line": 1582, + "line": 1589, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Gateway not connected", "surface": "android", @@ -4171,7 +4211,7 @@ }, { "kind": "conditional-branch", - "line": 1614, + "line": 1621, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "No provider usage", "surface": "android", @@ -4179,7 +4219,7 @@ }, { "kind": "conditional-branch", - "line": 1615, + "line": 1622, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "1 provider", "surface": "android", @@ -4187,7 +4227,7 @@ }, { "kind": "conditional-branch", - "line": 1616, + "line": 1623, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "$count providers", "surface": "android", @@ -4195,7 +4235,7 @@ }, { "kind": "conditional-branch", - "line": 1622, + "line": 1629, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "$ready/${skills.size} ready", "surface": "android", @@ -4203,7 +4243,7 @@ }, { "kind": "conditional-branch", - "line": 1622, + "line": 1629, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "No skills", "surface": "android", @@ -4211,7 +4251,7 @@ }, { "kind": "conditional-branch", - "line": 1636, + "line": 1643, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "$pending pending", "surface": "android", @@ -4219,7 +4259,7 @@ }, { "kind": "conditional-branch", - "line": 1636, + "line": 1643, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "1 pending", "surface": "android", @@ -4227,7 +4267,7 @@ }, { "kind": "conditional-branch", - "line": 1641, + "line": 1648, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "$held held", "surface": "android", @@ -4235,7 +4275,7 @@ }, { "kind": "conditional-branch", - "line": 1641, + "line": 1648, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "1 held", "surface": "android", @@ -4243,7 +4283,7 @@ }, { "kind": "conditional-branch", - "line": 1642, + "line": 1649, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "$applied applied", "surface": "android", @@ -4251,7 +4291,7 @@ }, { "kind": "conditional-branch", - "line": 1642, + "line": 1649, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "1 applied", "surface": "android", @@ -4259,7 +4299,7 @@ }, { "kind": "ui-named-argument", - "line": 1823, + "line": 1830, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "OpenClaw mobile", "surface": "android", @@ -4267,7 +4307,7 @@ }, { "kind": "ui-named-argument", - "line": 1827, + "line": 1834, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Open profile", "surface": "android", @@ -4275,7 +4315,7 @@ }, { "kind": "conditional-branch", - "line": 1912, + "line": 1919, "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt", "source": "Main session", "surface": "android", diff --git a/apps/android/README.md b/apps/android/README.md index c5eaed54bab..067bf2d1122 100644 --- a/apps/android/README.md +++ b/apps/android/README.md @@ -19,6 +19,7 @@ OpenClaw Android is the officially released Google Play app. It connects to an O - [x] Voice tab full functionality - [x] Screen tab full functionality - [x] Skill Workshop settings can filter proposals, inspect proposal content, and apply/reject/quarantine drafts through Gateway RPCs +- [x] Per-app language selection for translated resources follows Android system settings and persistence ## Open in Android Studio diff --git a/apps/android/app/build.gradle.kts b/apps/android/app/build.gradle.kts index 1863bb7f1c2..73be1f4ff13 100644 --- a/apps/android/app/build.gradle.kts +++ b/apps/android/app/build.gradle.kts @@ -124,11 +124,48 @@ android { } } + bundle { + language { + // The in-app picker can select a locale outside the device language list. + // Without a Play Core download path, every translated resource must stay installed. + enableSplit = false + } + } + buildFeatures { compose = true buildConfig = true } + androidResources { + generateLocaleConfig = true + localeFilters += + listOf( + "ar", + "de", + "en", + "es", + "fa", + "fr", + "hi", + "in", + "it", + "ja", + "ko", + "nl", + "pl", + "pt-rBR", + "ru", + "sv", + "th", + "tr", + "uk", + "vi", + "zh-rCN", + "zh-rTW", + ) + } + compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 @@ -200,6 +237,8 @@ dependencies { androidTestImplementation(composeBom) implementation(libs.androidx.core.ktx) + // AppCompat owns per-app locale persistence and Activity recreation on API 31-32. + implementation(libs.androidx.appcompat) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) implementation(libs.androidx.webkit) diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml index 2aa7eeb0810..89a94ab0867 100644 --- a/apps/android/app/src/main/AndroidManifest.xml +++ b/apps/android/app/src/main/AndroidManifest.xml @@ -52,6 +52,14 @@ android:name=".NodeForegroundService" android:exported="false" android:foregroundServiceType="connectedDevice|microphone" /> + + + language.languageTag?.equals(exactTag, ignoreCase = true) == true } + if (exactMatch != null) return exactMatch + + return entries.firstOrNull { language -> + val supportedLocale = language.languageTag?.let(Locale::forLanguageTag) ?: return@firstOrNull false + LocaleListCompat.matchesLanguageAndScript(locale, supportedLocale) + } + } + } +} + +internal fun appLanguageFromLocales(locales: LocaleListCompat): AppLanguage = + if (locales.isEmpty) { + AppLanguage.System + } else { + (0 until locales.size()).firstNotNullOfOrNull { index -> locales[index]?.let(AppLanguage::fromLocale) } + ?: AppLanguage.System + } + +internal fun currentAppLanguage(): AppLanguage = appLanguageFromLocales(AppCompatDelegate.getApplicationLocales()) + +internal fun localesForAppLanguage(language: AppLanguage): LocaleListCompat = language.languageTag?.let(LocaleListCompat::forLanguageTags) ?: LocaleListCompat.getEmptyLocaleList() + +internal fun setAppLanguage(language: AppLanguage) { + val locales = localesForAppLanguage(language) + if (locales != AppCompatDelegate.getApplicationLocales()) { + AppCompatDelegate.setApplicationLocales(locales) + } +} + +internal fun currentSystemLanguageTag(context: Context): String { + val systemLocales = LocaleManagerCompat.getSystemLocales(context) + val locale = systemLocales[0] ?: Resources.getSystem().configuration.locales[0] + return locale.toLanguageTag() +} + +internal fun appLanguageRowSubtitle( + language: AppLanguage, + systemLanguageTag: String, +): String { + val languageTag = language.languageTag + if (languageTag != null) return "OpenClaw translations · $languageTag" + return "Follow Android · $systemLanguageTag" +} diff --git a/apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt b/apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt index fd2a1aca187..67018220846 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt @@ -5,9 +5,9 @@ import ai.openclaw.app.ui.RootScreen import android.content.Intent import android.os.Bundle import android.view.WindowManager -import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Surface @@ -38,7 +38,7 @@ import kotlinx.coroutines.withContext /** * Main Android activity that owns Compose UI attachment and runtime UI wiring. */ -class MainActivity : ComponentActivity() { +class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by viewModels() private lateinit var permissionRequester: PermissionRequester private var initializedViewModel: MainViewModel? = null @@ -103,7 +103,9 @@ class MainActivity : ComponentActivity() { override fun onStop() { foreground = false - initializedViewModel?.setForeground(false) + if (shouldNotifyRuntimeBackgrounded(isChangingConfigurations)) { + initializedViewModel?.setForeground(false) + } super.onStop() } @@ -123,6 +125,9 @@ class MainActivity : ComponentActivity() { initializedViewModel = readyViewModel readyViewModel.setForeground(foreground) startViewModelCollectors(readyViewModel) + if (!readyViewModel.claimInitialIntentRouting()) { + pendingIntentRouter.discardInitialIntent() + } pendingIntentRouter.activate { initialIntent -> handleAssistantIntent(viewModel = readyViewModel, intent = initialIntent) } @@ -186,9 +191,13 @@ class MainActivity : ComponentActivity() { internal class MainActivityPendingIntentRouter { private var activated = false private var pendingIntent: Intent? = null + private var pendingIntentIsInitial = false fun setInitialIntent(intent: Intent?) { - if (!activated) pendingIntent = intent + if (!activated) { + pendingIntent = intent + pendingIntentIsInitial = true + } } fun onNewIntent( @@ -200,6 +209,13 @@ internal class MainActivityPendingIntentRouter { return } pendingIntent = intent + pendingIntentIsInitial = false + } + + fun discardInitialIntent() { + if (activated || !pendingIntentIsInitial) return + pendingIntent = null + pendingIntentIsInitial = false } fun activate(routeIntent: (Intent) -> Unit): Boolean { @@ -207,10 +223,24 @@ internal class MainActivityPendingIntentRouter { activated = true pendingIntent?.let(routeIntent) pendingIntent = null + pendingIntentIsInitial = false return true } } +/** Keeps launch intents one-shot across same-process Activity recreation, but not process death. */ +internal class MainActivityInitialIntentGate { + private var claimed = false + + fun claim(): Boolean { + if (claimed) return false + claimed = true + return true + } +} + +internal fun shouldNotifyRuntimeBackgrounded(isChangingConfigurations: Boolean): Boolean = !isChangingConfigurations + /** Preserves one-shot runtime UI startup while allowing screenshot fixtures to skip side effects. */ internal class MainActivityRuntimeUiStarter { private var completed = false diff --git a/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt b/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt index ff6ac8d2397..1effd9fe342 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt @@ -69,6 +69,7 @@ class MainViewModel( @Volatile private var foreground = false @Volatile private var runtimeStartupQueued = false + private val initialIntentGate = MainActivityInitialIntentGate() private val _requestedHomeDestination = MutableStateFlow(null) val requestedHomeDestination: StateFlow = _requestedHomeDestination @@ -92,9 +93,20 @@ class MainViewModel( return runtime } + internal fun claimInitialIntentRouting(): Boolean = initialIntentGate.claim() + internal fun enterScreenshotFixtureMode(scene: AndroidScreenshotScene) { check(BuildConfig.DEBUG) { "Android screenshot fixtures require a debug build" } - check(runtimeRef.value == null) { "Screenshot fixture mode must be selected before runtime startup" } + runtimeRef.value?.let { runtime -> + // The ViewModel survives locale recreation; keep the fixture runtime instead of + // treating the restored Activity as a second fixture startup. + check(runtime.mode == NodeRuntimeMode.ScreenshotFixture) { + "Screenshot fixture mode must be selected before live runtime startup" + } + runtime.setForeground(foreground) + _requestedHomeDestination.value = scene.homeDestination + return + } prefs.setOnboardingCompleted(true) prefs.setAppearanceThemeMode(AppearanceThemeMode.Dark) prefs.setDisplayName("Pixel") @@ -302,6 +314,9 @@ class MainViewModel( * Starts runtime on foreground entry only after onboarding has completed. */ fun setForeground(value: Boolean) { + // The ViewModel survives configuration recreation. Ignore the replacement + // Activity's duplicate true edge so it cannot restart gateway work. + if (foreground == value) return foreground = value if ( shouldStartRuntimeOnForeground( diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt index c035253d29c..b45971c2714 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt @@ -1,6 +1,7 @@ package ai.openclaw.app.ui import ai.openclaw.app.AndroidLicenseNotice +import ai.openclaw.app.AppLanguage import ai.openclaw.app.AppearanceThemeMode import ai.openclaw.app.BuildConfig import ai.openclaw.app.GatewayAgentSummary @@ -17,7 +18,10 @@ import ai.openclaw.app.LocationMode import ai.openclaw.app.MainViewModel import ai.openclaw.app.NotificationPackageFilterMode import ai.openclaw.app.SensitiveFeatureConfig +import ai.openclaw.app.appLanguageRowSubtitle import ai.openclaw.app.chat.ChatPendingToolCall +import ai.openclaw.app.currentAppLanguage +import ai.openclaw.app.currentSystemLanguageTag import ai.openclaw.app.gateway.GatewayRegistryEntryKind import ai.openclaw.app.gatewayTalkSetupDescription import ai.openclaw.app.gatewayTalkSetupStatusText @@ -27,6 +31,7 @@ import ai.openclaw.app.loadAndroidLicenseNotices import ai.openclaw.app.locationModeAfterBackgroundSettings import ai.openclaw.app.node.DeviceNotificationListenerService import ai.openclaw.app.photoReadPermissionsForRequest +import ai.openclaw.app.setAppLanguage import ai.openclaw.app.ui.design.ClawDetailRow import ai.openclaw.app.ui.design.ClawIconBadge import ai.openclaw.app.ui.design.ClawListItem @@ -96,6 +101,7 @@ import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.GraphicEq import androidx.compose.material.icons.filled.Image import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.Language import androidx.compose.material.icons.filled.LocationOn import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Mic @@ -1385,12 +1391,16 @@ private fun AppearanceSettingsScreen( onBack: () -> Unit, ) { val themeMode by viewModel.appearanceThemeMode.collectAsState() + val context = LocalContext.current + var appLanguage by remember { mutableStateOf(currentAppLanguage()) } + val systemLanguageTag = currentSystemLanguageTag(context) - SettingsDetailFrame(title = "Appearance", subtitle = "A calm, high-contrast OpenClaw interface.", icon = Icons.Default.Palette, onBack = onBack) { + SettingsDetailFrame(title = "Appearance", subtitle = "Theme and translated Android text.", icon = Icons.Default.Palette, onBack = onBack) { SettingsMetricPanel( rows = listOf( SettingsMetric("Theme", appearanceThemeSummary(themeMode)), + SettingsMetric("Language", appLanguage.displayName), SettingsMetric("Contrast", "High"), SettingsMetric("Typography", "Readable"), ), @@ -1405,9 +1415,59 @@ private fun AppearanceSettingsScreen( ) } } + ClawPanel { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + Text(text = "App language", style = ClawTheme.type.section, color = ClawTheme.colors.text) + Text( + text = "Changes Android text that OpenClaw has translated. Screens with English-only copy stay unchanged.", + style = ClawTheme.type.caption, + color = ClawTheme.colors.textMuted, + ) + AppLanguage.entries.forEachIndexed { index, language -> + if (index > 0) HorizontalDivider(color = ClawTheme.colors.border) + AppLanguageRow( + language = language, + selected = language == appLanguage, + systemLanguageTag = systemLanguageTag, + onClick = { + appLanguage = language + setAppLanguage(language) + }, + ) + } + } + } } } +@Composable +private fun AppLanguageRow( + language: AppLanguage, + selected: Boolean, + systemLanguageTag: String, + onClick: () -> Unit, +) { + ClawListItem( + title = language.displayName, + subtitle = appLanguageRowSubtitle(language = language, systemLanguageTag = systemLanguageTag), + leading = { ClawIconBadge(Icons.Default.Language) }, + trailing = + if (selected) { + { + Icon( + imageVector = Icons.Default.Check, + contentDescription = "Selected", + modifier = Modifier.size(18.dp), + tint = ClawTheme.colors.primary, + ) + } + } else { + null + }, + onClick = onClick, + ) +} + internal fun appearanceThemeSummary(mode: AppearanceThemeMode): String = mode.displayLabel internal fun appearanceThemeOptions(): List = AppearanceThemeMode.entries.map { it.displayLabel } 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 cfc1b88ed90..88875624144 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 @@ -15,6 +15,7 @@ import ai.openclaw.app.MainViewModel import ai.openclaw.app.NodeRuntime import ai.openclaw.app.R import ai.openclaw.app.chat.ChatSessionEntry +import ai.openclaw.app.currentAppLanguage import ai.openclaw.app.ui.chat.ChatScreen import ai.openclaw.app.ui.design.ClawBottomNav import ai.openclaw.app.ui.design.ClawDesignTheme @@ -1469,6 +1470,7 @@ private fun SettingsShellScreen( SettingsDetailScreen(viewModel = viewModel, route = route, onBack = onBack) return } + val appLanguage = currentAppLanguage() ClawScaffold( contentPadding = PaddingValues(start = 16.dp, top = 10.dp, end = 16.dp, bottom = 4.dp), @@ -1535,7 +1537,12 @@ private fun SettingsShellScreen( SettingsRow("Canvas", "Screen surface", Icons.AutoMirrored.Filled.ScreenShare, status = isConnected, route = SettingsRoute.Canvas), SettingsRow("Notifications", if (notificationForwardingEnabled) "Smart delivery" else "Off", Icons.Default.Notifications, route = SettingsRoute.Notifications), SettingsRow("Phone Capabilities", if (cameraEnabled) "Camera enabled" else "Locked", Icons.Default.Lock, status = !cameraEnabled, route = SettingsRoute.PhoneCapabilities), - SettingsRow("Appearance", appearanceThemeSummary(appearanceThemeMode), Icons.Default.Palette, route = SettingsRoute.Appearance), + SettingsRow( + "Appearance", + "${appearanceThemeSummary(appearanceThemeMode)} · ${appLanguage.displayName}", + Icons.Default.Palette, + route = SettingsRoute.Appearance, + ), SettingsRow("About", "Version and update", Icons.Default.Storage, route = SettingsRoute.About), SettingsRow("Health", "Diagnostics", Icons.Default.Settings, status = isConnected, route = SettingsRoute.Health), ) diff --git a/apps/android/app/src/main/res/resources.properties b/apps/android/app/src/main/res/resources.properties new file mode 100644 index 00000000000..92481bb0b1f --- /dev/null +++ b/apps/android/app/src/main/res/resources.properties @@ -0,0 +1 @@ +unqualifiedResLocale=en 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 new file mode 100644 index 00000000000..ad6d4d39b11 --- /dev/null +++ b/apps/android/app/src/test/java/ai/openclaw/app/AppLanguageTest.kt @@ -0,0 +1,114 @@ +package ai.openclaw.app + +import androidx.core.os.LocaleListCompat +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import org.xmlpull.v1.XmlPullParser + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34]) +class AppLanguageTest { + @Test + fun supportedLanguagesMatchPackagedTranslations() { + assertEquals( + setOf( + "ar", + "de", + "en", + "es", + "fa", + "fr", + "hi", + "id", + "it", + "ja", + "ko", + "nl", + "pl", + "pt-BR", + "ru", + "sv", + "th", + "tr", + "uk", + "vi", + "zh-CN", + "zh-TW", + ), + AppLanguage.entries.mapNotNull(AppLanguage::languageTag).toSet(), + ) + } + + @Test + fun everyLanguageRoundTripsThroughAndroidLocales() { + AppLanguage.entries.forEach { language -> + assertEquals(language, appLanguageFromLocales(localesForAppLanguage(language))) + } + } + + @Test + fun systemUsesAnEmptyLocaleList() { + assertTrue(localesForAppLanguage(AppLanguage.System).isEmpty) + assertEquals(AppLanguage.System, appLanguageFromLocales(LocaleListCompat.getEmptyLocaleList())) + } + + @Test + fun languageTagsNormalizeAtThePlatformBoundary() { + assertEquals(AppLanguage.Indonesian, AppLanguage.fromLanguageTag("in")) + assertEquals(AppLanguage.PortugueseBrazil, AppLanguage.fromLanguageTag("PT-br")) + assertEquals(AppLanguage.English, AppLanguage.fromLanguageTag("en-US")) + assertEquals(AppLanguage.German, AppLanguage.fromLanguageTag("de-DE")) + assertEquals(AppLanguage.ChineseTraditional, AppLanguage.fromLanguageTag("zh-Hant-HK")) + assertEquals(AppLanguage.System, AppLanguage.fromLanguageTag(null)) + } + + @Test + fun requestedLocaleListUsesTheFirstSupportedLanguage() { + assertEquals( + AppLanguage.French, + appLanguageFromLocales(LocaleListCompat.forLanguageTags("xx,fr-FR,de-DE")), + ) + } + + @Test + fun generatedLocaleConfigMatchesPickerLanguages() { + val parser = RuntimeEnvironment.getApplication().resources.getXml(R.xml._generated_res_locale_config) + val packagedTags = mutableSetOf() + var defaultLocaleTag: String? = null + while (parser.eventType != XmlPullParser.END_DOCUMENT) { + if (parser.eventType == XmlPullParser.START_TAG) { + when (parser.name) { + "locale-config" -> defaultLocaleTag = parser.getAttributeValue(androidNamespace, "defaultLocale") + "locale" -> packagedTags += AppLanguage.fromLanguageTag(parser.getAttributeValue(androidNamespace, "name")).languageTag + } + } + parser.next() + } + + assertEquals("en", defaultLocaleTag) + assertEquals(AppLanguage.entries.mapNotNull(AppLanguage::languageTag).toSet(), packagedTags) + } + + @Test + fun everyPickerOptionHasAUniqueLabel() { + val labels = AppLanguage.entries.map(AppLanguage::displayName) + assertFalse(labels.any(String::isBlank)) + assertEquals(labels.size, labels.toSet().size) + } + + @Test + fun systemSubtitleReportsTheActualSystemLocale() { + assertEquals("Follow Android · en-US", appLanguageRowSubtitle(AppLanguage.System, "en-US")) + assertEquals("OpenClaw translations · ja", appLanguageRowSubtitle(AppLanguage.Japanese, "en-US")) + } + + private companion object { + const val androidNamespace = "http://schemas.android.com/apk/res/android" + } +} diff --git a/apps/android/app/src/test/java/ai/openclaw/app/MainActivityLifecycleTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/MainActivityLifecycleTest.kt index a7174ad3234..6210fb40eea 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/MainActivityLifecycleTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/MainActivityLifecycleTest.kt @@ -41,6 +41,47 @@ class MainActivityLifecycleTest { assertEquals(listOf(next), routed) } + @Test + fun pendingIntentRouterDiscardsOnlyRecreatedInitialIntent() { + val router = MainActivityPendingIntentRouter() + val routed = mutableListOf() + + router.setInitialIntent(Intent("recreated")) + router.discardInitialIntent() + + assertTrue(router.activate(routed::add)) + assertTrue(routed.isEmpty()) + } + + @Test + fun pendingIntentRouterKeepsNewIntentAcrossRecreationGate() { + val router = MainActivityPendingIntentRouter() + val routed = mutableListOf() + val replacement = Intent("replacement") + + router.setInitialIntent(Intent("recreated")) + router.onNewIntent(replacement, routed::add) + router.discardInitialIntent() + + assertTrue(router.activate(routed::add)) + assertEquals(listOf(replacement), routed) + } + + @Test + fun initialIntentGateDistinguishesRecreationFromProcessRestoration() { + val retainedGate = MainActivityInitialIntentGate() + + assertTrue(retainedGate.claim()) + assertFalse(retainedGate.claim()) + assertTrue(MainActivityInitialIntentGate().claim()) + } + + @Test + fun runtimeStaysForegroundAcrossConfigurationRecreation() { + assertFalse(shouldNotifyRuntimeBackgrounded(isChangingConfigurations = true)) + assertTrue(shouldNotifyRuntimeBackgrounded(isChangingConfigurations = false)) + } + @Test fun runtimeUiStarterWaitsForReadinessAndStartsOnce() { val starter = MainActivityRuntimeUiStarter() diff --git a/apps/android/gradle/libs.versions.toml b/apps/android/gradle/libs.versions.toml index 3898e741d73..7fda068b369 100644 --- a/apps/android/gradle/libs.versions.toml +++ b/apps/android/gradle/libs.versions.toml @@ -1,6 +1,7 @@ [versions] agp = "9.2.1" androidx-activity = "1.13.0" +androidx-appcompat = "1.7.1" androidx-benchmark = "1.4.1" androidx-camera = "1.6.0" androidx-compose-bom = "2026.06.01" @@ -30,6 +31,7 @@ serialization-json = "1.11.0" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } +androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-benchmark-macro-junit4 = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "androidx-benchmark" } androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" } androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" }