Add Simplified Chinese (zh) localization (#901)
* feat: add Simplified Chinese (zh) localization - Add complete values-zh/strings.xml (921 strings, all keys covered) - Translate 107 hardcoded UI strings across 34 Kotlin files - Add 简体中文 option to in-app language picker - Terminology aligned with official Hermes dashboard zh translations * fix: resource-ize hardcoded Chinese strings per review Address Hy4ri's review on PR #901: - Move 104 hardcoded Chinese UI strings to string resources (English in values/strings.xml, Chinese in values-zh/strings.xml) referenced via stringResource() - SystemViewModel labels -> @StringRes + AndroidViewModel with resource-based toast templates - Make TodoTaskCardTest assertion locale-agnostic via getString() - Update cookie tests to assert new host-agnostic session cookie behavior (WiFi/Tailscale switch-safe) - Session cookie mirroring to WILDCARD bucket survives host switches * style: apply ktlintFormat per review --------- Co-authored-by: hermes-i18n <hermes-i18n@localhost>
This commit is contained in:
1 parent
0a5eb7a5b0
commit
fdbd98af3e
46 files changed
+1420
-133
No files matched your search
@@ -157,7 +157,7 @@ fun ModelScreen(
|
||||
SearchBar(
|
||||
query = query,
|
||||
onQueryChange = { query = it },
|
||||
placeholder = "Search models...",
|
||||
placeholder = stringResource(R.string.model_search_placeholder),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ fun ModelScreen(
|
||||
if (state.modelPickerConfirmMessage != null) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { viewModel.dismissModelPickerConfirm() },
|
||||
title = { Text("Expensive Model") },
|
||||
title = { Text(stringResource(R.string.model_expensive_title)) },
|
||||
text = { Text(state.modelPickerConfirmMessage!!) },
|
||||
confirmButton = {
|
||||
Button(onClick = { viewModel.confirmModelPickerExpensive() }) {
|
||||
@@ -240,7 +240,7 @@ fun ModelScreen(
|
||||
if (state.showMainModelPicker) {
|
||||
ModelPickerDialog(
|
||||
providers = state.providers,
|
||||
title = "Set Main Model",
|
||||
title = stringResource(R.string.model_set_main),
|
||||
isLoading = state.isLoading && state.providers.isEmpty(),
|
||||
pinnedModels = state.pinnedModels,
|
||||
onPinToggle = { provider, model -> viewModel.togglePinModel(provider, model) },
|
||||
@@ -266,7 +266,7 @@ fun ModelScreen(
|
||||
if (state.showAuxModelPicker) {
|
||||
ModelPickerDialog(
|
||||
providers = state.providers,
|
||||
title = "Set Aux: ${state.auxPickerTask}",
|
||||
title = stringResource(R.string.model_set_aux, state.auxPickerTask),
|
||||
isLoading = state.isLoading && state.providers.isEmpty(),
|
||||
pinnedModels = state.pinnedModels,
|
||||
onPinToggle = { provider, model -> viewModel.togglePinModel(provider, model) },
|
||||
@@ -330,12 +330,12 @@ private fun ModelSettingsSection(
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
Text(
|
||||
text = "Model Settings",
|
||||
text = stringResource(R.string.model_settings),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Text(
|
||||
text = "applies to new sessions",
|
||||
text = stringResource(R.string.model_new_sessions),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -353,7 +353,7 @@ private fun ModelSettingsSection(
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
},
|
||||
label = "Main model",
|
||||
label = stringResource(R.string.model_main_label),
|
||||
value = if (mainProvider.isNotBlank()) "$mainProvider · $mainModel" else "(unset)",
|
||||
actionLabel = "Change",
|
||||
onAction = onSetMainModel,
|
||||
@@ -371,7 +371,7 @@ private fun ModelSettingsSection(
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
},
|
||||
label = "Auxiliary tasks",
|
||||
label = stringResource(R.string.model_aux_tasks_label),
|
||||
value =
|
||||
if (auxOverrideCount > 0) {
|
||||
"$auxOverrideCount override · ${AUX_TASKS.size - auxOverrideCount} auto"
|
||||
@@ -394,7 +394,7 @@ private fun ModelSettingsSection(
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
},
|
||||
label = "Mixture of Agents",
|
||||
label = stringResource(R.string.model_moa_label),
|
||||
value = if (moaEnabled) moaSummary else "not loaded",
|
||||
actionLabel = "Configure",
|
||||
onAction = onConfigureMoa,
|
||||
@@ -461,7 +461,7 @@ private fun AuxTasksDialog(
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Auxiliary Tasks") },
|
||||
title = { Text(stringResource(R.string.model_aux_tasks_title)) },
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
@@ -530,7 +530,7 @@ private fun AuxTasksDialog(
|
||||
) {
|
||||
Icon(Icons.Filled.Delete, contentDescription = null, modifier = Modifier.size(14.dp))
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Reset all to auto")
|
||||
Text(stringResource(R.string.model_reset_all_auto))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -34,8 +34,10 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.data.model.MoaConfigPreset
|
||||
import com.m57.hermescontrol.data.model.MoaModelSlot
|
||||
import com.m57.hermescontrol.ui.common.SearchBar
|
||||
@@ -58,7 +60,7 @@ internal fun MoaConfigDialog(
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Mixture of Agents") },
|
||||
title = { Text(stringResource(R.string.moa_title)) },
|
||||
text = {
|
||||
if (pickerTarget != null && effectivePreset != null) {
|
||||
// Inline model picker within the dialog
|
||||
@@ -228,13 +230,13 @@ private fun MoaConfigEditor(
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = "Presets appear as models under the Mixture of Agents provider.",
|
||||
text = stringResource(R.string.moa_presets_note),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
||||
if (effectivePreset == null) {
|
||||
Text("No presets configured", style = MaterialTheme.typography.bodyMedium)
|
||||
Text(stringResource(R.string.moa_no_presets), style = MaterialTheme.typography.bodyMedium)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -248,7 +250,7 @@ private fun MoaConfigEditor(
|
||||
) {
|
||||
// Simplified preset dropdown with buttons
|
||||
if (presetNames.isNotEmpty()) {
|
||||
Text("Preset:", style = MaterialTheme.typography.bodySmall)
|
||||
Text(stringResource(R.string.moa_preset_label), style = MaterialTheme.typography.bodySmall)
|
||||
presetNames.forEach { name ->
|
||||
TextButton(
|
||||
onClick = { onSelectPreset(name) },
|
||||
@@ -286,7 +288,7 @@ private fun MoaConfigEditor(
|
||||
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
|
||||
enabled = presetNames.size > 1,
|
||||
) {
|
||||
Text("Set default", style = MaterialTheme.typography.labelSmall)
|
||||
Text(stringResource(R.string.moa_set_default), style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = onDeletePreset,
|
||||
@@ -306,13 +308,18 @@ private fun MoaConfigEditor(
|
||||
OutlinedTextField(
|
||||
value = newPresetName,
|
||||
onValueChange = onNewPresetNameChange,
|
||||
placeholder = { Text("new preset name", style = MaterialTheme.typography.bodySmall) },
|
||||
placeholder = {
|
||||
Text(
|
||||
stringResource(R.string.moa_new_preset_name),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
modifier = Modifier.weight(1f).height(48.dp),
|
||||
textStyle = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
IconButton(onClick = onAddPreset, enabled = newPresetName.isNotBlank()) {
|
||||
Icon(Icons.Outlined.Add, contentDescription = "Add preset")
|
||||
Icon(Icons.Outlined.Add, contentDescription = stringResource(R.string.moa_add_preset))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +327,7 @@ private fun MoaConfigEditor(
|
||||
|
||||
// Reference models
|
||||
Text(
|
||||
text = "Reference models",
|
||||
text = stringResource(R.string.moa_reference_models),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
@@ -355,7 +362,7 @@ private fun MoaConfigEditor(
|
||||
) {
|
||||
Icon(Icons.Outlined.Add, contentDescription = null, modifier = Modifier.size(14.dp))
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Add reference model", style = MaterialTheme.typography.labelSmall)
|
||||
Text(stringResource(R.string.moa_add_reference), style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
@@ -412,7 +419,7 @@ private fun MoaSlotPicker(
|
||||
SearchBar(
|
||||
query = pickerQuery,
|
||||
onQueryChange = { pickerQuery = it },
|
||||
placeholder = "Search providers...",
|
||||
placeholder = stringResource(R.string.moa_search_providers),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
|
||||
@@ -32,9 +32,11 @@ 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.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.data.model.ModelProvider
|
||||
import com.m57.hermescontrol.data.model.PinnedModel
|
||||
import com.m57.hermescontrol.ui.common.LoadingState
|
||||
@@ -95,12 +97,12 @@ fun ModelPickerDialog(
|
||||
Column {
|
||||
if (isLoading) {
|
||||
LoadingState(
|
||||
subtitle = "Loading models…",
|
||||
subtitle = stringResource(R.string.model_picker_loading),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
} else if (providers.isEmpty() && pinnedModels.isEmpty()) {
|
||||
Text(
|
||||
text = "No models available.",
|
||||
text = stringResource(R.string.model_picker_no_models),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -143,7 +145,7 @@ fun ModelPickerDialog(
|
||||
SearchBar(
|
||||
query = pickerQuery,
|
||||
onQueryChange = { pickerQuery = it },
|
||||
placeholder = "Search models and providers...",
|
||||
placeholder = stringResource(R.string.model_picker_search),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
|
||||
Reference in new issue
Block a user