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 +