feat: i18n — extract all UI strings to resources (de + en)
German (default) in values/strings.xml, English in values-en/strings.xml. Matches web app's next-intl de/en setup. Migrated: greeting, hero headline, chat input, error messages, mode pills, suggestion pills, sidebar (brand, search, dates, logout, settings), model sheet, login screen (welcome, fields, errors, submit button), conversation labels (locked, pinned). ChatModels: Suggestion/ChatMode now use resource IDs instead of hardcoded strings. greeting() is now @Composable (uses stringResource). ModePillsRow: selected state uses resource ID (Int) instead of String. Date labels (HEUTE/GESTERN/FRÜHER) localized via DateLabels data class.
This commit is contained in:
parent
94c0f85fb7
commit
0237eae1e6
7 changed files with 269 additions and 65 deletions
|
|
@ -52,6 +52,8 @@ import dev.kaizen.app.net.SessionViewModel
|
|||
import dev.kaizen.app.ui.theme.Amber
|
||||
import dev.kaizen.app.ui.theme.AmberDeep
|
||||
import dev.kaizen.app.ui.theme.OnAmber
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import dev.kaizen.app.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
|
|
@ -64,6 +66,7 @@ import kotlinx.coroutines.launch
|
|||
fun LoginScreen(session: SessionViewModel, modifier: Modifier = Modifier) {
|
||||
val cs = MaterialTheme.colorScheme
|
||||
val scope = rememberCoroutineScope()
|
||||
val ctx = androidx.compose.ui.platform.LocalContext.current
|
||||
|
||||
var baseUrl by remember { mutableStateOf(DEFAULT_BASE_URL) }
|
||||
var email by remember { mutableStateOf("") }
|
||||
|
|
@ -81,9 +84,9 @@ fun LoginScreen(session: SessionViewModel, modifier: Modifier = Modifier) {
|
|||
scope.launch {
|
||||
when (val result = session.login(baseUrl, email, password, deviceName)) {
|
||||
is LoginResult.Success -> Unit // session.config flips → MainActivity routes to chat
|
||||
LoginResult.InvalidCredentials -> error = "E-Mail oder Passwort ist falsch."
|
||||
LoginResult.RateLimited -> error = "Zu viele Versuche. Bitte kurz warten."
|
||||
is LoginResult.Error -> error = "Verbindung fehlgeschlagen. Server-Adresse prüfen."
|
||||
LoginResult.InvalidCredentials -> error = ctx.getString(R.string.login_error_credentials)
|
||||
LoginResult.RateLimited -> error = ctx.getString(R.string.login_error_rate_limited)
|
||||
is LoginResult.Error -> error = ctx.getString(R.string.login_error_connection)
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
|
|
@ -102,10 +105,10 @@ fun LoginScreen(session: SessionViewModel, modifier: Modifier = Modifier) {
|
|||
Spacer(Modifier.size(64.dp))
|
||||
KaizenOrb(96.dp)
|
||||
Spacer(Modifier.size(24.dp))
|
||||
Text("Willkommen", color = cs.onBackground, fontSize = 30.sp, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.login_welcome), color = cs.onBackground, fontSize = 30.sp, fontWeight = FontWeight.Bold)
|
||||
Spacer(Modifier.size(6.dp))
|
||||
Text(
|
||||
"Melde dich bei deiner Kaizen-Instanz an",
|
||||
stringResource(R.string.login_subtitle),
|
||||
color = cs.onSurfaceVariant.copy(alpha = 0.8f),
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
|
|
@ -115,7 +118,7 @@ fun LoginScreen(session: SessionViewModel, modifier: Modifier = Modifier) {
|
|||
GlassField(
|
||||
value = baseUrl,
|
||||
onValueChange = { baseUrl = it; error = null },
|
||||
placeholder = "https://deine-instanz.de",
|
||||
placeholder = stringResource(R.string.login_url_placeholder),
|
||||
keyboardType = KeyboardType.Uri,
|
||||
imeAction = ImeAction.Next,
|
||||
)
|
||||
|
|
@ -123,7 +126,7 @@ fun LoginScreen(session: SessionViewModel, modifier: Modifier = Modifier) {
|
|||
GlassField(
|
||||
value = email,
|
||||
onValueChange = { email = it; error = null },
|
||||
placeholder = "E-Mail",
|
||||
placeholder = stringResource(R.string.login_email_placeholder),
|
||||
keyboardType = KeyboardType.Email,
|
||||
imeAction = ImeAction.Next,
|
||||
)
|
||||
|
|
@ -131,7 +134,7 @@ fun LoginScreen(session: SessionViewModel, modifier: Modifier = Modifier) {
|
|||
GlassField(
|
||||
value = password,
|
||||
onValueChange = { password = it; error = null },
|
||||
placeholder = "Passwort",
|
||||
placeholder = stringResource(R.string.login_password_placeholder),
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done,
|
||||
password = true,
|
||||
|
|
@ -227,7 +230,7 @@ private fun SignInButton(enabled: Boolean, loading: Boolean, onClick: () -> Unit
|
|||
CircularProgressIndicator(color = OnAmber, strokeWidth = 2.4.dp, modifier = Modifier.size(22.dp))
|
||||
} else {
|
||||
Text(
|
||||
"Anmelden",
|
||||
stringResource(R.string.login_submit),
|
||||
color = if (enabled) OnAmber else cs.onSurfaceVariant,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ import dev.kaizen.app.ui.shape.KaizenShapes
|
|||
import dev.kaizen.app.ui.theme.Amber
|
||||
import dev.kaizen.app.ui.theme.AmberDeep
|
||||
import dev.kaizen.app.ui.theme.OnAmber
|
||||
import dev.kaizen.app.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
|
|
@ -122,7 +124,6 @@ fun EmptyHero(userName: String, onPick: (String) -> Unit, modifier: Modifier = M
|
|||
now.format(DateTimeFormatter.ofPattern("EEE, d. MMMM · HH:mm", Locale.GERMAN))
|
||||
}
|
||||
|
||||
// DESIGN.md §11.1: no suggestion chips on mobile — redundant with mode pills
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -135,7 +136,7 @@ fun EmptyHero(userName: String, onPick: (String) -> Unit, modifier: Modifier = M
|
|||
Spacer(Modifier.height(24.dp))
|
||||
Text("${greeting(now.hour)}, $userName", color = cs.onSurfaceVariant, fontSize = 17.sp)
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Text("Was liegt an?", color = cs.onBackground, fontSize = 32.sp, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.hero_headline), color = cs.onBackground, fontSize = 32.sp, fontWeight = FontWeight.Bold)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(dateLine, color = cs.onSurfaceVariant.copy(alpha = 0.7f), fontSize = 13.sp)
|
||||
Spacer(Modifier.height(142.dp))
|
||||
|
|
@ -143,7 +144,7 @@ fun EmptyHero(userName: String, onPick: (String) -> Unit, modifier: Modifier = M
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun ModePillsRow(selected: String, onSelect: (String) -> Unit, modifier: Modifier = Modifier) {
|
||||
fun ModePillsRow(selected: Int, onSelect: (Int) -> Unit, modifier: Modifier = Modifier) {
|
||||
val cs = MaterialTheme.colorScheme
|
||||
val isDark = isSystemInDarkTheme()
|
||||
|
||||
|
|
@ -163,7 +164,8 @@ fun ModePillsRow(selected: String, onSelect: (String) -> Unit, modifier: Modifie
|
|||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
chatModes.forEach { mode ->
|
||||
val active = mode.label == selected
|
||||
val label = stringResource(mode.labelRes)
|
||||
val active = mode.labelRes == selected
|
||||
val backgroundFill = if (active) Amber.copy(alpha = 0.16f)
|
||||
else if (isDark) Color(0x12FFFFFF) else Color(0x82FFFFFF)
|
||||
val borderBrush = if (active) glassBorderActive else glassBorderInactive
|
||||
|
|
@ -173,13 +175,13 @@ fun ModePillsRow(selected: String, onSelect: (String) -> Unit, modifier: Modifie
|
|||
.clip(KaizenShapes.pill)
|
||||
.background(backgroundFill)
|
||||
.border(1.2.dp, borderBrush, KaizenShapes.pill)
|
||||
.clickable { onSelect(mode.label) }
|
||||
.clickable { onSelect(mode.labelRes) }
|
||||
.padding(horizontal = 13.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(mode.icon, null, tint = if (active) Amber else cs.onSurfaceVariant, modifier = Modifier.size(16.dp))
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text(mode.label, color = if (active) cs.onBackground else cs.onSurfaceVariant, fontSize = 13.sp, fontWeight = FontWeight.Medium)
|
||||
Text(label, color = if (active) cs.onBackground else cs.onSurfaceVariant, fontSize = 13.sp, fontWeight = FontWeight.Medium)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -296,10 +298,10 @@ fun ChatInput(
|
|||
}
|
||||
Row(Modifier.padding(horizontal = 8.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(Modifier.size(40.dp).clip(KaizenShapes.circle).clickable(onClick = onAddClick), contentAlignment = Alignment.Center) {
|
||||
Icon(Icons.Rounded.Add, "Anhang", tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp))
|
||||
Icon(Icons.Rounded.Add, stringResource(R.string.chat_attachment), tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp))
|
||||
}
|
||||
Box(Modifier.weight(1f).heightIn(min = 36.dp).padding(horizontal = 4.dp), contentAlignment = Alignment.CenterStart) {
|
||||
if (value.isEmpty()) Text("Nachricht eingeben …", color = cs.onSurfaceVariant, fontSize = 16.sp)
|
||||
if (value.isEmpty()) Text(stringResource(R.string.chat_input_placeholder), color = cs.onSurfaceVariant, fontSize = 16.sp)
|
||||
BasicTextField(
|
||||
value = value, onValueChange = onValueChange,
|
||||
textStyle = TextStyle(color = cs.onBackground, fontSize = 16.sp, lineHeight = 22.sp),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import androidx.compose.material.icons.rounded.Language
|
|||
import androidx.compose.material.icons.rounded.Movie
|
||||
import androidx.compose.material.icons.rounded.MusicNote
|
||||
import androidx.compose.material.icons.rounded.Tune
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import dev.kaizen.app.R
|
||||
import dev.kaizen.app.net.Attachment
|
||||
|
||||
enum class Role { User, Assistant }
|
||||
|
|
@ -23,31 +26,30 @@ data class Message(
|
|||
val attachments: List<Attachment> = emptyList(),
|
||||
)
|
||||
|
||||
// Empty-state suggestion pills (mirrors the web hero)
|
||||
data class Suggestion(val label: String, val prompt: String, val icon: ImageVector)
|
||||
data class Suggestion(val labelRes: Int, val promptRes: Int, val icon: ImageVector)
|
||||
|
||||
val suggestions = listOf(
|
||||
Suggestion("Brainstormen", "Lass uns zu einem Thema brainstormen.", Icons.Rounded.AutoAwesome),
|
||||
Suggestion("Bild generieren", "Generiere ein Bild von einem Bergsee bei Sonnenaufgang.", Icons.Rounded.Image),
|
||||
Suggestion("Video erstellen", "Erstelle ein kurzes Video von einer Stadt bei Nacht.", Icons.Rounded.Movie),
|
||||
Suggestion("Web durchsuchen", "Durchsuche das Web nach den neuesten KI-Nachrichten.", Icons.Rounded.Language),
|
||||
Suggestion(R.string.suggest_brainstorm, R.string.suggest_brainstorm_prompt, Icons.Rounded.AutoAwesome),
|
||||
Suggestion(R.string.suggest_image, R.string.suggest_image_prompt, Icons.Rounded.Image),
|
||||
Suggestion(R.string.suggest_video, R.string.suggest_video_prompt, Icons.Rounded.Movie),
|
||||
Suggestion(R.string.suggest_web, R.string.suggest_web_prompt, Icons.Rounded.Language),
|
||||
)
|
||||
|
||||
// Mode pills above the input (visual only in Phase 0)
|
||||
data class ChatMode(val label: String, val icon: ImageVector)
|
||||
data class ChatMode(val labelRes: Int, val icon: ImageVector)
|
||||
|
||||
val chatModes = listOf(
|
||||
ChatMode("Standard", Icons.Rounded.Adjust),
|
||||
ChatMode("Sampling", Icons.Rounded.Tune),
|
||||
ChatMode("Bild", Icons.Rounded.Image),
|
||||
ChatMode("Suche", Icons.Rounded.Language),
|
||||
ChatMode("Video", Icons.Rounded.Movie),
|
||||
ChatMode("Audio", Icons.Rounded.MusicNote),
|
||||
ChatMode(R.string.mode_standard, Icons.Rounded.Adjust),
|
||||
ChatMode(R.string.mode_sampling, Icons.Rounded.Tune),
|
||||
ChatMode(R.string.mode_image, Icons.Rounded.Image),
|
||||
ChatMode(R.string.mode_search, Icons.Rounded.Language),
|
||||
ChatMode(R.string.mode_video, Icons.Rounded.Movie),
|
||||
ChatMode(R.string.mode_audio, Icons.Rounded.MusicNote),
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun greeting(hour: Int): String = when (hour) {
|
||||
in 5..11 -> "Guten Morgen"
|
||||
in 12..17 -> "Guten Tag"
|
||||
in 18..22 -> "Guten Abend"
|
||||
else -> "Hallo"
|
||||
in 5..11 -> stringResource(R.string.greeting_morning)
|
||||
in 12..17 -> stringResource(R.string.greeting_day)
|
||||
in 18..22 -> stringResource(R.string.greeting_evening)
|
||||
else -> stringResource(R.string.greeting_night)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import dev.kaizen.app.ui.effect.GlassSurface
|
|||
import dev.kaizen.app.ui.effect.GlassTiers
|
||||
import dev.kaizen.app.ui.effect.KaizenShadows
|
||||
import dev.kaizen.app.ui.shape.KaizenShapes
|
||||
import dev.kaizen.app.R
|
||||
import dev.kaizen.app.db.MessageEntity
|
||||
import dev.kaizen.app.db.toEntity
|
||||
import dev.kaizen.app.haptics.rememberHaptics
|
||||
|
|
@ -93,7 +94,7 @@ fun ChatScreen(
|
|||
var input by remember { mutableStateOf("") }
|
||||
var isStreaming by remember { mutableStateOf(false) }
|
||||
var nextId by remember { mutableStateOf(0L) }
|
||||
var selectedMode by remember { mutableStateOf("Standard") }
|
||||
var selectedMode by remember { mutableStateOf(R.string.mode_standard) }
|
||||
|
||||
// Navigation State
|
||||
var currentScreen by remember { mutableStateOf(AppScreen.Chat) }
|
||||
|
|
@ -315,11 +316,11 @@ fun ChatScreen(
|
|||
val idx = messages.indexOfLast { it.id == assistantId }
|
||||
if (idx >= 0) {
|
||||
val existing = messages[idx].content
|
||||
val errorSuffix = "\n\n⚠ ${chatErrorText(e)}"
|
||||
val errorSuffix = "\n\n⚠ ${chatErrorText(e, context)}"
|
||||
messages[idx] = messages[idx].copy(
|
||||
streaming = false,
|
||||
thinking = false,
|
||||
content = if (existing.isNotBlank()) existing + errorSuffix else chatErrorText(e),
|
||||
content = if (existing.isNotBlank()) existing + errorSuffix else chatErrorText(e, context),
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
|
|
@ -437,7 +438,7 @@ fun ChatScreen(
|
|||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Menu,
|
||||
contentDescription = "Menü öffnen",
|
||||
contentDescription = context.getString(R.string.chat_open_menu),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
|
|
@ -511,11 +512,10 @@ fun ChatScreen(
|
|||
}
|
||||
}
|
||||
|
||||
/** Maps a streaming failure to a short, human German message shown in the bubble. */
|
||||
private fun chatErrorText(e: Throwable): String = when {
|
||||
e is ChatHttpException && e.code == 401 -> "Sitzung abgelaufen. Bitte erneut anmelden."
|
||||
e is ChatHttpException && e.code == 402 -> "Guthaben aufgebraucht."
|
||||
e is ChatHttpException && e.code == 429 -> "Zu viele Anfragen. Kurz warten."
|
||||
e is ChatHttpException -> "Fehler vom Server (${e.code})."
|
||||
else -> "Keine Verbindung zum Server."
|
||||
private fun chatErrorText(e: Throwable, ctx: android.content.Context): String = when {
|
||||
e is ChatHttpException && e.code == 401 -> ctx.getString(R.string.error_session_expired)
|
||||
e is ChatHttpException && e.code == 402 -> ctx.getString(R.string.error_credits_exhausted)
|
||||
e is ChatHttpException && e.code == 429 -> ctx.getString(R.string.error_rate_limited)
|
||||
e is ChatHttpException -> ctx.getString(R.string.error_server, e.code)
|
||||
else -> ctx.getString(R.string.error_no_connection)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,29 +45,33 @@ import dev.kaizen.app.ui.effect.GlassSurface
|
|||
import dev.kaizen.app.ui.effect.GlassTiers
|
||||
import dev.kaizen.app.ui.effect.KaizenShadows
|
||||
import dev.kaizen.app.ui.shape.KaizenShapes
|
||||
import dev.kaizen.app.R
|
||||
import dev.kaizen.app.ui.theme.Amber
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
|
||||
private fun dateLabel(updatedAt: String?): String {
|
||||
val instant = runCatching { Instant.parse(updatedAt) }.getOrNull() ?: return "FRÜHER"
|
||||
private data class DateLabels(val today: String, val yesterday: String, val earlier: String, val pinned: String)
|
||||
|
||||
private fun dateLabel(updatedAt: String?, labels: DateLabels): String {
|
||||
val instant = runCatching { Instant.parse(updatedAt) }.getOrNull() ?: return labels.earlier
|
||||
val date = instant.atZone(ZoneId.systemDefault()).toLocalDate()
|
||||
val today = LocalDate.now()
|
||||
return when (date) {
|
||||
today -> "HEUTE"
|
||||
today.minusDays(1) -> "GESTERN"
|
||||
else -> date.format(DateTimeFormatter.ofPattern("dd. MMM yyyy", Locale.GERMAN)).uppercase()
|
||||
today -> labels.today
|
||||
today.minusDays(1) -> labels.yesterday
|
||||
else -> date.format(DateTimeFormatter.ofPattern("dd. MMM yyyy", Locale.getDefault())).uppercase()
|
||||
}
|
||||
}
|
||||
|
||||
private fun groupConversations(conversations: List<ConversationSummary>): Map<String, List<ConversationSummary>> {
|
||||
private fun groupConversations(conversations: List<ConversationSummary>, labels: DateLabels): Map<String, List<ConversationSummary>> {
|
||||
val groups = LinkedHashMap<String, MutableList<ConversationSummary>>()
|
||||
conversations.filter { it.pinned }.takeIf { it.isNotEmpty() }?.let { groups["★ ANGEHEFTET"] = it.toMutableList() }
|
||||
conversations.filter { it.pinned }.takeIf { it.isNotEmpty() }?.let { groups[labels.pinned] = it.toMutableList() }
|
||||
conversations.filterNot { it.pinned }.forEach {
|
||||
groups.getOrPut(dateLabel(it.updatedAt)) { mutableListOf() }.add(it)
|
||||
groups.getOrPut(dateLabel(it.updatedAt, labels)) { mutableListOf() }.add(it)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
|
@ -109,7 +113,7 @@ fun KaizenSidebar(
|
|||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text("kaizen", color = cs.onBackground, fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.sidebar_brand), color = cs.onBackground, fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
|
|
@ -118,7 +122,7 @@ fun KaizenSidebar(
|
|||
.clickable { onNewChat() },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(Icons.Rounded.Edit, "Neuer Chat", tint = cs.onBackground, modifier = Modifier.size(18.dp))
|
||||
Icon(Icons.Rounded.Edit, stringResource(R.string.chat_new), tint = cs.onBackground, modifier = Modifier.size(18.dp))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +140,7 @@ fun KaizenSidebar(
|
|||
) {
|
||||
Icon(Icons.Rounded.Search, null, tint = cs.onSurfaceVariant.copy(alpha = 0.6f), modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Suchen...", color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 14.sp)
|
||||
Text(stringResource(R.string.chat_search), color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 14.sp)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
|
|
@ -145,13 +149,19 @@ fun KaizenSidebar(
|
|||
Box(Modifier.weight(1f).fillMaxWidth()) {
|
||||
if (conversations.isEmpty()) {
|
||||
Text(
|
||||
"Noch keine Chats",
|
||||
stringResource(R.string.chat_no_conversations),
|
||||
color = cs.onSurfaceVariant.copy(alpha = 0.5f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.padding(start = 12.dp, top = 12.dp)
|
||||
)
|
||||
} else {
|
||||
val grouped = remember(conversations) { groupConversations(conversations) }
|
||||
val labels = DateLabels(
|
||||
today = stringResource(R.string.sidebar_today),
|
||||
yesterday = stringResource(R.string.sidebar_yesterday),
|
||||
earlier = stringResource(R.string.sidebar_earlier),
|
||||
pinned = stringResource(R.string.sidebar_pinned),
|
||||
)
|
||||
val grouped = remember(conversations, labels) { groupConversations(conversations, labels) }
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
|
|
@ -207,7 +217,7 @@ fun KaizenSidebar(
|
|||
Column(Modifier.weight(1f)) {
|
||||
Text(userName, color = cs.onBackground, fontSize = 15.sp, fontWeight = FontWeight.SemiBold)
|
||||
}
|
||||
Icon(Icons.Rounded.Settings, "Einstellungen", tint = cs.onSurfaceVariant.copy(alpha = 0.7f), modifier = Modifier.size(20.dp))
|
||||
Icon(Icons.Rounded.Settings, stringResource(R.string.sidebar_settings), tint = cs.onSurfaceVariant.copy(alpha = 0.7f), modifier = Modifier.size(20.dp))
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
|
@ -221,9 +231,9 @@ fun KaizenSidebar(
|
|||
.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(Icons.AutoMirrored.Rounded.Logout, "Abmelden", tint = cs.onSurfaceVariant.copy(alpha = 0.7f), modifier = Modifier.size(18.dp))
|
||||
Icon(Icons.AutoMirrored.Rounded.Logout, stringResource(R.string.sidebar_logout), tint = cs.onSurfaceVariant.copy(alpha = 0.7f), modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Text("Abmelden", color = cs.onSurfaceVariant.copy(alpha = 0.85f), fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
Text(stringResource(R.string.sidebar_logout), color = cs.onSurfaceVariant.copy(alpha = 0.85f), fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -260,7 +270,7 @@ private fun HistoryItemRow(item: ConversationSummary, selected: Boolean, onClick
|
|||
Spacer(Modifier.width(8.dp))
|
||||
}
|
||||
Text(
|
||||
text = if (item.locked) "Gesperrter Chat" else item.title,
|
||||
text = if (item.locked) stringResource(R.string.chat_locked) else item.title,
|
||||
color = if (item.locked) cs.onSurfaceVariant.copy(alpha = 0.5f) else cs.onBackground,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
|
|
@ -269,7 +279,7 @@ private fun HistoryItemRow(item: ConversationSummary, selected: Boolean, onClick
|
|||
)
|
||||
if (item.locked) {
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Icon(Icons.Rounded.Lock, "Gesperrt", tint = Amber, modifier = Modifier.size(15.dp))
|
||||
Icon(Icons.Rounded.Lock, stringResource(R.string.chat_locked_badge), tint = Amber, modifier = Modifier.size(15.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
94
app/src/main/res/values-en/strings.xml
Normal file
94
app/src/main/res/values-en/strings.xml
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<resources>
|
||||
<!-- Greeting -->
|
||||
<string name="greeting_morning">Good Morning</string>
|
||||
<string name="greeting_day">Good Afternoon</string>
|
||||
<string name="greeting_evening">Good Evening</string>
|
||||
<string name="greeting_night">Hello</string>
|
||||
<string name="hero_headline">What\'s up?</string>
|
||||
|
||||
<!-- Chat -->
|
||||
<string name="chat_input_placeholder">Type a message …</string>
|
||||
<string name="chat_no_conversations">No chats yet</string>
|
||||
<string name="chat_locked">Locked Chat</string>
|
||||
<string name="chat_locked_badge">Locked</string>
|
||||
<string name="chat_new">New Chat</string>
|
||||
<string name="chat_search">Search…</string>
|
||||
<string name="chat_attachment">Attachment</string>
|
||||
<string name="chat_call">Call</string>
|
||||
<string name="chat_remove">Remove</string>
|
||||
<string name="chat_error">Error</string>
|
||||
<string name="chat_open_menu">Open menu</string>
|
||||
|
||||
<!-- Chat errors -->
|
||||
<string name="error_session_expired">Session expired. Please sign in again.</string>
|
||||
<string name="error_credits_exhausted">Credits exhausted.</string>
|
||||
<string name="error_rate_limited">Too many requests. Please wait.</string>
|
||||
<string name="error_server">Server error (%1$d).</string>
|
||||
<string name="error_no_connection">No connection to server.</string>
|
||||
|
||||
<!-- Mode pills -->
|
||||
<string name="mode_standard">Standard</string>
|
||||
<string name="mode_sampling">Sampling</string>
|
||||
<string name="mode_image">Image</string>
|
||||
<string name="mode_search">Search</string>
|
||||
<string name="mode_video">Video</string>
|
||||
<string name="mode_audio">Audio</string>
|
||||
|
||||
<!-- Suggestion pills -->
|
||||
<string name="suggest_brainstorm">Brainstorm</string>
|
||||
<string name="suggest_brainstorm_prompt">Let\'s brainstorm on a topic.</string>
|
||||
<string name="suggest_image">Generate Image</string>
|
||||
<string name="suggest_image_prompt">Generate an image of a mountain lake at sunrise.</string>
|
||||
<string name="suggest_video">Create Video</string>
|
||||
<string name="suggest_video_prompt">Create a short video of a city at night.</string>
|
||||
<string name="suggest_web">Search the Web</string>
|
||||
<string name="suggest_web_prompt">Search the web for the latest AI news.</string>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<string name="sidebar_brand">kaizen</string>
|
||||
<string name="sidebar_pinned">★ PINNED</string>
|
||||
<string name="sidebar_today">TODAY</string>
|
||||
<string name="sidebar_yesterday">YESTERDAY</string>
|
||||
<string name="sidebar_earlier">EARLIER</string>
|
||||
<string name="sidebar_logout">Sign Out</string>
|
||||
<string name="sidebar_settings">Settings</string>
|
||||
|
||||
<!-- Model sheet -->
|
||||
<string name="model_sheet_title">Select Model</string>
|
||||
<string name="model_search_placeholder">Search models…</string>
|
||||
<string name="model_search_clear">Clear</string>
|
||||
<string name="model_connecting">Connecting…</string>
|
||||
<string name="model_no_results">No matches</string>
|
||||
<string name="model_favorite">Favorite</string>
|
||||
<string name="model_selected">Selected</string>
|
||||
<string name="model_favorites_group">★ Favorites</string>
|
||||
|
||||
<!-- Login -->
|
||||
<string name="login_welcome">Welcome</string>
|
||||
<string name="login_subtitle">Sign in to your Kaizen instance</string>
|
||||
<string name="login_url_placeholder">https://your-instance.com</string>
|
||||
<string name="login_email_placeholder">Email</string>
|
||||
<string name="login_password_placeholder">Password</string>
|
||||
<string name="login_submit">Sign In</string>
|
||||
<string name="login_error_credentials">Invalid email or password.</string>
|
||||
<string name="login_error_rate_limited">Too many attempts. Please wait.</string>
|
||||
<string name="login_error_connection">Connection failed. Check server address.</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">Settings</string>
|
||||
<string name="settings_back">Back</string>
|
||||
<string name="settings_section_appearance">APPEARANCE</string>
|
||||
<string name="settings_section_profile">PROFILE & ACCOUNT</string>
|
||||
<string name="settings_section_ai">AI PROFILE & CONTEXT</string>
|
||||
<string name="settings_accent_title">Accent Color</string>
|
||||
<string name="settings_accent_desc">Choose the app color scheme</string>
|
||||
<string name="settings_appearance_title">Appearance</string>
|
||||
<string name="settings_appearance_desc">Set light/dark appearance</string>
|
||||
<string name="settings_name_title">Display Name</string>
|
||||
<string name="settings_name_desc">Change your display name</string>
|
||||
<string name="settings_email_title">Email Address</string>
|
||||
<string name="settings_ai_title">System Prompt & Facts</string>
|
||||
<string name="settings_appearance_light">Light</string>
|
||||
<string name="settings_appearance_dark">Dark</string>
|
||||
<string name="settings_appearance_system">System</string>
|
||||
</resources>
|
||||
|
|
@ -1,3 +1,96 @@
|
|||
<resources>
|
||||
<string name="app_name">Kaizen</string>
|
||||
|
||||
<!-- Greeting -->
|
||||
<string name="greeting_morning">Guten Morgen</string>
|
||||
<string name="greeting_day">Guten Tag</string>
|
||||
<string name="greeting_evening">Guten Abend</string>
|
||||
<string name="greeting_night">Hallo</string>
|
||||
<string name="hero_headline">Was liegt an?</string>
|
||||
|
||||
<!-- Chat -->
|
||||
<string name="chat_input_placeholder">Nachricht eingeben …</string>
|
||||
<string name="chat_no_conversations">Noch keine Chats</string>
|
||||
<string name="chat_locked">Gesperrter Chat</string>
|
||||
<string name="chat_locked_badge">Gesperrt</string>
|
||||
<string name="chat_new">Neuer Chat</string>
|
||||
<string name="chat_search">Suchen…</string>
|
||||
<string name="chat_attachment">Anhang</string>
|
||||
<string name="chat_call">Anruf</string>
|
||||
<string name="chat_remove">Entfernen</string>
|
||||
<string name="chat_error">Fehler</string>
|
||||
<string name="chat_open_menu">Menü öffnen</string>
|
||||
|
||||
<!-- Chat errors -->
|
||||
<string name="error_session_expired">Sitzung abgelaufen. Bitte erneut anmelden.</string>
|
||||
<string name="error_credits_exhausted">Guthaben aufgebraucht.</string>
|
||||
<string name="error_rate_limited">Zu viele Anfragen. Kurz warten.</string>
|
||||
<string name="error_server">Fehler vom Server (%1$d).</string>
|
||||
<string name="error_no_connection">Keine Verbindung zum Server.</string>
|
||||
|
||||
<!-- Mode pills -->
|
||||
<string name="mode_standard">Standard</string>
|
||||
<string name="mode_sampling">Sampling</string>
|
||||
<string name="mode_image">Bild</string>
|
||||
<string name="mode_search">Suche</string>
|
||||
<string name="mode_video">Video</string>
|
||||
<string name="mode_audio">Audio</string>
|
||||
|
||||
<!-- Suggestion pills -->
|
||||
<string name="suggest_brainstorm">Brainstormen</string>
|
||||
<string name="suggest_brainstorm_prompt">Lass uns zu einem Thema brainstormen.</string>
|
||||
<string name="suggest_image">Bild generieren</string>
|
||||
<string name="suggest_image_prompt">Generiere ein Bild von einem Bergsee bei Sonnenaufgang.</string>
|
||||
<string name="suggest_video">Video erstellen</string>
|
||||
<string name="suggest_video_prompt">Erstelle ein kurzes Video von einer Stadt bei Nacht.</string>
|
||||
<string name="suggest_web">Web durchsuchen</string>
|
||||
<string name="suggest_web_prompt">Durchsuche das Web nach den neuesten KI-Nachrichten.</string>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<string name="sidebar_brand">kaizen</string>
|
||||
<string name="sidebar_pinned">★ ANGEHEFTET</string>
|
||||
<string name="sidebar_today">HEUTE</string>
|
||||
<string name="sidebar_yesterday">GESTERN</string>
|
||||
<string name="sidebar_earlier">FRÜHER</string>
|
||||
<string name="sidebar_logout">Abmelden</string>
|
||||
<string name="sidebar_settings">Einstellungen</string>
|
||||
|
||||
<!-- Model sheet -->
|
||||
<string name="model_sheet_title">Modell auswählen</string>
|
||||
<string name="model_search_placeholder">Modell suchen…</string>
|
||||
<string name="model_search_clear">Löschen</string>
|
||||
<string name="model_connecting">Verbindung wird hergestellt…</string>
|
||||
<string name="model_no_results">Keine Treffer</string>
|
||||
<string name="model_favorite">Favorit</string>
|
||||
<string name="model_selected">Ausgewählt</string>
|
||||
<string name="model_favorites_group">★ Favoriten</string>
|
||||
|
||||
<!-- Login -->
|
||||
<string name="login_welcome">Willkommen</string>
|
||||
<string name="login_subtitle">Melde dich bei deiner Kaizen-Instanz an</string>
|
||||
<string name="login_url_placeholder">https://deine-instanz.de</string>
|
||||
<string name="login_email_placeholder">E-Mail</string>
|
||||
<string name="login_password_placeholder">Passwort</string>
|
||||
<string name="login_submit">Anmelden</string>
|
||||
<string name="login_error_credentials">E-Mail oder Passwort ist falsch.</string>
|
||||
<string name="login_error_rate_limited">Zu viele Versuche. Bitte kurz warten.</string>
|
||||
<string name="login_error_connection">Verbindung fehlgeschlagen. Server-Adresse prüfen.</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">Einstellungen</string>
|
||||
<string name="settings_back">Zurück</string>
|
||||
<string name="settings_section_appearance">ERSCHEINUNGSBILD</string>
|
||||
<string name="settings_section_profile">PROFIL & KONTO</string>
|
||||
<string name="settings_section_ai">KI-PROFIL & KONTEXT</string>
|
||||
<string name="settings_accent_title">Akzentfarbe</string>
|
||||
<string name="settings_accent_desc">Wähle das Farbschema der App</string>
|
||||
<string name="settings_appearance_title">Helligkeitsmodus</string>
|
||||
<string name="settings_appearance_desc">Passe das Hell/Dunkel-Erscheinungsbild an</string>
|
||||
<string name="settings_name_title">Benutzername</string>
|
||||
<string name="settings_name_desc">Ändere deinen Anzeigenamen</string>
|
||||
<string name="settings_email_title">E-Mail-Adresse</string>
|
||||
<string name="settings_ai_title">System-Prompt & Fakten</string>
|
||||
<string name="settings_appearance_light">Hell</string>
|
||||
<string name="settings_appearance_dark">Dunkel</string>
|
||||
<string name="settings_appearance_system">System</string>
|
||||
</resources>
|
||||
Loading…
Reference in a new issue