diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/GalleryAppTopBar.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/GalleryAppTopBar.kt index 4cceaf382..610d6e142 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/GalleryAppTopBar.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/GalleryAppTopBar.kt @@ -133,7 +133,7 @@ fun GalleryTopAppBar( // Click a button to navigate up. AppBarActionType.NAVIGATE_UP -> { - TextButton(onClick = rightAction.actionFn) { Text("Done") } + TextButton(onClick = rightAction.actionFn) { Text(stringResource(R.string.done)) } } else -> {} diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/common/Types.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/common/Types.kt index 4abfd31f8..4ae69f730 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/common/Types.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/common/Types.kt @@ -16,6 +16,7 @@ package com.google.ai.edge.gallery.common +import androidx.annotation.StringRes import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import com.squareup.moshi.JsonClass @@ -65,6 +66,7 @@ data class SkillTryOutChip( val label: String, val prompt: String, val skillName: String, + @StringRes val labelResId: Int = 0, ) data class SkillInfo( diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/AddOrEditSkillBottomSheet.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/AddOrEditSkillBottomSheet.kt index 8318902f2..54b341b58 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/AddOrEditSkillBottomSheet.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/AddOrEditSkillBottomSheet.kt @@ -679,7 +679,7 @@ private fun ScriptsTabContent( AlertDialog( onDismissRequest = { showDeleteConfirmation = false }, title = { Text(stringResource(R.string.delete_script_dialog_title)) }, - text = { Text("Are you sure you want to delete '$selectedScript'?") }, + text = { Text(stringResource(R.string.delete_script_confirmation, selectedScript ?: "")) }, confirmButton = { Button( onClick = { diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/AgentChatScreen.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/AgentChatScreen.kt index 5311c4488..59bf5d2c3 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/AgentChatScreen.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/AgentChatScreen.kt @@ -376,14 +376,14 @@ fun AgentChatScreen( ) Text( buildAnnotatedString { - append("Use specialized, high-order reasoning by loading different skills or ") + append(stringResource(R.string.agent_skills_description_part1)) append( buildTrackableUrlAnnotatedString( url = "https://github.com/google-ai-edge/gallery/tree/main/skills", - linkText = "creating\u00A0your\u00A0own", + linkText = stringResource(R.string.agent_skills_link_text), ) ) - append(".\n\nTry tapping a sample prompt below to see Agent Skills in action!") + append(stringResource(R.string.agent_skills_description_part2)) }, style = MaterialTheme.typography.headlineSmall.copy(fontSize = 16.sp, lineHeight = 22.sp), @@ -434,7 +434,10 @@ fun AgentChatScreen( ) { Icon(promptChip.icon, contentDescription = null, modifier = Modifier.size(20.dp)) Spacer(modifier = Modifier.width(4.dp)) - Text(promptChip.label) + Text( + if (promptChip.labelResId != 0) stringResource(promptChip.labelResId) + else promptChip.label + ) } } } @@ -490,7 +493,7 @@ fun AgentChatScreen( if (showAlertForDisabledSkill) { AlertDialog( onDismissRequest = { showAlertForDisabledSkill = false }, - title = { Text("The \"$disabledSkillName\" skill is currently disabled") }, + title = { Text(stringResource(R.string.skill_disabled_title, disabledSkillName)) }, text = { Text(stringResource(R.string.enable_skill_dialog_content)) }, confirmButton = { Button(onClick = { showAlertForDisabledSkill = false }) { diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/AgentChatTaskModule.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/AgentChatTaskModule.kt index ee8b9b3d0..bad2ac8bc 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/AgentChatTaskModule.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/AgentChatTaskModule.kt @@ -30,24 +30,25 @@ import com.google.ai.edge.litertlm.tool import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntoSet import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -class AgentChatTask @Inject constructor() : CustomTask { +class AgentChatTask @Inject constructor(private val context: Context) : CustomTask { private val agentTools = AgentTools() override val task: Task = Task( id = BuiltInTaskId.LLM_AGENT_CHAT, - label = "Agent Skills", + label = context.getString(R.string.task_agent_chat_label), category = Category.LLM, iconVectorResourceId = R.drawable.agent, newFeature = true, models = mutableListOf(), - description = "Chat with on-device large language models with skills and tools", - shortDescription = "Complete agentic tasks with chat", + description = context.getString(R.string.task_agent_chat_description), + shortDescription = context.getString(R.string.task_agent_chat_short_description), docUrl = "https://github.com/google-ai-edge/LiteRT-LM/blob/main/kotlin/README.md", sourceCodeUrl = "https://github.com/google-ai-edge/gallery/blob/main/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/", @@ -122,7 +123,7 @@ class AgentChatTask @Inject constructor() : CustomTask { internal object AgentChatTaskModule { @Provides @IntoSet - fun provideTask(): CustomTask { - return AgentChatTask() + fun provideTask(@ApplicationContext context: Context): CustomTask { + return AgentChatTask(context) } } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/SkillManagerViewModel.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/SkillManagerViewModel.kt index 27738bddd..50f31e6f7 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/SkillManagerViewModel.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/SkillManagerViewModel.kt @@ -42,6 +42,7 @@ import com.google.ai.edge.gallery.data.AllowedSkill import com.google.ai.edge.gallery.data.DataStoreRepository import com.google.ai.edge.gallery.data.SkillAllowlist import com.google.ai.edge.gallery.proto.Skill +import com.google.ai.edge.gallery.R import com.google.ai.edge.litertlm.Contents import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext @@ -69,30 +70,35 @@ val TRYOUT_CHIPS: List = label = "Interactive Map", prompt = "Show me Googleplex on interactive map.", skillName = "interactive-map", + labelResId = R.string.skill_chip_interactive_map, ), SkillTryOutChip( icon = Icons.Outlined.Kitchen, label = "Kitchen Adventure", prompt = "Start kitchen adventure", skillName = "kitchen-adventure", + labelResId = R.string.skill_chip_kitchen_adventure, ), SkillTryOutChip( icon = Icons.Outlined.Tag, label = "Calculate Hash", prompt = "What is the sha1 hash of \"gemma\"?", skillName = "calculate-hash", + labelResId = R.string.skill_chip_calculate_hash, ), SkillTryOutChip( icon = Icons.Outlined.ScreenRotation, label = "Text Spinner", prompt = "Spin \"Gemma\" on my head", skillName = "text-spinner", + labelResId = R.string.skill_chip_text_spinner, ), SkillTryOutChip( icon = Icons.Outlined.Email, label = "Send Email", prompt = "Send email 'Good morning' to abc@example.com. Content: 'Any plans for tonight?'", skillName = "send-email", + labelResId = R.string.skill_chip_send_email, ), SkillTryOutChip( icon = Icons.Outlined.SentimentVerySatisfied, @@ -100,18 +106,21 @@ val TRYOUT_CHIPS: List = prompt = "Log yesterday's mood as 2 because it was raining quite heavily, and log today's mood as 9 because I had a great time playing pickleball again. Then show me my mood dashboard.", skillName = "mood-tracker", + labelResId = R.string.skill_chip_track_mood, ), SkillTryOutChip( icon = Icons.Outlined.LocalLibrary, label = "Query Wikipedia", prompt = "Check Wikipedia about Oscars 2026. Tell me who won the best picture.", skillName = "query-wikipedia", + labelResId = R.string.skill_chip_query_wikipedia, ), SkillTryOutChip( icon = Icons.Outlined.QrCode, label = "Generate QR code", prompt = "Generate QR code for https://deepmind.google/models/gemma/", skillName = "qr-code", + labelResId = R.string.skill_chip_generate_qr_code, ), ) diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/SkillTesterBottomSheet.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/SkillTesterBottomSheet.kt index f2c2c43ad..79f6b6ac4 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/SkillTesterBottomSheet.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/agentchat/SkillTesterBottomSheet.kt @@ -90,7 +90,7 @@ fun SkillTesterBottomSheet(agentTools: AgentTools, skill: Skill, onDismiss: () - OutlinedTextField( value = inputData, onValueChange = { inputData = it }, - label = { Text("Input Data") }, + label = { Text(stringResource(R.string.input_data)) }, modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), ) @@ -98,7 +98,7 @@ fun SkillTesterBottomSheet(agentTools: AgentTools, skill: Skill, onDismiss: () - OutlinedTextField( value = customData, onValueChange = { customData = it }, - label = { Text("Custom Data") }, + label = { Text(stringResource(R.string.custom_data)) }, modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp), ) diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/examplecustomtask/ExampleCustomTaskScreen.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/examplecustomtask/ExampleCustomTaskScreen.kt index 82560d4ce..2d91baf44 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/examplecustomtask/ExampleCustomTaskScreen.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/examplecustomtask/ExampleCustomTaskScreen.kt @@ -38,6 +38,8 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.google.ai.edge.gallery.R import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -63,9 +65,9 @@ data class ExampleCustomTaskModelInstance(val content: String) * These keys are used to uniquely identify and retrieve values for configurable parameters within a * model. */ -val EXAMPLE_CUSTOM_TASK_CONFIG_KEY_FONT_SIZE = ConfigKey(id = "font_size", label = "Font size") +val EXAMPLE_CUSTOM_TASK_CONFIG_KEY_FONT_SIZE = ConfigKey(id = "font_size", label = "Font size", labelResId = R.string.config_label_font_size) val EXAMPLE_CUSTOM_TASK_CONFIG_KEY_MAX_CHAR_COUNT = - ConfigKey(id = "max_char_count", label = "Max character count") + ConfigKey(id = "max_char_count", label = "Max character count", labelResId = R.string.config_label_max_char_count) /** * A list of configurable parameters for the `ExampleCustomTask`'s models. @@ -132,7 +134,10 @@ fun ExampleCustomTaskScreen( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(16.dp), ) { - Text("Text color: ") + Text( + stringResource(R.string.text_color_label), + color = MaterialTheme.colorScheme.onSurface, + ) for (color in colors) { Box( modifier = diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/MobileActionsModule.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/MobileActionsModule.kt index 451c371c4..4bd25c8ca 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/MobileActionsModule.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/MobileActionsModule.kt @@ -15,10 +15,12 @@ */ package com.google.ai.edge.gallery.customtasks.mobileactions +import android.content.Context import com.google.ai.edge.gallery.customtasks.common.CustomTask import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntoSet @@ -27,7 +29,7 @@ import dagger.multibindings.IntoSet internal object MobileActionsModule { @Provides @IntoSet - fun provideTask(): CustomTask { - return MobileActionsTask() + fun provideTask(@ApplicationContext context: Context): CustomTask { + return MobileActionsTask(context) } } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/MobileActionsTask.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/MobileActionsTask.kt index 592a60872..015e4cda9 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/MobileActionsTask.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/MobileActionsTask.kt @@ -42,16 +42,16 @@ private const val TAG = "AGMATask" * A custom task that demonstrates how to use function calling to control various device * functionalities. */ -class MobileActionsTask @Inject constructor() : CustomTask { +class MobileActionsTask @Inject constructor(private val context: Context) : CustomTask { private var curActions = mutableStateListOf() private val tools = listOf(tool(MobileActionsTools(onFunctionCalled = { curActions.add(it) }))) override val task = Task( id = BuiltInTaskId.LLM_MOBILE_ACTIONS, - label = "Mobile Actions", - description = "Perform various device actions through Function Gemma", - shortDescription = "Leverage device mobile actions", + label = context.getString(R.string.task_mobile_actions_label), + description = context.getString(R.string.task_mobile_actions_description), + shortDescription = context.getString(R.string.task_mobile_actions_short_description), docUrl = "https://github.com/google-ai-edge/LiteRT-LM/blob/main/kotlin/README.md", sourceCodeUrl = "https://github.com/google-ai-edge/gallery/blob/main/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions", diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/tinygarden/TinyGardenTask.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/tinygarden/TinyGardenTask.kt index 4dbd8af47..7818a1f1b 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/tinygarden/TinyGardenTask.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/tinygarden/TinyGardenTask.kt @@ -67,7 +67,7 @@ Tips: """ /** A custom task that demonstrates how to use FunctionGemma to play a simple gardening game. */ -class TinyGardenTask @Inject constructor() : CustomTask { +class TinyGardenTask @Inject constructor(private val context: Context) : CustomTask { private val _updateChannel = Channel(Channel.BUFFERED) private val commandFlow = _updateChannel.receiveAsFlow() private val tools = @@ -84,10 +84,9 @@ class TinyGardenTask @Inject constructor() : CustomTask { override val task = Task( id = BuiltInTaskId.LLM_TINY_GARDEN, - label = "Tiny Garden", - description = - "Use natural language to plant, water, and harvest in this fully offline mini-game.\n\nNote: This is powered by the experimental FunctionGemma model optimized for latency. Due to its compact size (270M), it works well on simple instructions but responses may vary to more complex interactions.", - shortDescription = "Use natural language to plant", + label = context.getString(R.string.task_tiny_garden_label), + description = context.getString(R.string.task_tiny_garden_description), + shortDescription = context.getString(R.string.task_tiny_garden_short_description), docUrl = "https://github.com/google-ai-edge/LiteRT-LM/blob/main/kotlin/README.md", sourceCodeUrl = "https://github.com/google-ai-edge/gallery/blob/main/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/tinygarden", diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/tinygarden/TinyGardenTaskModule.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/tinygarden/TinyGardenTaskModule.kt index 17bfef975..e16035fe2 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/tinygarden/TinyGardenTaskModule.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/tinygarden/TinyGardenTaskModule.kt @@ -15,10 +15,12 @@ */ package com.google.ai.edge.gallery.customtasks.tinygarden +import android.content.Context import com.google.ai.edge.gallery.customtasks.common.CustomTask import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntoSet @@ -27,7 +29,7 @@ import dagger.multibindings.IntoSet internal object TinyGardenTaskModule { @Provides @IntoSet - fun provideTask(): CustomTask { - return TinyGardenTask() + fun provideTask(@ApplicationContext context: Context): CustomTask { + return TinyGardenTask(context) } } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/data/Config.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/data/Config.kt index 4ca4b09d5..eb5bc1e12 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/data/Config.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/data/Config.kt @@ -17,6 +17,7 @@ package com.google.ai.edge.gallery.data import androidx.annotation.StringRes +import com.google.ai.edge.gallery.R import kotlin.math.abs /** @@ -42,40 +43,40 @@ enum class ValueType { BOOLEAN, } -data class ConfigKey(val id: String, val label: String) +data class ConfigKey(val id: String, val label: String, @StringRes val labelResId: Int? = null) object ConfigKeys { - val MAX_TOKENS = ConfigKey("max_tokens", "Max tokens") - val TOPK = ConfigKey("topk", "TopK") - val TOPP = ConfigKey("topp", "TopP") - val TEMPERATURE = ConfigKey("temperature", "Temperature") - val DEFAULT_MAX_TOKENS = ConfigKey("default_max_tokens", "Default max tokens") - val DEFAULT_TOPK = ConfigKey("default_topk", "Default TopK") - val DEFAULT_TOPP = ConfigKey("default_topp", "Default TopP") - val DEFAULT_TEMPERATURE = ConfigKey("default_temperature", "Default temperature") - val SUPPORT_IMAGE = ConfigKey("support_image", "Support image") - val SUPPORT_AUDIO = ConfigKey("support_audio", "Support audio") - val SUPPORT_TINY_GARDEN = ConfigKey("support_tiny_garden", "Support tiny garden") - val SUPPORT_MOBILE_ACTIONS = ConfigKey("support_mobile_actions", "Support mobile actions") - val SUPPORT_THINKING = ConfigKey("support_thinking", "Support thinking") - val ENABLE_THINKING = ConfigKey("enable_thinking", "Enable thinking") - val MAX_RESULT_COUNT = ConfigKey("max_result_count", "Max result count") - val USE_GPU = ConfigKey("use_gpu", "Use GPU") - val ACCELERATOR = ConfigKey("accelerator", "Accelerator") - val VISION_ACCELERATOR = ConfigKey("vision_accelerator", "Vision accelerator") - val COMPATIBLE_ACCELERATORS = ConfigKey("compatible_accelerators", "Compatible accelerators") - val WARM_UP_ITERATIONS = ConfigKey("warm_up_iterations", "Warm up iterations") - val BENCHMARK_ITERATIONS = ConfigKey("benchmark_iterations", "Benchmark iterations") - val ITERATIONS = ConfigKey("iterations", "Iterations") - val THEME = ConfigKey("theme", "Theme") - val NAME = ConfigKey("name", "Name") - val MODEL_TYPE = ConfigKey("model_type", "Model type") - val MODEL = ConfigKey("model", "Model") + val MAX_TOKENS = ConfigKey("max_tokens", "Max tokens", R.string.config_label_max_tokens) + val TOPK = ConfigKey("topk", "TopK", R.string.config_label_topk) + val TOPP = ConfigKey("topp", "TopP", R.string.config_label_topp) + val TEMPERATURE = ConfigKey("temperature", "Temperature", R.string.config_label_temperature) + val DEFAULT_MAX_TOKENS = ConfigKey("default_max_tokens", "Default max tokens", R.string.config_label_default_max_tokens) + val DEFAULT_TOPK = ConfigKey("default_topk", "Default TopK", R.string.config_label_default_topk) + val DEFAULT_TOPP = ConfigKey("default_topp", "Default TopP", R.string.config_label_default_topp) + val DEFAULT_TEMPERATURE = ConfigKey("default_temperature", "Default temperature", R.string.config_label_default_temperature) + val SUPPORT_IMAGE = ConfigKey("support_image", "Support image", R.string.config_label_support_image) + val SUPPORT_AUDIO = ConfigKey("support_audio", "Support audio", R.string.config_label_support_audio) + val SUPPORT_TINY_GARDEN = ConfigKey("support_tiny_garden", "Support tiny garden", R.string.config_label_support_tiny_garden) + val SUPPORT_MOBILE_ACTIONS = ConfigKey("support_mobile_actions", "Support mobile actions", R.string.config_label_support_mobile_actions) + val SUPPORT_THINKING = ConfigKey("support_thinking", "Support thinking", R.string.config_label_support_thinking) + val ENABLE_THINKING = ConfigKey("enable_thinking", "Enable thinking", R.string.config_label_enable_thinking) + val MAX_RESULT_COUNT = ConfigKey("max_result_count", "Max result count", R.string.config_label_max_result_count) + val USE_GPU = ConfigKey("use_gpu", "Use GPU", R.string.config_label_use_gpu) + val ACCELERATOR = ConfigKey("accelerator", "Accelerator", R.string.config_label_accelerator) + val VISION_ACCELERATOR = ConfigKey("vision_accelerator", "Vision accelerator", R.string.config_label_vision_accelerator) + val COMPATIBLE_ACCELERATORS = ConfigKey("compatible_accelerators", "Compatible accelerators", R.string.config_label_compatible_accelerators) + val WARM_UP_ITERATIONS = ConfigKey("warm_up_iterations", "Warm up iterations", R.string.config_label_warm_up_iterations) + val BENCHMARK_ITERATIONS = ConfigKey("benchmark_iterations", "Benchmark iterations", R.string.config_label_benchmark_iterations) + val ITERATIONS = ConfigKey("iterations", "Iterations", R.string.config_label_iterations) + val THEME = ConfigKey("theme", "Theme", R.string.config_label_theme) + val NAME = ConfigKey("name", "Name", R.string.config_label_name) + val MODEL_TYPE = ConfigKey("model_type", "Model type", R.string.config_label_model_type) + val MODEL = ConfigKey("model", "Model", R.string.config_label_model) val RESET_CONVERSATION_TURN_COUNT = - ConfigKey("reset_conversation_turn_count", "Number of turns before the conversation resets") - val PREFILL_TOKENS = ConfigKey("prefill_tokens", "Prefill tokens") - val DECODE_TOKENS = ConfigKey("decode_tokens", "Decode tokens") - val NUMBER_OF_RUNS = ConfigKey("number_of_runs", "Number of runs") + ConfigKey("reset_conversation_turn_count", "Number of turns before the conversation resets", R.string.config_label_reset_conversation_turn_count) + val PREFILL_TOKENS = ConfigKey("prefill_tokens", "Prefill tokens", R.string.config_label_prefill_tokens) + val DECODE_TOKENS = ConfigKey("decode_tokens", "Decode tokens", R.string.config_label_decode_tokens) + val NUMBER_OF_RUNS = ConfigKey("number_of_runs", "Number of runs", R.string.config_label_number_of_runs) } /** diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/benchmark/BenchmarkResultsViewer.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/benchmark/BenchmarkResultsViewer.kt index a94b02441..6fc9758cb 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/benchmark/BenchmarkResultsViewer.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/benchmark/BenchmarkResultsViewer.kt @@ -400,24 +400,24 @@ fun BenchmarkResultsViewer( verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(start = 6.dp, top = 6.dp, bottom = 4.dp), ) { - StatRow(label = "Model", value = llmResult.baiscInfo.modelName) + StatRow(label = stringResource(R.string.benchmark_label_model), value = llmResult.baiscInfo.modelName) StatRow( - label = "Accelerator", + label = stringResource(R.string.benchmark_label_accelerator), value = llmResult.baiscInfo.accelerator, ) StatRow( - label = "Prefill tokens", + label = stringResource(R.string.benchmark_label_prefill_tokens), value = "${llmResult.baiscInfo.prefillTokens}", ) StatRow( - label = "Decode tokens", + label = stringResource(R.string.benchmark_label_decode_tokens), value = "${llmResult.baiscInfo.decodeTokens}", ) StatRow( - label = "Number of runs", + label = stringResource(R.string.benchmark_label_number_of_runs), value = "${llmResult.baiscInfo.numberOfRuns}", ) - StatRow(label = "App version", value = llmResult.baiscInfo.appVersion) + StatRow(label = stringResource(R.string.benchmark_label_app_version), value = llmResult.baiscInfo.appVersion) } } @@ -499,10 +499,10 @@ fun BenchmarkResultsViewer( val baselineStats = uiState.baselineResult?.benchmarkResult?.llmResult?.stats ValueSeriesRow( - label = "Prefill speed", + label = stringResource(R.string.benchmark_label_prefill_speed), valueSeries = llmResult.stats.prefillSpeed, aggregation = result.aggregation, - unit = "tokens/sec", + unit = stringResource(R.string.benchmark_unit_tokens_per_sec), baselineValueSeries = if (result.id != uiState.baselineResult?.id) { baselineStats?.prefillSpeed @@ -517,10 +517,10 @@ fun BenchmarkResultsViewer( }, ) ValueSeriesRow( - label = "Decode speed", + label = stringResource(R.string.benchmark_label_decode_speed), valueSeries = llmResult.stats.decodeSpeed, aggregation = result.aggregation, - unit = "tokens/sec", + unit = stringResource(R.string.benchmark_unit_tokens_per_sec), baselineValueSeries = if (result.id != uiState.baselineResult?.id) { baselineStats?.decodeSpeed @@ -535,10 +535,10 @@ fun BenchmarkResultsViewer( }, ) ValueSeriesRow( - label = "Time to first token", + label = stringResource(R.string.benchmark_label_time_to_first_token), valueSeries = llmResult.stats.timeToFirstToken, aggregation = result.aggregation, - unit = "sec", + unit = stringResource(R.string.benchmark_unit_sec), baselineValueSeries = if (result.id != uiState.baselineResult?.id) { baselineStats?.timeToFirstToken @@ -554,14 +554,14 @@ fun BenchmarkResultsViewer( lessIsBetter = true, ) StatRow( - label = "First init time", + label = stringResource(R.string.benchmark_label_first_init_time), value = String.format( Locale.getDefault(), "%.2f", llmResult.stats.firstInitTimeMs, ), - unit = "ms", + unit = stringResource(R.string.benchmark_unit_ms), baselineValue = if (result.id != uiState.baselineResult?.id) { baselineStats?.firstInitTimeMs @@ -572,10 +572,10 @@ fun BenchmarkResultsViewer( ) if (llmResult.stats.nonFirstInitTimeMs.valueCount > 1) { ValueSeriesRow( - label = "Steady init time", + label = stringResource(R.string.benchmark_label_steady_init_time), valueSeries = llmResult.stats.nonFirstInitTimeMs, aggregation = result.aggregation, - unit = "ms", + unit = stringResource(R.string.benchmark_unit_ms), baselineValueSeries = if (result.id != uiState.baselineResult?.id) { baselineStats?.nonFirstInitTimeMs diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ConfigDialog.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ConfigDialog.kt index 04f2e53d8..40de26a7d 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ConfigDialog.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ConfigDialog.kt @@ -107,6 +107,15 @@ import kotlinx.coroutines.launch private const val TAG = "AGConfigDialog" +@Composable +private fun getLocalizedLabel(label: String, @StringRes labelResId: Int?): String { + return if (labelResId != null && labelResId != 0) { + stringResource(labelResId) + } else { + label + } +} + private data class Tab(@StringRes val labelResId: Int) private val TABS = @@ -244,7 +253,7 @@ fun ConfigDialog( ) { // Cancel button. if (showCancel) { - TextButton(onClick = { onDismissed() }) { Text("Cancel") } + TextButton(onClick = { onDismissed() }) { Text(stringResource(R.string.cancel)) } } // Ok button @@ -302,7 +311,7 @@ fun ConfigEditorsPanel(configs: List, values: SnapshotStateMap) { Column(modifier = Modifier.fillMaxWidth()) { // Field label. - Text(config.key.label, style = MaterialTheme.typography.titleSmall) + Text(getLocalizedLabel(config.key.label, config.key.labelResId), style = MaterialTheme.typography.titleSmall) // Content label. val label = try { @@ -347,7 +356,7 @@ fun NumberSliderRow(config: NumberSliderConfig, values: SnapshotStateMap SegmentedButton( @@ -555,7 +564,7 @@ fun BottomSheetSelectorRow( verticalArrangement = Arrangement.spacedBy(4.dp), ) { if (showLabel) { - Text(config.key.label, style = MaterialTheme.typography.titleSmall) + Text(getLocalizedLabel(config.key.label, config.key.labelResId), style = MaterialTheme.typography.titleSmall) } Row( horizontalArrangement = Arrangement.SpaceBetween, diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/DownloadAndTryButton.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/DownloadAndTryButton.kt index b10c8fdb4..ff98cae15 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/DownloadAndTryButton.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/DownloadAndTryButton.kt @@ -523,9 +523,9 @@ fun DownloadAndTryButton( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(horizontal = 16.dp), ) { - Text("Acknowledge user agreement", style = MaterialTheme.typography.titleLarge) + Text(stringResource(R.string.acknowledge_user_agreement), style = MaterialTheme.typography.titleLarge) Text( - "This is a gated model. Please click the button below to view and agree to the user agreement. After accepting, simply close that tab to proceed with the model download.", + stringResource(R.string.gated_model_agreement_message), style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(vertical = 16.dp), ) @@ -545,7 +545,7 @@ fun DownloadAndTryButton( showAgreementAckSheet = false } ) { - Text("Open user agreement") + Text(stringResource(R.string.open_user_agreement)) } } } @@ -560,10 +560,10 @@ fun DownloadAndTryButton( tint = MaterialTheme.colorScheme.error, ) }, - title = { Text("Unknown network error") }, - text = { Text("Please check your internet connection.") }, + title = { Text(stringResource(R.string.error_unknown_network)) }, + text = { Text(stringResource(R.string.error_check_internet)) }, onDismissRequest = { showErrorDialog = false }, - confirmButton = { TextButton(onClick = { showErrorDialog = false }) { Text("Close") } }, + confirmButton = { TextButton(onClick = { showErrorDialog = false }) { Text(stringResource(R.string.close)) } }, ) } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ErrorDialog.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ErrorDialog.kt index 430d21927..c685fe74e 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ErrorDialog.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ErrorDialog.kt @@ -28,8 +28,10 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog +import com.google.ai.edge.gallery.R @Composable fun ErrorDialog(error: String, onDismiss: () -> Unit) { @@ -41,7 +43,7 @@ fun ErrorDialog(error: String, onDismiss: () -> Unit) { ) { // Title Text( - "Error", + stringResource(R.string.error), style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(bottom = 8.dp), ) @@ -54,7 +56,7 @@ fun ErrorDialog(error: String, onDismiss: () -> Unit) { ) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { - Button(onClick = onDismiss) { Text("Close") } + Button(onClick = onDismiss) { Text(stringResource(R.string.close)) } } } } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ChatView.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ChatView.kt index f374fd2c9..5c305d636 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ChatView.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ChatView.kt @@ -114,6 +114,9 @@ fun ChatView( val modelManagerUiState by modelManagerViewModel.uiState.collectAsState() val selectedModel = modelManagerUiState.selectedModel + // Pre-fetch string resources for use in lambdas + val liveCameraSessionEndedText = stringResource(R.string.live_camera_session_ended) + // Image viewer related. var selectedImageIndex by remember { mutableIntStateOf(-1) } var allImageViewerImages by remember { mutableStateOf>(listOf()) } @@ -230,7 +233,7 @@ fun ChatView( model = selectedModel, message = ChatMessageInfo( - content = "Live camera session ended. Average FPS: $averageFps" + content = liveCameraSessionEndedText.format(averageFps) ), ) }, diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageInputText.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageInputText.kt index 3ba2c0a75..2279faaa4 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageInputText.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageInputText.kt @@ -452,7 +452,7 @@ fun MessageInputText( horizontalArrangement = Arrangement.spacedBy(6.dp), ) { Icon(Icons.Rounded.PhotoCamera, contentDescription = null) - Text("Take a picture") + Text(stringResource(R.string.take_a_picture)) } }, enabled = enableAddImageMenuItems, @@ -489,7 +489,7 @@ fun MessageInputText( horizontalArrangement = Arrangement.spacedBy(6.dp), ) { Icon(Icons.Rounded.Photo, contentDescription = null) - Text("Pick from album") + Text(stringResource(R.string.pick_from_album)) } }, enabled = enableAddImageMenuItems, @@ -521,7 +521,7 @@ fun MessageInputText( horizontalArrangement = Arrangement.spacedBy(6.dp), ) { Icon(Icons.Rounded.Mic, contentDescription = null) - Text("Record audio clip") + Text(stringResource(R.string.record_audio_clip)) } }, enabled = enableRecordAudioClipMenuItems, @@ -553,7 +553,7 @@ fun MessageInputText( horizontalArrangement = Arrangement.spacedBy(6.dp), ) { Icon(Icons.Rounded.AudioFile, contentDescription = null) - Text("Pick wav file") + Text(stringResource(R.string.pick_wav_file)) } }, enabled = enableRecordAudioClipMenuItems, @@ -588,7 +588,7 @@ fun MessageInputText( horizontalArrangement = Arrangement.spacedBy(6.dp), ) { Icon(Icons.Rounded.History, contentDescription = null) - Text("Input history") + Text(stringResource(R.string.input_history)) } }, onClick = { diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ModelNotDownloaded.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ModelNotDownloaded.kt index 4b445fd9f..e19b9973f 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ModelNotDownloaded.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ModelNotDownloaded.kt @@ -27,6 +27,8 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import com.google.ai.edge.gallery.R /** * Composable function to display a button to download model if the model has not been downloaded. @@ -38,7 +40,7 @@ fun ModelNotDownloaded(modifier: Modifier = Modifier, onClicked: () -> Unit) { verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { - Button(onClick = onClicked) { Text("Download & Try it", maxLines = 1) } + Button(onClick = onClicked) { Text(stringResource(R.string.download_and_try_it), maxLines = 1) } } } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/TextInputHistorySheet.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/TextInputHistorySheet.kt index 4e322f43a..56d37b431 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/TextInputHistorySheet.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/TextInputHistorySheet.kt @@ -176,8 +176,8 @@ private fun SheetContent( if (showConfirmDeleteDialog) { AlertDialog( onDismissRequest = { showConfirmDeleteDialog = false }, - title = { Text("Clear history?") }, - text = { Text("Are you sure you want to clear the history? This action cannot be undone.") }, + title = { Text(stringResource(R.string.clear_history_title)) }, + text = { Text(stringResource(R.string.clear_history_content)) }, confirmButton = { Button( onClick = { diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/tos/GemmaTermsOfUseDialog.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/tos/GemmaTermsOfUseDialog.kt index beb050011..cf5c821be 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/tos/GemmaTermsOfUseDialog.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/tos/GemmaTermsOfUseDialog.kt @@ -72,14 +72,14 @@ fun GemmaTermsOfUseDialog( Column(modifier = Modifier.verticalScroll(rememberScrollState()).weight(1f, fill = false)) { Text( buildAnnotatedString { - append("Gemma models on the Google AI Edge Gallery app are governed by the ") + append(stringResource(R.string.gemma_tos_content_part1)) append( buildTrackableUrlAnnotatedString( url = "https://ai.google.dev/gemma/terms", - linkText = "Gemma Terms of Service", + linkText = stringResource(R.string.gemma_tos_link_text), ) ) - append(". Please review these terms and ensure you agree before continuing.") + append(stringResource(R.string.gemma_tos_content_part2)) }, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/HomeScreen.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/HomeScreen.kt index e0540dbef..d48fdfd65 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/HomeScreen.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/HomeScreen.kt @@ -528,14 +528,14 @@ fun HomeScreen( ) }, title = { Text(uiState.loadingModelAllowlistError) }, - text = { Text("Please check your internet connection and try again later.") }, + text = { Text(stringResource(R.string.load_model_list_error_content)) }, onDismissRequest = { modelManagerViewModel.loadModelAllowlist() }, confirmButton = { - TextButton(onClick = { modelManagerViewModel.loadModelAllowlist() }) { Text("Retry") } + TextButton(onClick = { modelManagerViewModel.loadModelAllowlist() }) { Text(stringResource(R.string.retry)) } }, dismissButton = { TextButton(onClick = { modelManagerViewModel.clearLoadModelAllowlistError() }) { - Text("Cancel") + Text(stringResource(R.string.cancel)) } }, ) @@ -620,8 +620,8 @@ private fun AppTitle(enableAnimation: Boolean) { @Composable fun AppTitleGm4(enableAnimation: Boolean) { - val text1 = "Google" - val text2 = "AI Edge Gallery" + val text1 = stringResource(R.string.app_name_google) + val text2 = stringResource(R.string.app_name_ai_edge_gallery) val annotatedText = buildAnnotatedString { withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.onSurface)) { append(text1) } append(" ") @@ -664,10 +664,10 @@ private fun IntroText(enableAnimation: Boolean, gm4: Boolean) { val introText = buildAnnotatedString { val gemma4Url = "https://ai.google.dev/gemma" if (gm4) { - append("Discover the power of on-device AI models from the ") - append(buildTrackableUrlAnnotatedString(url = litertUrl, linkText = "LiteRT community")) - append(", featuring the all-new ") - append(buildTrackableUrlAnnotatedString(url = gemma4Url, linkText = "Gemma 4")) + append(stringResource(R.string.gm4_intro_part1)) + append(buildTrackableUrlAnnotatedString(url = litertUrl, linkText = stringResource(R.string.gm4_intro_litert_community))) + append(stringResource(R.string.gm4_intro_part2)) + append(buildTrackableUrlAnnotatedString(url = gemma4Url, linkText = stringResource(R.string.gm4_intro_gemma4))) append(".") } else { append("${stringResource(R.string.app_intro)} ") @@ -719,7 +719,7 @@ private fun TryGm4IntroText(enableAnimation: Boolean) { tint = MaterialTheme.colorScheme.primary, ) Text( - text = "Try Gemma 4 today", + text = stringResource(R.string.gm4_try_today), style = MaterialTheme.typography.headlineSmall.copy( fontWeight = FontWeight.Medium, @@ -731,7 +731,7 @@ private fun TryGm4IntroText(enableAnimation: Boolean) { } Text( - "Gemma 4 E2B & E4B are here! Try them in AI Chat, Agent Skills, or the use cases below.", + stringResource(R.string.gm4_intro_text), style = MaterialTheme.typography.bodyMedium, modifier = Modifier.graphicsLayer { @@ -861,10 +861,10 @@ private fun TaskList( ) { val chatToDescription = mapOf( - BuiltInTaskId.LLM_CHAT to "Chat with the latest Gemma 4 model today", + BuiltInTaskId.LLM_CHAT to stringResource(R.string.gm4_chat_description), // use "\u00a0" to make sure the word before and after it should always be together when // wrapping lines. - BuiltInTaskId.LLM_AGENT_CHAT to "Have Gemma 4 complete agentic tasks for\u00A0you", + BuiltInTaskId.LLM_AGENT_CHAT to stringResource(R.string.gm4_agent_description), ) for (task in listOf( @@ -882,7 +882,7 @@ private fun TaskList( } Text( - text = "Explore other use cases", + text = stringResource(R.string.explore_other_use_cases), style = MaterialTheme.typography.headlineSmall.copy( fontWeight = FontWeight.Medium, @@ -977,6 +977,8 @@ private fun TaskCard( description: String = "", square: Boolean = false, ) { + val modelCountOneStr = stringResource(R.string.model_count_one) + val modelCountMultipleStr = stringResource(R.string.model_count_multiple) // Observes the model count and updates the model count label with a fade-in/fade-out animation // whenever the count changes. val modelCount by remember { @@ -992,8 +994,8 @@ private fun TaskCard( val modelCountLabel by remember { derivedStateOf { when (modelCount) { - 1 -> "1 Model" - else -> "%d Models".format(modelCount) + 1 -> modelCountOneStr + else -> modelCountMultipleStr.format(modelCount) } } } @@ -1107,7 +1109,7 @@ private fun TaskCard( contentAlignment = Alignment.Center, ) { Text( - "New", + stringResource(R.string.new_feature_label), color = MaterialTheme.customColors.newFeatureTextColor, style = MaterialTheme.typography.labelLarge, ) @@ -1134,7 +1136,7 @@ private fun TaskCard( if (task.experimental) { Icon( painter = painterResource(R.drawable.ic_experiment), - contentDescription = "Experimental", + contentDescription = stringResource(R.string.category_experimental), modifier = Modifier.size(20.dp).padding(start = 4.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant, ) diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/MobileActionsChallengeDialog.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/MobileActionsChallengeDialog.kt index ac25d98fc..b75ae8dbd 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/MobileActionsChallengeDialog.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/MobileActionsChallengeDialog.kt @@ -79,17 +79,17 @@ fun MobileActionsChallengeDialog( fontWeight = FontWeight.Bold, ) val instructions = buildAnnotatedString { - append("1. ") - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { append("On your computer") } - append(", open ") - append(buildTrackableUrlAnnotatedString(url = guideUrl, linkText = "this guide")) + append(stringResource(R.string.mobile_actions_challenge_instruction_1_part1)) + withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { append(stringResource(R.string.mobile_actions_challenge_instruction_1_bold)) } + append(stringResource(R.string.mobile_actions_challenge_instruction_1_part2)) + append(buildTrackableUrlAnnotatedString(url = guideUrl, linkText = stringResource(R.string.mobile_actions_challenge_instruction_1_link))) append( - "\n2. Follow the instructions to fine tune the model and convert it to .litertlm format." + "\n2. ${stringResource(R.string.mobile_actions_challenge_instruction_2).substring(3)}" ) - append("\n3. Transfer the file to this phone.") - append("\n4. Tap ") - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { append("Load Model") } - append(" below to unlock the demo.") + append("\n3. ${stringResource(R.string.mobile_actions_challenge_instruction_3).substring(3)}") + append(stringResource(R.string.mobile_actions_challenge_instruction_4_part1)) + withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { append(stringResource(R.string.mobile_actions_challenge_instruction_4_bold)) } + append(stringResource(R.string.mobile_actions_challenge_instruction_4_part2)) } Text( text = instructions, diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/NewReleaseNotification.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/NewReleaseNotification.kt index 28868757b..224ac963b 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/NewReleaseNotification.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/NewReleaseNotification.kt @@ -39,12 +39,14 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner import com.google.ai.edge.gallery.BuildConfig +import com.google.ai.edge.gallery.R import com.google.ai.edge.gallery.common.getJsonResponse import com.google.ai.edge.gallery.ui.common.ClickableLink import kotlin.math.max @@ -113,7 +115,7 @@ fun NewReleaseNotification() { .padding(4.dp), ) { Text( - "New release $newReleaseVersion available", + stringResource(R.string.new_release_available, newReleaseVersion), style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(start = 12.dp), ) diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/PromoScreenGm4.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/PromoScreenGm4.kt index 466ede6f4..1be237909 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/PromoScreenGm4.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/PromoScreenGm4.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -89,17 +90,17 @@ fun PromoScreenGm4(onDismiss: () -> Unit) { // Center text. Image(ImageVector.vectorResource(R.drawable.gemini_star), contentDescription = null) Text( - "Introducing", + stringResource(R.string.promo_gm4_introducing), style = MaterialTheme.typography.headlineSmall.copy(fontSize = 20.sp), color = Color.White, ) Text( - "Gemma 4", + stringResource(R.string.promo_gm4_name), style = MaterialTheme.typography.headlineSmall.copy(fontSize = 38.sp), color = Color.White, ) Text( - "Experience the world’s most capable open models, designed to run frontier-level intelligence directly on your hardware.", + stringResource(R.string.promo_gm4_description), style = MaterialTheme.typography.headlineSmall.copy(fontSize = 16.sp, lineHeight = 21.sp), textAlign = TextAlign.Center, color = Color(0xfff2f2f2), @@ -107,7 +108,7 @@ fun PromoScreenGm4(onDismiss: () -> Unit) { // Dismiss button. TextButton(onClick = onDismiss, modifier = Modifier.padding(top = 24.dp)) { - Text("Dismiss", color = Color(0xFFA8C7FA)) + Text(stringResource(R.string.dismiss), color = Color(0xFFA8C7FA)) } } } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/SettingsDialog.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/SettingsDialog.kt index 4f34880bf..26e7cf87a 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/SettingsDialog.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/home/SettingsDialog.kt @@ -126,7 +126,7 @@ fun SettingsDialog( // Dialog title and subtitle. Column { Text( - "Settings", + stringResource(R.string.settings_title), style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(bottom = 8.dp), ) @@ -147,7 +147,7 @@ fun SettingsDialog( // Theme switcher. Column(modifier = Modifier.fillMaxWidth().semantics(mergeDescendants = true) {}) { Text( - "Theme", + stringResource(R.string.settings_theme), style = MaterialTheme.typography.titleSmall.copy(fontWeight = FontWeight.Medium), ) MultiChoiceSegmentedButtonRow { @@ -193,7 +193,7 @@ fun SettingsDialog( verticalArrangement = Arrangement.spacedBy(4.dp), ) { Text( - "HuggingFace access token", + stringResource(R.string.settings_huggingface_token), style = MaterialTheme.typography.titleSmall.copy(fontWeight = FontWeight.Medium), ) // Show the start of the token. @@ -211,12 +211,12 @@ fun SettingsDialog( ) } else { Text( - "Not available", + stringResource(R.string.settings_token_not_available), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) Text( - "The token will be automatically retrieved when a gated model is downloaded", + stringResource(R.string.settings_token_auto_retrieved), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -229,7 +229,7 @@ fun SettingsDialog( }, enabled = curHfToken != null, ) { - Text("Clear") + Text(stringResource(R.string.clear)) } val handleSaveToken = { modelManagerViewModel.saveAccessToken( @@ -270,7 +270,7 @@ fun SettingsDialog( Box(modifier = Modifier.padding(start = 16.dp).weight(1f)) { if (customHfToken.isEmpty()) { Text( - "Enter token manually", + stringResource(R.string.settings_enter_token_manually), color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodySmall, ) @@ -294,7 +294,7 @@ fun SettingsDialog( // Third party licenses. Column(modifier = Modifier.fillMaxWidth().semantics(mergeDescendants = true) {}) { Text( - "Third-party libraries", + stringResource(R.string.settings_third_party_libraries), style = MaterialTheme.typography.titleSmall.copy(fontWeight = FontWeight.Medium), ) OutlinedButton( @@ -305,7 +305,7 @@ fun SettingsDialog( context.startActivity(intent) } ) { - Text("View licenses") + Text(stringResource(R.string.view_licenses)) } } @@ -337,7 +337,7 @@ fun SettingsDialog( horizontalArrangement = Arrangement.End, ) { // Close button - Button(onClick = { onDismissed() }) { Text("Close") } + Button(onClick = { onDismissed() }) { Text(stringResource(R.string.close)) } } } } @@ -348,11 +348,13 @@ fun SettingsDialog( } } +@Composable private fun themeLabel(theme: Theme): String { + val context = LocalContext.current return when (theme) { - Theme.THEME_AUTO -> "Auto" - Theme.THEME_LIGHT -> "Light" - Theme.THEME_DARK -> "Dark" - else -> "Unknown" + Theme.THEME_AUTO -> stringResource(R.string.theme_auto) + Theme.THEME_LIGHT -> stringResource(R.string.theme_light) + Theme.THEME_DARK -> stringResource(R.string.theme_dark) + else -> stringResource(R.string.theme_unknown) } } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatTaskModule.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatTaskModule.kt index 9bcdb49e3..c5fd1b1d4 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatTaskModule.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatTaskModule.kt @@ -47,6 +47,7 @@ import com.google.ai.edge.gallery.ui.theme.emptyStateTitle import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntoSet import javax.inject.Inject @@ -55,16 +56,16 @@ import kotlinx.coroutines.CoroutineScope //////////////////////////////////////////////////////////////////////////////////////////////////// // AI Chat. -class LlmChatTask @Inject constructor() : CustomTask { +class LlmChatTask @Inject constructor(private val context: Context) : CustomTask { override val task: Task = Task( id = BuiltInTaskId.LLM_CHAT, - label = "AI Chat", + label = context.getString(R.string.task_llm_chat_label), category = Category.LLM, icon = Icons.Outlined.Forum, models = mutableListOf(), - description = "Chat with on-device large language models", - shortDescription = "Chat with an on-device LLM", + description = context.getString(R.string.task_llm_chat_description), + shortDescription = context.getString(R.string.task_llm_chat_short_description), docUrl = "https://github.com/google-ai-edge/LiteRT-LM/blob/main/kotlin/README.md", sourceCodeUrl = "https://github.com/google-ai-edge/gallery/blob/main/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatModelHelper.kt", @@ -129,24 +130,24 @@ class LlmChatTask @Inject constructor() : CustomTask { internal object LlmChatTaskModule { @Provides @IntoSet - fun provideTask(): CustomTask { - return LlmChatTask() + fun provideTask(@ApplicationContext context: Context): CustomTask { + return LlmChatTask(context) } } //////////////////////////////////////////////////////////////////////////////////////////////////// // Ask image. -class LlmAskImageTask @Inject constructor() : CustomTask { +class LlmAskImageTask(private val context: Context) : CustomTask { override val task: Task = Task( id = BuiltInTaskId.LLM_ASK_IMAGE, - label = "Ask Image", + label = context.getString(R.string.task_llm_ask_image_label), category = Category.LLM, icon = Icons.Outlined.Mms, models = mutableListOf(), - description = "Ask questions about images with on-device large language models", - shortDescription = "Ask questions about images", + description = context.getString(R.string.task_llm_ask_image_description), + shortDescription = context.getString(R.string.task_llm_ask_image_short_description), docUrl = "https://github.com/google-ai-edge/LiteRT-LM/blob/main/kotlin/README.md", sourceCodeUrl = "https://github.com/google-ai-edge/gallery/blob/main/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatModelHelper.kt", @@ -193,25 +194,24 @@ class LlmAskImageTask @Inject constructor() : CustomTask { internal object LlmAskImageModule { @Provides @IntoSet - fun provideTask(): CustomTask { - return LlmAskImageTask() + fun provideTask(@ApplicationContext context: Context): CustomTask { + return LlmAskImageTask(context) } } //////////////////////////////////////////////////////////////////////////////////////////////////// // Ask audio. -class LlmAskAudioTask @Inject constructor() : CustomTask { +class LlmAskAudioTask(private val context: Context) : CustomTask { override val task: Task = Task( id = BuiltInTaskId.LLM_ASK_AUDIO, - label = "Audio Scribe", + label = context.getString(R.string.task_llm_ask_audio_label), category = Category.LLM, icon = Icons.Outlined.Mic, models = mutableListOf(), - description = - "Instantly transcribe and/or translate audio clips using on-device large language models", - shortDescription = "Transcribe and translate audio", + description = context.getString(R.string.task_llm_ask_audio_description), + shortDescription = context.getString(R.string.task_llm_ask_audio_short_description), docUrl = "https://github.com/google-ai-edge/LiteRT-LM/blob/main/kotlin/README.md", sourceCodeUrl = "https://github.com/google-ai-edge/gallery/blob/main/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatModelHelper.kt", @@ -258,7 +258,7 @@ class LlmAskAudioTask @Inject constructor() : CustomTask { internal object LlmAskAudioModule { @Provides @IntoSet - fun provideTask(): CustomTask { - return LlmAskAudioTask() + fun provideTask(@ApplicationContext context: Context): CustomTask { + return LlmAskAudioTask(context) } } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/LlmSingleTurnTaskModule.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/LlmSingleTurnTaskModule.kt index dab3ea7c1..ca30334f1 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/LlmSingleTurnTaskModule.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/LlmSingleTurnTaskModule.kt @@ -31,21 +31,22 @@ import com.google.ai.edge.gallery.ui.llmchat.LlmChatModelHelper import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntoSet import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -class LlmSingleTurnTask @Inject constructor() : CustomTask { +class LlmSingleTurnTask @Inject constructor(private val context: Context) : CustomTask { override val task: Task = Task( id = BuiltInTaskId.LLM_PROMPT_LAB, - label = "Prompt Lab", + label = context.getString(R.string.task_llm_prompt_lab_label), category = Category.LLM, icon = Icons.Outlined.Widgets, models = mutableListOf(), - description = "Single turn use cases with on-device large language models", - shortDescription = "Single turn use cases", + description = context.getString(R.string.task_llm_prompt_lab_description), + shortDescription = context.getString(R.string.task_llm_prompt_lab_short_description), docUrl = "https://github.com/google-ai-edge/LiteRT-LM/blob/main/kotlin/README.md", sourceCodeUrl = "https://github.com/google-ai-edge/gallery/blob/main/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatModelHelper.kt", @@ -91,7 +92,7 @@ class LlmSingleTurnTask @Inject constructor() : CustomTask { internal object LlmSingleTurnTaskModule { @Provides @IntoSet - fun provideTask(): CustomTask { - return LlmSingleTurnTask() + fun provideTask(@ApplicationContext context: Context): CustomTask { + return LlmSingleTurnTask(context) } } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/PromptTemplatesPanel.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/PromptTemplatesPanel.kt index 62b7895db..3f38ad82f 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/PromptTemplatesPanel.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/PromptTemplatesPanel.kt @@ -247,7 +247,7 @@ fun PromptTemplatesPanel( disabledContainerColor = Color.Transparent, ), textStyle = bodyLargeNarrow, - placeholder = { Text("Enter content") }, + placeholder = { Text(stringResource(R.string.enter_content)) }, modifier = Modifier.padding(bottom = 40.dp).focusRequester(focusRequester).semantics { contentDescription = cdContentInput @@ -302,7 +302,7 @@ fun PromptTemplatesPanel( modifier = Modifier.size(FilterChipDefaults.IconSize).alpha(0.3f), ) } - Text("Preview prompt", style = MaterialTheme.typography.labelMedium) + Text(stringResource(R.string.preview_prompt), style = MaterialTheme.typography.labelMedium) } } @@ -416,7 +416,7 @@ fun PromptTemplatesPanel( Column(modifier = Modifier.padding(bottom = 16.dp)) { // Title Text( - "Select an example", + stringResource(R.string.select_an_example), modifier = Modifier.fillMaxWidth().padding(16.dp), style = MaterialTheme.typography.titleLarge, ) diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/GlobalModelManager.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/GlobalModelManager.kt index 9e934756e..55e993f99 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/GlobalModelManager.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/GlobalModelManager.kt @@ -377,7 +377,7 @@ fun GlobalModelManager( if (showImportModelSheet) { ModalBottomSheet(onDismissRequest = { showImportModelSheet = false }, sheetState = sheetState) { Text( - "Import model", + stringResource(R.string.import_model_sheet_title), style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(vertical = 4.dp, horizontal = 16.dp), ) @@ -412,7 +412,7 @@ fun GlobalModelManager( modifier = Modifier.fillMaxWidth().padding(16.dp), ) { Icon(Icons.AutoMirrored.Outlined.NoteAdd, contentDescription = null) - Text("From local model file", modifier = Modifier.clearAndSetSemantics {}) + Text(stringResource(R.string.import_from_local_file), modifier = Modifier.clearAndSetSemantics {}) } } } @@ -446,7 +446,7 @@ fun GlobalModelManager( showImportingDialog = false // Show a snack bar for successful import. - scope.launch { snackbarHostState.showSnackbar("Model imported successfully") } + scope.launch { snackbarHostState.showSnackbar(context.getString(R.string.model_imported_successfully)) } }, ) } @@ -464,8 +464,8 @@ fun GlobalModelManager( ) }, onDismissRequest = { showUnsupportedFileTypeDialog = false }, - title = { Text("Unsupported file type") }, - text = { Text("Only \".task\" or \".litertlm\" file type is supported.") }, + title = { Text(stringResource(R.string.error_unsupported_file_type)) }, + text = { Text(stringResource(R.string.error_supported_file_types)) }, confirmButton = { Button(onClick = { showUnsupportedFileTypeDialog = false }) { Text(stringResource(R.string.ok)) @@ -485,8 +485,8 @@ fun GlobalModelManager( ) }, onDismissRequest = { showUnsupportedWebModelDialog = false }, - title = { Text("Unsupported model type") }, - text = { Text("Looks like the model is a web-only model and is not supported by the app.") }, + title = { Text(stringResource(R.string.error_unsupported_model_type)) }, + text = { Text(stringResource(R.string.error_web_only_model)) }, confirmButton = { Button(onClick = { showUnsupportedWebModelDialog = false }) { Text(stringResource(R.string.ok)) diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/ModelImportDialog.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/ModelImportDialog.kt index 78442588f..764ef97a4 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/ModelImportDialog.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/ModelImportDialog.kt @@ -193,7 +193,7 @@ fun ModelImportDialog( ) { // Title. Text( - "Import Model", + stringResource(R.string.import_model_dialog_title), style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(bottom = 8.dp), ) @@ -212,7 +212,7 @@ fun ModelImportDialog( horizontalArrangement = Arrangement.End, ) { // Cancel button. - TextButton(onClick = { onDismiss() }) { Text("Cancel") } + TextButton(onClick = { onDismiss() }) { Text(stringResource(R.string.cancel)) } // Import button Button( @@ -300,7 +300,7 @@ fun ModelImportDialog( onDone(importedModel) } ) { - Text("Import") + Text(stringResource(R.string.import_model)) } } } @@ -345,7 +345,7 @@ fun ModelImportingDialog( ) { // Title. Text( - "Import Model", + stringResource(R.string.import_model_dialog_title), style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(bottom = 8.dp), ) @@ -387,7 +387,7 @@ fun ModelImportingDialog( ) } Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { - Button(onClick = { onDismiss() }) { Text("Close") } + Button(onClick = { onDismiss() }) { Text(stringResource(R.string.close)) } } } } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/ModelList.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/ModelList.kt index 34cacdc65..d6059070f 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/ModelList.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/ModelList.kt @@ -251,14 +251,14 @@ fun ModelList( if (task.docUrl.isNotEmpty()) { ClickableLink( url = task.docUrl, - linkText = "API Documentation", + linkText = stringResource(R.string.api_documentation), icon = Icons.Outlined.Description, ) } if (task.sourceCodeUrl.isNotEmpty()) { ClickableLink( url = task.sourceCodeUrl, - linkText = "Example code", + linkText = stringResource(R.string.example_code), icon = Icons.Outlined.Code, ) } diff --git a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/PromoBannerGm4.kt b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/PromoBannerGm4.kt index 125c7e59c..1173bb43e 100644 --- a/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/PromoBannerGm4.kt +++ b/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/PromoBannerGm4.kt @@ -48,6 +48,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -92,9 +93,9 @@ fun PromoBannerGm4(onDismiss: () -> Unit, modifier: Modifier = Modifier) { .padding(horizontal = 16.dp) .padding(top = 16.dp, bottom = 8.dp) ) { - Text(text = "Gemma 4: now available", style = MaterialTheme.typography.titleMedium) + Text(text = stringResource(R.string.promo_gm4_title), style = MaterialTheme.typography.titleMedium) Text( - "Built from the same world-class technology as Gemini 3, Gemma 4 brings frontier intelligence to your mobile and edge devices.", + stringResource(R.string.promo_gm4_content), style = MaterialTheme.typography.bodyMedium.copy(fontSize = 12.sp, lineHeight = 15.sp), modifier = Modifier.padding(top = 4.dp), ) @@ -103,13 +104,13 @@ fun PromoBannerGm4(onDismiss: () -> Unit, modifier: Modifier = Modifier) { verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.End, ) { - TextButton(onClick = onDismiss, contentPadding = BUTTON_PADDING) { Text("Dismiss") } + TextButton(onClick = onDismiss, contentPadding = BUTTON_PADDING) { Text(stringResource(R.string.dismiss)) } Button( onClick = { uriHandler.openUri("https://ai.google.dev/gemma") }, modifier = Modifier.padding(start = 8.dp).height(32.dp), contentPadding = BUTTON_PADDING, ) { - Text("Read more") + Text(stringResource(R.string.read_more)) } } } diff --git a/Android/src/app/src/main/res/values-zh/strings.xml b/Android/src/app/src/main/res/values-zh/strings.xml new file mode 100644 index 000000000..da6b484e7 --- /dev/null +++ b/Android/src/app/src/main/res/values-zh/strings.xml @@ -0,0 +1,606 @@ + + + + 全部 + 添加 + 已添加 + +音频 + 当前模型每次对话只支持一张图片。要提问其他图片,请创建新对话。 + 正在初始化模型 + 请稍候,这可能需要最多1分钟 + 探索来自 + Google AI Edge Gallery + Google AI + Edge Gallery + 询问图片 + 开始使用,请点击下方的+添加图片(每次对话最多10张图片),然后输入提示词询问关于图片的问题! + 开始使用,请点击下方的+来添加一张图片(每次对话最多一张图片),然后输入提示词询问关于图片的问题! + 询问音频 + 开始使用,请点击下方的+添加音频片段(限制1个片段,最长30秒),然后输入提示词进行转录或翻译! + Gallery新闻 + 基准 + 基本信息 + 基准测试 + 如何比较基准测试结果 + 通过点击任意运行结果卡片上的**基准**标签来设置比较基准。我们将自动计算并显示**所有其他**运行结果卡片的百分比差异。 + 基准测试模型 + 基准测试结果 + 无基准测试结果 + 请确保预填充和解码令牌的总数保持在模型的最大令牌限制内(%d vs %d) + 整体最佳 + 实验性 + 取消 + 未标记 + 大语言模型 + 智能体 + 经典机器学习 + 实验性 + 输入消息… + + 大语言模型 + 显示思考过程 + 正在检查访问权限... + 关闭 + 全部折叠 + 放弃 + 放弃更改? + 您有未保存的更改。确定要放弃吗? + 删除基准测试结果 + 确定要删除该基准测试结果吗? + 错误 + 删除下载 + 确定要删除已下载的模型"%s"吗? + 对话已重置 + 引擎已重置 + 智能体 + 模型 + 结果 + 清除 + 收集 + 颜色 + 模型配置 + 系统提示词 + 继续 + 对话历史 + 复制 + 创建 + 删除 + 描述* + 已禁用 + 关闭 + 完成 + 下载 + 已下载 %1$s + 设置 + 管理应用设置 + 模型 + 浏览、试用和基准测试模型 + 编辑 + 编辑密钥 + 输入指令 + 全部展开 + 保存失败 + 我已关闭手电筒。 + 我已打开手电筒。 + 函数名称 + 生成并复制 + 授予权限 + 授予录音权限 + 按住说话 + 输入历史 + 指令 + 介绍 + 无效响应 + 了解更多并查看模型许可 + 正在聆听... + LiteRT社区 + 正在加载模型列表... + 过滤日志 + 日志查看器 + 没有匹配的日志 + 您选择的模型可能超出设备内存,这可能导致应用崩溃。为获得最佳体验,我们建议尝试较小的模型。 + 仍然继续 + 内存警告 + 设置最大令牌数超过10000可能导致应用不稳定。 + 正在初始化模型… + 已导入的模型 + + %d 个模型可用 + %d 个模型可用 + + 推荐模型 + 模型管理器 + 选择一个用例试用 + 模型尚未下载 + 名称 + 未收集剪贴画 + 下载模型"%s"失败 + 模型"%s"已下载完成 + 模型下载失败 + 模型下载成功 + 确定 + 从剪贴板粘贴 + 从相册选择 + 预览 + 松开发送 + 替换 + 结果 + 重置 + 点击"重置"将仅重置LiteRT-LM引擎。游戏进度不会受影响。 + 正在重置引擎... + 恢复默认 + 游戏进度不会受影响 + 运行 + 正在运行... + + %d 次运行 + %d 次运行 + + 再次运行 + 运行基准测试 + 此过程通常需要几分钟,一旦开始就无法中断。您要继续吗? + 正在运行模型基准测试... + 保存 + 密钥 + 选择已下载的模型 + 选择模型 + 已选择 %d 个 + 服务条款 + 查看应用服务条款 + Gemma禁止使用政策 + 分享 + 上滑取消 + 系统提示词已更新 + 拍照 + 点击更改颜色 + 点击图表查看数值 + 测试 + 输入提示词… + 输入提示词… + 欢迎使用Google AI Edge Gallery + Gemma使用条款 + 接受并继续 + 同意并继续 + 试用 + 全部开启 + 全部关闭 + 撤销 + 不支持的操作 + 我已将屏幕亮度设置为 %1$d%%。 + 正在设置定时器 %1$d 秒。 + 正在将"团队会议(上午10点-11点)"添加到您的日历。 + 正在打开相机应用。 + 移动操作 + 使用简单命令控制您的设备 + 支持的示例操作: + 模型响应 + 调用的函数 + 查看结果 + 查看控制台日志 + 预热中… + 未识别到函数 + 未识别到函数 + 未知错误 + 查看 + 全屏查看 + + + 参数 + 参数 + + 打开/关闭手电筒 + 打开手电筒 + 关闭手电筒 + 创建联系人 + 发送邮件 + 创建日历事件 + 在地图上显示位置 + 打开WIFI设置 + + + 移动操作微调挑战 + 构建它来玩它! + 此演示已锁定。要解锁移动操作智能体,您需要使用我们的开源方法自行编译模型。 + 说明: + 1. 在您的电脑上]]>,打开 此指南]]> + 2. 按照说明微调模型并将其转换为.litertlm格式。 + 3. 将文件传输到此手机。 + 4. 点击下方的 加载模型]]> 以解锁演示。 + 1. + 在您的电脑上 + ,打开 + 此指南 + \n4. 点击 + 加载模型 + 以解锁演示。 + 通过邮件发送指南给我 + 加载模型 + + + AI聊天 + 与设备上的大语言模型聊天。 + 添加脚本 + 添加默认脚本 + 添加技能 + 从精选列表添加技能 + 从社区贡献的精选技能列表中添加技能 + 从URL加载技能 + 输入托管在Web上的技能目录的URL + 导入本地技能 + 在本地导入技能目录 + 技能创建器 + 在应用内从头创建技能 + 从URL添加技能 + 导入本地技能 + 请先将整个技能目录"adb push"到/sdcard/Download + 创建技能 + 智能体技能 + 通过加载不同的技能或创建自己的技能,使用专业的高级推理能力。\n\n尝试点击下面的示例提示词,看看智能体技能的实际效果! + 第三方技能并非由Google编写或认可。Google对其内容、安全性或数据处理方式不承担责任。\n\n在提供敏感信息(如个人身份、令牌或API密钥)之前,请务必谨慎。\n\n使用智能体技能受该技能的适用条款约束。 + 添加第三方技能 + 同意 + 删除脚本 + 删除技能 + 确定要删除该技能吗? + 删除选中的技能 + + 确定要删除 %d 个技能吗? + 确定要删除 %d 个技能吗? + + + 已选择 %d 个 + 已选择 %d 个 + + 内置技能 + 自定义技能 + 描述需求 + 描述输入数据 + 字段名称需要与指令中的名称匹配。 + 描述输出数据 + 重复的脚本名称 + 请点击下方的技能按钮启用技能。 + 编辑技能 + 输入技能根目录的URL + 精选技能 + 社区贡献的精选技能列表 + 生成LLM提示词 + LLM提示词 + 学习如何编写技能 + 正在加载精选技能... + 管理技能 + 查看、创建和管理您的技能 + 启用超过 %s 个可能会影响性能。 + 未选择目录 + 选择技能根目录 + 提示词已复制到剪贴板。将其粘贴到您喜欢的LLM应用中生成代码,然后将代码复制/粘贴到这里。 + 厨房冒险 + 计算哈希 + 查询维基百科 + 绘制图表 + 交互式地图 + 旋转头上的标签 + 发送邮件 + 替换现有技能 + 已存在同名技能。您要替换它吗? + 搜索技能 + 选择脚本 + 脚本名称 + 脚本内容 + 技能 + + %d 个技能 + %d 个技能 + + 必填。格式为my-skill-name + 必填。技能功能和触发条件或关键词的简要描述。 + LLM必须遵循的Markdown格式的详细指令。 + 使用Call JS模板 + + + + + 添加 + + 导入模型 + + %1$s 任务,包含 %2$d 个模型 + + 错误 + + 从本地文件导入模型 + + 返回 + + 设置 + + 完成 + + 折叠 + + 展开 + + 停止 + + 删除 + + 关闭 + + 已选择 + + 模型设置 + + 重置会话 + + 当前模型是 %1$s。点击更改模型。 + + 关闭图片查看器 + + 复制到剪贴板 + + 提示词输入 + + 发送提示词 + + 模型响应文本 + + 清除输入历史 + + 删除输入历史条目 + + 内容输入 + + 添加示例提示词 + + 图片缩略图 + + 用户图片 %1$d + + 用户图片 %1$d,共 %2$d 张 + + 添加内容 + + 相机快门 + + 切换前置或后置摄像头 + + 选择文件 + + 播放音频 + + 停止播放 + + 发送音频片段 + + 开始录音 + + 已下载 + + 未下载 + + 正在下载 + + 下载失败 + + 聊天面板 + + 切换到键盘 + + 切换到语音 + + 显示历史 + + 更多选项 + + 帮助 + + 菜单 + + + 重试 + 请检查您的网络连接并稍后重试。 + Google + AI Edge Gallery + 探索来自 + LiteRT社区 + 的设备端AI模型的强大功能,包含全新的 + Gemma 4 + 立即试用Gemma 4 + Gemma 4 E2B和E4B来了!在AI聊天、智能体技能或下面的用例中试用它们。 + 立即与最新的Gemma 4模型聊天 + 让Gemma 4为您完成智能体任务 + 探索其他用例 + + Gemma 4:现已推出 + Gemma 4采用与Gemini 3相同的尖端技术构建,为您的移动和边缘设备带来前沿智能。 + 了解更多 + 导入 + 不支持的文件类型 + 不支持的模型类型 + 该模型似乎是仅限Web的模型,应用不支持。 + 输入内容 + 查看许可证 + 清除历史? + 确定要清除历史吗?此操作无法撤销。 + 录制音频片段 + 选择wav文件 + 打开用户协议 + 未知网络错误 + 请检查您的网络连接。 + 实时摄像头会话已结束。平均帧率:%1$d + 确定要删除\'%1$s\'吗? + 输入数据 + 自定义数据 + 文本颜色: + + + 设置 + 主题 + HuggingFace 访问令牌 + 不可用 + 当下载受限模型时,令牌将自动检索 + 手动输入令牌 + 第三方库 + 自动 + 浅色 + 深色 + 未知 + + + 导入模型 + 导入模型 + 从本地模型文件 + 模型导入成功 + 仅支持 \".task\" 或 \".litertlm\" 文件类型。 + + + 介绍 + Gemma 4 + 体验世界上最强大的开源模型,旨在直接在您的硬件上运行前沿智能。 + + + 1 个模型 + %d 个模型 + + + API 文档 + 示例代码 + + + 新版本 %s 可用 + + + AI 聊天 + 与设备上的大型语言模型聊天 + 与设备上的 LLM 聊天 + + + 询问图像 + 使用设备上的大型语言模型询问关于图像的问题 + 询问关于图像的问题 + + + 音频转录 + 使用设备上的大型语言模型即时转录和/或翻译音频片段 + 转录和翻译音频 + + + 提示实验室 + 使用设备上的大型语言模型进行单轮对话 + 单轮对话用例 + + + 智能体技能 + 与具有技能和工具的设备上大型语言模型聊天 + 通过聊天完成智能体任务 + + + 小花园 + 在这个完全离线的小游戏中使用自然语言种植、浇水和收获。\n\n注意:这由实验性的 FunctionGemma 模型驱动,针对延迟进行了优化。由于其紧凑的尺寸(270M),它适用于简单的指令,但对更复杂的交互响应可能会有所不同。 + 使用自然语言种植 + + + 移动操作 + 通过 Function Gemma 执行各种设备操作 + 利用设备移动操作 + + + 预览提示 + 选择示例 + + + 确认用户协议 + 这是一个受限模型。请点击下面的按钮查看并同意用户协议。接受后,只需关闭该标签页即可继续下载模型。 + + + 下载并试用 + + + "%s" 技能当前已禁用 + 通过加载不同的技能或 + 创建自己的技能 + ,使用专业的高级推理能力。\n\n尝试点击下面的示例提示词,看看智能体技能的实际效果! + + + Google AI Edge Gallery 应用中的 Gemma 模型受 + Gemma 服务条款 + 管辖。请在继续之前查看这些条款并确保您同意。 + + + 交互式地图 + 厨房冒险 + 计算哈希 + 文本旋转器 + 发送邮件 + 追踪我的心情 + 查询维基百科 + 生成二维码 + + + 模型 + 加速器 + 预填充令牌 + 解码令牌 + 运行次数 + 应用版本 + 预填充速度 + 解码速度 + 首个令牌时间 + 首次初始化时间 + 稳定初始化时间 + 令牌/秒 + + 毫秒 + + + 最大令牌数 + TopK + TopP + 温度 + 默认最大令牌数 + 默认 TopK + 默认 TopP + 默认温度 + 支持图像 + 支持音频 + 支持小花园 + 支持移动操作 + 支持思考 + 启用思考 + 最大结果数 + 使用 GPU + 加速器 + 视觉加速器 + 兼容的加速器 + 预热迭代次数 + 基准测试迭代次数 + 迭代次数 + 主题 + 名称 + 模型类型 + 模型 + 对话重置前的轮数 + 预填充令牌 + 解码令牌 + 运行次数 + + + 字体大小 + 最大字符数 + 你还不能访问这个模型。请完成这些步骤后再重试。 + 打开指引 + + diff --git a/Android/src/app/src/main/res/values/strings.xml b/Android/src/app/src/main/res/values/strings.xml index b4f975a33..4f700e5b9 100644 --- a/Android/src/app/src/main/res/values/strings.xml +++ b/Android/src/app/src/main/res/values/strings.xml @@ -15,394 +15,593 @@ --> - - All - Add - Added - +Audio - You cannot access this model yet. Please follow the steps and return when complete. - Open guide - The current model can only support 1 image per chat. To ask about another image, create a new chat. - Initializing model - Sit tight, this can take up to 1 minute - Explore a world of amazing on-device models from - Google AI Edge Gallery - Google AI - Edge Gallery - Ask Image - To get started, tap the + below to add an image (max 10 images per chat) and type a prompt to ask a question about it! - To get started, tap the + below to add an image (max 1 image per chat) and type a prompt to ask a question about it! - Ask Audio - To get started, tap the + below to add a audio clip (limited to 1 clip up to 30 seconds) and type a prompt to transcribe or translate it! - Gallery News - Baseline - Basic info - Benchmark - How to compare benchmark runs - Compare benchmark runs by tapping the **Baseline** chip on any run result card to set the run as the comparison baseline. We\'ll automatically calculate and display the percentage differences in **all other** run result cards. - Benchmark model - Benchmark results - No benchmark results - Ensure that the sum of prefill and decode tokens remains under the model\'s maximum token limit (%d vs %d) - Best overall - EXPERIMENTAL - Cancel - Unlabeled - LLM - Agents - Classical ML - Experimental - Type message… - You - LLM - Show thinking - Checking access... - Close - Collapse all - Discard - Discard changes? - You have unsaved changes. Are you sure you want to discard them? - Delete benchmark result - Are you sure you want to delete the benchmark result? - Error - Delete download - Are you sure you want to delete the downloaded model \"%s\"? - Conversation was reset - Engine was reset - Agent - Model - Result - Clear - Collect - Color - Model configs - System prompt - Continue - Conversation History - Copy - Create - Delete - Description* - Disabled - Dismiss - Done - Download - %1$s downloaded - Settings - Manage application settings - Models - Browse, try, and benchmark models - Edit - Edit secret - Enter instruction - Expand all - Failed to save - I\'ve turned off the flashlight. - I\'ve turned on the flashlight. - Function name - Generate and copy - Grant permissions - Grant record audio permission - Hold to talk - Input history - Instructions - Introducing - Invalid response - Learn more and see model license - Listening... - LiteRT community - Loading model list... - Filter logs - Logs viewer - No logs match the filter - The model you\'ve selected may exceed your device\'s memory, which can cause the app to crash. For the best experience, we recommend trying a smaller model. - Proceed anyway - Memory Warning - Setting max tokens above 10000 may cause app to be unstable. - Initializing model… - Imported models - - %d model available - %d models available - - Recommended models - Model Manager - Select a use case to try - Model not downloaded yet - Name - No cutouts collected - Failed to download model "%s" - Model "%s" has been downloaded - Model download failed - Model download succeeded - OK - Paste from clipboard - Pick from album - Preview - Release to send - Replace - Results - Reset - Tapping \"Reset\" will reset LiteRT-LM engine only. The game progress won\'t be affected. - Resetting engine... - Restore default - The game progress won\'t be affected - Run - Running... - - %d run - %d runs - - Run again - Run benchmark - This process typically takes several minutes and cannot be interrupted once started. Would you like to proceed? - Running model benchmark... - Save - Secret - Select a downloaded model - Select a model - %d selected - Terms of service - View App Terms of Service - Gemma Prohibited Use Policy - Share - Slide up to cancel - System prompt updated - Take a picture - Tap to change color - Tap on graph to see value - Test - Type prompt… - Type prompt… - Welcome to Google AI Edge Gallery - Gemma Terms of Use - Accept & continue - Agree & continue - Try it - Turn on all - Turn off all - Undo - Unsupported action - I\'ve set the screen brightness to %1$d%%. - Setting the timer for %1$d seconds. - Adding \"Team meeting (10AM-11AM)\" to your calendar. - Opening the camera app. - Mobile Actions - Control your device with simple commands - Supported sample actions: - Model response - Function(s) called - View results - View console logs - warming up… - No function recognized - No function recognized - Unknown error - View - View in full screen - - - Parameter - Parameters - - Turn flashlight on/off - Flashlight on - Flashlight off - Create contact - Send email - Create calendar event - Show location on map - Open WIFI settings - - - Mobile Actions Finetune Challenge - Build it to play it! - This demo is locked. To unlock the Mobile Actions agent, you need to compile the model yourself using our open-source recipe. - Instructions: - 1. On your computer]]>, open this guide]]> - 2. Follow the instructions to fine tune the model and convert it to .litertlm format. - 3. Transfer the file to this phone. - 4. Tap Load Model]]> below to unlock the demo. - Email guide to myself - Load Model - - - AI Chat - Chat with on-device large language models. - Add script - Add default script - Add skill - Add skill from featured list - Add a skill from a curated list of community contributed skills - Load skill from URL - Enter the URL of a skill directory hosted on Web - Import local skill - Import the skill directory locally - Skill creator - Create a skill from scratch in-app - Add skill from URL - Import local skill - Please first \"adb push\" the whole skill directory to /sdcard/Download - Create a skill - Agent Skills - Use specialized, high-order reasoning by loading different skills or creating your own.\n\nTry tapping a sample prompt below to see Agent Skills in action! - Third-party skills are not authored or endorsed by Google. Google is not responsible for their contents, security, or data handling practices.\n\nPlease exercise caution before providing sensitive information, such as personal identification, tokens, or API keys.\n\nAny use of an Agent Skill is subject to the applicable terms for that Skill. - Add third-party skill - Agree - Delete script - Delete skill - Are you sure you want to delete the skill? - Delete selected skills - - Are you sure you want to delete %d skill? - Are you sure you want to delete %d skills? - - - %d selected - %d selected - - Built-in Skills - Custom Skills - Describe requirements - Describe input data - The field names need to match the ones in instructions. - Describe output data - Duplicated script name - Please enable the skill by tapping the Skills button below. - Edit skill - Enter URL of skill\'s root directory - Featured skills - A curated list of community contributed skills - Generate LLM prompt - LLM prompt - Learn how to write skills - Loading featured skills... - Manage skills - View, create, and manage your skills - Enabling more than %s may impact performance. - No directory selected - Pick skill\'s root directory - Prompt copied to clipboard. Paste it into your favorite LLM app to generate the code and copy/paste the code here. - Kitchen adventure - Calculate hash - Query Wikipedia - Plot chart - Interactive map - Spin label on my head - Send email - Replace existing skill - A skill with the same name already exists. Do you want to replace it? - Search for a skill - Select script - Script name - Script content - Skills - - %d skill - %d skills - - REQUIRED. In the form of my-skill-name - REQUIRED. A brief description of the skill\'s function and trigger conditions or keywords. - Detailed instructions in Markdown format that LLM must follow to accomplish the task. - Use Call JS template - - - - - Add - - Import model - - %1$s task with %2$d models - - Error - - Import model from local file - - Go back - - Settings - - Done - - Collapse - - Expand - - Stop - - Delete - - Close - - Selected - - Model settings - - Reset session - - Current model is %1$s. Tap to change model. - - Close image viewer - - Copy to clipboard - - Prompt input - - Send prompt - - Model response text - - Clear input history - - Delete input history entry - - Content input - - Add example prompt - - Image thumbnail - - User image %1$d - - User image %1$d of %2$d - - Add content - - Camera shutter - - Toggle front or back camera - - Pick file - - Play audio - - Stop playback - - Send audio clip - - Start recording - - Downloaded - - Not downloaded - - Downloading - - Download failed - - Chat panel - - Switch to keyboard - - Switch to voice - - Show history - - More options - - Help - - Menu + + All + Add + Added + +Audio + You cannot access this model yet. Please follow the steps and return when complete. + Open guide + The current model can only support 1 image per chat. To ask about another image, create a new chat. + Initializing model + Sit tight, this can take up to 1 minute + Explore a world of amazing on-device models from + Google AI Edge Gallery + Google AI + Edge Gallery + Ask Image + To get started, tap the + below to add an image (max 10 images per chat) and type a prompt to ask a question about it! + To get started, tap the + below to add an image (max 1 image per chat) and type a prompt to ask a question about it! + Ask Audio + To get started, tap the + below to add a audio clip (limited to 1 clip up to 30 seconds) and type a prompt to transcribe or translate it! + Gallery News + Baseline + Basic info + Benchmark + How to compare benchmark runs + Compare benchmark runs by tapping the **Baseline** chip on any run result card to set the run as the comparison baseline. We\'ll automatically calculate and display the percentage differences in **all other** run result cards. + Benchmark model + Benchmark results + No benchmark results + Ensure that the sum of prefill and decode tokens remains under the model\'s maximum token limit (%d vs %d) + Best overall + EXPERIMENTAL + Cancel + Unlabeled + LLM + Agents + Classical ML + Experimental + Type message… + You + LLM + Show thinking + Checking access... + Close + Collapse all + Discard + Discard changes? + You have unsaved changes. Are you sure you want to discard them? + Delete benchmark result + Are you sure you want to delete the benchmark result? + Error + Delete download + Are you sure you want to delete the downloaded model \"%s\"? + Conversation was reset + Engine was reset + Agent + Model + Result + Clear + Collect + Color + Model configs + System prompt + Continue + Conversation History + Copy + Create + Delete + Description* + Disabled + Dismiss + Done + Download + %1$s downloaded + Settings + Manage application settings + Models + Browse, try, and benchmark models + Edit + Edit secret + Enter instruction + Expand all + Failed to save + I\'ve turned off the flashlight. + I\'ve turned on the flashlight. + Function name + Generate and copy + Grant permissions + Grant record audio permission + Hold to talk + Input history + Instructions + Introducing + Invalid response + Learn more and see model license + Listening... + LiteRT community + Loading model list... + Filter logs + Logs viewer + No logs match the filter + The model you\'ve selected may exceed your device\'s memory, which can cause the app to crash. For the best experience, we recommend trying a smaller model. + Proceed anyway + Memory Warning + Setting max tokens above 10000 may cause app to be unstable. + Initializing model… + Imported models + + %d model available + %d models available + + Recommended models + Model Manager + Select a use case to try + Model not downloaded yet + Name + No cutouts collected + Failed to download model "%s" + Model "%s" has been downloaded + Model download failed + Model download succeeded + OK + Paste from clipboard + Pick from album + Preview + Release to send + Replace + Results + Reset + Tapping \"Reset\" will reset LiteRT-LM engine only. The game progress won\'t be affected. + Resetting engine... + Restore default + The game progress won\'t be affected + Run + Running... + + %d run + %d runs + + Run again + Run benchmark + This process typically takes several minutes and cannot be interrupted once started. Would you like to proceed? + Running model benchmark... + Save + Secret + Select a downloaded model + Select a model + %d selected + Terms of service + View App Terms of Service + Gemma Prohibited Use Policy + Share + Slide up to cancel + System prompt updated + Take a picture + Tap to change color + Tap on graph to see value + Test + Type prompt… + Type prompt… + Welcome to Google AI Edge Gallery + Gemma Terms of Use + Accept & continue + Agree & continue + Try it + Turn on all + Turn off all + Undo + Unsupported action + I\'ve set the screen brightness to %1$d%%. + Setting the timer for %1$d seconds. + Adding \"Team meeting (10AM-11AM)\" to your calendar. + Opening the camera app. + Mobile Actions + Control your device with simple commands + Supported sample actions: + Model response + Function(s) called + View results + View console logs + warming up… + No function recognized + No function recognized + Unknown error + View + View in full screen + + + Parameter + Parameters + + Turn flashlight on/off + Flashlight on + Flashlight off + Create contact + Send email + Create calendar event + Show location on map + Open WIFI settings + + + Mobile Actions Finetune Challenge + Build it to play it! + This demo is locked. To unlock the Mobile Actions agent, you need to compile the model yourself using our open-source recipe. + Instructions: + 1. On your computer]]>, open this guide]]> + 2. Follow the instructions to fine tune the model and convert it to .litertlm format. + 3. Transfer the file to this phone. + 4. Tap Load Model]]> below to unlock the demo. + 1. + On your computer + , open + this guide + \n4. Tap + Load Model + below to unlock the demo. + Email guide to myself + Load Model + + + AI Chat + Chat with on-device large language models. + Add script + Add default script + Add skill + Add skill from featured list + Add a skill from a curated list of community contributed skills + Load skill from URL + Enter the URL of a skill directory hosted on Web + Import local skill + Import the skill directory locally + Skill creator + Create a skill from scratch in-app + Add skill from URL + Import local skill + Please first \"adb push\" the whole skill directory to /sdcard/Download + Create a skill + Agent Skills + Use specialized, high-order reasoning by loading different skills or creating your own.\n\nTry tapping a sample prompt below to see Agent Skills in action! + Third-party skills are not authored or endorsed by Google. Google is not responsible for their contents, security, or data handling practices.\n\nPlease exercise caution before providing sensitive information, such as personal identification, tokens, or API keys.\n\nAny use of an Agent Skill is subject to the applicable terms for that Skill. + Add third-party skill + Agree + Delete script + Delete skill + Are you sure you want to delete the skill? + Delete selected skills + + Are you sure you want to delete %d skill? + Are you sure you want to delete %d skills? + + + %d selected + %d selected + + Built-in Skills + Custom Skills + Describe requirements + Describe input data + The field names need to match the ones in instructions. + Describe output data + Duplicated script name + Please enable the skill by tapping the Skills button below. + Edit skill + Enter URL of skill\'s root directory + Featured skills + A curated list of community contributed skills + Generate LLM prompt + LLM prompt + Learn how to write skills + Loading featured skills... + Manage skills + View, create, and manage your skills + Enabling more than %s may impact performance. + No directory selected + Pick skill\'s root directory + Prompt copied to clipboard. Paste it into your favorite LLM app to generate the code and copy/paste the code here. + Kitchen adventure + Calculate hash + Query Wikipedia + Plot chart + Interactive map + Spin label on my head + Send email + Replace existing skill + A skill with the same name already exists. Do you want to replace it? + Search for a skill + Select script + Script name + Script content + Skills + + %d skill + %d skills + + REQUIRED. In the form of my-skill-name + REQUIRED. A brief description of the skill\'s function and trigger conditions or keywords. + Detailed instructions in Markdown format that LLM must follow to accomplish the task. + Use Call JS template + + + + + Add + + Import model + + %1$s task with %2$d models + + Error + + Import model from local file + + Go back + + Settings + + Done + + Collapse + + Expand + + Stop + + Delete + + Close + + Selected + + Model settings + + Reset session + + Current model is %1$s. Tap to change model. + + Close image viewer + + Copy to clipboard + + Prompt input + + Send prompt + + Model response text + + Clear input history + + Delete input history entry + + Content input + + Add example prompt + + Image thumbnail + + User image %1$d + + User image %1$d of %2$d + + Add content + + Camera shutter + + Toggle front or back camera + + Pick file + + Play audio + + Stop playback + + Send audio clip + + Start recording + + Downloaded + + Not downloaded + + Downloading + + Download failed + + Chat panel + + Switch to keyboard + + Switch to voice + + Show history + + More options + + Help + + Menu + + + Retry + Please check your internet connection and try again later. + Google + AI Edge Gallery + Discover the power of on-device AI models from the + LiteRT community + , featuring the all-new + Gemma 4 + Try Gemma 4 today + Gemma 4 E2B & E4B are here! Try them in AI Chat, Agent Skills, or the use cases below. + Chat with the latest Gemma 4 model today + Have Gemma 4 complete agentic tasks for\u00A0you + Explore other use cases + New + Gemma 4: now available + Built from the same world-class technology as Gemini 3, Gemma 4 brings frontier intelligence to your mobile and edge devices. + Read more + Import + Unsupported file type + Unsupported model type + Looks like the model is a web-only model and is not supported by the app. + Enter content + View licenses + Clear history? + Are you sure you want to clear the history? This action cannot be undone. + Record audio clip + Pick wav file + Open user agreement + Unknown network error + Please check your internet connection. + Live camera session ended. Average FPS: %1$d + Are you sure you want to delete \'%1$s\'? + Input Data + Custom Data + Text color: + + + Settings + Theme + HuggingFace access token + Not available + The token will be automatically retrieved when a gated model is downloaded + Enter token manually + Third-party libraries + Auto + Light + Dark + Unknown + + + Import Model + Import model + From local model file + Model imported successfully + Only \".task\" or \".litertlm\" file type is supported. + + + Introducing + Gemma 4 + Experience the world\'s most capable open models, designed to run frontier-level intelligence directly on your hardware. + + + 1 Model + %d Models + + + API Documentation + Example code + + + New release %s available + + + AI Chat + Chat with on-device large language models + Chat with an on-device LLM + + + Ask Image + Ask questions about images with on-device large language models + Ask questions about images + + + Audio Scribe + Instantly transcribe and/or translate audio clips using on-device large language models + Transcribe and translate audio + + + Prompt Lab + Single turn use cases with on-device large language models + Single turn use cases + + + Agent Skills + Chat with on-device large language models with skills and tools + Complete agentic tasks with chat + + + Tiny Garden + Use natural language to plant, water, and harvest in this fully offline mini-game.\n\nNote: This is powered by the experimental FunctionGemma model optimized for latency. Due to its compact size (270M), it works well on simple instructions but responses may vary to more complex interactions. + Use natural language to plant + + + Mobile Actions + Perform various device actions through Function Gemma + Leverage device mobile actions + + + Preview prompt + Select an example + + + Acknowledge user agreement + This is a gated model. Please click the button below to view and agree to the user agreement. After accepting, simply close that tab to proceed with the model download. + + + Download & Try it + + + The "%s" skill is currently disabled + Use specialized, high-order reasoning by loading different skills or + creating\u00A0your\u00A0own + .\n\nTry tapping a sample prompt below to see Agent Skills in action! + + + Gemma models on the Google AI Edge Gallery app are governed by the + Gemma Terms of Service + . Please review these terms and ensure you agree before continuing. + + + Interactive Map + Kitchen Adventure + Calculate Hash + Text Spinner + Send Email + Track my mood + Query Wikipedia + Generate QR code + + + Model + Accelerator + Prefill tokens + Decode tokens + Number of runs + App version + Prefill speed + Decode speed + Time to first token + First init time + Steady init time + tokens/sec + sec + ms + + + Max tokens + TopK + TopP + Temperature + Default max tokens + Default TopK + Default TopP + Default temperature + Support image + Support audio + Support tiny garden + Support mobile actions + Support thinking + Enable thinking + Max result count + Use GPU + Accelerator + Vision accelerator + Compatible accelerators + Warm up iterations + Benchmark iterations + Iterations + Theme + Name + Model type + Model + Number of turns before the conversation resets + Prefill tokens + Decode tokens + Number of runs + + + Font size + Max character count