diff --git a/app/src/main/java/com/keylightpiano/pianotimeline/domain/Recording.kt b/app/src/main/java/com/keylightpiano/pianotimeline/domain/Recording.kt
index 32d7548..b23d6d1 100644
--- a/app/src/main/java/com/keylightpiano/pianotimeline/domain/Recording.kt
+++ b/app/src/main/java/com/keylightpiano/pianotimeline/domain/Recording.kt
@@ -26,9 +26,11 @@ data class Recording(
) {
val hasMidi: Boolean get() = midiFileName != null
- /** "22 Jun 2026" style for display. */
+ /** "22 Jun 2026" style for display, in the device's current locale. */
val formattedDate: String
- get() = date.format(DateTimeFormatter.ofPattern("d MMM yyyy", Locale.ENGLISH))
+ get() = date.format(
+ DateTimeFormatter.ofPattern("d MMM yyyy", Locale.getDefault())
+ )
val groupingArtist: String
get() = if (artist.contains("soundtrack", ignoreCase = true)) "Soundtracks" else artist
diff --git a/app/src/main/java/com/keylightpiano/pianotimeline/ui/MiniPlayer.kt b/app/src/main/java/com/keylightpiano/pianotimeline/ui/MiniPlayer.kt
index 77a483a..679121e 100644
--- a/app/src/main/java/com/keylightpiano/pianotimeline/ui/MiniPlayer.kt
+++ b/app/src/main/java/com/keylightpiano/pianotimeline/ui/MiniPlayer.kt
@@ -36,9 +36,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
+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.keylightpiano.pianotimeline.R
import com.keylightpiano.pianotimeline.domain.Recording
import com.keylightpiano.pianotimeline.player.PlaybackState
@@ -154,7 +156,7 @@ fun MiniPlayer(
IconButton(onClick = onPrevious, modifier = Modifier.size(40.dp)) {
Icon(
painter = painterResource(android.R.drawable.ic_media_previous),
- contentDescription = "Previous",
+ contentDescription = stringResource(R.string.cd_previous),
modifier = Modifier.size(22.dp)
)
}
@@ -165,7 +167,7 @@ fun MiniPlayer(
if (playback.isPlaying) android.R.drawable.ic_media_pause
else android.R.drawable.ic_media_play
),
- contentDescription = if (playback.isPlaying) "Pause" else "Play",
+ contentDescription = stringResource(if (playback.isPlaying) R.string.cd_pause else R.string.cd_play),
modifier = Modifier.size(24.dp)
)
}
@@ -173,7 +175,7 @@ fun MiniPlayer(
IconButton(onClick = onNext, modifier = Modifier.size(40.dp)) {
Icon(
painter = painterResource(android.R.drawable.ic_media_next),
- contentDescription = "Next",
+ contentDescription = stringResource(R.string.cd_next),
modifier = Modifier.size(22.dp)
)
}
@@ -207,7 +209,7 @@ fun MiniPlayer(
IconButton(onClick = onDismiss, modifier = Modifier.size(40.dp)) {
Icon(
imageVector = Icons.Default.Close,
- contentDescription = "Stop",
+ contentDescription = stringResource(R.string.cd_stop),
modifier = Modifier.size(20.dp)
)
}
diff --git a/app/src/main/java/com/keylightpiano/pianotimeline/ui/RecordingItem.kt b/app/src/main/java/com/keylightpiano/pianotimeline/ui/RecordingItem.kt
index c7a069d..4c38e50 100644
--- a/app/src/main/java/com/keylightpiano/pianotimeline/ui/RecordingItem.kt
+++ b/app/src/main/java/com/keylightpiano/pianotimeline/ui/RecordingItem.kt
@@ -36,9 +36,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
+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.keylightpiano.pianotimeline.R
import com.keylightpiano.pianotimeline.domain.Recording
private val NotesGreen = Color(0xFF25B300)
@@ -88,7 +90,7 @@ fun RecordingItem(
if (isPlaying) android.R.drawable.ic_media_pause
else android.R.drawable.ic_media_play
),
- contentDescription = if (isPlaying) "Pause" else "Play",
+ contentDescription = stringResource(if (isPlaying) R.string.cd_pause else R.string.cd_play),
modifier = Modifier.size(22.dp)
)
}
@@ -148,7 +150,7 @@ fun RecordingItem(
Icon(
imageVector = if (recording.isFavorite) Icons.Filled.Star
else Icons.Outlined.StarBorder,
- contentDescription = if (recording.isFavorite) "Unfavorite" else "Favorite",
+ contentDescription = stringResource(if (recording.isFavorite) R.string.cd_unfavorite else R.string.cd_favorite),
tint = if (recording.isFavorite) Color(0xFFFFC107)
else MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(20.dp)
@@ -164,7 +166,7 @@ fun RecordingItem(
border = BorderStroke(1.5.dp, NotesGreen)
) {
Text(
- "Notes",
+ stringResource(R.string.notes),
style = MaterialTheme.typography.labelSmall,
color = NotesGreen
)
@@ -191,7 +193,7 @@ fun RecordingItem(
onDismissRequest = { menuExpanded = false }
) {
DropdownMenuItem(
- text = { Text("Share audio") },
+ text = { Text(stringResource(R.string.menu_share_audio)) },
onClick = {
menuExpanded = false
onShareAudio()
@@ -199,7 +201,7 @@ fun RecordingItem(
)
if (onShareVisualization != null) {
DropdownMenuItem(
- text = { Text("Share visualization") },
+ text = { Text(stringResource(R.string.menu_share_visualization)) },
onClick = {
menuExpanded = false
onShareVisualization()
@@ -207,7 +209,7 @@ fun RecordingItem(
)
}
DropdownMenuItem(
- text = { Text("Copy link") },
+ text = { Text(stringResource(R.string.menu_copy_link)) },
onClick = {
menuExpanded = false
onCopyLink()
diff --git a/app/src/main/java/com/keylightpiano/pianotimeline/ui/RecordingListScreen.kt b/app/src/main/java/com/keylightpiano/pianotimeline/ui/RecordingListScreen.kt
index 46913f8..454c9e6 100644
--- a/app/src/main/java/com/keylightpiano/pianotimeline/ui/RecordingListScreen.kt
+++ b/app/src/main/java/com/keylightpiano/pianotimeline/ui/RecordingListScreen.kt
@@ -57,6 +57,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -172,7 +173,7 @@ fun RecordingListScreen(
.size(28.dp)
.padding(end = 8.dp)
)
- Text("Piano Timeline", fontWeight = FontWeight.Bold)
+ Text(stringResource(R.string.title_app), fontWeight = FontWeight.Bold)
}
},
actions = {
@@ -180,7 +181,7 @@ fun RecordingListScreen(
IconButton(onClick = { vm.toggleShuffle() }) {
Icon(
imageVector = Icons.Default.Shuffle,
- contentDescription = "Shuffle",
+ contentDescription = stringResource(R.string.cd_shuffle),
tint = if (shuffle) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant
)
@@ -190,7 +191,7 @@ fun RecordingListScreen(
Icon(
imageVector = if (repeatMode == RepeatMode.ONE)
Icons.Default.RepeatOne else Icons.Default.Repeat,
- contentDescription = "Repeat",
+ contentDescription = stringResource(R.string.cd_repeat),
tint = if (repeatMode == RepeatMode.OFF)
MaterialTheme.colorScheme.onSurfaceVariant
else MaterialTheme.colorScheme.primary
@@ -200,7 +201,7 @@ fun RecordingListScreen(
IconButton(onClick = { showControls = !showControls }) {
Icon(
imageVector = Icons.Default.Search,
- contentDescription = "Search & Filter",
+ contentDescription = stringResource(R.string.cd_search_filter),
tint = if (hasActiveFilter) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant
)
@@ -229,7 +230,7 @@ fun RecordingListScreen(
Column(horizontalAlignment = Alignment.CenterHorizontally) {
CircularProgressIndicator()
Spacer(Modifier.height(12.dp))
- Text("Syncing...", style = MaterialTheme.typography.bodyMedium)
+ Text(stringResource(R.string.syncing), style = MaterialTheme.typography.bodyMedium)
}
}
}
@@ -241,9 +242,9 @@ fun RecordingListScreen(
verticalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.padding(24.dp)
) {
- Text("Couldn't load recordings", style = MaterialTheme.typography.titleMedium)
+ Text(stringResource(R.string.error_load_title), style = MaterialTheme.typography.titleMedium)
Text(s.message, style = MaterialTheme.typography.bodySmall)
- Button(onClick = { vm.retry() }) { Text("Retry") }
+ Button(onClick = { vm.retry() }) { Text(stringResource(R.string.retry)) }
}
}
}
@@ -260,12 +261,12 @@ fun RecordingListScreen(
value = searchQuery,
onValueChange = { vm.setSearchQuery(it) },
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 4.dp),
- placeholder = { Text("Search artist, title...") },
+ placeholder = { Text(stringResource(R.string.search_placeholder)) },
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
trailingIcon = {
if (searchQuery.isNotEmpty()) {
IconButton(onClick = { vm.setSearchQuery("") }) {
- Icon(Icons.Default.Clear, contentDescription = "Clear")
+ Icon(Icons.Default.Clear, contentDescription = stringResource(R.string.cd_clear))
}
}
},
@@ -280,14 +281,14 @@ fun RecordingListScreen(
) {
Box {
TextButton(onClick = { showSortMenu = true }) {
- Text(sortMode.label, style = MaterialTheme.typography.labelMedium)
+ Text(stringResource(sortMode.labelRes), style = MaterialTheme.typography.labelMedium)
}
DropdownMenu(expanded = showSortMenu, onDismissRequest = { showSortMenu = false }) {
SortMode.entries.forEach { mode ->
DropdownMenuItem(
text = {
Text(
- mode.label,
+ stringResource(mode.labelRes),
fontWeight = if (mode == sortMode) FontWeight.Bold else FontWeight.Normal
)
},
@@ -302,16 +303,16 @@ fun RecordingListScreen(
TextButton(onClick = { showArtistFilter = !showArtistFilter }) {
Text(
- if (hiddenArtists.isEmpty()) "Artists"
- else "Artists (${hiddenArtists.size} hidden)",
+ if (hiddenArtists.isEmpty()) stringResource(R.string.filter_artists)
+ else stringResource(R.string.filter_artists_hidden, hiddenArtists.size),
style = MaterialTheme.typography.labelMedium
)
}
TextButton(onClick = { showYearFilter = !showYearFilter }) {
Text(
- if (hiddenYears.isEmpty()) "Years"
- else "Years (${hiddenYears.size} hidden)",
+ if (hiddenYears.isEmpty()) stringResource(R.string.filter_years)
+ else stringResource(R.string.filter_years_hidden, hiddenYears.size),
style = MaterialTheme.typography.labelMedium
)
}
@@ -325,10 +326,10 @@ fun RecordingListScreen(
Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp)) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
TextButton(onClick = { vm.showAllArtists() }) {
- Text("All", style = MaterialTheme.typography.labelSmall)
+ Text(stringResource(R.string.filter_all), style = MaterialTheme.typography.labelSmall)
}
TextButton(onClick = { vm.hideAllArtists() }) {
- Text("None", style = MaterialTheme.typography.labelSmall)
+ Text(stringResource(R.string.filter_none), style = MaterialTheme.typography.labelSmall)
}
}
FlowRow(
@@ -354,10 +355,10 @@ fun RecordingListScreen(
Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp)) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
TextButton(onClick = { vm.showAllYears() }) {
- Text("All", style = MaterialTheme.typography.labelSmall)
+ Text(stringResource(R.string.filter_all), style = MaterialTheme.typography.labelSmall)
}
TextButton(onClick = { vm.hideAllYears() }) {
- Text("None", style = MaterialTheme.typography.labelSmall)
+ Text(stringResource(R.string.filter_none), style = MaterialTheme.typography.labelSmall)
}
}
FlowRow(
@@ -380,7 +381,7 @@ fun RecordingListScreen(
}
Text(
- text = "${recordings.size} recordings",
+ text = stringResource(R.string.recordings_count, recordings.size),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)
@@ -450,6 +451,6 @@ private fun copyLink(context: android.content.Context, url: String) {
// On Android 13+ the system shows its own "copied" confirmation; on older
// versions a Toast is the norm.
if (android.os.Build.VERSION.SDK_INT < 33) {
- android.widget.Toast.makeText(context, "Link copied", android.widget.Toast.LENGTH_SHORT).show()
+ android.widget.Toast.makeText(context, context.getString(R.string.toast_link_copied), android.widget.Toast.LENGTH_SHORT).show()
}
}
diff --git a/app/src/main/java/com/keylightpiano/pianotimeline/ui/SortMode.kt b/app/src/main/java/com/keylightpiano/pianotimeline/ui/SortMode.kt
index 9ee355b..fe0c2cf 100644
--- a/app/src/main/java/com/keylightpiano/pianotimeline/ui/SortMode.kt
+++ b/app/src/main/java/com/keylightpiano/pianotimeline/ui/SortMode.kt
@@ -1,15 +1,19 @@
package com.keylightpiano.pianotimeline.ui
+import androidx.annotation.StringRes
+import com.keylightpiano.pianotimeline.R
+
/**
* Available sort modes for the recording list.
- * Each knows how to produce a Comparator for Recording.
+ * Each carries a string-resource id for its label (resolved at display time via
+ * stringResource), so the labels localize automatically.
*/
-enum class SortMode(val label: String) {
- DATE_DESC("Newest first"),
- DATE_ASC("Oldest first"),
- ARTIST_AZ("Artist A → Z"),
- ARTIST_ZA("Artist Z → A"),
- TITLE_AZ("Title A → Z"),
- DURATION_DESC("Longest first"),
- DURATION_ASC("Shortest first");
+enum class SortMode(@StringRes val labelRes: Int) {
+ DATE_DESC(R.string.sort_newest),
+ DATE_ASC(R.string.sort_oldest),
+ ARTIST_AZ(R.string.sort_artist_az),
+ ARTIST_ZA(R.string.sort_artist_za),
+ TITLE_AZ(R.string.sort_title_az),
+ DURATION_DESC(R.string.sort_longest),
+ DURATION_ASC(R.string.sort_shortest);
}
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..7dab319
--- /dev/null
+++ b/app/src/main/res/values-es/strings.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+ Piano Timeline
+ Aleatorio
+ Repetir
+ Buscar y filtrar
+
+
+ Sincronizando\u2026
+ No se pudieron cargar las grabaciones
+ Reintentar
+
+
+ Buscar artista, t\u00EDtulo\u2026
+ Borrar
+ Artistas
+ Artistas (%1$d ocultos)
+ A\u00F1os
+ A\u00F1os (%1$d ocultos)
+ Todos
+ Ninguno
+
+
+ %1$d grabaciones
+
+
+ Reproducir
+ Pausar
+ A\u00F1adir a favoritos
+ Quitar de favoritos
+ Notas
+
+
+ Compartir audio
+ Compartir visualizaci\u00F3n
+ Copiar enlace
+ Enlace copiado
+
+
+ Anterior
+ Siguiente
+ Detener
+
+
+ M\u00E1s recientes
+ M\u00E1s antiguas
+ Artista A \u2192 Z
+ Artista Z \u2192 A
+ T\u00EDtulo A \u2192 Z
+ M\u00E1s largas
+ M\u00E1s cortas
+
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000..979006a
--- /dev/null
+++ b/app/src/main/res/values-it/strings.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+ Piano Timeline
+ Casuale
+ Ripeti
+ Cerca e filtra
+
+
+ Sincronizzazione\u2026
+ Impossibile caricare le registrazioni
+ Riprova
+
+
+ Cerca artista, titolo\u2026
+ Cancella
+ Artisti
+ Artisti (%1$d nascosti)
+ Anni
+ Anni (%1$d nascosti)
+ Tutti
+ Nessuno
+
+
+ %1$d registrazioni
+
+
+ Riproduci
+ Pausa
+ Aggiungi ai preferiti
+ Rimuovi dai preferiti
+ Note
+
+
+ Condividi audio
+ Condividi visualizzazione
+ Copia link
+ Link copiato
+
+
+ Precedente
+ Successivo
+ Ferma
+
+
+ Pi\u00F9 recenti
+ Meno recenti
+ Artista A \u2192 Z
+ Artista Z \u2192 A
+ Titolo A \u2192 Z
+ Pi\u00F9 lunghi
+ Pi\u00F9 brevi
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cd12690..a6196ed 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,55 @@
+
- PianoTimeline
-
\ No newline at end of file
+ Piano Timeline
+
+
+ Piano Timeline
+ Shuffle
+ Repeat
+ Search & Filter
+
+
+ Syncing\u2026
+ Couldn\'t load recordings
+ Retry
+
+
+ Search artist, title\u2026
+ Clear
+ Artists
+ Artists (%1$d hidden)
+ Years
+ Years (%1$d hidden)
+ All
+ None
+
+
+ %1$d recordings
+
+
+ Play
+ Pause
+ Favorite
+ Unfavorite
+ Notes
+
+
+ Share audio
+ Share visualization
+ Copy link
+ Link copied
+
+
+ Previous
+ Next
+ Stop
+
+
+ Newest first
+ Oldest first
+ Artist A \u2192 Z
+ Artist Z \u2192 A
+ Title A \u2192 Z
+ Longest first
+ Shortest first
+