From a5864e15f855ed6b5d1fdd82525d381bf826d4a4 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:53:00 +0700 Subject: [PATCH 001/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 138 ++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 8dfbd9d9..949846ad 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -991,7 +991,7 @@ "@setupNotificationProgressDescription": { "description": "Info about notification usage" }, - "setupNotificationBackgroundDescription": "Werde benachrichtigt über Download-Fortschritt und -Fertigstellung. Dies hilft Ihnen, Downloads zu verfolgen, wenn die App im Hintergrund ist.", + "setupNotificationBackgroundDescription": "Erhalte Benachrichtigungen über den Fortschritt und die Fertigstellung deiner Downloads, selbst wenn die App im Hintergrund läuft.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" }, @@ -1836,7 +1836,7 @@ } } }, - "logEntries": "Entries ({count})", + "logEntries": "{count} Einträge", "@logEntries": { "description": "Total log count", "placeholders": { @@ -1845,7 +1845,7 @@ } } }, - "credentialsTitle": "Spotify Credentials", + "credentialsTitle": "Spotify-Anmeldedaten", "@credentialsTitle": { "description": "Credentials dialog title" }, @@ -1925,7 +1925,7 @@ "@lyricsModeDescription": { "description": "Lyrics mode picker description" }, - "lyricsModeEmbed": "In Datei einbinden", + "lyricsModeEmbed": "In Datei einbetten", "@lyricsModeEmbed": { "description": "Lyrics mode option - embed in audio file" }, @@ -1945,7 +1945,7 @@ "@lyricsModeBoth": { "description": "Lyrics mode option - embed and external" }, - "lyricsModeBothSubtitle": "Lyrics einbinden und als .lrc speichern", + "lyricsModeBothSubtitle": "Lyrics einbetten und als .lrc speichern", "@lyricsModeBothSubtitle": { "description": "Subtitle for both option" }, @@ -1969,43 +1969,43 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "Choose your preferred language", + "appearanceLanguageSubtitle": "Deine bevorzugte Sprache wählen", "@appearanceLanguageSubtitle": { "description": "Language setting subtitle" }, - "settingsAppearanceSubtitle": "Theme, colors, display", + "settingsAppearanceSubtitle": "Design, Farben, Anzeige", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" }, - "settingsDownloadSubtitle": "Service, quality, filename format", + "settingsDownloadSubtitle": "Dienst, Qualität, Dateinamen-Format", "@settingsDownloadSubtitle": { "description": "Download settings description" }, - "settingsOptionsSubtitle": "Fallback, lyrics, cover art, updates", + "settingsOptionsSubtitle": "Fallback, Lyrics, Covers, Updates", "@settingsOptionsSubtitle": { "description": "Options settings description" }, - "settingsExtensionsSubtitle": "Manage download providers", + "settingsExtensionsSubtitle": "Download-Anbieter verwalten", "@settingsExtensionsSubtitle": { "description": "Extensions settings description" }, - "settingsLogsSubtitle": "View app logs for debugging", + "settingsLogsSubtitle": "App-Logs zum Debuggen anzeigen", "@settingsLogsSubtitle": { "description": "Logs settings description" }, - "loadingSharedLink": "Loading shared link...", + "loadingSharedLink": "Link wird geladen...", "@loadingSharedLink": { "description": "Status when opening shared URL" }, - "pressBackAgainToExit": "Press back again to exit", + "pressBackAgainToExit": "Drücke wieder \"zurück\" um die App zu beenden", "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "Tracks", + "tracksHeader": "Titel", "@tracksHeader": { "description": "Section header for track list" }, - "downloadAllCount": "Download All ({count})", + "downloadAllCount": "Alle {count} Titel herunterladen", "@downloadAllCount": { "description": "Download all button with count", "placeholders": { @@ -2014,7 +2014,7 @@ } } }, - "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "tracksCount": "{count, plural, =1{1 Titel} other{{count} Titel}}", "@tracksCount": { "description": "Track count display", "placeholders": { @@ -2023,23 +2023,23 @@ } } }, - "trackCopyFilePath": "Copy file path", + "trackCopyFilePath": "Dateipfad kopieren", "@trackCopyFilePath": { "description": "Action - copy file path" }, - "trackRemoveFromDevice": "Remove from device", + "trackRemoveFromDevice": "Vom Gerät entfernen", "@trackRemoveFromDevice": { "description": "Action - delete downloaded file" }, - "trackLoadLyrics": "Load Lyrics", + "trackLoadLyrics": "Lade Lyrics", "@trackLoadLyrics": { "description": "Action - fetch lyrics" }, - "trackMetadata": "Metadata", + "trackMetadata": "Metadaten", "@trackMetadata": { "description": "Tab title - track metadata" }, - "trackFileInfo": "File Info", + "trackFileInfo": "Datei-Info", "@trackFileInfo": { "description": "Tab title - file information" }, @@ -2047,27 +2047,27 @@ "@trackLyrics": { "description": "Tab title - lyrics" }, - "trackFileNotFound": "File not found", + "trackFileNotFound": "Datei nicht gefunden", "@trackFileNotFound": { "description": "Error - file doesn't exist" }, - "trackOpenInDeezer": "Open in Deezer", + "trackOpenInDeezer": "In Deezer öffnen", "@trackOpenInDeezer": { "description": "Action - open track in Deezer app" }, - "trackOpenInSpotify": "Open in Spotify", + "trackOpenInSpotify": "In Spotify öffnen", "@trackOpenInSpotify": { "description": "Action - open track in Spotify app" }, - "trackTrackName": "Track name", + "trackTrackName": "Name des Titels", "@trackTrackName": { "description": "Metadata label - track title" }, - "trackArtist": "Artist", + "trackArtist": "Künstler", "@trackArtist": { "description": "Metadata label - artist name" }, - "trackAlbumArtist": "Album artist", + "trackAlbumArtist": "Album Künstler", "@trackAlbumArtist": { "description": "Metadata label - album artist" }, @@ -2075,23 +2075,23 @@ "@trackAlbum": { "description": "Metadata label - album name" }, - "trackTrackNumber": "Track number", + "trackTrackNumber": "Titelnummer", "@trackTrackNumber": { "description": "Metadata label - track number" }, - "trackDiscNumber": "Disc number", + "trackDiscNumber": "CD-Nummer", "@trackDiscNumber": { "description": "Metadata label - disc number" }, - "trackDuration": "Duration", + "trackDuration": "Länge", "@trackDuration": { "description": "Metadata label - track length" }, - "trackAudioQuality": "Audio quality", + "trackAudioQuality": "Audioqualität", "@trackAudioQuality": { "description": "Metadata label - audio quality" }, - "trackReleaseDate": "Release date", + "trackReleaseDate": "Erscheinungsdatum", "@trackReleaseDate": { "description": "Metadata label - release date" }, @@ -2103,31 +2103,31 @@ "@trackLabel": { "description": "Metadata label - record label" }, - "trackCopyright": "Copyright", + "trackCopyright": "Urheberrecht", "@trackCopyright": { "description": "Metadata label - copyright information" }, - "trackDownloaded": "Downloaded", + "trackDownloaded": "Heruntergeladen", "@trackDownloaded": { "description": "Metadata label - download date" }, - "trackCopyLyrics": "Copy lyrics", + "trackCopyLyrics": "Lyrics kopieren", "@trackCopyLyrics": { "description": "Action - copy lyrics to clipboard" }, - "trackLyricsNotAvailable": "Lyrics not available for this track", + "trackLyricsNotAvailable": "Lyrics sind für diesen Titel nicht verfügbar", "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, - "trackLyricsTimeout": "Request timed out. Try again later.", + "trackLyricsTimeout": "Anfrage Timeout. Versuchen Sie es später erneut.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" }, - "trackLyricsLoadFailed": "Failed to load lyrics", + "trackLyricsLoadFailed": "Fehler beim Laden der Lyrics", "@trackLyricsLoadFailed": { "description": "Message when lyrics loading fails" }, - "trackEmbedLyrics": "Embed Lyrics", + "trackEmbedLyrics": "Lyrics einbetten", "@trackEmbedLyrics": { "description": "Action - embed lyrics into audio file" }, @@ -2160,7 +2160,7 @@ } } }, - "dateToday": "Today", + "dateToday": "Heute", "@dateToday": { "description": "Relative date - today" }, @@ -2211,7 +2211,7 @@ "@tapToSeeError": { "description": "Tooltip for failed download" }, - "storeFilterAll": "All", + "storeFilterAll": "Alle", "@storeFilterAll": { "description": "Store filter - all extensions" }, @@ -2259,7 +2259,7 @@ "@extensionDefaultProviderSubtitle": { "description": "Subtitle for default provider" }, - "extensionAuthor": "Author", + "extensionAuthor": "Entwickler", "@extensionAuthor": { "description": "Extension detail - author" }, @@ -2267,7 +2267,7 @@ "@extensionId": { "description": "Extension detail - unique ID" }, - "extensionError": "Error", + "extensionError": "Fehler", "@extensionError": { "description": "Extension detail - error message" }, @@ -2450,7 +2450,7 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Lossy", + "qualityLossy": "Verlustbehaftet", "@qualityLossy": { "description": "Quality option - lossy format (MP3/Opus)" }, @@ -2562,7 +2562,7 @@ "@downloadBestAvailable": { "description": "Quality option - highest available" }, - "folderNone": "None", + "folderNone": "Keine Ausgewählt", "@folderNone": { "description": "Folder option - no organization" }, @@ -2570,7 +2570,7 @@ "@folderNoneSubtitle": { "description": "Subtitle for no folder organization" }, - "folderArtist": "Artist", + "folderArtist": "Künstler", "@folderArtist": { "description": "Folder option - by artist" }, @@ -2670,15 +2670,15 @@ "@settingsDownloadNetwork": { "description": "Setting for network type preference" }, - "settingsDownloadNetworkAny": "WiFi + Mobile Data", + "settingsDownloadNetworkAny": "WLAN + Mobile Daten", "@settingsDownloadNetworkAny": { "description": "Network option - use any connection" }, - "settingsDownloadNetworkWifiOnly": "WiFi Only", + "settingsDownloadNetworkWifiOnly": "Nur WLAN", "@settingsDownloadNetworkWifiOnly": { "description": "Network option - only use WiFi" }, - "settingsDownloadNetworkSubtitle": "Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.", + "settingsDownloadNetworkSubtitle": "Wähle aus, welches Netzwerk für Downloads verwendet werden soll. Wenn nur WLAN aktiviert wird, werden Downloads auf mobilen Daten angehalten.", "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, @@ -2698,7 +2698,7 @@ "@queueDownloadFailed": { "description": "Error dialog title" }, - "queueTrackLabel": "Track:", + "queueTrackLabel": "Titel:", "@queueTrackLabel": { "description": "Label in error dialog" }, @@ -2706,7 +2706,7 @@ "@queueArtistLabel": { "description": "Label in error dialog" }, - "queueErrorLabel": "Error:", + "queueErrorLabel": "Fehler:", "@queueErrorLabel": { "description": "Label in error dialog" }, @@ -2767,7 +2767,7 @@ } } }, - "downloadedAlbumTracksHeader": "Tracks", + "downloadedAlbumTracksHeader": "Titel", "@downloadedAlbumTracksHeader": { "description": "Section header for tracks" }, @@ -2824,7 +2824,7 @@ "@utilityFunctions": { "description": "Extension capability - utility functions" }, - "recentTypeArtist": "Artist", + "recentTypeArtist": "Künstler", "@recentTypeArtist": { "description": "Recent access item type - artist" }, @@ -2832,7 +2832,7 @@ "@recentTypeAlbum": { "description": "Recent access item type - album" }, - "recentTypeSong": "Song", + "recentTypeSong": "Titel", "@recentTypeSong": { "description": "Recent access item type - song/track" }, @@ -3002,7 +3002,7 @@ "@allFilesAccessDisabledSubtitle": { "description": "Subtitle when all files access is disabled" }, - "allFilesAccessDescription": "Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.", + "allFilesAccessDescription": "Aktiviere die Option, wenn beim Speichern in benutzerdefinierten Ordnern Schreibfehler auftreten. Weil Android 13+ standardmäßig den Zugriff auf bestimmte Verzeichnisse einschränkt.", "@allFilesAccessDescription": { "description": "Description explaining when to enable all files access" }, @@ -3132,7 +3132,7 @@ } } }, - "libraryLastScannedNever": "Never", + "libraryLastScannedNever": "Nie", "@libraryLastScannedNever": { "description": "Shown when library has never been scanned" }, @@ -3185,11 +3185,11 @@ "@librarySourceDownloaded": { "description": "Badge for tracks downloaded via SpotiFLAC" }, - "librarySourceLocal": "Local", + "librarySourceLocal": "Lokal", "@librarySourceLocal": { "description": "Badge for tracks from local library scan" }, - "libraryFilterAll": "All", + "libraryFilterAll": "Alle", "@libraryFilterAll": { "description": "Filter chip - show all library items" }, @@ -3197,7 +3197,7 @@ "@libraryFilterDownloaded": { "description": "Filter chip - show only downloaded items" }, - "libraryFilterLocal": "Local", + "libraryFilterLocal": "Lokal", "@libraryFilterLocal": { "description": "Filter chip - show only local library items" }, @@ -3205,15 +3205,15 @@ "@libraryFilterTitle": { "description": "Filter bottom sheet title" }, - "libraryFilterReset": "Reset", + "libraryFilterReset": "Zurücksetzen", "@libraryFilterReset": { "description": "Reset all filters button" }, - "libraryFilterApply": "Apply", + "libraryFilterApply": "Anwenden", "@libraryFilterApply": { "description": "Apply filters button" }, - "libraryFilterSource": "Source", + "libraryFilterSource": "Quelle", "@libraryFilterSource": { "description": "Filter section - source type" }, @@ -3229,7 +3229,7 @@ "@libraryFilterQualityCD": { "description": "Filter option - CD quality audio" }, - "libraryFilterQualityLossy": "Lossy", + "libraryFilterQualityLossy": "Verlustbehaftet", "@libraryFilterQualityLossy": { "description": "Filter option - lossy compressed audio" }, @@ -3241,7 +3241,7 @@ "@libraryFilterDate": { "description": "Filter section - date range" }, - "libraryFilterDateToday": "Today", + "libraryFilterDateToday": "Heute", "@libraryFilterDateToday": { "description": "Filter option - today only" }, @@ -3257,15 +3257,15 @@ "@libraryFilterDateYear": { "description": "Filter option - this year" }, - "libraryFilterSort": "Sort", + "libraryFilterSort": "Sortieren", "@libraryFilterSort": { "description": "Filter section - sort order" }, - "libraryFilterSortLatest": "Latest", + "libraryFilterSortLatest": "Neuste", "@libraryFilterSortLatest": { "description": "Sort option - newest first" }, - "libraryFilterSortOldest": "Oldest", + "libraryFilterSortOldest": "Älteste", "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, @@ -3312,11 +3312,11 @@ "@storageSwitchToAppTitle": { "description": "Dialog title when switching to app storage" }, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", + "storageSwitchToSafMessage": "Deine vorhandenen Downloads bleiben im aktuellen Verzeichnis erhalten und bleiben zugänglich.\n\nNeue Downloads werden dann aber in den ausgewählten SAF-Ordner gespeichert.", "@storageSwitchToSafMessage": { "description": "Explanation when switching to SAF" }, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", + "storageSwitchToAppMessage": "Deine vorhandenen Downloads bleiben im aktuellen SAF-Verzeichnis erhalten und bleiben zugänglich.\n\nNeue Downloads werden dann aber in den Music/SpotiFLAC Ordner gespeichert.", "@storageSwitchToAppMessage": { "description": "Explanation when switching to app storage" }, From 924569aefbda4564de434ec94ee09d3dc9790915 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:53:01 +0700 Subject: [PATCH 002/104] New translations app_en.arb (Russian) --- lib/l10n/arb/app_ru.arb | 102 ++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 6080e5e9..2ca3c6bf 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -89,7 +89,7 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} other {{count} треков}}", + "historyTracksCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}", "@historyTracksCount": { "description": "Track count with plural form", "placeholders": { @@ -98,7 +98,7 @@ } } }, - "historyAlbumsCount": "{count, plural, one {{count} альбом} few {{count} альбома} many {{count} альбомов} other {{count} альбомов}}", + "historyAlbumsCount": "{count, plural, one {{count} альбом} few {{count} альбома} many {{count} альбомов} =1 {1 альбом} other {{count} альбомов}}", "@historyAlbumsCount": { "description": "Album count with plural form", "placeholders": { @@ -320,11 +320,11 @@ "@optionsUseExtensionProvidersOff": { "description": "Status when extension providers disabled" }, - "optionsEmbedLyrics": "Вставить текст песни", + "optionsEmbedLyrics": "Вписать текст песни", "@optionsEmbedLyrics": { "description": "Embed lyrics in audio files" }, - "optionsEmbedLyricsSubtitle": "Вставить синхронизированные тексты в FLAC файлы", + "optionsEmbedLyricsSubtitle": "Вписать синхронизированные тексты во FLAC файлы", "@optionsEmbedLyricsSubtitle": { "description": "Subtitle for embed lyrics" }, @@ -592,7 +592,7 @@ "@aboutVersion": { "description": "Version info label" }, - "aboutBinimumDesc": "Создатель QQDL & HiFi API. Без этого API загрузки Tidal не существовали бы!", + "aboutBinimumDesc": "Создатель QQDL & HiFi API. Без него API загрузки Tidal не существовали бы!", "@aboutBinimumDesc": { "description": "Credit description for binimum" }, @@ -636,7 +636,7 @@ "@albumTitle": { "description": "Album screen title" }, - "albumTracks": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} other {{count} треков}}", + "albumTracks": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}", "@albumTracks": { "description": "Album track count", "placeholders": { @@ -673,7 +673,7 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, one {{count} релиз} few {{count} релиза} many {{count} релизов} other {{count} релизов}}", + "artistReleases": "{count, plural, one {{count} релиз} few {{count} релиза} many {{count} релизов} =1 {1 релиз} other {{count} релизов}}", "@artistReleases": { "description": "Artist release count", "placeholders": { @@ -1152,7 +1152,7 @@ "@dialogDeleteSelectedTitle": { "description": "Dialog title - delete selected items" }, - "dialogDeleteSelectedMessage": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} other {треков}} из истории?\n\nЭто также удалит файлы из хранилища.", + "dialogDeleteSelectedMessage": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}} из истории?\n\nЭто также удалит файлы из хранилища.", "@dialogDeleteSelectedMessage": { "description": "Dialog message - delete selected tracks", "placeholders": { @@ -1166,7 +1166,7 @@ "description": "Dialog title - import CSV playlist" }, "dialogImportPlaylistMessage": "Найдено {count} треков в CSV. Добавить их в очередь загрузки?", - "csvImportTracks": "{count} треков из CSV", + "csvImportTracks": "{count} трек(-ов) из CSV", "@csvImportTracks": { "description": "Label shown in quality picker for CSV import", "placeholders": { @@ -1231,7 +1231,7 @@ "@snackbarCredentialsCleared": { "description": "Snackbar - Spotify credentials removed" }, - "snackbarDeletedTracks": "Удалено {count} {count, plural, one {трек} few {трека} many {треков} other {треков}}", + "snackbarDeletedTracks": "Удалено {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", "@snackbarDeletedTracks": { "description": "Snackbar - tracks deleted", "placeholders": { @@ -1438,7 +1438,7 @@ "@selectionTapToSelect": { "description": "Hint - how to select items" }, - "selectionDeleteTracks": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} other {треков}}", + "selectionDeleteTracks": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", "@selectionDeleteTracks": { "description": "Delete button with count", "placeholders": { @@ -1925,7 +1925,7 @@ "@lyricsModeDescription": { "description": "Lyrics mode picker description" }, - "lyricsModeEmbed": "Вставить в файл", + "lyricsModeEmbed": "Вписать в файл", "@lyricsModeEmbed": { "description": "Lyrics mode option - embed in audio file" }, @@ -1945,7 +1945,7 @@ "@lyricsModeBoth": { "description": "Lyrics mode option - embed and external" }, - "lyricsModeBothSubtitle": "Вставить и сохранить файл .lrc", + "lyricsModeBothSubtitle": "Вписать и сохранить .lrc файл", "@lyricsModeBothSubtitle": { "description": "Subtitle for both option" }, @@ -2014,7 +2014,7 @@ } } }, - "tracksCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} other {{count} треков}}", + "tracksCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}", "@tracksCount": { "description": "Track count display", "placeholders": { @@ -2127,7 +2127,7 @@ "@trackLyricsLoadFailed": { "description": "Message when lyrics loading fails" }, - "trackEmbedLyrics": "Вставить текст песни", + "trackEmbedLyrics": "Вписать текст песни", "@trackEmbedLyrics": { "description": "Action - embed lyrics into audio file" }, @@ -2482,7 +2482,7 @@ "@lossyFormatDescription": { "description": "Description for lossy format picker" }, - "lossyFormatMp3Subtitle": "320Кбит/с, лучшая совместимость", + "lossyFormatMp3Subtitle": "320 кбит/с, лучшая совместимость", "@lossyFormatMp3Subtitle": { "description": "MP3 format description" }, @@ -2518,7 +2518,7 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Для папок исполнителей используется исполнитель альбома, если он указан", "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { "description": "Subtitle when Album Artist is used for folder naming" }, @@ -2534,7 +2534,7 @@ "@downloadUsePrimaryArtistOnlyEnabled": { "description": "Subtitle when primary artist only is enabled" }, - "downloadUsePrimaryArtistOnlyDisabled": "Full artist string used for folder name", + "downloadUsePrimaryArtistOnlyDisabled": "Полная строка исполнителя, используемая для имени папки", "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, @@ -2670,7 +2670,7 @@ "@settingsDownloadNetwork": { "description": "Setting for network type preference" }, - "settingsDownloadNetworkAny": "WiFi и мобильная сеть", + "settingsDownloadNetworkAny": "WiFi и Мобильная сеть", "@settingsDownloadNetworkAny": { "description": "Network option - use any connection" }, @@ -2758,7 +2758,7 @@ "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" }, - "downloadedAlbumDeleteMessage": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} other {треков}} из этого альбома?\n\nЭто также удалит файлы из хранилища.", + "downloadedAlbumDeleteMessage": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}} из этого альбома?\n\nЭто также удалит файлы из хранилища.", "@downloadedAlbumDeleteMessage": { "description": "Delete confirmation with count", "placeholders": { @@ -2797,7 +2797,7 @@ "@downloadedAlbumTapToSelect": { "description": "Selection hint" }, - "downloadedAlbumDeleteCount": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} other {треков}}", + "downloadedAlbumDeleteCount": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", "@downloadedAlbumDeleteCount": { "description": "Delete button text with count", "placeholders": { @@ -3114,7 +3114,7 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", + "libraryTracksCount": "{count} {count, plural, one {Трек} few {Трека} many {Треков} other{Треков}}", "@libraryTracksCount": { "description": "Track count in library", "placeholders": { @@ -3156,7 +3156,7 @@ "@libraryInLibrary": { "description": "Badge shown on tracks that exist in local library" }, - "libraryRemovedMissingFiles": "Удалено {count} {count, plural, one {отсутствующий файл} few {трека} many {отсутствующих файлов} =1{отсутствующий файл} other{отсутствующих файлов}} в библиотеке", + "libraryRemovedMissingFiles": "Удалено {count} {count, plural, one {отсутствующий файл} few {трека} many {отсутствующих файлов} other{отсутствующих файлов}} в библиотеке", "@libraryRemovedMissingFiles": { "description": "Snackbar after cleanup", "placeholders": { @@ -3229,7 +3229,7 @@ "@libraryFilterQualityCD": { "description": "Filter option - CD quality audio" }, - "libraryFilterQualityLossy": "С потерями", + "libraryFilterQualityLossy": "Lossy", "@libraryFilterQualityLossy": { "description": "Filter option - lossy compressed audio" }, @@ -3496,7 +3496,7 @@ "@tutorialExtensionsTip2": { "description": "Tutorial extensions tip 2" }, - "tutorialExtensionsTip3": "Get lyrics, enhanced metadata, and more features", + "tutorialExtensionsTip3": "Получайте тексты песен, улучшенные метаданные и другие возможности", "@tutorialExtensionsTip3": { "description": "Tutorial extensions tip 3" }, @@ -3504,7 +3504,7 @@ "@tutorialSettingsTitle": { "description": "Tutorial settings page title" }, - "tutorialSettingsDesc": "Personalize the app in Settings to match your preferences.", + "tutorialSettingsDesc": "Персонализируйте приложение в Настройках, чтобы оно соответствовало вашим предпочтениям.", "@tutorialSettingsDesc": { "description": "Tutorial settings page description" }, @@ -3536,11 +3536,11 @@ "@libraryForceFullScanSubtitle": { "description": "Subtitle for force full scan button" }, - "cleanupOrphanedDownloads": "Cleanup Orphaned Downloads", + "cleanupOrphanedDownloads": "Очистка отложенных скачиваний", "@cleanupOrphanedDownloads": { "description": "Button to remove history entries for deleted files" }, - "cleanupOrphanedDownloadsSubtitle": "Remove history entries for files that no longer exist", + "cleanupOrphanedDownloadsSubtitle": "Удалить историю записи для файлов, которых больше не существует", "@cleanupOrphanedDownloadsSubtitle": { "description": "Subtitle for orphaned cleanup button" }, @@ -3553,7 +3553,7 @@ } } }, - "cleanupOrphanedDownloadsNone": "No orphaned entries found", + "cleanupOrphanedDownloadsNone": "Записей без описания не найдено", "@cleanupOrphanedDownloadsNone": { "description": "Snackbar when no orphans found" }, @@ -3565,11 +3565,11 @@ "@cacheSummaryTitle": { "description": "Heading for cache summary card" }, - "cacheSummarySubtitle": "Clearing cache will not remove downloaded music files.", + "cacheSummarySubtitle": "Очистка кэша не приведет к удалению загруженных музыкальных файлов.", "@cacheSummarySubtitle": { "description": "Helper text for cache summary card" }, - "cacheEstimatedTotal": "Estimated cache usage: {size}", + "cacheEstimatedTotal": "Приблизительное использование кэша: {size}", "@cacheEstimatedTotal": { "description": "Total cache size shown in summary", "placeholders": { @@ -3594,19 +3594,19 @@ "@cacheAppDirectoryDesc": { "description": "Description of what app cache directory contains" }, - "cacheTempDirectory": "Temporary directory", + "cacheTempDirectory": "Временная директория", "@cacheTempDirectory": { "description": "Cache item title for temporary files directory" }, - "cacheTempDirectoryDesc": "Temporary files from downloads and audio conversion.", + "cacheTempDirectoryDesc": "Временные файлы из загрузок и аудио конвертации.", "@cacheTempDirectoryDesc": { "description": "Description of what temporary directory contains" }, - "cacheCoverImage": "Cover image cache", + "cacheCoverImage": "Кэш обложек", "@cacheCoverImage": { "description": "Cache item title for persistent cover images" }, - "cacheCoverImageDesc": "Downloaded album and track cover art. Will re-download when viewed.", + "cacheCoverImageDesc": "Скачанный альбом и трек обложки. Будет заново скачан после просмотра.", "@cacheCoverImageDesc": { "description": "Description of what cover image cache contains" }, @@ -3614,11 +3614,11 @@ "@cacheLibraryCover": { "description": "Cache item title for local library cover art images" }, - "cacheLibraryCoverDesc": "Cover art extracted from local music files. Will re-extract on next scan.", + "cacheLibraryCoverDesc": "Обложка извлечена из локальных музыкальных файлов. Будет повторно извлечено при следующем сканировании.", "@cacheLibraryCoverDesc": { "description": "Description of what library cover cache contains" }, - "cacheExploreFeed": "Explore feed cache", + "cacheExploreFeed": "Просмотреть кэш ленты", "@cacheExploreFeed": { "description": "Cache item title for explore home feed cache" }, @@ -3626,7 +3626,7 @@ "@cacheExploreFeedDesc": { "description": "Description of what explore feed cache contains" }, - "cacheTrackLookup": "Track lookup cache", + "cacheTrackLookup": "Отслеживать кэш поиска", "@cacheTrackLookup": { "description": "Cache item title for track ID lookup cache" }, @@ -3663,7 +3663,7 @@ } } }, - "cacheEntries": "{count} entries", + "cacheEntries": "{count} записей", "@cacheEntries": { "description": "Track cache entry count", "placeholders": { @@ -3714,7 +3714,7 @@ "@cacheCleanupUnusedSubtitle": { "description": "Subtitle for cleanup unused data action" }, - "cacheCleanupResult": "Cleanup completed: {downloadCount} orphaned downloads, {libraryCount} missing library entries", + "cacheCleanupResult": "Очистка завершена: {downloadCount} потерянных загрузок, {libraryCount} отсутствующих записей в библиотеке", "@cacheCleanupResult": { "description": "Snackbar after unused data cleanup", "placeholders": { @@ -3746,7 +3746,7 @@ "@trackSaveLyricsSubtitle": { "description": "Subtitle for save lyrics action" }, - "trackSaveLyricsProgress": "Saving lyrics...", + "trackSaveLyricsProgress": "Сохранение текста...", "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, @@ -3813,31 +3813,31 @@ } } }, - "trackConvertFormat": "Convert Format", + "trackConvertFormat": "Переконвертировать формат", "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "trackConvertFormatSubtitle": "Конвертировать в MP3 или Opus", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, - "trackConvertTitle": "Convert Audio", + "trackConvertTitle": "Конвертировать аудио", "@trackConvertTitle": { "description": "Title of convert bottom sheet" }, - "trackConvertTargetFormat": "Target Format", + "trackConvertTargetFormat": "Целевой формат", "@trackConvertTargetFormat": { "description": "Label for format selection" }, - "trackConvertBitrate": "Bitrate", + "trackConvertBitrate": "Битрейт", "@trackConvertBitrate": { "description": "Label for bitrate selection" }, - "trackConvertConfirmTitle": "Confirm Conversion", + "trackConvertConfirmTitle": "Подтвердить конвертацию", "@trackConvertConfirmTitle": { "description": "Confirmation dialog title" }, - "trackConvertConfirmMessage": "Convert from {sourceFormat} to {targetFormat} at {bitrate}?\n\nThe original file will be deleted after conversion.", + "trackConvertConfirmMessage": "Конвертировать из {sourceFormat} в {targetFormat} {bitrate}?\n\nОригинальный файл будет удален после конвертации.", "@trackConvertConfirmMessage": { "description": "Confirmation dialog message", "placeholders": { @@ -3852,11 +3852,11 @@ } } }, - "trackConvertConverting": "Converting audio...", + "trackConvertConverting": "Конвертация аудио...", "@trackConvertConverting": { "description": "Snackbar while converting" }, - "trackConvertSuccess": "Converted to {format} successfully", + "trackConvertSuccess": "Успешно конвертировано в {format}", "@trackConvertSuccess": { "description": "Snackbar after successful conversion", "placeholders": { @@ -3865,7 +3865,7 @@ } } }, - "trackConvertFailed": "Conversion failed", + "trackConvertFailed": "Ошибка конвертации", "@trackConvertFailed": { "description": "Snackbar when conversion fails" } From d4d661d6d48a321cd2b2491be3769eb6d0dedcad Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 13 Feb 2026 17:08:10 +0700 Subject: [PATCH 003/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 949846ad..5491e1c1 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -2490,11 +2490,11 @@ "@lossyFormatOpusSubtitle": { "description": "Opus format description" }, - "qualityNote": "Actual quality depends on track availability from the service", + "qualityNote": "Die eigentliche Qualität hängt von der Verfügbarkeit des Dienstes ab", "@qualityNote": { "description": "Note about quality availability" }, - "youtubeQualityNote": "YouTube provides lossy audio only. Not part of lossless fallback.", + "youtubeQualityNote": "YouTube bietet nur verlustbehaftete Audioqualität. Deswegen ist es kein Teil des verlustfreien Fallbacks.", "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, @@ -3010,7 +3010,7 @@ "@allFilesAccessDeniedMessage": { "description": "Message when permission is permanently denied" }, - "allFilesAccessDisabledMessage": "All Files Access disabled. The app will use limited storage access.", + "allFilesAccessDisabledMessage": "Zugriff auf alle Dateien ist deaktiviert. Die App verwendet nur begrenzten Zugriff auf den Speicher.", "@allFilesAccessDisabledMessage": { "description": "Snackbar message when user disables all files access" }, @@ -3282,7 +3282,7 @@ "@timeJustNow": { "description": "Relative time - less than a minute ago" }, - "timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}", + "timeMinutesAgo": "{count, plural, one {}=1{vor 1 Minute} other{vor {count} Minuten}}", "@timeMinutesAgo": { "description": "Relative time - minutes ago", "placeholders": { @@ -3408,7 +3408,7 @@ "@tutorialWelcomeDesc": { "description": "Tutorial welcome page description" }, - "tutorialWelcomeTip1": "Download music from Spotify, Deezer, or paste any supported URL", + "tutorialWelcomeTip1": "Lade Musik von Spotify, Deezer herunter oder jeden unterstützten Link einfügen", "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, @@ -3424,7 +3424,7 @@ "@tutorialSearchTitle": { "description": "Tutorial search page title" }, - "tutorialSearchDesc": "There are two easy ways to find music you want to download.", + "tutorialSearchDesc": "Es gibt zwei einfache Möglichkeiten, Musik zu finden, die du herunterladen möchtest.", "@tutorialSearchDesc": { "description": "Tutorial search page description" }, @@ -3448,7 +3448,7 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", + "tutorialDownloadTip1": "Tippe auf den Download-Button neben einem Titel, um mit dem Download zu beginnen", "@tutorialDownloadTip1": { "description": "Tutorial download tip 1" }, @@ -3590,7 +3590,7 @@ "@cacheAppDirectory": { "description": "Cache item title for app cache directory" }, - "cacheAppDirectoryDesc": "HTTP responses, WebView data, and other temporary app data.", + "cacheAppDirectoryDesc": "HTTP-Antworten, WebView Daten und andere temporäre App-Daten.", "@cacheAppDirectoryDesc": { "description": "Description of what app cache directory contains" }, @@ -3606,7 +3606,7 @@ "@cacheCoverImage": { "description": "Cache item title for persistent cover images" }, - "cacheCoverImageDesc": "Downloaded album and track cover art. Will re-download when viewed.", + "cacheCoverImageDesc": "Album- und Titelcover heruntergeladen. Werden erneut heruntergeladen.", "@cacheCoverImageDesc": { "description": "Description of what cover image cache contains" }, @@ -3710,7 +3710,7 @@ "@cacheCleanupUnused": { "description": "Action title for cleaning unused entries" }, - "cacheCleanupUnusedSubtitle": "Remove orphaned download history and missing library entries", + "cacheCleanupUnusedSubtitle": "Verwaisten Downloadverlauf und fehlende Bibliothekseinträge löschen", "@cacheCleanupUnusedSubtitle": { "description": "Subtitle for cleanup unused data action" }, From 838c0ea421a053a38e28c458e228b01fa05c4137 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:13 +0700 Subject: [PATCH 004/104] Update source file app_en.arb --- lib/l10n/arb/app_en.arb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index ecf0e423..2ff691cb 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -874,6 +874,14 @@ "@filenameAvailablePlaceholders": {"description": "Label for placeholder list"}, "filenameHint": "{artist} - {title}", "@filenameHint": {"description": "Default filename format hint"}, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, "folderOrganization": "Folder Organization", "@folderOrganization": {"description": "Setting title - folder structure"}, From 3b70b071e35d298baad205045248ab2b2b681cd7 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:16 +0700 Subject: [PATCH 005/104] New translations app_en.arb (French) --- lib/l10n/arb/app_fr.arb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index 41685440..90199b08 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -1532,6 +1532,14 @@ "@filenameHint": { "description": "Default filename format hint" }, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, "folderOrganization": "Folder Organization", "@folderOrganization": { "description": "Setting title - folder structure" From 1e2e383794888d6e84a19aa40a82c8b98edeffa7 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:17 +0700 Subject: [PATCH 006/104] New translations app_en.arb (Spanish) --- lib/l10n/arb/app_es-ES.arb | 3880 ++++++++++++++++++++++++++++++++++++ 1 file changed, 3880 insertions(+) create mode 100644 lib/l10n/arb/app_es-ES.arb diff --git a/lib/l10n/arb/app_es-ES.arb b/lib/l10n/arb/app_es-ES.arb new file mode 100644 index 00000000..3b35c79c --- /dev/null +++ b/lib/l10n/arb/app_es-ES.arb @@ -0,0 +1,3880 @@ +{ + "@@locale": "es-ES", + "@@last_modified": "2026-01-16", + "appName": "SpotiFLAC", + "@appName": { + "description": "App name - DO NOT TRANSLATE" + }, + "appDescription": "Descargue pistas de Spotify con calidad sin pérdida de Tidal, Qobuz y Amazon Music.", + "@appDescription": { + "description": "App description shown in about page" + }, + "navHome": "Inicio", + "@navHome": { + "description": "Bottom navigation - Home tab" + }, + "navLibrary": "Biblioteca", + "@navLibrary": { + "description": "Bottom navigation - Library tab" + }, + "navHistory": "Historial", + "@navHistory": { + "description": "Bottom navigation - History tab (legacy)" + }, + "navSettings": "Ajustes", + "@navSettings": { + "description": "Bottom navigation - Settings tab" + }, + "navStore": "Tienda", + "@navStore": { + "description": "Bottom navigation - Extension store tab" + }, + "homeTitle": "Inicio", + "@homeTitle": { + "description": "Home screen title" + }, + "homeSearchHint": "Pegar URL Spotify o buscar...", + "@homeSearchHint": { + "description": "Placeholder text in search box" + }, + "homeSearchHintExtension": "Buscar con {extensionName}...", + "@homeSearchHintExtension": { + "description": "Placeholder when extension search is active", + "placeholders": { + "extensionName": { + "type": "String", + "description": "Name of the active extension" + } + } + }, + "homeSubtitle": "Pegar enlace de Spotify o buscar por nombre", + "@homeSubtitle": { + "description": "Subtitle shown below search box" + }, + "homeSupports": "Soportes: Pista, Álbum, Lista de reproducción, URLs de Artistas", + "@homeSupports": { + "description": "Info text about supported URL types" + }, + "homeRecent": "Recientes", + "@homeRecent": { + "description": "Section header for recent searches" + }, + "historyTitle": "Historial", + "@historyTitle": { + "description": "History screen title" + }, + "historyDownloading": "Descargando ({count})", + "@historyDownloading": { + "description": "Tab showing active downloads count", + "placeholders": { + "count": { + "type": "int", + "description": "Number of active downloads" + } + } + }, + "historyDownloaded": "Descargado", + "@historyDownloaded": { + "description": "Tab showing completed downloads" + }, + "historyFilterAll": "Todo", + "@historyFilterAll": { + "description": "Filter chip - show all items" + }, + "historyFilterAlbums": "Álbumes", + "@historyFilterAlbums": { + "description": "Filter chip - show albums only" + }, + "historyFilterSingles": "Pistas", + "@historyFilterSingles": { + "description": "Filter chip - show singles only" + }, + "historyTracksCount": "{count, plural, one {}=1{1 pista} other{{count} pistas}}", + "@historyTracksCount": { + "description": "Track count with plural form", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "historyAlbumsCount": "{count, plural, one {}=1{1 álbum} other{{count} álbumes}}", + "@historyAlbumsCount": { + "description": "Album count with plural form", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "historyNoDownloads": "No hay historial de descargas", + "@historyNoDownloads": { + "description": "Empty state title" + }, + "historyNoDownloadsSubtitle": "Las pistas descargadas aparecerán aquí", + "@historyNoDownloadsSubtitle": { + "description": "Empty state subtitle" + }, + "historyNoAlbums": "No hay descargas de álbum", + "@historyNoAlbums": { + "description": "Empty state when filtering albums" + }, + "historyNoAlbumsSubtitle": "Descargar múltiples pistas de un álbum para verlas aquí", + "@historyNoAlbumsSubtitle": { + "description": "Empty state subtitle for albums filter" + }, + "historyNoSingles": "No hay descargas", + "@historyNoSingles": { + "description": "Empty state when filtering singles" + }, + "historyNoSinglesSubtitle": "Las descargas de una sola pista aparecerán aquí", + "@historyNoSinglesSubtitle": { + "description": "Empty state subtitle for singles filter" + }, + "historySearchHint": "Buscar en historial...", + "@historySearchHint": { + "description": "Search bar placeholder in history" + }, + "settingsTitle": "Ajustes", + "@settingsTitle": { + "description": "Settings screen title" + }, + "settingsDownload": "Descargar", + "@settingsDownload": { + "description": "Settings section - download options" + }, + "settingsAppearance": "Apariencia", + "@settingsAppearance": { + "description": "Settings section - visual customization" + }, + "settingsOptions": "Opciones", + "@settingsOptions": { + "description": "Settings section - app options" + }, + "settingsExtensions": "Extensiones", + "@settingsExtensions": { + "description": "Settings section - extension management" + }, + "settingsAbout": "Acerca de", + "@settingsAbout": { + "description": "Settings section - app info" + }, + "downloadTitle": "Descargar", + "@downloadTitle": { + "description": "Download settings page title" + }, + "downloadLocation": "Ubicación de descarga", + "@downloadLocation": { + "description": "Setting for download folder" + }, + "downloadLocationSubtitle": "Elija dónde guardar los archivos", + "@downloadLocationSubtitle": { + "description": "Subtitle for download location" + }, + "downloadLocationDefault": "Ubicación predeterminada", + "@downloadLocationDefault": { + "description": "Shown when using default folder" + }, + "downloadDefaultService": "Servicio por defecto", + "@downloadDefaultService": { + "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" + }, + "downloadDefaultServiceSubtitle": "Servicio usado para descargas", + "@downloadDefaultServiceSubtitle": { + "description": "Subtitle for default service" + }, + "downloadDefaultQuality": "Calidad por defecto", + "@downloadDefaultQuality": { + "description": "Setting for audio quality" + }, + "downloadAskQuality": "Preguntar calidad antes de descargar", + "@downloadAskQuality": { + "description": "Toggle to show quality picker" + }, + "downloadAskQualitySubtitle": "Mostrar selector de calidad para cada descarga", + "@downloadAskQualitySubtitle": { + "description": "Subtitle for ask quality toggle" + }, + "downloadFilenameFormat": "Formato del nombre del archivo", + "@downloadFilenameFormat": { + "description": "Setting for output filename pattern" + }, + "downloadFolderOrganization": "Organización de carpetas", + "@downloadFolderOrganization": { + "description": "Setting for folder structure" + }, + "downloadSeparateSingles": "Separar Pistas", + "@downloadSeparateSingles": { + "description": "Toggle to separate single tracks" + }, + "downloadSeparateSinglesSubtitle": "Colocar pistas individuales en una carpeta separada", + "@downloadSeparateSinglesSubtitle": { + "description": "Subtitle for separate singles toggle" + }, + "qualityBest": "Mejor disponible", + "@qualityBest": { + "description": "Audio quality option - highest available" + }, + "qualityFlac": "FLAC", + "@qualityFlac": { + "description": "Audio quality option - FLAC lossless" + }, + "quality320": "320 kbps", + "@quality320": { + "description": "Audio quality option - 320kbps MP3" + }, + "quality128": "128 kbps", + "@quality128": { + "description": "Audio quality option - 128kbps MP3" + }, + "appearanceTitle": "Apariencia", + "@appearanceTitle": { + "description": "Appearance settings page title" + }, + "appearanceTheme": "Tema", + "@appearanceTheme": { + "description": "Theme mode setting" + }, + "appearanceThemeSystem": "Sistema", + "@appearanceThemeSystem": { + "description": "Follow system theme" + }, + "appearanceThemeLight": "Claro", + "@appearanceThemeLight": { + "description": "Light theme" + }, + "appearanceThemeDark": "Oscuro", + "@appearanceThemeDark": { + "description": "Dark theme" + }, + "appearanceDynamicColor": "Color dinámico", + "@appearanceDynamicColor": { + "description": "Material You dynamic colors" + }, + "appearanceDynamicColorSubtitle": "Usar colores de tu fondo de pantalla", + "@appearanceDynamicColorSubtitle": { + "description": "Subtitle for dynamic color" + }, + "appearanceAccentColor": "Color Secundario", + "@appearanceAccentColor": { + "description": "Custom accent color picker" + }, + "appearanceHistoryView": "Vista de Historial", + "@appearanceHistoryView": { + "description": "Layout style for history" + }, + "appearanceHistoryViewList": "Lista", + "@appearanceHistoryViewList": { + "description": "List layout option" + }, + "appearanceHistoryViewGrid": "Cuadrícula", + "@appearanceHistoryViewGrid": { + "description": "Grid layout option" + }, + "optionsTitle": "Opciones", + "@optionsTitle": { + "description": "Options settings page title" + }, + "optionsSearchSource": "Buscar Fuente", + "@optionsSearchSource": { + "description": "Section for search provider settings" + }, + "optionsPrimaryProvider": "Proveedor Principal", + "@optionsPrimaryProvider": { + "description": "Main search provider setting" + }, + "optionsPrimaryProviderSubtitle": "Servicio usado al buscar por nombre de la pista.", + "@optionsPrimaryProviderSubtitle": { + "description": "Subtitle for primary provider" + }, + "optionsUsingExtension": "Usando la extensión: {extensionName}", + "@optionsUsingExtension": { + "description": "Shows active extension name", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "optionsSwitchBack": "Toque Deezer o Spotify para volver desde la extensión", + "@optionsSwitchBack": { + "description": "Hint to switch back to built-in providers" + }, + "optionsAutoFallback": "Alternativa automática", + "@optionsAutoFallback": { + "description": "Auto-retry with other services" + }, + "optionsAutoFallbackSubtitle": "Pruebe otros servicios si falla la descarga", + "@optionsAutoFallbackSubtitle": { + "description": "Subtitle for auto fallback" + }, + "optionsUseExtensionProviders": "Usar proveedores de extensiones", + "@optionsUseExtensionProviders": { + "description": "Enable extension download providers" + }, + "optionsUseExtensionProvidersOn": "Las extensiones serán probadas primero", + "@optionsUseExtensionProvidersOn": { + "description": "Status when extension providers enabled" + }, + "optionsUseExtensionProvidersOff": "Utilizando sólo proveedores integrados", + "@optionsUseExtensionProvidersOff": { + "description": "Status when extension providers disabled" + }, + "optionsEmbedLyrics": "Incrustar Letras", + "@optionsEmbedLyrics": { + "description": "Embed lyrics in audio files" + }, + "optionsEmbedLyricsSubtitle": "Insertar letras sincronizadas en archivos FLAC", + "@optionsEmbedLyricsSubtitle": { + "description": "Subtitle for embed lyrics" + }, + "optionsMaxQualityCover": "Carátula de calidad máxima", + "@optionsMaxQualityCover": { + "description": "Download highest quality album art" + }, + "optionsMaxQualityCoverSubtitle": "Descargar carátula de resolución máxima", + "@optionsMaxQualityCoverSubtitle": { + "description": "Subtitle for max quality cover" + }, + "optionsConcurrentDownloads": "Descargas Simultáneas", + "@optionsConcurrentDownloads": { + "description": "Number of parallel downloads" + }, + "optionsConcurrentSequential": "Secuencial (1 a la vez)", + "@optionsConcurrentSequential": { + "description": "Download one at a time" + }, + "optionsConcurrentParallel": "{count} descargas paralelas", + "@optionsConcurrentParallel": { + "description": "Multiple parallel downloads", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "optionsConcurrentWarning": "Las descargas paralelas pueden activar la limitación de velocidad", + "@optionsConcurrentWarning": { + "description": "Warning about rate limits" + }, + "optionsExtensionStore": "Tienda de extensiones", + "@optionsExtensionStore": { + "description": "Show/hide store tab" + }, + "optionsExtensionStoreSubtitle": "Mostrar pestaña de tienda en la navegación", + "@optionsExtensionStoreSubtitle": { + "description": "Subtitle for extension store toggle" + }, + "optionsCheckUpdates": "Comprobar actualizaciones", + "@optionsCheckUpdates": { + "description": "Auto update check toggle" + }, + "optionsCheckUpdatesSubtitle": "Notificar cuando una nueva versión esté disponible", + "@optionsCheckUpdatesSubtitle": { + "description": "Subtitle for update check" + }, + "optionsUpdateChannel": "Tipo de actualizaciones", + "@optionsUpdateChannel": { + "description": "Stable vs preview releases" + }, + "optionsUpdateChannelStable": "Sólo versiones estables", + "@optionsUpdateChannelStable": { + "description": "Only stable updates" + }, + "optionsUpdateChannelPreview": "Versión preliminar", + "@optionsUpdateChannelPreview": { + "description": "Include beta/preview updates" + }, + "optionsUpdateChannelWarning": "La Versión preliminar puede contener errores o características incompletas", + "@optionsUpdateChannelWarning": { + "description": "Warning about preview channel" + }, + "optionsClearHistory": "Borrar el historial de descargas", + "@optionsClearHistory": { + "description": "Delete all download history" + }, + "optionsClearHistorySubtitle": "Eliminar todas las pistas descargadas del historial", + "@optionsClearHistorySubtitle": { + "description": "Subtitle for clear history" + }, + "optionsDetailedLogging": "Registro detallado", + "@optionsDetailedLogging": { + "description": "Enable verbose logs for debugging" + }, + "optionsDetailedLoggingOn": "Registros detallados están siendo registrados", + "@optionsDetailedLoggingOn": { + "description": "Status when logging enabled" + }, + "optionsDetailedLoggingOff": "Habilitar para informes de errores", + "@optionsDetailedLoggingOff": { + "description": "Status when logging disabled" + }, + "optionsSpotifyCredentials": "Credenciales de Spotify", + "@optionsSpotifyCredentials": { + "description": "Spotify API credentials setting" + }, + "optionsSpotifyCredentialsConfigured": "ID de cliente: {clientId}...", + "@optionsSpotifyCredentialsConfigured": { + "description": "Shows configured client ID preview", + "placeholders": { + "clientId": { + "type": "String" + } + } + }, + "optionsSpotifyCredentialsRequired": "Requerido - toque para configurar", + "@optionsSpotifyCredentialsRequired": { + "description": "Prompt to set up credentials" + }, + "optionsSpotifyWarning": "Spotify requiere tus propias credenciales API. Obténgalas gratis de developer.spotify.com", + "@optionsSpotifyWarning": { + "description": "Info about Spotify API requirement" + }, + "optionsSpotifyDeprecationWarning": "Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.", + "@optionsSpotifyDeprecationWarning": { + "description": "Warning about Spotify API deprecation" + }, + "extensionsTitle": "Extensiones", + "@extensionsTitle": { + "description": "Extensions page title" + }, + "extensionsInstalled": "Extensiones instaladas", + "@extensionsInstalled": { + "description": "Section header for installed extensions" + }, + "extensionsNone": "No hay extensiones instaladas", + "@extensionsNone": { + "description": "Empty state title" + }, + "extensionsNoneSubtitle": "Instalar extensiones desde la pestaña Tienda", + "@extensionsNoneSubtitle": { + "description": "Empty state subtitle" + }, + "extensionsEnabled": "Habilitado", + "@extensionsEnabled": { + "description": "Extension status - active" + }, + "extensionsDisabled": "Deshabilitado", + "@extensionsDisabled": { + "description": "Extension status - inactive" + }, + "extensionsVersion": "Versión {version}", + "@extensionsVersion": { + "description": "Extension version display", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "extensionsAuthor": "por {author}", + "@extensionsAuthor": { + "description": "Extension author credit", + "placeholders": { + "author": { + "type": "String" + } + } + }, + "extensionsUninstall": "Desinstalar", + "@extensionsUninstall": { + "description": "Uninstall extension button" + }, + "extensionsSetAsSearch": "Establecer como proveedor de búsqueda", + "@extensionsSetAsSearch": { + "description": "Use extension for search" + }, + "storeTitle": "Tienda de extensiones", + "@storeTitle": { + "description": "Store screen title" + }, + "storeSearch": "Buscar extensiones...", + "@storeSearch": { + "description": "Store search placeholder" + }, + "storeInstall": "Instalar", + "@storeInstall": { + "description": "Install extension button" + }, + "storeInstalled": "Instalada", + "@storeInstalled": { + "description": "Already installed badge" + }, + "storeUpdate": "Actualizar", + "@storeUpdate": { + "description": "Update available button" + }, + "aboutTitle": "Acerca de", + "@aboutTitle": { + "description": "About page title" + }, + "aboutContributors": "Colaboradores", + "@aboutContributors": { + "description": "Section for contributors" + }, + "aboutMobileDeveloper": "Desarrollador de versiones móviles", + "@aboutMobileDeveloper": { + "description": "Role description for mobile dev" + }, + "aboutOriginalCreator": "Creador original de SpotiFLAC", + "@aboutOriginalCreator": { + "description": "Role description for original creator" + }, + "aboutLogoArtist": "¡El talentoso artista que creó nuestro hermoso logo!", + "@aboutLogoArtist": { + "description": "Role description for logo artist" + }, + "aboutTranslators": "Traductores", + "@aboutTranslators": { + "description": "Section for translators" + }, + "aboutSpecialThanks": "Agradecimientos especiales", + "@aboutSpecialThanks": { + "description": "Section for special thanks" + }, + "aboutLinks": "Enlaces", + "@aboutLinks": { + "description": "Section for external links" + }, + "aboutMobileSource": "Código fuente móvil", + "@aboutMobileSource": { + "description": "Link to mobile GitHub repo" + }, + "aboutPCSource": "Código fuente de PC", + "@aboutPCSource": { + "description": "Link to PC GitHub repo" + }, + "aboutReportIssue": "Reportar un problema", + "@aboutReportIssue": { + "description": "Link to report bugs" + }, + "aboutReportIssueSubtitle": "Reporta cualquier problema que encuentres", + "@aboutReportIssueSubtitle": { + "description": "Subtitle for report issue" + }, + "aboutFeatureRequest": "Sugerir una función", + "@aboutFeatureRequest": { + "description": "Link to suggest features" + }, + "aboutFeatureRequestSubtitle": "Sugerir nuevas funciones para la aplicación", + "@aboutFeatureRequestSubtitle": { + "description": "Subtitle for feature request" + }, + "aboutTelegramChannel": "Canal de Telegram", + "@aboutTelegramChannel": { + "description": "Link to Telegram channel" + }, + "aboutTelegramChannelSubtitle": "Anuncios y actualizaciones", + "@aboutTelegramChannelSubtitle": { + "description": "Subtitle for Telegram channel" + }, + "aboutTelegramChat": "Comunidad de Telegram", + "@aboutTelegramChat": { + "description": "Link to Telegram chat group" + }, + "aboutTelegramChatSubtitle": "Chatear con otros usuarios", + "@aboutTelegramChatSubtitle": { + "description": "Subtitle for Telegram chat" + }, + "aboutSocial": "Redes sociales", + "@aboutSocial": { + "description": "Section for social links" + }, + "aboutSupport": "Soporte", + "@aboutSupport": { + "description": "Section for support/donation links" + }, + "aboutApp": "Aplicación", + "@aboutApp": { + "description": "Section for app info" + }, + "aboutVersion": "Versión", + "@aboutVersion": { + "description": "Version info label" + }, + "aboutBinimumDesc": "El creador de la API QQDL & Hi-Fi. ¡Sin esta API, las descargas de Tidal no existiría!", + "@aboutBinimumDesc": { + "description": "Credit description for binimum" + }, + "aboutSachinsenalDesc": "El creador original del proyecto Hi-Fi. ¡La base de la integración de Tidal!", + "@aboutSachinsenalDesc": { + "description": "Credit description for sachinsenal0x64" + }, + "aboutSjdonadoDesc": "Creador de I No tengo Spotify (IDHS). ¡La solución de enlace de reserva que salva el día!", + "@aboutSjdonadoDesc": { + "description": "Credit description for sjdonado" + }, + "aboutDoubleDouble": "DoubleDouble", + "@aboutDoubleDouble": { + "description": "Name of Amazon API service - DO NOT TRANSLATE" + }, + "aboutDoubleDoubleDesc": "API increible para descargas de Amazon Music. ¡Gracias por hacerla gratis!", + "@aboutDoubleDoubleDesc": { + "description": "Credit for DoubleDouble API" + }, + "aboutDabMusic": "Música DAB", + "@aboutDabMusic": { + "description": "Name of Qobuz API service - DO NOT TRANSLATE" + }, + "aboutDabMusicDesc": "La mejor API de streaming de Qobuz. ¡Las descargas de Hi-Res no serían posibles sin esto!", + "@aboutDabMusicDesc": { + "description": "Credit for DAB Music API" + }, + "aboutSpotiSaver": "SpotiSaver", + "@aboutSpotiSaver": { + "description": "Name of SpotiSaver API service - DO NOT TRANSLATE" + }, + "aboutSpotiSaverDesc": "Tidal de transmisión Hi-Res FLAC. ¡Una pieza clave del rompecabezas sin pérdida!", + "@aboutSpotiSaverDesc": { + "description": "Credit for SpotiSaver API" + }, + "aboutAppDescription": "Descarga pistas de Spotify con calidad sin pérdida de Tidal, Qobuz y Amazon Music.", + "@aboutAppDescription": { + "description": "App description in header card" + }, + "albumTitle": "Álbum", + "@albumTitle": { + "description": "Album screen title" + }, + "albumTracks": "{count, plural, one {}=1{1 pista} other{{count} pistas}}", + "@albumTracks": { + "description": "Album track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "albumDownloadAll": "Descargar Todo", + "@albumDownloadAll": { + "description": "Button to download all tracks" + }, + "albumDownloadRemaining": "Descargas Restantes", + "@albumDownloadRemaining": { + "description": "Button to download remaining tracks" + }, + "playlistTitle": "Lista de reproducción", + "@playlistTitle": { + "description": "Playlist screen title" + }, + "artistTitle": "Artista", + "@artistTitle": { + "description": "Artist screen title" + }, + "artistAlbums": "Álbumes", + "@artistAlbums": { + "description": "Section header for artist albums" + }, + "artistSingles": "Pistas y EPs", + "@artistSingles": { + "description": "Section header for singles/EPs" + }, + "artistCompilations": "Compilaciones", + "@artistCompilations": { + "description": "Section header for compilations" + }, + "artistReleases": "{count, plural, one {}=1{1 lanzamiento} other{{count} lanzamientos}}", + "@artistReleases": { + "description": "Artist release count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "artistPopular": "Populares", + "@artistPopular": { + "description": "Section header for popular/top tracks" + }, + "artistMonthlyListeners": "{count} oyentes mensuales", + "@artistMonthlyListeners": { + "description": "Monthly listener count display", + "placeholders": { + "count": { + "type": "String", + "description": "Formatted listener count" + } + } + }, + "trackMetadataTitle": "Información de pista", + "@trackMetadataTitle": { + "description": "Track metadata screen title" + }, + "trackMetadataArtist": "Artista", + "@trackMetadataArtist": { + "description": "Metadata field - artist name" + }, + "trackMetadataAlbum": "Álbum", + "@trackMetadataAlbum": { + "description": "Metadata field - album name" + }, + "trackMetadataDuration": "Duración", + "@trackMetadataDuration": { + "description": "Metadata field - track length" + }, + "trackMetadataQuality": "Calidad", + "@trackMetadataQuality": { + "description": "Metadata field - audio quality" + }, + "trackMetadataPath": "Ruta del archivo", + "@trackMetadataPath": { + "description": "Metadata field - file location" + }, + "trackMetadataDownloadedAt": "Descargado", + "@trackMetadataDownloadedAt": { + "description": "Metadata field - download date" + }, + "trackMetadataService": "Servicio", + "@trackMetadataService": { + "description": "Metadata field - download service used" + }, + "trackMetadataPlay": "Reproducir", + "@trackMetadataPlay": { + "description": "Action button - play track" + }, + "trackMetadataShare": "Compartir", + "@trackMetadataShare": { + "description": "Action button - share track" + }, + "trackMetadataDelete": "Eliminar", + "@trackMetadataDelete": { + "description": "Action button - delete track" + }, + "trackMetadataRedownload": "Volver a descargar", + "@trackMetadataRedownload": { + "description": "Action button - download again" + }, + "trackMetadataOpenFolder": "Abrir carpeta", + "@trackMetadataOpenFolder": { + "description": "Action button - open containing folder" + }, + "setupTitle": "Bienvenido a SpotiFLAC", + "@setupTitle": { + "description": "Setup wizard title" + }, + "setupSubtitle": "Comencemos", + "@setupSubtitle": { + "description": "Setup wizard subtitle" + }, + "setupStoragePermission": "Permiso de almacenamiento", + "@setupStoragePermission": { + "description": "Storage permission step title" + }, + "setupStoragePermissionSubtitle": "Necesario para guardar los archivos descargados", + "@setupStoragePermissionSubtitle": { + "description": "Explanation for storage permission" + }, + "setupStoragePermissionGranted": "Permiso aprobado", + "@setupStoragePermissionGranted": { + "description": "Status when permission granted" + }, + "setupStoragePermissionDenied": "Permiso denegado", + "@setupStoragePermissionDenied": { + "description": "Status when permission denied" + }, + "setupGrantPermission": "Conceder permiso", + "@setupGrantPermission": { + "description": "Button to request permission" + }, + "setupDownloadLocation": "Ubicación de descarga", + "@setupDownloadLocation": { + "description": "Download folder step title" + }, + "setupChooseFolder": "Seleccionar Carpeta", + "@setupChooseFolder": { + "description": "Button to pick folder" + }, + "setupContinue": "Continuar", + "@setupContinue": { + "description": "Continue to next step button" + }, + "setupSkip": "Omitir por ahora", + "@setupSkip": { + "description": "Skip current step button" + }, + "setupStorageAccessRequired": "Acceso al almacenamiento requerido", + "@setupStorageAccessRequired": { + "description": "Title when storage access needed" + }, + "setupStorageAccessMessage": "SpotiFLAC necesita permiso de \"Todos los archivos de acceso\" para guardar los archivos de música en la carpeta elegida.", + "@setupStorageAccessMessage": { + "description": "Explanation for storage access" + }, + "setupStorageAccessMessageAndroid11": "Android 11+ requiere permiso \"Todos los archivos de acceso\" para guardar los archivos en la carpeta de descargas elegida.", + "@setupStorageAccessMessageAndroid11": { + "description": "Android 11+ specific explanation" + }, + "setupOpenSettings": "Abrir ajustes", + "@setupOpenSettings": { + "description": "Button to open system settings" + }, + "setupPermissionDeniedMessage": "Permiso denegado. Por favor, conceda todos los permisos para continuar.", + "@setupPermissionDeniedMessage": { + "description": "Error when permission denied" + }, + "setupPermissionRequired": "Permiso de {permissionType} requerido", + "@setupPermissionRequired": { + "description": "Generic permission required title", + "placeholders": { + "permissionType": { + "type": "String", + "description": "Type of permission (Storage/Notification)" + } + } + }, + "setupPermissionRequiredMessage": "Se requiere un permiso {permissionType} para la mejor experiencia. Puedes cambiar esto más tarde en ajustes.", + "@setupPermissionRequiredMessage": { + "description": "Generic permission required message", + "placeholders": { + "permissionType": { + "type": "String" + } + } + }, + "setupSelectDownloadFolder": "Seleccionar carpeta de descarga", + "@setupSelectDownloadFolder": { + "description": "Folder selection step title" + }, + "setupUseDefaultFolder": "¿Usar carpeta por defecto?", + "@setupUseDefaultFolder": { + "description": "Dialog title for default folder" + }, + "setupNoFolderSelected": "No se ha seleccionado ninguna carpeta. ¿Desea utilizar la carpeta por defecto?", + "@setupNoFolderSelected": { + "description": "Prompt when no folder selected" + }, + "setupUseDefault": "Usar por defecto", + "@setupUseDefault": { + "description": "Button to use default folder" + }, + "setupDownloadLocationTitle": "Ubicación de descarga", + "@setupDownloadLocationTitle": { + "description": "Download location dialog title" + }, + "setupDownloadLocationIosMessage": "En iOS, las descargas se guardan en la carpeta de documentos de la aplicación. Puede acceder a ellas desde la aplicación Archivos.", + "@setupDownloadLocationIosMessage": { + "description": "iOS-specific folder info" + }, + "setupAppDocumentsFolder": "Carpeta de documentos de App", + "@setupAppDocumentsFolder": { + "description": "iOS documents folder option" + }, + "setupAppDocumentsFolderSubtitle": "Recomendado - accesible desde la aplicación Archivos", + "@setupAppDocumentsFolderSubtitle": { + "description": "Subtitle for documents folder" + }, + "setupChooseFromFiles": "Elegir de archivos", + "@setupChooseFromFiles": { + "description": "iOS file picker option" + }, + "setupChooseFromFilesSubtitle": "Seleccione iCloud u otra ubicación", + "@setupChooseFromFilesSubtitle": { + "description": "Subtitle for file picker" + }, + "setupIosEmptyFolderWarning": "Limitación de iOS: No se pueden seleccionar carpetas vacías. Elige una carpeta con al menos un archivo.", + "@setupIosEmptyFolderWarning": { + "description": "iOS folder selection warning" + }, + "setupIcloudNotSupported": "iCloud Drive no es compatible. Utilice la carpeta Documentos de la aplicación.", + "@setupIcloudNotSupported": { + "description": "Error when user selects iCloud Drive on iOS" + }, + "setupDownloadInFlac": "Descargar pistas de Spotify en FLAC", + "@setupDownloadInFlac": { + "description": "App tagline in setup" + }, + "setupStepStorage": "Almacenamiento", + "@setupStepStorage": { + "description": "Setup step indicator - storage" + }, + "setupStepNotification": "Notificación", + "@setupStepNotification": { + "description": "Setup step indicator - notification" + }, + "setupStepFolder": "Carpeta", + "@setupStepFolder": { + "description": "Setup step indicator - folder" + }, + "setupStepSpotify": "Spotify", + "@setupStepSpotify": { + "description": "Setup step indicator - Spotify API" + }, + "setupStepPermission": "Permiso", + "@setupStepPermission": { + "description": "Setup step indicator - permission" + }, + "setupStorageGranted": "¡Permiso de almacenamiento concedido!", + "@setupStorageGranted": { + "description": "Success message for storage permission" + }, + "setupStorageRequired": "Permiso de almacenamiento requerido", + "@setupStorageRequired": { + "description": "Title when storage permission needed" + }, + "setupStorageDescription": "SpotiFLAC necesita permiso de almacenamiento para guardar sus archivos de música descargados.", + "@setupStorageDescription": { + "description": "Explanation for storage permission" + }, + "setupNotificationGranted": "¡Acceso a las notificaciones permitido!", + "@setupNotificationGranted": { + "description": "Success message for notification permission" + }, + "setupNotificationEnable": "Activar notificaciones", + "@setupNotificationEnable": { + "description": "Button to enable notifications" + }, + "setupNotificationDescription": "Recibe notificaciones cuando las descargas completen o requieran atención.", + "@setupNotificationDescription": { + "description": "Explanation for notifications" + }, + "setupFolderSelected": "¡Carpeta de descarga seleccionada!", + "@setupFolderSelected": { + "description": "Success message for folder selection" + }, + "setupFolderChoose": "Cambiar carpeta de descargas", + "@setupFolderChoose": { + "description": "Button to choose folder" + }, + "setupFolderDescription": "Seleccione una carpeta donde se guardará la música descargada.", + "@setupFolderDescription": { + "description": "Explanation for folder selection" + }, + "setupChangeFolder": "Cambiar carpeta", + "@setupChangeFolder": { + "description": "Button to change selected folder" + }, + "setupSelectFolder": "Seleccionar Carpeta", + "@setupSelectFolder": { + "description": "Button to select folder" + }, + "setupSpotifyApiOptional": "API de Spotify (opcional)", + "@setupSpotifyApiOptional": { + "description": "Spotify API step title" + }, + "setupSpotifyApiDescription": "Añade tus credenciales de la API de Spotify para mejores resultados de búsqueda y acceso al contenido exclusivo de Spotify.", + "@setupSpotifyApiDescription": { + "description": "Explanation for Spotify API" + }, + "setupUseSpotifyApi": "Usar API de Spotify", + "@setupUseSpotifyApi": { + "description": "Toggle to enable Spotify API" + }, + "setupEnterCredentialsBelow": "Ingresa tus credenciales a continuación", + "@setupEnterCredentialsBelow": { + "description": "Prompt to enter credentials" + }, + "setupUsingDeezer": "Usando Deezer (no se necesita cuenta)", + "@setupUsingDeezer": { + "description": "Status when using Deezer" + }, + "setupEnterClientId": "Introduzca el ID de cliente de Spotify", + "@setupEnterClientId": { + "description": "Placeholder for client ID field" + }, + "setupEnterClientSecret": "Ingresa el Client Secret de Spotify", + "@setupEnterClientSecret": { + "description": "Placeholder for client secret field" + }, + "setupGetFreeCredentials": "Obtén tus credenciales gratuitas de la API desde el Spotify Developer Dashboard.", + "@setupGetFreeCredentials": { + "description": "Info about getting Spotify credentials" + }, + "setupEnableNotifications": "Activar notificaciones", + "@setupEnableNotifications": { + "description": "Button to enable notifications" + }, + "setupProceedToNextStep": "Ahora puedes continuar con el siguiente paso.", + "@setupProceedToNextStep": { + "description": "Message after completing a step" + }, + "setupNotificationProgressDescription": "Recibirás notificaciones de progreso de descargas.", + "@setupNotificationProgressDescription": { + "description": "Info about notification usage" + }, + "setupNotificationBackgroundDescription": "Recibe notificaciones sobre el progreso de la descarga y la finalización. Esto te ayuda a rastrear las descargas cuando la aplicación está en segundo plano.", + "@setupNotificationBackgroundDescription": { + "description": "Detailed notification explanation" + }, + "setupSkipForNow": "Omitir por ahora", + "@setupSkipForNow": { + "description": "Skip button text" + }, + "setupBack": "Atrás", + "@setupBack": { + "description": "Back button text" + }, + "setupNext": "Siguiente", + "@setupNext": { + "description": "Next button text" + }, + "setupGetStarted": "Empezar", + "@setupGetStarted": { + "description": "Final setup button" + }, + "setupSkipAndStart": "Saltar y empezar", + "@setupSkipAndStart": { + "description": "Skip setup and start app" + }, + "setupAllowAccessToManageFiles": "Por favor, activa \"Permitir el acceso para gestionar todos los archivos\" en la siguiente pantalla.", + "@setupAllowAccessToManageFiles": { + "description": "Instruction for file access permission" + }, + "setupGetCredentialsFromSpotify": "Obtener credenciales de developer.spotify.com", + "@setupGetCredentialsFromSpotify": { + "description": "Link text for Spotify developer portal" + }, + "dialogCancel": "Cancelar", + "@dialogCancel": { + "description": "Dialog button - cancel action" + }, + "dialogOk": "Aceptar", + "@dialogOk": { + "description": "Dialog button - confirm/acknowledge" + }, + "dialogSave": "Guardar", + "@dialogSave": { + "description": "Dialog button - save changes" + }, + "dialogDelete": "Eliminar", + "@dialogDelete": { + "description": "Dialog button - delete item" + }, + "dialogRetry": "Volver a intentar", + "@dialogRetry": { + "description": "Dialog button - retry action" + }, + "dialogClose": "Cerrar", + "@dialogClose": { + "description": "Dialog button - close dialog" + }, + "dialogYes": "Sí", + "@dialogYes": { + "description": "Dialog button - confirm yes" + }, + "dialogNo": "No", + "@dialogNo": { + "description": "Dialog button - confirm no" + }, + "dialogClear": "Borrar", + "@dialogClear": { + "description": "Dialog button - clear items" + }, + "dialogConfirm": "Confirmar", + "@dialogConfirm": { + "description": "Dialog button - confirm action" + }, + "dialogDone": "Hecho", + "@dialogDone": { + "description": "Dialog button - action completed" + }, + "dialogImport": "Importar", + "@dialogImport": { + "description": "Dialog button - import data" + }, + "dialogDiscard": "Descartar", + "@dialogDiscard": { + "description": "Dialog button - discard changes" + }, + "dialogRemove": "Eliminar", + "@dialogRemove": { + "description": "Dialog button - remove item" + }, + "dialogUninstall": "Desinstalar", + "@dialogUninstall": { + "description": "Dialog button - uninstall extension" + }, + "dialogDiscardChanges": "¿Descartar cambios?", + "@dialogDiscardChanges": { + "description": "Dialog title - unsaved changes warning" + }, + "dialogUnsavedChanges": "Tienes cambios sin guardar. ¿Quieres descartarlos?", + "@dialogUnsavedChanges": { + "description": "Dialog message - unsaved changes" + }, + "dialogDownloadFailed": "Descarga fallida", + "@dialogDownloadFailed": { + "description": "Dialog title - download error" + }, + "dialogTrackLabel": "Pista:", + "@dialogTrackLabel": { + "description": "Label for track name in error dialog" + }, + "dialogArtistLabel": "Artista:", + "@dialogArtistLabel": { + "description": "Label for artist name in error dialog" + }, + "dialogErrorLabel": "Error:", + "@dialogErrorLabel": { + "description": "Label for error message" + }, + "dialogClearAll": "Eliminar todo", + "@dialogClearAll": { + "description": "Dialog title - clear all items" + }, + "dialogClearAllDownloads": "¿Estás seguro de que quieres borrar todas las descargas?", + "@dialogClearAllDownloads": { + "description": "Dialog message - clear downloads confirmation" + }, + "dialogRemoveFromDevice": "¿Eliminar del dispositivo?", + "@dialogRemoveFromDevice": { + "description": "Dialog title - delete file confirmation" + }, + "dialogRemoveExtension": "Eliminar extensión", + "@dialogRemoveExtension": { + "description": "Dialog title - uninstall extension" + }, + "dialogRemoveExtensionMessage": "¿Estás seguro de que quieres eliminar esta extensión? Esto no se puede deshacer.", + "@dialogRemoveExtensionMessage": { + "description": "Dialog message - uninstall confirmation" + }, + "dialogUninstallExtension": "¿Desinstalar extensión?", + "@dialogUninstallExtension": { + "description": "Dialog title - uninstall extension" + }, + "dialogUninstallExtensionMessage": "¿Estás seguro de que quieres eliminar {extensionName}?", + "@dialogUninstallExtensionMessage": { + "description": "Dialog message - uninstall specific extension", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "dialogClearHistoryTitle": "Borrar historial", + "@dialogClearHistoryTitle": { + "description": "Dialog title - clear download history" + }, + "dialogClearHistoryMessage": "¿Estás seguro de que quieres borrar todo el historial de descargas? Esta acción no se puede deshacer.", + "@dialogClearHistoryMessage": { + "description": "Dialog message - clear history confirmation" + }, + "dialogDeleteSelectedTitle": "Borrar Seleccionados", + "@dialogDeleteSelectedTitle": { + "description": "Dialog title - delete selected items" + }, + "dialogDeleteSelectedMessage": "¿Eliminar {count} {count, plural, one {}=1{pista} other{pistas}} del historial?\n\nEsto también eliminará los archivos del almacenamiento.", + "@dialogDeleteSelectedMessage": { + "description": "Dialog message - delete selected tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogImportPlaylistTitle": "Importar lista de reproducción", + "@dialogImportPlaylistTitle": { + "description": "Dialog title - import CSV playlist" + }, + "dialogImportPlaylistMessage": "Se han encontrado pistas {count} en CSV. ¿Añadirlas para descargar la cola?", + "csvImportTracks": "{count} pistas de CSV", + "@csvImportTracks": { + "description": "Label shown in quality picker for CSV import", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@dialogImportPlaylistMessage": { + "description": "Dialog message - import playlist confirmation", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedToQueue": "Añadido \"{trackName}\" a la cola", + "@snackbarAddedToQueue": { + "description": "Snackbar - track added to download queue", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarAddedTracksToQueue": "Añadidas pistas {count} a la cola", + "@snackbarAddedTracksToQueue": { + "description": "Snackbar - multiple tracks added to queue", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAlreadyDownloaded": "\"{trackName}\" ya descargado", + "@snackbarAlreadyDownloaded": { + "description": "Snackbar - track already exists", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarAlreadyInLibrary": "\"{trackName}\" ya existe en tu biblioteca", + "@snackbarAlreadyInLibrary": { + "description": "Snackbar - track already exists in local library", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarHistoryCleared": "Historial borrado", + "@snackbarHistoryCleared": { + "description": "Snackbar - history deleted" + }, + "snackbarCredentialsSaved": "Credenciales guardadas", + "@snackbarCredentialsSaved": { + "description": "Snackbar - Spotify credentials saved" + }, + "snackbarCredentialsCleared": "Credenciales borradas", + "@snackbarCredentialsCleared": { + "description": "Snackbar - Spotify credentials removed" + }, + "snackbarDeletedTracks": "Eliminado {count} {count, plural, one {}=1{pista} other{pistas}}", + "@snackbarDeletedTracks": { + "description": "Snackbar - tracks deleted", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarCannotOpenFile": "No se puede abrir el archivo: {error}", + "@snackbarCannotOpenFile": { + "description": "Snackbar - file open error", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarFillAllFields": "Por favor, completa todos los campos", + "@snackbarFillAllFields": { + "description": "Snackbar - validation error" + }, + "snackbarViewQueue": "Ver cola", + "@snackbarViewQueue": { + "description": "Snackbar action - view download queue" + }, + "snackbarFailedToLoad": "Error al cargar: {error}", + "@snackbarFailedToLoad": { + "description": "Snackbar - loading error", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarUrlCopied": "URL {platform} copiada al portapapeles", + "@snackbarUrlCopied": { + "description": "Snackbar - URL copied", + "placeholders": { + "platform": { + "type": "String", + "description": "Platform name (Spotify/Deezer)" + } + } + }, + "snackbarFileNotFound": "Archivo no encontrado", + "@snackbarFileNotFound": { + "description": "Snackbar - file doesn't exist" + }, + "snackbarSelectExtFile": "Por favor, seleccione un archivo .spotiflac-ext", + "@snackbarSelectExtFile": { + "description": "Snackbar - wrong file type selected" + }, + "snackbarProviderPrioritySaved": "Prioridad de proveedor guardada", + "@snackbarProviderPrioritySaved": { + "description": "Snackbar - provider order saved" + }, + "snackbarMetadataProviderSaved": "Prioridad de proveedor de metadatos guardada", + "@snackbarMetadataProviderSaved": { + "description": "Snackbar - metadata provider order saved" + }, + "snackbarExtensionInstalled": "{extensionName} instalado.", + "@snackbarExtensionInstalled": { + "description": "Snackbar - extension installed successfully", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "snackbarExtensionUpdated": "{extensionName} actualizada.", + "@snackbarExtensionUpdated": { + "description": "Snackbar - extension updated successfully", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "snackbarFailedToInstall": "Fallo al instalar la extensión", + "@snackbarFailedToInstall": { + "description": "Snackbar - extension install error" + }, + "snackbarFailedToUpdate": "Error al actualizar la extensión", + "@snackbarFailedToUpdate": { + "description": "Snackbar - extension update error" + }, + "errorRateLimited": "Límite Excedido", + "@errorRateLimited": { + "description": "Error title - too many requests" + }, + "errorRateLimitedMessage": "Demasiadas solicitudes. Por favor, espere un momento antes de buscar de nuevo.", + "@errorRateLimitedMessage": { + "description": "Error message - rate limit explanation" + }, + "errorFailedToLoad": "Error al cargar {item}", + "@errorFailedToLoad": { + "description": "Error message - loading failed", + "placeholders": { + "item": { + "type": "String", + "description": "Item that failed to load (album/playlist/etc)" + } + } + }, + "errorNoTracksFound": "No se encontraron pistas", + "@errorNoTracksFound": { + "description": "Error - search returned no results" + }, + "errorMissingExtensionSource": "No se puede cargar {item}: falta una fuente de extensión", + "@errorMissingExtensionSource": { + "description": "Error - extension source not available", + "placeholders": { + "item": { + "type": "String" + } + } + }, + "statusQueued": "En cola", + "@statusQueued": { + "description": "Download status - waiting in queue" + }, + "statusDownloading": "Descargando", + "@statusDownloading": { + "description": "Download status - in progress" + }, + "statusFinalizing": "Finalizando", + "@statusFinalizing": { + "description": "Download status - writing metadata" + }, + "statusCompleted": "Completado", + "@statusCompleted": { + "description": "Download status - finished" + }, + "statusFailed": "Error", + "@statusFailed": { + "description": "Download status - error occurred" + }, + "statusSkipped": "Omitido", + "@statusSkipped": { + "description": "Download status - already exists" + }, + "statusPaused": "Pausado", + "@statusPaused": { + "description": "Download status - paused" + }, + "actionPause": "Pausar", + "@actionPause": { + "description": "Action button - pause download" + }, + "actionResume": "Reanudar", + "@actionResume": { + "description": "Action button - resume download" + }, + "actionCancel": "Cancelar", + "@actionCancel": { + "description": "Action button - cancel operation" + }, + "actionStop": "Detener", + "@actionStop": { + "description": "Action button - stop operation" + }, + "actionSelect": "Seleccionar", + "@actionSelect": { + "description": "Action button - enter selection mode" + }, + "actionSelectAll": "Seleccionar Todo", + "@actionSelectAll": { + "description": "Action button - select all items" + }, + "actionDeselect": "Deseleccionar", + "@actionDeselect": { + "description": "Action button - deselect all" + }, + "actionPaste": "Pegar", + "@actionPaste": { + "description": "Action button - paste from clipboard" + }, + "actionImportCsv": "Importar CSV", + "@actionImportCsv": { + "description": "Action button - import CSV file" + }, + "actionRemoveCredentials": "Eliminar credenciales", + "@actionRemoveCredentials": { + "description": "Action button - delete Spotify credentials" + }, + "actionSaveCredentials": "Guardar credenciales", + "@actionSaveCredentials": { + "description": "Action button - save Spotify credentials" + }, + "selectionSelected": "{count} seleccionado", + "@selectionSelected": { + "description": "Selection count indicator", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionAllSelected": "Todas las pistas seleccionadas", + "@selectionAllSelected": { + "description": "Status - all items selected" + }, + "selectionTapToSelect": "Toca las pistas para seleccionar", + "@selectionTapToSelect": { + "description": "Hint - how to select items" + }, + "selectionDeleteTracks": "¡Eliminar {count} {count, plural, one {}=1{pista} other{pistas}}", + "@selectionDeleteTracks": { + "description": "Delete button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionSelectToDelete": "Seleccionar pistas a eliminar", + "@selectionSelectToDelete": { + "description": "Placeholder when nothing selected" + }, + "progressFetchingMetadata": "Obteniendo metadatos... {current}/{total}", + "@progressFetchingMetadata": { + "description": "Progress indicator - loading track info", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "progressReadingCsv": "Leyendo CSV...", + "@progressReadingCsv": { + "description": "Progress indicator - parsing CSV file" + }, + "searchSongs": "Canciones", + "@searchSongs": { + "description": "Search result category - songs" + }, + "searchArtists": "Artistas", + "@searchArtists": { + "description": "Search result category - artists" + }, + "searchAlbums": "Álbumes", + "@searchAlbums": { + "description": "Search result category - albums" + }, + "searchPlaylists": "Listas de reproducción", + "@searchPlaylists": { + "description": "Search result category - playlists" + }, + "tooltipPlay": "Reproducir", + "@tooltipPlay": { + "description": "Tooltip - play button" + }, + "tooltipCancel": "Cancelar", + "@tooltipCancel": { + "description": "Tooltip - cancel button" + }, + "tooltipStop": "Detener", + "@tooltipStop": { + "description": "Tooltip - stop button" + }, + "tooltipRetry": "Volver a intentar", + "@tooltipRetry": { + "description": "Tooltip - retry button" + }, + "tooltipRemove": "Eliminar", + "@tooltipRemove": { + "description": "Tooltip - remove button" + }, + "tooltipClear": "Borrar", + "@tooltipClear": { + "description": "Tooltip - clear button" + }, + "tooltipPaste": "Pegar", + "@tooltipPaste": { + "description": "Tooltip - paste button" + }, + "filenameFormat": "Formato del nombre del archivo", + "@filenameFormat": { + "description": "Setting title - filename pattern" + }, + "filenameFormatPreview": "Vista previa: {preview}", + "@filenameFormatPreview": { + "description": "Preview of filename pattern", + "placeholders": { + "preview": { + "type": "String" + } + } + }, + "filenameAvailablePlaceholders": "Marcadores disponibles:", + "@filenameAvailablePlaceholders": { + "description": "Label for placeholder list" + }, + "filenameHint": "{artist} - {title}", + "@filenameHint": { + "description": "Default filename format hint" + }, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, + "folderOrganization": "Organización de carpetas", + "@folderOrganization": { + "description": "Setting title - folder structure" + }, + "folderOrganizationNone": "Ninguna organización", + "@folderOrganizationNone": { + "description": "Folder option - flat structure" + }, + "folderOrganizationByArtist": "Por Artista", + "@folderOrganizationByArtist": { + "description": "Folder option - artist folders" + }, + "folderOrganizationByAlbum": "Por Álbum", + "@folderOrganizationByAlbum": { + "description": "Folder option - album folders" + }, + "folderOrganizationByArtistAlbum": "Artista/Álbum", + "@folderOrganizationByArtistAlbum": { + "description": "Folder option - nested folders" + }, + "folderOrganizationDescription": "Organizar los archivos descargados en carpetas", + "@folderOrganizationDescription": { + "description": "Folder organization sheet description" + }, + "folderOrganizationNoneSubtitle": "Todos los archivos de la carpeta de descargas", + "@folderOrganizationNoneSubtitle": { + "description": "Subtitle for no organization option" + }, + "folderOrganizationByArtistSubtitle": "Carpeta separada para cada artista", + "@folderOrganizationByArtistSubtitle": { + "description": "Subtitle for artist folder option" + }, + "folderOrganizationByAlbumSubtitle": "Carpeta separada para cada artista", + "@folderOrganizationByAlbumSubtitle": { + "description": "Subtitle for album folder option" + }, + "folderOrganizationByArtistAlbumSubtitle": "Carpetas organizadas por artista y álbum", + "@folderOrganizationByArtistAlbumSubtitle": { + "description": "Subtitle for nested folder option" + }, + "updateAvailable": "Actualización Disponible", + "@updateAvailable": { + "description": "Update dialog title" + }, + "updateNewVersion": "Versión {version} está disponible", + "@updateNewVersion": { + "description": "Update available message", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "updateDownload": "Descargar", + "@updateDownload": { + "description": "Update button - download update" + }, + "updateLater": "Más tarde", + "@updateLater": { + "description": "Update button - dismiss" + }, + "updateChangelog": "Historial de cambios", + "@updateChangelog": { + "description": "Link to changelog" + }, + "updateStartingDownload": "Iniciando descarga...", + "@updateStartingDownload": { + "description": "Update status - initializing" + }, + "updateDownloadFailed": "Descarga fallida", + "@updateDownloadFailed": { + "description": "Update error title" + }, + "updateFailedMessage": "Error al descargar la actualización", + "@updateFailedMessage": { + "description": "Update error message" + }, + "updateNewVersionReady": "Una nueva versión está lista", + "@updateNewVersionReady": { + "description": "Update subtitle" + }, + "updateCurrent": "Actual", + "@updateCurrent": { + "description": "Label for current version" + }, + "updateNew": "Nuevo", + "@updateNew": { + "description": "Label for new version" + }, + "updateDownloading": "Descargando...", + "@updateDownloading": { + "description": "Update status - downloading" + }, + "updateWhatsNew": "Novedades", + "@updateWhatsNew": { + "description": "Changelog section title" + }, + "updateDownloadInstall": "Descargar & Instalar", + "@updateDownloadInstall": { + "description": "Update button - download and install" + }, + "updateDontRemind": "No recordar", + "@updateDontRemind": { + "description": "Update button - skip this version" + }, + "providerPriority": "Prioridad del proveedor", + "@providerPriority": { + "description": "Setting title - download provider order" + }, + "providerPrioritySubtitle": "Arrastre para reordenar los proveedores de descarga", + "@providerPrioritySubtitle": { + "description": "Subtitle for provider priority" + }, + "providerPriorityTitle": "Prioridad del proveedor", + "@providerPriorityTitle": { + "description": "Provider priority page title" + }, + "providerPriorityDescription": "Arrastra para reordenar los proveedores de descarga. La aplicación intentará usar los proveedores de arriba hacia abajo al descargar las pistas.", + "@providerPriorityDescription": { + "description": "Provider priority page description" + }, + "providerPriorityInfo": "Si una pista no está disponible en el primer proveedor, la aplicación intentará automáticamente el siguiente.", + "@providerPriorityInfo": { + "description": "Info tip about fallback behavior" + }, + "providerBuiltIn": "Integrado", + "@providerBuiltIn": { + "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + }, + "providerExtension": "Extensión", + "@providerExtension": { + "description": "Label for extension-provided providers" + }, + "metadataProviderPriority": "Prioridad del proveedor de metadatos", + "@metadataProviderPriority": { + "description": "Setting title - metadata provider order" + }, + "metadataProviderPrioritySubtitle": "Orden usado al recuperar metadatos de la pista", + "@metadataProviderPrioritySubtitle": { + "description": "Subtitle for metadata priority" + }, + "metadataProviderPriorityTitle": "Prioridad de los metadatos", + "@metadataProviderPriorityTitle": { + "description": "Metadata priority page title" + }, + "metadataProviderPriorityDescription": "Arrastra para reordenar los proveedores de metadatos. La aplicación probará los proveedores de arriba hacia abajo al buscar pistas y obtener los metadatos.", + "@metadataProviderPriorityDescription": { + "description": "Metadata priority page description" + }, + "metadataProviderPriorityInfo": "Deezer no tiene límites de tasa y se recomienda como principal. Spotify puede valorar el límite después de muchas solicitudes.", + "@metadataProviderPriorityInfo": { + "description": "Info tip about rate limits" + }, + "metadataNoRateLimits": "Sin límites de tasa", + "@metadataNoRateLimits": { + "description": "Deezer provider description" + }, + "metadataMayRateLimit": "Sin límites de tasa", + "@metadataMayRateLimit": { + "description": "Spotify provider description" + }, + "logTitle": "Registros", + "@logTitle": { + "description": "Logs screen title" + }, + "logCopy": "Copiar Registros", + "@logCopy": { + "description": "Action - copy logs to clipboard" + }, + "logClear": "Limpiar registros", + "@logClear": { + "description": "Action - delete all logs" + }, + "logShare": "Compartir Registros", + "@logShare": { + "description": "Action - share logs file" + }, + "logEmpty": "No hay registros aún", + "@logEmpty": { + "description": "Empty state title" + }, + "logCopied": "Registros copiados al portapapeles", + "@logCopied": { + "description": "Snackbar - logs copied" + }, + "logSearchHint": "Buscar registros...", + "@logSearchHint": { + "description": "Log search placeholder" + }, + "logFilterLevel": "Nivel", + "@logFilterLevel": { + "description": "Filter by log level" + }, + "logFilterSection": "Filtrar", + "@logFilterSection": { + "description": "Filter section title" + }, + "logShareLogs": "Compartir registros", + "@logShareLogs": { + "description": "Share button tooltip" + }, + "logClearLogs": "Borrar registros", + "@logClearLogs": { + "description": "Clear button tooltip" + }, + "logClearLogsTitle": "Limpiar registros", + "@logClearLogsTitle": { + "description": "Clear logs dialog title" + }, + "logClearLogsMessage": "¿Estás seguro que deseas limpiar todos los registros?", + "@logClearLogsMessage": { + "description": "Clear logs confirmation message" + }, + "logIspBlocking": "BLOQUEO POR EL ISP DETECTADO", + "@logIspBlocking": { + "description": "Error category - ISP blocking" + }, + "logRateLimited": "TASA LIMITADA", + "@logRateLimited": { + "description": "Error category - rate limiting" + }, + "logNetworkError": "ERROR DE RED", + "@logNetworkError": { + "description": "Error category - network issues" + }, + "logTrackNotFound": "PISTA NO ENCONTRADA", + "@logTrackNotFound": { + "description": "Error category - missing tracks" + }, + "logFilterBySeverity": "Filtrar los registros por gravedad", + "@logFilterBySeverity": { + "description": "Filter dialog title" + }, + "logNoLogsYet": "No hay registros aún", + "@logNoLogsYet": { + "description": "Empty state title" + }, + "logNoLogsYetSubtitle": "Los registros aparecerán aquí mientras usas la aplicación", + "@logNoLogsYetSubtitle": { + "description": "Empty state subtitle" + }, + "logIssueSummary": "Resumen de Incidencias", + "@logIssueSummary": { + "description": "Section header for error summary" + }, + "logIspBlockingDescription": "Tu ISP puede estar bloqueando el acceso a los servicios de descarga", + "@logIspBlockingDescription": { + "description": "ISP blocking explanation" + }, + "logIspBlockingSuggestion": "Intente usar una VPN o cambie el DNS a 1.1.1.1 o 8.8.8.8", + "@logIspBlockingSuggestion": { + "description": "ISP blocking fix suggestion" + }, + "logRateLimitedDescription": "Demasiadas solicitudes al servicio", + "@logRateLimitedDescription": { + "description": "Rate limit explanation" + }, + "logRateLimitedSuggestion": "Espere unos minutos antes de volver a intentarlo", + "@logRateLimitedSuggestion": { + "description": "Rate limit fix suggestion" + }, + "logNetworkErrorDescription": "Problemas de conexión detectados", + "@logNetworkErrorDescription": { + "description": "Network error explanation" + }, + "logNetworkErrorSuggestion": "Comprueba tu conexión a internet", + "@logNetworkErrorSuggestion": { + "description": "Network error fix suggestion" + }, + "logTrackNotFoundDescription": "No se pudieron encontrar algunas pistas en los servicios de descarga", + "@logTrackNotFoundDescription": { + "description": "Track not found explanation" + }, + "logTrackNotFoundSuggestion": "La pista puede no estar disponible en calidad sin pérdida", + "@logTrackNotFoundSuggestion": { + "description": "Track not found explanation" + }, + "logTotalErrors": "Total de errores: {count}", + "@logTotalErrors": { + "description": "Error count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "logAffected": "Afectado: {domains}", + "@logAffected": { + "description": "Affected domains display", + "placeholders": { + "domains": { + "type": "String" + } + } + }, + "logEntriesFiltered": "Entradas ({count} filtradas)", + "@logEntriesFiltered": { + "description": "Log count with filter active", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "logEntries": "Entradas ({count})", + "@logEntries": { + "description": "Total log count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "credentialsTitle": "Credenciales de Spotify", + "@credentialsTitle": { + "description": "Credentials dialog title" + }, + "credentialsDescription": "Introduzca su ID de cliente y secreto para utilizar su propia cuota de aplicación de Spotify.", + "@credentialsDescription": { + "description": "Credentials dialog explanation" + }, + "credentialsClientId": "ID del cliente", + "@credentialsClientId": { + "description": "Client ID field label - DO NOT TRANSLATE" + }, + "credentialsClientIdHint": "Pegar ID de cliente", + "@credentialsClientIdHint": { + "description": "Client ID placeholder" + }, + "credentialsClientSecret": "Client Secret", + "@credentialsClientSecret": { + "description": "Client Secret field label - DO NOT TRANSLATE" + }, + "credentialsClientSecretHint": "Pegar Client Secret", + "@credentialsClientSecretHint": { + "description": "Client Secret placeholder" + }, + "channelStable": "Estable", + "@channelStable": { + "description": "Update channel - stable releases" + }, + "channelPreview": "Vista previa", + "@channelPreview": { + "description": "Update channel - beta/preview releases" + }, + "sectionSearchSource": "Buscar Fuente", + "@sectionSearchSource": { + "description": "Settings section header" + }, + "sectionDownload": "Descargar", + "@sectionDownload": { + "description": "Settings section header" + }, + "sectionPerformance": "Alto rendimiento", + "@sectionPerformance": { + "description": "Settings section header" + }, + "sectionApp": "Aplicación", + "@sectionApp": { + "description": "Settings section header" + }, + "sectionData": "Datos", + "@sectionData": { + "description": "Settings section header" + }, + "sectionDebug": "Depuración", + "@sectionDebug": { + "description": "Settings section header" + }, + "sectionService": "Servicio", + "@sectionService": { + "description": "Settings section header" + }, + "sectionAudioQuality": "Calidad de Sonido", + "@sectionAudioQuality": { + "description": "Settings section header" + }, + "sectionFileSettings": "Ajustes del archivo", + "@sectionFileSettings": { + "description": "Settings section header" + }, + "sectionLyrics": "Letras", + "@sectionLyrics": { + "description": "Settings section header" + }, + "lyricsMode": "Modo Letras", + "@lyricsMode": { + "description": "Setting - how to save lyrics" + }, + "lyricsModeDescription": "Elige cómo se guardan las letras de tus descargas", + "@lyricsModeDescription": { + "description": "Lyrics mode picker description" + }, + "lyricsModeEmbed": "Insertar en archivo", + "@lyricsModeEmbed": { + "description": "Lyrics mode option - embed in audio file" + }, + "lyricsModeEmbedSubtitle": "Letras almacenadas en los metadatos FLAC", + "@lyricsModeEmbedSubtitle": { + "description": "Subtitle for embed option" + }, + "lyricsModeExternal": "Archivo .lrc externo", + "@lyricsModeExternal": { + "description": "Lyrics mode option - separate LRC file" + }, + "lyricsModeExternalSubtitle": "Archivo .lrc separado para reproductores como Samsung Music", + "@lyricsModeExternalSubtitle": { + "description": "Subtitle for external option" + }, + "lyricsModeBoth": "Ambos", + "@lyricsModeBoth": { + "description": "Lyrics mode option - embed and external" + }, + "lyricsModeBothSubtitle": "Insertar y guardar archivo .lrc", + "@lyricsModeBothSubtitle": { + "description": "Subtitle for both option" + }, + "sectionColor": "Colores", + "@sectionColor": { + "description": "Settings section header" + }, + "sectionTheme": "Tema", + "@sectionTheme": { + "description": "Settings section header" + }, + "sectionLayout": "Diseño", + "@sectionLayout": { + "description": "Settings section header" + }, + "sectionLanguage": "Idioma", + "@sectionLanguage": { + "description": "Settings section header for language" + }, + "appearanceLanguage": "Idioma de la aplicación", + "@appearanceLanguage": { + "description": "Language setting title" + }, + "appearanceLanguageSubtitle": "Elija su idioma preferido", + "@appearanceLanguageSubtitle": { + "description": "Language setting subtitle" + }, + "settingsAppearanceSubtitle": "Tema, colores, pantalla", + "@settingsAppearanceSubtitle": { + "description": "Appearance settings description" + }, + "settingsDownloadSubtitle": "Servicio, calidad, formato del nombre del archivo", + "@settingsDownloadSubtitle": { + "description": "Download settings description" + }, + "settingsOptionsSubtitle": "Alternativa, letras, carátula, actualizaciones", + "@settingsOptionsSubtitle": { + "description": "Options settings description" + }, + "settingsExtensionsSubtitle": "Administrar proveedores de descarga", + "@settingsExtensionsSubtitle": { + "description": "Extensions settings description" + }, + "settingsLogsSubtitle": "Ver registros de aplicaciones para depuración", + "@settingsLogsSubtitle": { + "description": "Logs settings description" + }, + "loadingSharedLink": "Cargando enlace compartido...", + "@loadingSharedLink": { + "description": "Status when opening shared URL" + }, + "pressBackAgainToExit": "Presione de nuevo para salir", + "@pressBackAgainToExit": { + "description": "Exit confirmation message" + }, + "tracksHeader": "Pistas", + "@tracksHeader": { + "description": "Section header for track list" + }, + "downloadAllCount": "Descargar Todo ({count})", + "@downloadAllCount": { + "description": "Download all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "tracksCount": "{count, plural, one {}=1{1 pista} other{{count} pistas}}", + "@tracksCount": { + "description": "Track count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "trackCopyFilePath": "Copiar ruta de archivo", + "@trackCopyFilePath": { + "description": "Action - copy file path" + }, + "trackRemoveFromDevice": "Eliminar del dispositivo", + "@trackRemoveFromDevice": { + "description": "Action - delete downloaded file" + }, + "trackLoadLyrics": "Cargar letras", + "@trackLoadLyrics": { + "description": "Action - fetch lyrics" + }, + "trackMetadata": "Metadatos", + "@trackMetadata": { + "description": "Tab title - track metadata" + }, + "trackFileInfo": "Información de archivo", + "@trackFileInfo": { + "description": "Tab title - file information" + }, + "trackLyrics": "Letras", + "@trackLyrics": { + "description": "Tab title - lyrics" + }, + "trackFileNotFound": "Archivo no encontrado", + "@trackFileNotFound": { + "description": "Error - file doesn't exist" + }, + "trackOpenInDeezer": "Abrir en Deezer", + "@trackOpenInDeezer": { + "description": "Action - open track in Deezer app" + }, + "trackOpenInSpotify": "Abrir en Spotify", + "@trackOpenInSpotify": { + "description": "Action - open track in Spotify app" + }, + "trackTrackName": "Nombre de pista", + "@trackTrackName": { + "description": "Metadata label - track title" + }, + "trackArtist": "Artista", + "@trackArtist": { + "description": "Metadata label - artist name" + }, + "trackAlbumArtist": "Artista del álbum", + "@trackAlbumArtist": { + "description": "Metadata label - album artist" + }, + "trackAlbum": "Álbum", + "@trackAlbum": { + "description": "Metadata label - album name" + }, + "trackTrackNumber": "Número de pista", + "@trackTrackNumber": { + "description": "Metadata label - track number" + }, + "trackDiscNumber": "Número de disco", + "@trackDiscNumber": { + "description": "Metadata label - disc number" + }, + "trackDuration": "Duración", + "@trackDuration": { + "description": "Metadata label - track length" + }, + "trackAudioQuality": "Calidad del sonido", + "@trackAudioQuality": { + "description": "Metadata label - audio quality" + }, + "trackReleaseDate": "Fecha de lanzamiento", + "@trackReleaseDate": { + "description": "Metadata label - release date" + }, + "trackGenre": "Género", + "@trackGenre": { + "description": "Metadata label - music genre" + }, + "trackLabel": "Etiqueta", + "@trackLabel": { + "description": "Metadata label - record label" + }, + "trackCopyright": "Derechos de autor", + "@trackCopyright": { + "description": "Metadata label - copyright information" + }, + "trackDownloaded": "Descargado", + "@trackDownloaded": { + "description": "Metadata label - download date" + }, + "trackCopyLyrics": "Copiar letras", + "@trackCopyLyrics": { + "description": "Action - copy lyrics to clipboard" + }, + "trackLyricsNotAvailable": "Letras no disponibles para este tema", + "@trackLyricsNotAvailable": { + "description": "Message when lyrics not found" + }, + "trackLyricsTimeout": "Tiempo de espera agotado. Inténtalo de nuevo más tarde.", + "@trackLyricsTimeout": { + "description": "Message when lyrics request times out" + }, + "trackLyricsLoadFailed": "Error al cargar la letra", + "@trackLyricsLoadFailed": { + "description": "Message when lyrics loading fails" + }, + "trackEmbedLyrics": "Incrustar Letras", + "@trackEmbedLyrics": { + "description": "Action - embed lyrics into audio file" + }, + "trackLyricsEmbedded": "Letra incrustada con éxito", + "@trackLyricsEmbedded": { + "description": "Snackbar - lyrics saved to file" + }, + "trackInstrumental": "Pista intrumental", + "@trackInstrumental": { + "description": "Message when track is instrumental (no lyrics)" + }, + "trackCopiedToClipboard": "Copiado al portapapeles", + "@trackCopiedToClipboard": { + "description": "Snackbar - content copied" + }, + "trackDeleteConfirmTitle": "¿Eliminar del dispositivo?", + "@trackDeleteConfirmTitle": { + "description": "Delete confirmation title" + }, + "trackDeleteConfirmMessage": "Esto eliminará permanentemente el archivo descargado y lo eliminará de tu historial.", + "@trackDeleteConfirmMessage": { + "description": "Delete confirmation message" + }, + "trackCannotOpen": "No se puede abrir: {message}", + "@trackCannotOpen": { + "description": "Error opening file", + "placeholders": { + "message": { + "type": "String" + } + } + }, + "dateToday": "Hoy", + "@dateToday": { + "description": "Relative date - today" + }, + "dateYesterday": "Ayer", + "@dateYesterday": { + "description": "Relative date - yesterday" + }, + "dateDaysAgo": "Hace {count} días", + "@dateDaysAgo": { + "description": "Relative date - days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dateWeeksAgo": "{count} semanas antes", + "@dateWeeksAgo": { + "description": "Relative date - weeks ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dateMonthsAgo": "{count} meses atrás", + "@dateMonthsAgo": { + "description": "Relative date - months ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "concurrentSequential": "Secuencial", + "@concurrentSequential": { + "description": "Download mode - one at a time" + }, + "concurrentParallel2": "2 simultáneamente", + "@concurrentParallel2": { + "description": "Download mode - 2 simultaneous" + }, + "concurrentParallel3": "3 simultáneamente", + "@concurrentParallel3": { + "description": "Download mode - 3 simultaneous" + }, + "tapToSeeError": "Pulse para ver los detalles del error", + "@tapToSeeError": { + "description": "Tooltip for failed download" + }, + "storeFilterAll": "Todo", + "@storeFilterAll": { + "description": "Store filter - all extensions" + }, + "storeFilterMetadata": "Metadatos", + "@storeFilterMetadata": { + "description": "Store filter - metadata providers" + }, + "storeFilterDownload": "Descargar", + "@storeFilterDownload": { + "description": "Store filter - download providers" + }, + "storeFilterUtility": "Utilidad", + "@storeFilterUtility": { + "description": "Store filter - utility extensions" + }, + "storeFilterLyrics": "Letras", + "@storeFilterLyrics": { + "description": "Store filter - lyrics providers" + }, + "storeFilterIntegration": "Integración", + "@storeFilterIntegration": { + "description": "Store filter - integrations" + }, + "storeClearFilters": "Limpiar filtros", + "@storeClearFilters": { + "description": "Button to clear all filters" + }, + "storeNoResults": "No se encontraron extensiones", + "@storeNoResults": { + "description": "Empty state when no extensions match filters" + }, + "extensionProviderPriority": "Prioridad del proveedor", + "@extensionProviderPriority": { + "description": "Extension capability - provider priority" + }, + "extensionInstallButton": "Instalar extensión", + "@extensionInstallButton": { + "description": "Button to install extension" + }, + "extensionDefaultProvider": "Por defecto (Deezer/Spotify)", + "@extensionDefaultProvider": { + "description": "Default search provider option" + }, + "extensionDefaultProviderSubtitle": "Usar búsqueda integrada", + "@extensionDefaultProviderSubtitle": { + "description": "Subtitle for default provider" + }, + "extensionAuthor": "Autor/a", + "@extensionAuthor": { + "description": "Extension detail - author" + }, + "extensionId": "ID", + "@extensionId": { + "description": "Extension detail - unique ID" + }, + "extensionError": "Error", + "@extensionError": { + "description": "Extension detail - error message" + }, + "extensionCapabilities": "Recursos", + "@extensionCapabilities": { + "description": "Section header - extension features" + }, + "extensionMetadataProvider": "Proveedor de metadatos", + "@extensionMetadataProvider": { + "description": "Capability - provides metadata" + }, + "extensionDownloadProvider": "Proveedor de descargas", + "@extensionDownloadProvider": { + "description": "Capability - provides downloads" + }, + "extensionLyricsProvider": "Proveedor de letras", + "@extensionLyricsProvider": { + "description": "Capability - provides lyrics" + }, + "extensionUrlHandler": "Gestor de URL", + "@extensionUrlHandler": { + "description": "Capability - handles URLs" + }, + "extensionQualityOptions": "Opciones de calidad", + "@extensionQualityOptions": { + "description": "Capability - quality selection" + }, + "extensionPostProcessingHooks": "Hooks post-procesamiento", + "@extensionPostProcessingHooks": { + "description": "Capability - post-processing" + }, + "extensionPermissions": "Permisos", + "@extensionPermissions": { + "description": "Section header - required permissions" + }, + "extensionSettings": "Ajustes", + "@extensionSettings": { + "description": "Section header - extension settings" + }, + "extensionRemoveButton": "Eliminar extensión", + "@extensionRemoveButton": { + "description": "Button to uninstall extension" + }, + "extensionUpdated": "Actualizado", + "@extensionUpdated": { + "description": "Extension detail - last update" + }, + "extensionMinAppVersion": "Versión Mínima de la aplicación", + "@extensionMinAppVersion": { + "description": "Extension detail - minimum app version" + }, + "extensionCustomTrackMatching": "Coincidencia de pista personalizada", + "@extensionCustomTrackMatching": { + "description": "Capability - custom track matching algorithm" + }, + "extensionPostProcessing": "Post-Procesamiento", + "@extensionPostProcessing": { + "description": "Capability - post-download processing" + }, + "extensionHooksAvailable": "{count} hook(s) disponibles", + "@extensionHooksAvailable": { + "description": "Post-processing hooks count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "extensionPatternsCount": "Patrón(es) {count}", + "@extensionPatternsCount": { + "description": "URL patterns count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "extensionStrategy": "Estrategia: {strategy}", + "@extensionStrategy": { + "description": "Track matching strategy name", + "placeholders": { + "strategy": { + "type": "String" + } + } + }, + "extensionsProviderPrioritySection": "Prioridad del proveedor", + "@extensionsProviderPrioritySection": { + "description": "Section header - provider priority" + }, + "extensionsInstalledSection": "Extensiones instaladas", + "@extensionsInstalledSection": { + "description": "Section header - installed extensions" + }, + "extensionsNoExtensions": "No hay extensiones instaladas", + "@extensionsNoExtensions": { + "description": "Empty state - no extensions" + }, + "extensionsNoExtensionsSubtitle": "Instalar archivos .spotiflac-ext para añadir nuevos proveedores", + "@extensionsNoExtensionsSubtitle": { + "description": "Empty state subtitle" + }, + "extensionsInstallButton": "Instalar extensión", + "@extensionsInstallButton": { + "description": "Button to install extension from file" + }, + "extensionsInfoTip": "Las extensiones pueden añadir nuevos metadatos y proveedores de descargas. Sólo instalar extensiones desde fuentes confiables.", + "@extensionsInfoTip": { + "description": "Security warning about extensions" + }, + "extensionsInstalledSuccess": "Extensión instalada correctamente", + "@extensionsInstalledSuccess": { + "description": "Success message after install" + }, + "extensionsDownloadPriority": "Prioridad de descarga", + "@extensionsDownloadPriority": { + "description": "Setting - download provider order" + }, + "extensionsDownloadPrioritySubtitle": "Establecer orden de servicio de descarga", + "@extensionsDownloadPrioritySubtitle": { + "description": "Subtitle for download priority" + }, + "extensionsNoDownloadProvider": "No hay extensiones con proveedor de descargas", + "@extensionsNoDownloadProvider": { + "description": "Empty state - no download providers" + }, + "extensionsMetadataPriority": "Prioridad de los metadatos", + "@extensionsMetadataPriority": { + "description": "Setting - metadata provider order" + }, + "extensionsMetadataPrioritySubtitle": "Establecer orden de búsqueda y metadatos", + "@extensionsMetadataPrioritySubtitle": { + "description": "Subtitle for metadata priority" + }, + "extensionsNoMetadataProvider": "No hay extensiones con el proveedor de metadatos", + "@extensionsNoMetadataProvider": { + "description": "Empty state - no metadata providers" + }, + "extensionsSearchProvider": "Proveedor de búsqueda", + "@extensionsSearchProvider": { + "description": "Setting - search provider selection" + }, + "extensionsNoCustomSearch": "No hay extensiones con búsqueda personalizada", + "@extensionsNoCustomSearch": { + "description": "Empty state - no search providers" + }, + "extensionsSearchProviderDescription": "Elegir qué servicio usar para buscar pistas", + "@extensionsSearchProviderDescription": { + "description": "Search provider setting description" + }, + "extensionsCustomSearch": "Búsqueda personalizada", + "@extensionsCustomSearch": { + "description": "Label for custom search provider" + }, + "extensionsErrorLoading": "Error al cargar la extensión", + "@extensionsErrorLoading": { + "description": "Error message when extension fails to load" + }, + "qualityFlacLossless": "FLAC Lossless", + "@qualityFlacLossless": { + "description": "Quality option - CD quality FLAC" + }, + "qualityFlacLosslessSubtitle": "16-bit / 44.1kHz", + "@qualityFlacLosslessSubtitle": { + "description": "Technical spec for lossless" + }, + "qualityHiResFlac": "Hi-Res FLAC", + "@qualityHiResFlac": { + "description": "Quality option - high resolution FLAC" + }, + "qualityHiResFlacSubtitle": "24 bits/hasta 96kHz", + "@qualityHiResFlacSubtitle": { + "description": "Technical spec for hi-res" + }, + "qualityHiResFlacMax": "Hi-Res FLAC Max", + "@qualityHiResFlacMax": { + "description": "Quality option - maximum resolution FLAC" + }, + "qualityHiResFlacMaxSubtitle": "24 bits / hasta 192kHz", + "@qualityHiResFlacMaxSubtitle": { + "description": "Technical spec for hi-res max" + }, + "qualityLossy": "Con pérdidas", + "@qualityLossy": { + "description": "Quality option - lossy format (MP3/Opus)" + }, + "qualityLossyMp3Subtitle": "MP3 320kbps (convertido desde FLAC)", + "@qualityLossyMp3Subtitle": { + "description": "Technical spec for lossy MP3" + }, + "qualityLossyOpusSubtitle": "Opus 128kbps (convertido de FLAC)", + "@qualityLossyOpusSubtitle": { + "description": "Technical spec for lossy Opus" + }, + "enableLossyOption": "Habilitar opción con pérdida", + "@enableLossyOption": { + "description": "Setting - enable lossy quality option" + }, + "enableLossyOptionSubtitleOn": "La opción de calidad con pérdida está disponible", + "@enableLossyOptionSubtitleOn": { + "description": "Subtitle when lossy is enabled" + }, + "enableLossyOptionSubtitleOff": "Descargas FLAC y luego se convierten en formato con pérdida", + "@enableLossyOptionSubtitleOff": { + "description": "Subtitle when lossy is disabled" + }, + "lossyFormat": "Formato con Perdido", + "@lossyFormat": { + "description": "Setting - choose lossy format" + }, + "lossyFormatDescription": "Elegir el formato con pérdida para la conversión", + "@lossyFormatDescription": { + "description": "Description for lossy format picker" + }, + "lossyFormatMp3Subtitle": "320kbps, mejor compatibilidad", + "@lossyFormatMp3Subtitle": { + "description": "MP3 format description" + }, + "lossyFormatOpusSubtitle": "128kbps, mejor calidad a menor tamaño", + "@lossyFormatOpusSubtitle": { + "description": "Opus format description" + }, + "qualityNote": "La calidad real depende de la disponibilidad de la pista del servicio", + "@qualityNote": { + "description": "Note about quality availability" + }, + "youtubeQualityNote": "YouTube provides lossy audio only. Not part of lossless fallback.", + "@youtubeQualityNote": { + "description": "Note for YouTube service explaining lossy-only quality" + }, + "downloadAskBeforeDownload": "Preguntar antes de descargar", + "@downloadAskBeforeDownload": { + "description": "Setting - show quality picker" + }, + "downloadDirectory": "Carpeta de descarga", + "@downloadDirectory": { + "description": "Setting - download folder" + }, + "downloadSeparateSinglesFolder": "Carpeta separada para pistas", + "@downloadSeparateSinglesFolder": { + "description": "Setting - separate folder for singles" + }, + "downloadAlbumFolderStructure": "Estructura de carpeta del álbum", + "@downloadAlbumFolderStructure": { + "description": "Setting - album folder organization" + }, + "downloadUseAlbumArtistForFolders": "Use Album Artist for folders", + "@downloadUseAlbumArtistForFolders": { + "description": "Setting - choose whether artist folders use Album Artist or Track Artist" + }, + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" + }, + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" + }, + "downloadUsePrimaryArtistOnly": "Primary artist only for folders", + "@downloadUsePrimaryArtistOnly": { + "description": "Setting - strip featured artists from folder name" + }, + "downloadUsePrimaryArtistOnlyEnabled": "Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)", + "@downloadUsePrimaryArtistOnlyEnabled": { + "description": "Subtitle when primary artist only is enabled" + }, + "downloadUsePrimaryArtistOnlyDisabled": "Full artist string used for folder name", + "@downloadUsePrimaryArtistOnlyDisabled": { + "description": "Subtitle when primary artist only is disabled" + }, + "downloadSaveFormat": "Guardar Formato", + "@downloadSaveFormat": { + "description": "Setting - output file format" + }, + "downloadSelectService": "Seleccionar Servicio", + "@downloadSelectService": { + "description": "Dialog title - choose download service" + }, + "downloadSelectQuality": "Seleccionar Calidad", + "@downloadSelectQuality": { + "description": "Dialog title - choose audio quality" + }, + "downloadFrom": "Descargar Desde", + "@downloadFrom": { + "description": "Label - download source" + }, + "downloadDefaultQualityLabel": "Calidad por Defecto", + "@downloadDefaultQualityLabel": { + "description": "Label - default quality setting" + }, + "downloadBestAvailable": "La mejor disponible", + "@downloadBestAvailable": { + "description": "Quality option - highest available" + }, + "folderNone": "Ninguna", + "@folderNone": { + "description": "Folder option - no organization" + }, + "folderNoneSubtitle": "Guardar todos los archivos directamente para descargar la carpeta", + "@folderNoneSubtitle": { + "description": "Subtitle for no folder organization" + }, + "folderArtist": "Artista", + "@folderArtist": { + "description": "Folder option - by artist" + }, + "folderArtistSubtitle": "Nombre del Artista/nombre de archivo", + "@folderArtistSubtitle": { + "description": "Folder structure example" + }, + "folderAlbum": "Álbum", + "@folderAlbum": { + "description": "Folder option - by album" + }, + "folderAlbumSubtitle": "Nombre del álbum/nombre de archivo", + "@folderAlbumSubtitle": { + "description": "Folder structure example" + }, + "folderArtistAlbum": "Artista/Álbum", + "@folderArtistAlbum": { + "description": "Folder option - nested" + }, + "folderArtistAlbumSubtitle": "Nombre del Artista/Nombre del Álbum/Nombre del Archivo", + "@folderArtistAlbumSubtitle": { + "description": "Folder structure example" + }, + "serviceTidal": "Tidal", + "@serviceTidal": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceQobuz": "Qobuz", + "@serviceQobuz": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceAmazon": "Amazon", + "@serviceAmazon": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceDeezer": "Deezer", + "@serviceDeezer": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceSpotify": "Spotify", + "@serviceSpotify": { + "description": "Service name - DO NOT TRANSLATE" + }, + "appearanceAmoledDark": "AMOLED Oscuro", + "@appearanceAmoledDark": { + "description": "Theme option - pure black" + }, + "appearanceAmoledDarkSubtitle": "Fondo negro puro", + "@appearanceAmoledDarkSubtitle": { + "description": "Subtitle for AMOLED dark" + }, + "appearanceChooseAccentColor": "Elegir color principal", + "@appearanceChooseAccentColor": { + "description": "Color picker dialog title" + }, + "appearanceChooseTheme": "Modo de tema", + "@appearanceChooseTheme": { + "description": "Theme picker dialog title" + }, + "queueTitle": "Descargas en proceso", + "@queueTitle": { + "description": "Queue screen title" + }, + "queueClearAll": "Eliminar todo", + "@queueClearAll": { + "description": "Button - clear all queue items" + }, + "queueClearAllMessage": "¿Estás seguro de que quieres borrar todas las descargas?", + "@queueClearAllMessage": { + "description": "Clear queue confirmation" + }, + "queueExportFailed": "Exportar", + "@queueExportFailed": { + "description": "Button - export failed downloads to TXT" + }, + "queueExportFailedSuccess": "Descarga fallida exportada al archivo TXT", + "@queueExportFailedSuccess": { + "description": "Success message after exporting failed downloads" + }, + "queueExportFailedClear": "Limpieza Fallida", + "@queueExportFailedClear": { + "description": "Action to clear failed downloads after export" + }, + "queueExportFailedError": "Error al exportar descargas", + "@queueExportFailedError": { + "description": "Error message when export fails" + }, + "settingsAutoExportFailed": "Autoexportar descargas fallidas", + "@settingsAutoExportFailed": { + "description": "Setting toggle for auto-export" + }, + "settingsAutoExportFailedSubtitle": "Guardar descargas fallidas en el archivo TXT automáticamente", + "@settingsAutoExportFailedSubtitle": { + "description": "Subtitle for auto-export setting" + }, + "settingsDownloadNetwork": "Red de descarga", + "@settingsDownloadNetwork": { + "description": "Setting for network type preference" + }, + "settingsDownloadNetworkAny": "WiFi + Datos móviles", + "@settingsDownloadNetworkAny": { + "description": "Network option - use any connection" + }, + "settingsDownloadNetworkWifiOnly": "Iniciar solo por Wifi", + "@settingsDownloadNetworkWifiOnly": { + "description": "Network option - only use WiFi" + }, + "settingsDownloadNetworkSubtitle": "Elegir qué red usar para descargas. Cuando se establece en WiFi solamente, las descargas se detendrán en los datos móviles.", + "@settingsDownloadNetworkSubtitle": { + "description": "Subtitle explaining network preference" + }, + "queueEmpty": "No hay descargas en cola", + "@queueEmpty": { + "description": "Empty queue state title" + }, + "queueEmptySubtitle": "Añadir pistas desde la pantalla de inicio", + "@queueEmptySubtitle": { + "description": "Empty queue state subtitle" + }, + "queueClearCompleted": "Limpiar tareas finalizadas", + "@queueClearCompleted": { + "description": "Button - clear finished downloads" + }, + "queueDownloadFailed": "Descarga fallida", + "@queueDownloadFailed": { + "description": "Error dialog title" + }, + "queueTrackLabel": "Pista:", + "@queueTrackLabel": { + "description": "Label in error dialog" + }, + "queueArtistLabel": "Artista:", + "@queueArtistLabel": { + "description": "Label in error dialog" + }, + "queueErrorLabel": "Error:", + "@queueErrorLabel": { + "description": "Label in error dialog" + }, + "queueUnknownError": "Error desconocido", + "@queueUnknownError": { + "description": "Fallback error message" + }, + "albumFolderArtistAlbum": "Artista / Álbum", + "@albumFolderArtistAlbum": { + "description": "Album folder option" + }, + "albumFolderArtistAlbumSubtitle": "Álbumes/Nombre del Artista/Nombre del Álbum/", + "@albumFolderArtistAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistYearAlbum": "Artista / [Año] Álbum", + "@albumFolderArtistYearAlbum": { + "description": "Album folder option with year" + }, + "albumFolderArtistYearAlbumSubtitle": "Álbumes/Nombre del Artista /[2005] Nombre del Álbum/", + "@albumFolderArtistYearAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderAlbumOnly": "Sólo álbum", + "@albumFolderAlbumOnly": { + "description": "Album folder option" + }, + "albumFolderAlbumOnlySubtitle": "Álbumes/Nombre del Álbum/", + "@albumFolderAlbumOnlySubtitle": { + "description": "Folder structure example" + }, + "albumFolderYearAlbum": "Álbum [Año]", + "@albumFolderYearAlbum": { + "description": "Album folder option with year" + }, + "albumFolderYearAlbumSubtitle": "Álbumes/[2005] Nombre del Álbum/", + "@albumFolderYearAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistAlbumSingles": "Artista / Álbum + Pistas", + "@albumFolderArtistAlbumSingles": { + "description": "Album folder option with singles inside artist" + }, + "albumFolderArtistAlbumSinglesSubtitle": "Artista/Álbum/ y Artista/pistas/", + "@albumFolderArtistAlbumSinglesSubtitle": { + "description": "Folder structure example" + }, + "downloadedAlbumDeleteSelected": "Borrar Seleccionados", + "@downloadedAlbumDeleteSelected": { + "description": "Button - delete selected tracks" + }, + "downloadedAlbumDeleteMessage": "¿Eliminar {count} {count, plural, one {}=1{pista} other{pistas}} del historial?\n\nEsto también eliminará los archivos del almacenamiento.", + "@downloadedAlbumDeleteMessage": { + "description": "Delete confirmation with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumTracksHeader": "Pistas", + "@downloadedAlbumTracksHeader": { + "description": "Section header for tracks" + }, + "downloadedAlbumDownloadedCount": "{count} descargado", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumSelectedCount": "{count} seleccionado", + "@downloadedAlbumSelectedCount": { + "description": "Selection count indicator", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumAllSelected": "Todas las pistas seleccionadas", + "@downloadedAlbumAllSelected": { + "description": "Status - all items selected" + }, + "downloadedAlbumTapToSelect": "Toca las pistas para seleccionar", + "@downloadedAlbumTapToSelect": { + "description": "Selection hint" + }, + "downloadedAlbumDeleteCount": "¡Eliminar {count} {count, plural, one {}=1{pista} other{pistas}}", + "@downloadedAlbumDeleteCount": { + "description": "Delete button text with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumSelectToDelete": "Seleccionar pistas a eliminar", + "@downloadedAlbumSelectToDelete": { + "description": "Placeholder when nothing selected" + }, + "downloadedAlbumDiscHeader": "Disco {discNumber}", + "@downloadedAlbumDiscHeader": { + "description": "Header for disc separator in multi-disc albums", + "placeholders": { + "discNumber": { + "type": "int", + "example": "1" + } + } + }, + "utilityFunctions": "Funciones de utilidad", + "@utilityFunctions": { + "description": "Extension capability - utility functions" + }, + "recentTypeArtist": "Artista", + "@recentTypeArtist": { + "description": "Recent access item type - artist" + }, + "recentTypeAlbum": "Álbum", + "@recentTypeAlbum": { + "description": "Recent access item type - album" + }, + "recentTypeSong": "Canción", + "@recentTypeSong": { + "description": "Recent access item type - song/track" + }, + "recentTypePlaylist": "Lista de reproducción", + "@recentTypePlaylist": { + "description": "Recent access item type - playlist" + }, + "recentEmpty": "No recent items yet", + "@recentEmpty": { + "description": "Empty state text for recent access list" + }, + "recentShowAllDownloads": "Show All Downloads", + "@recentShowAllDownloads": { + "description": "Button label to unhide hidden downloads in recent access" + }, + "recentPlaylistInfo": "Lista de reproducción: {name}", + "@recentPlaylistInfo": { + "description": "Snackbar message when tapping playlist in recent access", + "placeholders": { + "name": { + "type": "String", + "description": "Playlist name" + } + } + }, + "errorGeneric": "Error: {message}", + "@errorGeneric": { + "description": "Generic error message format", + "placeholders": { + "message": { + "type": "String", + "description": "Error message" + } + } + }, + "discographyDownload": "Descargar Discografía", + "@discographyDownload": { + "description": "Button - download artist discography" + }, + "discographyDownloadAll": "Descargar Todo", + "@discographyDownloadAll": { + "description": "Option - download entire discography" + }, + "discographyDownloadAllSubtitle": "{count} pistas de {albumCount} lanzamientos", + "@discographyDownloadAllSubtitle": { + "description": "Subtitle showing total tracks and albums", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographyAlbumsOnly": "Sólo álbumes", + "@discographyAlbumsOnly": { + "description": "Option - download only albums" + }, + "discographyAlbumsOnlySubtitle": "{count} pistas de {albumCount} álbumes", + "@discographyAlbumsOnlySubtitle": { + "description": "Subtitle showing album tracks count", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographySinglesOnly": "Solo sencillos & EPs ", + "@discographySinglesOnly": { + "description": "Option - download only singles" + }, + "discographySinglesOnlySubtitle": "{count} tracks from {albumCount} singles", + "@discographySinglesOnlySubtitle": { + "description": "Subtitle showing singles tracks count", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographySelectAlbums": "Select Albums...", + "@discographySelectAlbums": { + "description": "Option - manually select albums to download" + }, + "discographySelectAlbumsSubtitle": "Choose specific albums or singles", + "@discographySelectAlbumsSubtitle": { + "description": "Subtitle for select albums option" + }, + "discographyFetchingTracks": "Fetching tracks...", + "@discographyFetchingTracks": { + "description": "Progress - fetching album tracks" + }, + "discographyFetchingAlbum": "Fetching {current} of {total}...", + "@discographyFetchingAlbum": { + "description": "Progress - fetching specific album", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "discographySelectedCount": "{count} selected", + "@discographySelectedCount": { + "description": "Selection count badge", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discographyDownloadSelected": "Download Selected", + "@discographyDownloadSelected": { + "description": "Button - download selected albums" + }, + "discographyAddedToQueue": "Added {count} tracks to queue", + "@discographyAddedToQueue": { + "description": "Snackbar - tracks added from discography", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discographySkippedDownloaded": "{added} added, {skipped} already downloaded", + "@discographySkippedDownloaded": { + "description": "Snackbar - with skipped tracks count", + "placeholders": { + "added": { + "type": "int" + }, + "skipped": { + "type": "int" + } + } + }, + "discographyNoAlbums": "No albums available", + "@discographyNoAlbums": { + "description": "Error - no albums found for artist" + }, + "discographyFailedToFetch": "Failed to fetch some albums", + "@discographyFailedToFetch": { + "description": "Error - some albums failed to load" + }, + "sectionStorageAccess": "Storage Access", + "@sectionStorageAccess": { + "description": "Section header for storage access settings" + }, + "allFilesAccess": "All Files Access", + "@allFilesAccess": { + "description": "Toggle for MANAGE_EXTERNAL_STORAGE permission" + }, + "allFilesAccessEnabledSubtitle": "Can write to any folder", + "@allFilesAccessEnabledSubtitle": { + "description": "Subtitle when all files access is enabled" + }, + "allFilesAccessDisabledSubtitle": "Limited to media folders only", + "@allFilesAccessDisabledSubtitle": { + "description": "Subtitle when all files access is disabled" + }, + "allFilesAccessDescription": "Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.", + "@allFilesAccessDescription": { + "description": "Description explaining when to enable all files access" + }, + "allFilesAccessDeniedMessage": "Permission was denied. Please enable 'All files access' manually in system settings.", + "@allFilesAccessDeniedMessage": { + "description": "Message when permission is permanently denied" + }, + "allFilesAccessDisabledMessage": "All Files Access disabled. The app will use limited storage access.", + "@allFilesAccessDisabledMessage": { + "description": "Snackbar message when user disables all files access" + }, + "settingsLocalLibrary": "Local Library", + "@settingsLocalLibrary": { + "description": "Settings menu item - local library" + }, + "settingsLocalLibrarySubtitle": "Scan music & detect duplicates", + "@settingsLocalLibrarySubtitle": { + "description": "Subtitle for local library settings" + }, + "settingsCache": "Storage & Cache", + "@settingsCache": { + "description": "Settings menu item - cache management" + }, + "settingsCacheSubtitle": "View size and clear cached data", + "@settingsCacheSubtitle": { + "description": "Subtitle for cache management menu" + }, + "libraryTitle": "Local Library", + "@libraryTitle": { + "description": "Library settings page title" + }, + "libraryStatus": "Library Status", + "@libraryStatus": { + "description": "Section header for library status" + }, + "libraryScanSettings": "Scan Settings", + "@libraryScanSettings": { + "description": "Section header for scan settings" + }, + "libraryEnableLocalLibrary": "Enable Local Library", + "@libraryEnableLocalLibrary": { + "description": "Toggle to enable library scanning" + }, + "libraryEnableLocalLibrarySubtitle": "Scan and track your existing music", + "@libraryEnableLocalLibrarySubtitle": { + "description": "Subtitle for enable toggle" + }, + "libraryFolder": "Library Folder", + "@libraryFolder": { + "description": "Folder selection setting" + }, + "libraryFolderHint": "Tap to select folder", + "@libraryFolderHint": { + "description": "Placeholder when no folder selected" + }, + "libraryShowDuplicateIndicator": "Show Duplicate Indicator", + "@libraryShowDuplicateIndicator": { + "description": "Toggle for duplicate indicator in search" + }, + "libraryShowDuplicateIndicatorSubtitle": "Show when searching for existing tracks", + "@libraryShowDuplicateIndicatorSubtitle": { + "description": "Subtitle for duplicate indicator toggle" + }, + "libraryActions": "Actions", + "@libraryActions": { + "description": "Section header for library actions" + }, + "libraryScan": "Scan Library", + "@libraryScan": { + "description": "Button to start library scan" + }, + "libraryScanSubtitle": "Scan for audio files", + "@libraryScanSubtitle": { + "description": "Subtitle for scan button" + }, + "libraryScanSelectFolderFirst": "Select a folder first", + "@libraryScanSelectFolderFirst": { + "description": "Message when trying to scan without folder" + }, + "libraryCleanupMissingFiles": "Cleanup Missing Files", + "@libraryCleanupMissingFiles": { + "description": "Button to remove entries for missing files" + }, + "libraryCleanupMissingFilesSubtitle": "Remove entries for files that no longer exist", + "@libraryCleanupMissingFilesSubtitle": { + "description": "Subtitle for cleanup button" + }, + "libraryClear": "Clear Library", + "@libraryClear": { + "description": "Button to clear all library entries" + }, + "libraryClearSubtitle": "Remove all scanned tracks", + "@libraryClearSubtitle": { + "description": "Subtitle for clear button" + }, + "libraryClearConfirmTitle": "Clear Library", + "@libraryClearConfirmTitle": { + "description": "Dialog title for clear confirmation" + }, + "libraryClearConfirmMessage": "This will remove all scanned tracks from your library. Your actual music files will not be deleted.", + "@libraryClearConfirmMessage": { + "description": "Dialog message for clear confirmation" + }, + "libraryAbout": "About Local Library", + "@libraryAbout": { + "description": "Section header for about info" + }, + "libraryAboutDescription": "Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.", + "@libraryAboutDescription": { + "description": "Description of local library feature" + }, + "libraryTracksCount": "{count} tracks", + "@libraryTracksCount": { + "description": "Track count in library", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "libraryLastScanned": "Last scanned: {time}", + "@libraryLastScanned": { + "description": "Last scan time display", + "placeholders": { + "time": { + "type": "String" + } + } + }, + "libraryLastScannedNever": "Never", + "@libraryLastScannedNever": { + "description": "Shown when library has never been scanned" + }, + "libraryScanning": "Scanning...", + "@libraryScanning": { + "description": "Status during scan" + }, + "libraryScanProgress": "{progress}% of {total} files", + "@libraryScanProgress": { + "description": "Scan progress display", + "placeholders": { + "progress": { + "type": "String" + }, + "total": { + "type": "int" + } + } + }, + "libraryInLibrary": "In Library", + "@libraryInLibrary": { + "description": "Badge shown on tracks that exist in local library" + }, + "libraryRemovedMissingFiles": "Removed {count} missing files from library", + "@libraryRemovedMissingFiles": { + "description": "Snackbar after cleanup", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "libraryCleared": "Library cleared", + "@libraryCleared": { + "description": "Snackbar after clearing library" + }, + "libraryStorageAccessRequired": "Storage Access Required", + "@libraryStorageAccessRequired": { + "description": "Dialog title for storage permission" + }, + "libraryStorageAccessMessage": "SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.", + "@libraryStorageAccessMessage": { + "description": "Dialog message for storage permission" + }, + "libraryFolderNotExist": "Selected folder does not exist", + "@libraryFolderNotExist": { + "description": "Error when folder doesn't exist" + }, + "librarySourceDownloaded": "Downloaded", + "@librarySourceDownloaded": { + "description": "Badge for tracks downloaded via SpotiFLAC" + }, + "librarySourceLocal": "Local", + "@librarySourceLocal": { + "description": "Badge for tracks from local library scan" + }, + "libraryFilterAll": "All", + "@libraryFilterAll": { + "description": "Filter chip - show all library items" + }, + "libraryFilterDownloaded": "Downloaded", + "@libraryFilterDownloaded": { + "description": "Filter chip - show only downloaded items" + }, + "libraryFilterLocal": "Local", + "@libraryFilterLocal": { + "description": "Filter chip - show only local library items" + }, + "libraryFilterTitle": "Filters", + "@libraryFilterTitle": { + "description": "Filter bottom sheet title" + }, + "libraryFilterReset": "Reset", + "@libraryFilterReset": { + "description": "Reset all filters button" + }, + "libraryFilterApply": "Apply", + "@libraryFilterApply": { + "description": "Apply filters button" + }, + "libraryFilterSource": "Source", + "@libraryFilterSource": { + "description": "Filter section - source type" + }, + "libraryFilterQuality": "Quality", + "@libraryFilterQuality": { + "description": "Filter section - audio quality" + }, + "libraryFilterQualityHiRes": "Hi-Res (24bit)", + "@libraryFilterQualityHiRes": { + "description": "Filter option - high resolution audio" + }, + "libraryFilterQualityCD": "CD (16bit)", + "@libraryFilterQualityCD": { + "description": "Filter option - CD quality audio" + }, + "libraryFilterQualityLossy": "Lossy", + "@libraryFilterQualityLossy": { + "description": "Filter option - lossy compressed audio" + }, + "libraryFilterFormat": "Format", + "@libraryFilterFormat": { + "description": "Filter section - file format" + }, + "libraryFilterDate": "Date Added", + "@libraryFilterDate": { + "description": "Filter section - date range" + }, + "libraryFilterDateToday": "Today", + "@libraryFilterDateToday": { + "description": "Filter option - today only" + }, + "libraryFilterDateWeek": "This Week", + "@libraryFilterDateWeek": { + "description": "Filter option - this week" + }, + "libraryFilterDateMonth": "This Month", + "@libraryFilterDateMonth": { + "description": "Filter option - this month" + }, + "libraryFilterDateYear": "This Year", + "@libraryFilterDateYear": { + "description": "Filter option - this year" + }, + "libraryFilterSort": "Sort", + "@libraryFilterSort": { + "description": "Filter section - sort order" + }, + "libraryFilterSortLatest": "Latest", + "@libraryFilterSortLatest": { + "description": "Sort option - newest first" + }, + "libraryFilterSortOldest": "Oldest", + "@libraryFilterSortOldest": { + "description": "Sort option - oldest first" + }, + "libraryFilterActive": "{count} filter(s) active", + "@libraryFilterActive": { + "description": "Badge showing number of active filters", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "timeJustNow": "Just now", + "@timeJustNow": { + "description": "Relative time - less than a minute ago" + }, + "timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}", + "@timeMinutesAgo": { + "description": "Relative time - minutes ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}", + "@timeHoursAgo": { + "description": "Relative time - hours ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "storageSwitchTitle": "Switch Storage Mode", + "@storageSwitchTitle": { + "description": "Dialog title when switching storage mode" + }, + "storageSwitchToSafTitle": "Switch to SAF Storage?", + "@storageSwitchToSafTitle": { + "description": "Dialog title when switching to SAF" + }, + "storageSwitchToAppTitle": "Switch to App Storage?", + "@storageSwitchToAppTitle": { + "description": "Dialog title when switching to app storage" + }, + "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", + "@storageSwitchToSafMessage": { + "description": "Explanation when switching to SAF" + }, + "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", + "@storageSwitchToAppMessage": { + "description": "Explanation when switching to app storage" + }, + "storageSwitchExistingDownloads": "Existing Downloads", + "@storageSwitchExistingDownloads": { + "description": "Section header for existing downloads info" + }, + "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", + "@storageSwitchExistingDownloadsInfo": { + "description": "Info about existing downloads count", + "placeholders": { + "count": { + "type": "int" + }, + "mode": { + "type": "String" + } + } + }, + "storageSwitchNewDownloads": "New Downloads", + "@storageSwitchNewDownloads": { + "description": "Section header for new downloads info" + }, + "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", + "@storageSwitchNewDownloadsLocation": { + "description": "Shows where new downloads will go", + "placeholders": { + "location": { + "type": "String" + } + } + }, + "storageSwitchContinue": "Continue", + "@storageSwitchContinue": { + "description": "Button to proceed with storage switch" + }, + "storageSwitchSelectFolder": "Select SAF Folder", + "@storageSwitchSelectFolder": { + "description": "Button to select SAF folder" + }, + "storageAppStorage": "App Storage", + "@storageAppStorage": { + "description": "Label for app storage mode" + }, + "storageSafStorage": "SAF Storage", + "@storageSafStorage": { + "description": "Label for SAF storage mode" + }, + "storageModeBadge": "Storage: {mode}", + "@storageModeBadge": { + "description": "Badge showing storage mode for a track", + "placeholders": { + "mode": { + "type": "String" + } + } + }, + "storageStatsTitle": "Storage Statistics", + "@storageStatsTitle": { + "description": "Section title for storage stats" + }, + "storageStatsAppCount": "{count} tracks in App Storage", + "@storageStatsAppCount": { + "description": "Count of tracks in app storage", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "storageStatsSafCount": "{count} tracks in SAF Storage", + "@storageStatsSafCount": { + "description": "Count of tracks in SAF storage", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "storageModeInfo": "Your files are stored in multiple locations", + "@storageModeInfo": { + "description": "Info when user has files in both storage modes" + }, + "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", + "@tutorialWelcomeTitle": { + "description": "Tutorial welcome page title" + }, + "tutorialWelcomeDesc": "Let's learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.", + "@tutorialWelcomeDesc": { + "description": "Tutorial welcome page description" + }, + "tutorialWelcomeTip1": "Download music from Spotify, Deezer, or paste any supported URL", + "@tutorialWelcomeTip1": { + "description": "Tutorial welcome tip 1" + }, + "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "@tutorialWelcomeTip2": { + "description": "Tutorial welcome tip 2" + }, + "tutorialWelcomeTip3": "Automatic metadata, cover art, and lyrics embedding", + "@tutorialWelcomeTip3": { + "description": "Tutorial welcome tip 3" + }, + "tutorialSearchTitle": "Finding Music", + "@tutorialSearchTitle": { + "description": "Tutorial search page title" + }, + "tutorialSearchDesc": "There are two easy ways to find music you want to download.", + "@tutorialSearchDesc": { + "description": "Tutorial search page description" + }, + "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", + "@tutorialSearchTip1": { + "description": "Tutorial search tip 1" + }, + "tutorialSearchTip2": "Or type the song name, artist, or album to search", + "@tutorialSearchTip2": { + "description": "Tutorial search tip 2" + }, + "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", + "@tutorialSearchTip3": { + "description": "Tutorial search tip 3" + }, + "tutorialDownloadTitle": "Downloading Music", + "@tutorialDownloadTitle": { + "description": "Tutorial download page title" + }, + "tutorialDownloadDesc": "Downloading music is simple and fast. Here's how it works.", + "@tutorialDownloadDesc": { + "description": "Tutorial download page description" + }, + "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", + "@tutorialDownloadTip1": { + "description": "Tutorial download tip 1" + }, + "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", + "@tutorialDownloadTip2": { + "description": "Tutorial download tip 2" + }, + "tutorialDownloadTip3": "Download entire albums or playlists with one tap", + "@tutorialDownloadTip3": { + "description": "Tutorial download tip 3" + }, + "tutorialLibraryTitle": "Your Library", + "@tutorialLibraryTitle": { + "description": "Tutorial library page title" + }, + "tutorialLibraryDesc": "All your downloaded music is organized in the Library tab.", + "@tutorialLibraryDesc": { + "description": "Tutorial library page description" + }, + "tutorialLibraryTip1": "View download progress and queue in the Library tab", + "@tutorialLibraryTip1": { + "description": "Tutorial library tip 1" + }, + "tutorialLibraryTip2": "Tap any track to play it with your music player", + "@tutorialLibraryTip2": { + "description": "Tutorial library tip 2" + }, + "tutorialLibraryTip3": "Switch between list and grid view for better browsing", + "@tutorialLibraryTip3": { + "description": "Tutorial library tip 3" + }, + "tutorialExtensionsTitle": "Extensions", + "@tutorialExtensionsTitle": { + "description": "Tutorial extensions page title" + }, + "tutorialExtensionsDesc": "Extend the app's capabilities with community extensions.", + "@tutorialExtensionsDesc": { + "description": "Tutorial extensions page description" + }, + "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "@tutorialExtensionsTip1": { + "description": "Tutorial extensions tip 1" + }, + "tutorialExtensionsTip2": "Add new download providers or search sources", + "@tutorialExtensionsTip2": { + "description": "Tutorial extensions tip 2" + }, + "tutorialExtensionsTip3": "Get lyrics, enhanced metadata, and more features", + "@tutorialExtensionsTip3": { + "description": "Tutorial extensions tip 3" + }, + "tutorialSettingsTitle": "Customize Your Experience", + "@tutorialSettingsTitle": { + "description": "Tutorial settings page title" + }, + "tutorialSettingsDesc": "Personalize the app in Settings to match your preferences.", + "@tutorialSettingsDesc": { + "description": "Tutorial settings page description" + }, + "tutorialSettingsTip1": "Change download location and folder organization", + "@tutorialSettingsTip1": { + "description": "Tutorial settings tip 1" + }, + "tutorialSettingsTip2": "Set default audio quality and format preferences", + "@tutorialSettingsTip2": { + "description": "Tutorial settings tip 2" + }, + "tutorialSettingsTip3": "Customize app theme and appearance", + "@tutorialSettingsTip3": { + "description": "Tutorial settings tip 3" + }, + "tutorialReadyMessage": "You're all set! Start downloading your favorite music now.", + "@tutorialReadyMessage": { + "description": "Tutorial completion message" + }, + "tutorialExample": "EXAMPLE", + "@tutorialExample": { + "description": "Example label in tutorial" + }, + "libraryForceFullScan": "Force Full Scan", + "@libraryForceFullScan": { + "description": "Button to force a complete rescan of library" + }, + "libraryForceFullScanSubtitle": "Rescan all files, ignoring cache", + "@libraryForceFullScanSubtitle": { + "description": "Subtitle for force full scan button" + }, + "cleanupOrphanedDownloads": "Cleanup Orphaned Downloads", + "@cleanupOrphanedDownloads": { + "description": "Button to remove history entries for deleted files" + }, + "cleanupOrphanedDownloadsSubtitle": "Remove history entries for files that no longer exist", + "@cleanupOrphanedDownloadsSubtitle": { + "description": "Subtitle for orphaned cleanup button" + }, + "cleanupOrphanedDownloadsResult": "Removed {count} orphaned entries from history", + "@cleanupOrphanedDownloadsResult": { + "description": "Snackbar after orphan cleanup", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cleanupOrphanedDownloadsNone": "No orphaned entries found", + "@cleanupOrphanedDownloadsNone": { + "description": "Snackbar when no orphans found" + }, + "cacheTitle": "Storage & Cache", + "@cacheTitle": { + "description": "Cache management page title" + }, + "cacheSummaryTitle": "Cache overview", + "@cacheSummaryTitle": { + "description": "Heading for cache summary card" + }, + "cacheSummarySubtitle": "Clearing cache will not remove downloaded music files.", + "@cacheSummarySubtitle": { + "description": "Helper text for cache summary card" + }, + "cacheEstimatedTotal": "Estimated cache usage: {size}", + "@cacheEstimatedTotal": { + "description": "Total cache size shown in summary", + "placeholders": { + "size": { + "type": "String" + } + } + }, + "cacheSectionStorage": "Cached Data", + "@cacheSectionStorage": { + "description": "Section header for cache entries" + }, + "cacheSectionMaintenance": "Maintenance", + "@cacheSectionMaintenance": { + "description": "Section header for cleanup actions" + }, + "cacheAppDirectory": "App cache directory", + "@cacheAppDirectory": { + "description": "Cache item title for app cache directory" + }, + "cacheAppDirectoryDesc": "HTTP responses, WebView data, and other temporary app data.", + "@cacheAppDirectoryDesc": { + "description": "Description of what app cache directory contains" + }, + "cacheTempDirectory": "Temporary directory", + "@cacheTempDirectory": { + "description": "Cache item title for temporary files directory" + }, + "cacheTempDirectoryDesc": "Temporary files from downloads and audio conversion.", + "@cacheTempDirectoryDesc": { + "description": "Description of what temporary directory contains" + }, + "cacheCoverImage": "Cover image cache", + "@cacheCoverImage": { + "description": "Cache item title for persistent cover images" + }, + "cacheCoverImageDesc": "Downloaded album and track cover art. Will re-download when viewed.", + "@cacheCoverImageDesc": { + "description": "Description of what cover image cache contains" + }, + "cacheLibraryCover": "Library cover cache", + "@cacheLibraryCover": { + "description": "Cache item title for local library cover art images" + }, + "cacheLibraryCoverDesc": "Cover art extracted from local music files. Will re-extract on next scan.", + "@cacheLibraryCoverDesc": { + "description": "Description of what library cover cache contains" + }, + "cacheExploreFeed": "Explore feed cache", + "@cacheExploreFeed": { + "description": "Cache item title for explore home feed cache" + }, + "cacheExploreFeedDesc": "Explore tab content (new releases, trending). Will refresh on next visit.", + "@cacheExploreFeedDesc": { + "description": "Description of what explore feed cache contains" + }, + "cacheTrackLookup": "Track lookup cache", + "@cacheTrackLookup": { + "description": "Cache item title for track ID lookup cache" + }, + "cacheTrackLookupDesc": "Spotify/Deezer track ID lookups. Clearing may slow next few searches.", + "@cacheTrackLookupDesc": { + "description": "Description of what track lookup cache contains" + }, + "cacheCleanupUnusedDesc": "Remove orphaned download history and library entries for missing files.", + "@cacheCleanupUnusedDesc": { + "description": "Description of what cleanup unused data does" + }, + "cacheNoData": "No cached data", + "@cacheNoData": { + "description": "Label when cache category has no data" + }, + "cacheSizeWithFiles": "{size} in {count} files", + "@cacheSizeWithFiles": { + "description": "Cache size and file count", + "placeholders": { + "size": { + "type": "String" + }, + "count": { + "type": "int" + } + } + }, + "cacheSizeOnly": "{size}", + "@cacheSizeOnly": { + "description": "Cache size only", + "placeholders": { + "size": { + "type": "String" + } + } + }, + "cacheEntries": "{count} entries", + "@cacheEntries": { + "description": "Track cache entry count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cacheClearSuccess": "Cleared: {target}", + "@cacheClearSuccess": { + "description": "Snackbar after clearing selected cache", + "placeholders": { + "target": { + "type": "String" + } + } + }, + "cacheClearConfirmTitle": "Clear cache?", + "@cacheClearConfirmTitle": { + "description": "Dialog title before clearing one cache category" + }, + "cacheClearConfirmMessage": "This will clear cached data for {target}. Downloaded music files will not be deleted.", + "@cacheClearConfirmMessage": { + "description": "Dialog message before clearing selected cache", + "placeholders": { + "target": { + "type": "String" + } + } + }, + "cacheClearAllConfirmTitle": "Clear all cache?", + "@cacheClearAllConfirmTitle": { + "description": "Dialog title before clearing all caches" + }, + "cacheClearAllConfirmMessage": "This will clear all cache categories on this page. Downloaded music files will not be deleted.", + "@cacheClearAllConfirmMessage": { + "description": "Dialog message before clearing all caches" + }, + "cacheClearAll": "Clear all cache", + "@cacheClearAll": { + "description": "Button label to clear all caches" + }, + "cacheCleanupUnused": "Cleanup unused data", + "@cacheCleanupUnused": { + "description": "Action title for cleaning unused entries" + }, + "cacheCleanupUnusedSubtitle": "Remove orphaned download history and missing library entries", + "@cacheCleanupUnusedSubtitle": { + "description": "Subtitle for cleanup unused data action" + }, + "cacheCleanupResult": "Cleanup completed: {downloadCount} orphaned downloads, {libraryCount} missing library entries", + "@cacheCleanupResult": { + "description": "Snackbar after unused data cleanup", + "placeholders": { + "downloadCount": { + "type": "int" + }, + "libraryCount": { + "type": "int" + } + } + }, + "cacheRefreshStats": "Refresh stats", + "@cacheRefreshStats": { + "description": "Button label to refresh cache statistics" + }, + "trackSaveCoverArt": "Save Cover Art", + "@trackSaveCoverArt": { + "description": "Menu action - save album cover art as file" + }, + "trackSaveCoverArtSubtitle": "Save album art as .jpg file", + "@trackSaveCoverArtSubtitle": { + "description": "Subtitle for save cover art action" + }, + "trackSaveLyrics": "Save Lyrics (.lrc)", + "@trackSaveLyrics": { + "description": "Menu action - save lyrics as .lrc file" + }, + "trackSaveLyricsSubtitle": "Fetch and save lyrics as .lrc file", + "@trackSaveLyricsSubtitle": { + "description": "Subtitle for save lyrics action" + }, + "trackSaveLyricsProgress": "Saving lyrics...", + "@trackSaveLyricsProgress": { + "description": "Snackbar while saving lyrics to file" + }, + "trackReEnrich": "Re-enrich Metadata", + "@trackReEnrich": { + "description": "Menu action - re-embed metadata into audio file" + }, + "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", + "@trackReEnrichSubtitle": { + "description": "Subtitle for re-enrich metadata action" + }, + "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", + "@trackReEnrichOnlineSubtitle": { + "description": "Subtitle for re-enrich metadata action for local items" + }, + "trackEditMetadata": "Edit Metadata", + "@trackEditMetadata": { + "description": "Menu action - edit embedded metadata" + }, + "trackCoverSaved": "Cover art saved to {fileName}", + "@trackCoverSaved": { + "description": "Snackbar after cover art saved", + "placeholders": { + "fileName": { + "type": "String" + } + } + }, + "trackCoverNoSource": "No cover art source available", + "@trackCoverNoSource": { + "description": "Snackbar when no cover art URL or embedded cover" + }, + "trackLyricsSaved": "Lyrics saved to {fileName}", + "@trackLyricsSaved": { + "description": "Snackbar after lyrics saved", + "placeholders": { + "fileName": { + "type": "String" + } + } + }, + "trackReEnrichProgress": "Re-enriching metadata...", + "@trackReEnrichProgress": { + "description": "Snackbar while re-enriching metadata" + }, + "trackReEnrichSearching": "Searching metadata online...", + "@trackReEnrichSearching": { + "description": "Snackbar while searching metadata from internet for local items" + }, + "trackReEnrichSuccess": "Metadata re-enriched successfully", + "@trackReEnrichSuccess": { + "description": "Snackbar after successful re-enrichment" + }, + "trackReEnrichFfmpegFailed": "FFmpeg metadata embed failed", + "@trackReEnrichFfmpegFailed": { + "description": "Snackbar when FFmpeg embed fails for MP3/Opus" + }, + "trackSaveFailed": "Failed: {error}", + "@trackSaveFailed": { + "description": "Snackbar when save operation fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "trackConvertFormat": "Convert Format", + "@trackConvertFormat": { + "description": "Menu item - convert audio format" + }, + "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "@trackConvertFormatSubtitle": { + "description": "Subtitle for convert format menu item" + }, + "trackConvertTitle": "Convert Audio", + "@trackConvertTitle": { + "description": "Title of convert bottom sheet" + }, + "trackConvertTargetFormat": "Target Format", + "@trackConvertTargetFormat": { + "description": "Label for format selection" + }, + "trackConvertBitrate": "Bitrate", + "@trackConvertBitrate": { + "description": "Label for bitrate selection" + }, + "trackConvertConfirmTitle": "Confirm Conversion", + "@trackConvertConfirmTitle": { + "description": "Confirmation dialog title" + }, + "trackConvertConfirmMessage": "Convert from {sourceFormat} to {targetFormat} at {bitrate}?\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessage": { + "description": "Confirmation dialog message", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "trackConvertConverting": "Converting audio...", + "@trackConvertConverting": { + "description": "Snackbar while converting" + }, + "trackConvertSuccess": "Converted to {format} successfully", + "@trackConvertSuccess": { + "description": "Snackbar after successful conversion", + "placeholders": { + "format": { + "type": "String" + } + } + }, + "trackConvertFailed": "Conversion failed", + "@trackConvertFailed": { + "description": "Snackbar when conversion fails" + } +} \ No newline at end of file From 73f4a91fa135ca15c80237d1c5f2528cd713e168 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:18 +0700 Subject: [PATCH 007/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 46 ++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 5491e1c1..6917c56c 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -1532,6 +1532,14 @@ "@filenameHint": { "description": "Default filename format hint" }, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, "folderOrganization": "Ordnerstruktur", "@folderOrganization": { "description": "Setting title - folder structure" @@ -2147,7 +2155,7 @@ "@trackDeleteConfirmTitle": { "description": "Delete confirmation title" }, - "trackDeleteConfirmMessage": "This will permanently delete the downloaded file and remove it from your history.", + "trackDeleteConfirmMessage": "Dies wird die heruntergeladene Datei dauerhaft löschen und sie aus deinem Verlauf entfernen.", "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, @@ -2374,7 +2382,7 @@ "@extensionsInstallButton": { "description": "Button to install extension from file" }, - "extensionsInfoTip": "Extensions can add new metadata and download providers. Only install extensions from trusted sources.", + "extensionsInfoTip": "Erweiterungen können neue Metadaten und Download-Anbieter hinzufügen. Installiere nur Erweiterungen von vertrauenswürdigen Quellen.", "@extensionsInfoTip": { "description": "Security warning about extensions" }, @@ -2530,7 +2538,7 @@ "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" }, - "downloadUsePrimaryArtistOnlyEnabled": "Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)", + "downloadUsePrimaryArtistOnlyEnabled": "Vorgestellte Künstler aus dem Ordnernamen entfernt (z.B. Justin Bieber, Quavo → Justin Bieber)", "@downloadUsePrimaryArtistOnlyEnabled": { "description": "Subtitle when primary artist only is enabled" }, @@ -2758,7 +2766,7 @@ "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" }, - "downloadedAlbumDeleteMessage": "Delete {count} {count, plural, =1{track} other{tracks}} from this album?\n\nThis will also delete the files from storage.", + "downloadedAlbumDeleteMessage": "{count} {count, plural, one {}=1{Titel} other{Titel}} aus diesem Album löschen?\n\nDadurch werden auch die Dateien aus dem Speicher gelöscht.", "@downloadedAlbumDeleteMessage": { "description": "Delete confirmation with count", "placeholders": { @@ -3006,7 +3014,7 @@ "@allFilesAccessDescription": { "description": "Description explaining when to enable all files access" }, - "allFilesAccessDeniedMessage": "Permission was denied. Please enable 'All files access' manually in system settings.", + "allFilesAccessDeniedMessage": "Zugriff verweigert. Bitte aktiviere \"Zugriff auf alle Dateien\" manuell in den Systemeinstellungen.", "@allFilesAccessDeniedMessage": { "description": "Message when permission is permanently denied" }, @@ -3102,7 +3110,7 @@ "@libraryClearConfirmTitle": { "description": "Dialog title for clear confirmation" }, - "libraryClearConfirmMessage": "This will remove all scanned tracks from your library. Your actual music files will not be deleted.", + "libraryClearConfirmMessage": "Dadurch werden alle gescannten Titel aus Ihrer Bibliothek entfernt. Ihre eigentlichen Musikdateien werden nicht gelöscht.", "@libraryClearConfirmMessage": { "description": "Dialog message for clear confirmation" }, @@ -3110,7 +3118,7 @@ "@libraryAbout": { "description": "Section header for about info" }, - "libraryAboutDescription": "Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.", + "libraryAboutDescription": "Durchsucht deine bestehende Musiksammlung, um Duplikate beim Herunterladen zu erkennen. Unterstützt die Formate FLAC, M4A, MP3, Opus und OGG. Metadaten werden, sofern verfügbar, aus den Dateitags gelesen.", "@libraryAboutDescription": { "description": "Description of local library feature" }, @@ -3173,7 +3181,7 @@ "@libraryStorageAccessRequired": { "description": "Dialog title for storage permission" }, - "libraryStorageAccessMessage": "SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.", + "libraryStorageAccessMessage": "SpotiFLAC benötigt Speicherzugriff, um deine Musikbibliothek zu scannen. Bitte erteile die Berechtigung in den Einstellungen.", "@libraryStorageAccessMessage": { "description": "Dialog message for storage permission" }, @@ -3404,7 +3412,7 @@ "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" }, - "tutorialWelcomeDesc": "Let's learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.", + "tutorialWelcomeDesc": "Lass uns lernen, wie du deine Lieblingsmusik in verlustfreier Qualität herunterlädst. Dieses schnelle Tutorial zeigt dir die Grundlagen.", "@tutorialWelcomeDesc": { "description": "Tutorial welcome page description" }, @@ -3504,7 +3512,7 @@ "@tutorialSettingsTitle": { "description": "Tutorial settings page title" }, - "tutorialSettingsDesc": "Personalize the app in Settings to match your preferences.", + "tutorialSettingsDesc": "Personalisiere die App in den Einstellungen nach deiner Präferenz.", "@tutorialSettingsDesc": { "description": "Tutorial settings page description" }, @@ -3520,7 +3528,7 @@ "@tutorialSettingsTip3": { "description": "Tutorial settings tip 3" }, - "tutorialReadyMessage": "You're all set! Start downloading your favorite music now.", + "tutorialReadyMessage": "Das ist alles! Lade jetzt deine Lieblingsmusik herunter.", "@tutorialReadyMessage": { "description": "Tutorial completion message" }, @@ -3614,7 +3622,7 @@ "@cacheLibraryCover": { "description": "Cache item title for local library cover art images" }, - "cacheLibraryCoverDesc": "Cover art extracted from local music files. Will re-extract on next scan.", + "cacheLibraryCoverDesc": "Cover aus lokalen Musikdateien extrahiert. Wird beim nächsten Scannen neu extrahiert.", "@cacheLibraryCoverDesc": { "description": "Description of what library cover cache contains" }, @@ -3622,7 +3630,7 @@ "@cacheExploreFeed": { "description": "Cache item title for explore home feed cache" }, - "cacheExploreFeedDesc": "Explore tab content (new releases, trending). Will refresh on next visit.", + "cacheExploreFeedDesc": "Startseiten-Inhalt (neue Releases, Trends). Wird bei einem Neustart aktualisiert.", "@cacheExploreFeedDesc": { "description": "Description of what explore feed cache contains" }, @@ -3630,11 +3638,11 @@ "@cacheTrackLookup": { "description": "Cache item title for track ID lookup cache" }, - "cacheTrackLookupDesc": "Spotify/Deezer track ID lookups. Clearing may slow next few searches.", + "cacheTrackLookupDesc": "Spotify/Deezer Track-ID-Lookups. Das Löschen kann die nächsten Suchergebnisse verlangsamen.", "@cacheTrackLookupDesc": { "description": "Description of what track lookup cache contains" }, - "cacheCleanupUnusedDesc": "Remove orphaned download history and library entries for missing files.", + "cacheCleanupUnusedDesc": "Verwaisten Downloadverlauf und Bibliothekseinträge für fehlende Dateien entfernen.", "@cacheCleanupUnusedDesc": { "description": "Description of what cleanup unused data does" }, @@ -3685,7 +3693,7 @@ "@cacheClearConfirmTitle": { "description": "Dialog title before clearing one cache category" }, - "cacheClearConfirmMessage": "This will clear cached data for {target}. Downloaded music files will not be deleted.", + "cacheClearConfirmMessage": "Dies löscht zwischengespeicherte Daten in {target}. Die Musikdateien werden nicht gelöscht.", "@cacheClearConfirmMessage": { "description": "Dialog message before clearing selected cache", "placeholders": { @@ -3698,7 +3706,7 @@ "@cacheClearAllConfirmTitle": { "description": "Dialog title before clearing all caches" }, - "cacheClearAllConfirmMessage": "This will clear all cache categories on this page. Downloaded music files will not be deleted.", + "cacheClearAllConfirmMessage": "Dadurch werden alle Cache-Kategorien auf dieser Seite gelöscht. Heruntergeladene Musikdateien werden nicht gelöscht.", "@cacheClearAllConfirmMessage": { "description": "Dialog message before clearing all caches" }, @@ -3714,7 +3722,7 @@ "@cacheCleanupUnusedSubtitle": { "description": "Subtitle for cleanup unused data action" }, - "cacheCleanupResult": "Cleanup completed: {downloadCount} orphaned downloads, {libraryCount} missing library entries", + "cacheCleanupResult": "Bereinigung: {downloadCount} verwaiste Downloads, {libraryCount} fehlende Bibliothekseinträge", "@cacheCleanupResult": { "description": "Snackbar after unused data cleanup", "placeholders": { @@ -3837,7 +3845,7 @@ "@trackConvertConfirmTitle": { "description": "Confirmation dialog title" }, - "trackConvertConfirmMessage": "Convert from {sourceFormat} to {targetFormat} at {bitrate}?\n\nThe original file will be deleted after conversion.", + "trackConvertConfirmMessage": "Konvertieren von {sourceFormat} in {targetFormat} bei {bitrate}?\n\nDie Originaldatei wird nach der Konvertierung gelöscht.", "@trackConvertConfirmMessage": { "description": "Confirmation dialog message", "placeholders": { From bbd32d40a65dd55428678ccfe57fc102a8138f74 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:19 +0700 Subject: [PATCH 008/104] New translations app_en.arb (Japanese) --- lib/l10n/arb/app_ja.arb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/l10n/arb/app_ja.arb b/lib/l10n/arb/app_ja.arb index fd193b3c..4746d216 100644 --- a/lib/l10n/arb/app_ja.arb +++ b/lib/l10n/arb/app_ja.arb @@ -1532,6 +1532,14 @@ "@filenameHint": { "description": "Default filename format hint" }, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, "folderOrganization": "フォルダ構成", "@folderOrganization": { "description": "Setting title - folder structure" From 4c365032ffbdaa3e11300df68ea13479879ac971 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:20 +0700 Subject: [PATCH 009/104] New translations app_en.arb (Korean) --- lib/l10n/arb/app_ko.arb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/l10n/arb/app_ko.arb b/lib/l10n/arb/app_ko.arb index 9d982e1a..173ef898 100644 --- a/lib/l10n/arb/app_ko.arb +++ b/lib/l10n/arb/app_ko.arb @@ -1532,6 +1532,14 @@ "@filenameHint": { "description": "Default filename format hint" }, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, "folderOrganization": "Folder Organization", "@folderOrganization": { "description": "Setting title - folder structure" From 9a7096c30177f6017600be3461a783d186ffeaaa Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:21 +0700 Subject: [PATCH 010/104] New translations app_en.arb (Dutch) --- lib/l10n/arb/app_nl.arb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/l10n/arb/app_nl.arb b/lib/l10n/arb/app_nl.arb index 8bae60f8..3ef8bfff 100644 --- a/lib/l10n/arb/app_nl.arb +++ b/lib/l10n/arb/app_nl.arb @@ -1532,6 +1532,14 @@ "@filenameHint": { "description": "Default filename format hint" }, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, "folderOrganization": "Folder Organization", "@folderOrganization": { "description": "Setting title - folder structure" From 33e20845f13906e85abf89c66a63a0c33bac9111 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:22 +0700 Subject: [PATCH 011/104] New translations app_en.arb (Portuguese) --- lib/l10n/arb/app_pt-PT.arb | 3880 ++++++++++++++++++++++++++++++++++++ 1 file changed, 3880 insertions(+) create mode 100644 lib/l10n/arb/app_pt-PT.arb diff --git a/lib/l10n/arb/app_pt-PT.arb b/lib/l10n/arb/app_pt-PT.arb new file mode 100644 index 00000000..359e8ed3 --- /dev/null +++ b/lib/l10n/arb/app_pt-PT.arb @@ -0,0 +1,3880 @@ +{ + "@@locale": "pt-PT", + "@@last_modified": "2026-01-16", + "appName": "SpotiFLAC", + "@appName": { + "description": "App name - DO NOT TRANSLATE" + }, + "appDescription": "Baixe faixas do Spotify em qualidade sem perdas de Tidal, Qobuz e Amazon Music.", + "@appDescription": { + "description": "App description shown in about page" + }, + "navHome": "Início", + "@navHome": { + "description": "Bottom navigation - Home tab" + }, + "navLibrary": "Library", + "@navLibrary": { + "description": "Bottom navigation - Library tab" + }, + "navHistory": "Histórico", + "@navHistory": { + "description": "Bottom navigation - History tab (legacy)" + }, + "navSettings": "Configurações", + "@navSettings": { + "description": "Bottom navigation - Settings tab" + }, + "navStore": "Loja", + "@navStore": { + "description": "Bottom navigation - Extension store tab" + }, + "homeTitle": "Início", + "@homeTitle": { + "description": "Home screen title" + }, + "homeSearchHint": "Pesquise ou cole a URL do Spotify...", + "@homeSearchHint": { + "description": "Placeholder text in search box" + }, + "homeSearchHintExtension": "Pesquisar com {extensionName}...", + "@homeSearchHintExtension": { + "description": "Placeholder when extension search is active", + "placeholders": { + "extensionName": { + "type": "String", + "description": "Name of the active extension" + } + } + }, + "homeSubtitle": "Cole um link do Spotify ou procure por nome", + "@homeSubtitle": { + "description": "Subtitle shown below search box" + }, + "homeSupports": "Suporte: Faixas, Álbuns, Playlists, URLs de Artista", + "@homeSupports": { + "description": "Info text about supported URL types" + }, + "homeRecent": "Recentes", + "@homeRecent": { + "description": "Section header for recent searches" + }, + "historyTitle": "Histórico", + "@historyTitle": { + "description": "History screen title" + }, + "historyDownloading": "Baixando ({count})", + "@historyDownloading": { + "description": "Tab showing active downloads count", + "placeholders": { + "count": { + "type": "int", + "description": "Number of active downloads" + } + } + }, + "historyDownloaded": "Baixados", + "@historyDownloaded": { + "description": "Tab showing completed downloads" + }, + "historyFilterAll": "Tudo", + "@historyFilterAll": { + "description": "Filter chip - show all items" + }, + "historyFilterAlbums": "Álbuns", + "@historyFilterAlbums": { + "description": "Filter chip - show albums only" + }, + "historyFilterSingles": "Singles", + "@historyFilterSingles": { + "description": "Filter chip - show singles only" + }, + "historyTracksCount": "{count, plural, one {}=1{1 faixa} other{{count} faixas}}", + "@historyTracksCount": { + "description": "Track count with plural form", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "historyAlbumsCount": "{count, plural, one {}=1{1 álbum} other{{count} álbuns}}", + "@historyAlbumsCount": { + "description": "Album count with plural form", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "historyNoDownloads": "Nenhum histórico de downloads", + "@historyNoDownloads": { + "description": "Empty state title" + }, + "historyNoDownloadsSubtitle": "As faixas baixadas aparecerão aqui", + "@historyNoDownloadsSubtitle": { + "description": "Empty state subtitle" + }, + "historyNoAlbums": "Sem álbuns baixados", + "@historyNoAlbums": { + "description": "Empty state when filtering albums" + }, + "historyNoAlbumsSubtitle": "Baixe várias faixas de um álbum para vê-las aqui", + "@historyNoAlbumsSubtitle": { + "description": "Empty state subtitle for albums filter" + }, + "historyNoSingles": "Sem singles baixados", + "@historyNoSingles": { + "description": "Empty state when filtering singles" + }, + "historyNoSinglesSubtitle": "Os downloads de faixa individuais aparecerão aqui", + "@historyNoSinglesSubtitle": { + "description": "Empty state subtitle for singles filter" + }, + "historySearchHint": "Pesquisar histórico...", + "@historySearchHint": { + "description": "Search bar placeholder in history" + }, + "settingsTitle": "Configurações", + "@settingsTitle": { + "description": "Settings screen title" + }, + "settingsDownload": "Download", + "@settingsDownload": { + "description": "Settings section - download options" + }, + "settingsAppearance": "Aparência", + "@settingsAppearance": { + "description": "Settings section - visual customization" + }, + "settingsOptions": "Opções", + "@settingsOptions": { + "description": "Settings section - app options" + }, + "settingsExtensions": "Extensões", + "@settingsExtensions": { + "description": "Settings section - extension management" + }, + "settingsAbout": "Sobre", + "@settingsAbout": { + "description": "Settings section - app info" + }, + "downloadTitle": "Download", + "@downloadTitle": { + "description": "Download settings page title" + }, + "downloadLocation": "Local dos Downloads", + "@downloadLocation": { + "description": "Setting for download folder" + }, + "downloadLocationSubtitle": "Escolha onde salvar os arquivos", + "@downloadLocationSubtitle": { + "description": "Subtitle for download location" + }, + "downloadLocationDefault": "Local padrão", + "@downloadLocationDefault": { + "description": "Shown when using default folder" + }, + "downloadDefaultService": "Serviço Padrão", + "@downloadDefaultService": { + "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" + }, + "downloadDefaultServiceSubtitle": "Serviço usado para downloads", + "@downloadDefaultServiceSubtitle": { + "description": "Subtitle for default service" + }, + "downloadDefaultQuality": "Qualidade Predefinida", + "@downloadDefaultQuality": { + "description": "Setting for audio quality" + }, + "downloadAskQuality": "Perguntar qualidade antes de baixar", + "@downloadAskQuality": { + "description": "Toggle to show quality picker" + }, + "downloadAskQualitySubtitle": "Mostrar seletor de qualidade para cada download", + "@downloadAskQualitySubtitle": { + "description": "Subtitle for ask quality toggle" + }, + "downloadFilenameFormat": "Formato do Nome do Arquivo", + "@downloadFilenameFormat": { + "description": "Setting for output filename pattern" + }, + "downloadFolderOrganization": "Organização de Pastas", + "@downloadFolderOrganization": { + "description": "Setting for folder structure" + }, + "downloadSeparateSingles": "Separar Singles", + "@downloadSeparateSingles": { + "description": "Toggle to separate single tracks" + }, + "downloadSeparateSinglesSubtitle": "Colocar singles numa pasta separada", + "@downloadSeparateSinglesSubtitle": { + "description": "Subtitle for separate singles toggle" + }, + "qualityBest": "Melhor Disponível", + "@qualityBest": { + "description": "Audio quality option - highest available" + }, + "qualityFlac": "FLAC", + "@qualityFlac": { + "description": "Audio quality option - FLAC lossless" + }, + "quality320": "320 kbps", + "@quality320": { + "description": "Audio quality option - 320kbps MP3" + }, + "quality128": "128 kbps", + "@quality128": { + "description": "Audio quality option - 128kbps MP3" + }, + "appearanceTitle": "Aparência", + "@appearanceTitle": { + "description": "Appearance settings page title" + }, + "appearanceTheme": "Tema", + "@appearanceTheme": { + "description": "Theme mode setting" + }, + "appearanceThemeSystem": "Sistema", + "@appearanceThemeSystem": { + "description": "Follow system theme" + }, + "appearanceThemeLight": "Claro", + "@appearanceThemeLight": { + "description": "Light theme" + }, + "appearanceThemeDark": "Escuro", + "@appearanceThemeDark": { + "description": "Dark theme" + }, + "appearanceDynamicColor": "Cores Dinâmicas", + "@appearanceDynamicColor": { + "description": "Material You dynamic colors" + }, + "appearanceDynamicColorSubtitle": "Usar cores do seu papel de parede", + "@appearanceDynamicColorSubtitle": { + "description": "Subtitle for dynamic color" + }, + "appearanceAccentColor": "Cor de Destaque", + "@appearanceAccentColor": { + "description": "Custom accent color picker" + }, + "appearanceHistoryView": "Visualização do Histórico", + "@appearanceHistoryView": { + "description": "Layout style for history" + }, + "appearanceHistoryViewList": "Lista", + "@appearanceHistoryViewList": { + "description": "List layout option" + }, + "appearanceHistoryViewGrid": "Grade", + "@appearanceHistoryViewGrid": { + "description": "Grid layout option" + }, + "optionsTitle": "Opções", + "@optionsTitle": { + "description": "Options settings page title" + }, + "optionsSearchSource": "Origem da Pesquisa", + "@optionsSearchSource": { + "description": "Section for search provider settings" + }, + "optionsPrimaryProvider": "Provedor Primário", + "@optionsPrimaryProvider": { + "description": "Main search provider setting" + }, + "optionsPrimaryProviderSubtitle": "Serviço usado ao pesquisar por nome da faixa.", + "@optionsPrimaryProviderSubtitle": { + "description": "Subtitle for primary provider" + }, + "optionsUsingExtension": "Usando a extensão: {extensionName}", + "@optionsUsingExtension": { + "description": "Shows active extension name", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "optionsSwitchBack": "Toque no Deezer ou Spotify para alternar de volta da extensão", + "@optionsSwitchBack": { + "description": "Hint to switch back to built-in providers" + }, + "optionsAutoFallback": "Fallback Automático", + "@optionsAutoFallback": { + "description": "Auto-retry with other services" + }, + "optionsAutoFallbackSubtitle": "Tentar outros serviços se o download falhar", + "@optionsAutoFallbackSubtitle": { + "description": "Subtitle for auto fallback" + }, + "optionsUseExtensionProviders": "Usar Provedores de Extensão", + "@optionsUseExtensionProviders": { + "description": "Enable extension download providers" + }, + "optionsUseExtensionProvidersOn": "Extensões serão tentadas primeiro", + "@optionsUseExtensionProvidersOn": { + "description": "Status when extension providers enabled" + }, + "optionsUseExtensionProvidersOff": "Usando apenas provedores integrados", + "@optionsUseExtensionProvidersOff": { + "description": "Status when extension providers disabled" + }, + "optionsEmbedLyrics": "Incorporar Letras", + "@optionsEmbedLyrics": { + "description": "Embed lyrics in audio files" + }, + "optionsEmbedLyricsSubtitle": "Incorporar letras sincronizadas aos arquivos FLAC", + "@optionsEmbedLyricsSubtitle": { + "description": "Subtitle for embed lyrics" + }, + "optionsMaxQualityCover": "Capa de Qualidade Máxima", + "@optionsMaxQualityCover": { + "description": "Download highest quality album art" + }, + "optionsMaxQualityCoverSubtitle": "Baixar capa do álbum com a mais alta resolução", + "@optionsMaxQualityCoverSubtitle": { + "description": "Subtitle for max quality cover" + }, + "optionsConcurrentDownloads": "Downloads Simultâneos", + "@optionsConcurrentDownloads": { + "description": "Number of parallel downloads" + }, + "optionsConcurrentSequential": "Sequencial (1 por vez)", + "@optionsConcurrentSequential": { + "description": "Download one at a time" + }, + "optionsConcurrentParallel": "{count} downloads paralelos", + "@optionsConcurrentParallel": { + "description": "Multiple parallel downloads", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "optionsConcurrentWarning": "Downloads simultâneos podem causar um limite da taxa (ratelimit)", + "@optionsConcurrentWarning": { + "description": "Warning about rate limits" + }, + "optionsExtensionStore": "Loja de Extensões", + "@optionsExtensionStore": { + "description": "Show/hide store tab" + }, + "optionsExtensionStoreSubtitle": "Mostrar aba da Loja na navegação", + "@optionsExtensionStoreSubtitle": { + "description": "Subtitle for extension store toggle" + }, + "optionsCheckUpdates": "Procurar Atualizações", + "@optionsCheckUpdates": { + "description": "Auto update check toggle" + }, + "optionsCheckUpdatesSubtitle": "Notificar quando uma nova versão estiver disponível", + "@optionsCheckUpdatesSubtitle": { + "description": "Subtitle for update check" + }, + "optionsUpdateChannel": "Canal de Atualização", + "@optionsUpdateChannel": { + "description": "Stable vs preview releases" + }, + "optionsUpdateChannelStable": "Somente versões estáveis", + "@optionsUpdateChannelStable": { + "description": "Only stable updates" + }, + "optionsUpdateChannelPreview": "Obter versões de prévia", + "@optionsUpdateChannelPreview": { + "description": "Include beta/preview updates" + }, + "optionsUpdateChannelWarning": "A prévia pode conter erros ou recursos incompletos", + "@optionsUpdateChannelWarning": { + "description": "Warning about preview channel" + }, + "optionsClearHistory": "Limpar Histórico de Download", + "@optionsClearHistory": { + "description": "Delete all download history" + }, + "optionsClearHistorySubtitle": "Remover todas as faixas baixadas do histórico", + "@optionsClearHistorySubtitle": { + "description": "Subtitle for clear history" + }, + "optionsDetailedLogging": "Registro detalhado", + "@optionsDetailedLogging": { + "description": "Enable verbose logs for debugging" + }, + "optionsDetailedLoggingOn": "Registros detalhados estão sendo gravados", + "@optionsDetailedLoggingOn": { + "description": "Status when logging enabled" + }, + "optionsDetailedLoggingOff": "Habilitar para relatórios de erros", + "@optionsDetailedLoggingOff": { + "description": "Status when logging disabled" + }, + "optionsSpotifyCredentials": "Credenciais do Spotify", + "@optionsSpotifyCredentials": { + "description": "Spotify API credentials setting" + }, + "optionsSpotifyCredentialsConfigured": "Client ID: {clientId}...", + "@optionsSpotifyCredentialsConfigured": { + "description": "Shows configured client ID preview", + "placeholders": { + "clientId": { + "type": "String" + } + } + }, + "optionsSpotifyCredentialsRequired": "Obrigatório - toque para configurar", + "@optionsSpotifyCredentialsRequired": { + "description": "Prompt to set up credentials" + }, + "optionsSpotifyWarning": "O Spotify requer as suas próprias credenciais de API. Consiga gratuitamente em developer.spotify.com", + "@optionsSpotifyWarning": { + "description": "Info about Spotify API requirement" + }, + "optionsSpotifyDeprecationWarning": "Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.", + "@optionsSpotifyDeprecationWarning": { + "description": "Warning about Spotify API deprecation" + }, + "extensionsTitle": "Extensões", + "@extensionsTitle": { + "description": "Extensions page title" + }, + "extensionsInstalled": "Extensões Instaladas", + "@extensionsInstalled": { + "description": "Section header for installed extensions" + }, + "extensionsNone": "Nenhuma extensão instalada", + "@extensionsNone": { + "description": "Empty state title" + }, + "extensionsNoneSubtitle": "Instalar extensões a partir da aba Loja", + "@extensionsNoneSubtitle": { + "description": "Empty state subtitle" + }, + "extensionsEnabled": "Habilitado", + "@extensionsEnabled": { + "description": "Extension status - active" + }, + "extensionsDisabled": "Desabilitado", + "@extensionsDisabled": { + "description": "Extension status - inactive" + }, + "extensionsVersion": "Versão {version}", + "@extensionsVersion": { + "description": "Extension version display", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "extensionsAuthor": "por {author}", + "@extensionsAuthor": { + "description": "Extension author credit", + "placeholders": { + "author": { + "type": "String" + } + } + }, + "extensionsUninstall": "Desinstalar", + "@extensionsUninstall": { + "description": "Uninstall extension button" + }, + "extensionsSetAsSearch": "Definir como Provedor de Pesquisa", + "@extensionsSetAsSearch": { + "description": "Use extension for search" + }, + "storeTitle": "Loja de Extensões", + "@storeTitle": { + "description": "Store screen title" + }, + "storeSearch": "Pesquisar extensões...", + "@storeSearch": { + "description": "Store search placeholder" + }, + "storeInstall": "Instalar", + "@storeInstall": { + "description": "Install extension button" + }, + "storeInstalled": "Instalado", + "@storeInstalled": { + "description": "Already installed badge" + }, + "storeUpdate": "Atualizar", + "@storeUpdate": { + "description": "Update available button" + }, + "aboutTitle": "Sobre", + "@aboutTitle": { + "description": "About page title" + }, + "aboutContributors": "Colaboradores", + "@aboutContributors": { + "description": "Section for contributors" + }, + "aboutMobileDeveloper": "Desenvolvedor da versão móvel", + "@aboutMobileDeveloper": { + "description": "Role description for mobile dev" + }, + "aboutOriginalCreator": "Criador do SpotiFLAC original", + "@aboutOriginalCreator": { + "description": "Role description for original creator" + }, + "aboutLogoArtist": "O artista talentoso que criou o nosso lindo logotipo do aplicativo!", + "@aboutLogoArtist": { + "description": "Role description for logo artist" + }, + "aboutTranslators": "Tradutores", + "@aboutTranslators": { + "description": "Section for translators" + }, + "aboutSpecialThanks": "Agradecimentos Especiais", + "@aboutSpecialThanks": { + "description": "Section for special thanks" + }, + "aboutLinks": "Links", + "@aboutLinks": { + "description": "Section for external links" + }, + "aboutMobileSource": "Código-fonte do app móvel", + "@aboutMobileSource": { + "description": "Link to mobile GitHub repo" + }, + "aboutPCSource": "Código-fonte do app desktop", + "@aboutPCSource": { + "description": "Link to PC GitHub repo" + }, + "aboutReportIssue": "Reportar um problema", + "@aboutReportIssue": { + "description": "Link to report bugs" + }, + "aboutReportIssueSubtitle": "Reporte qualquer problema que encontrar", + "@aboutReportIssueSubtitle": { + "description": "Subtitle for report issue" + }, + "aboutFeatureRequest": "Solicitação de recurso", + "@aboutFeatureRequest": { + "description": "Link to suggest features" + }, + "aboutFeatureRequestSubtitle": "Sugira novos recursos para o aplicativo", + "@aboutFeatureRequestSubtitle": { + "description": "Subtitle for feature request" + }, + "aboutTelegramChannel": "Canal do Telegram", + "@aboutTelegramChannel": { + "description": "Link to Telegram channel" + }, + "aboutTelegramChannelSubtitle": "Anúncios e atualizações", + "@aboutTelegramChannelSubtitle": { + "description": "Subtitle for Telegram channel" + }, + "aboutTelegramChat": "Comunidade do Telegram", + "@aboutTelegramChat": { + "description": "Link to Telegram chat group" + }, + "aboutTelegramChatSubtitle": "Converse com outros usuários", + "@aboutTelegramChatSubtitle": { + "description": "Subtitle for Telegram chat" + }, + "aboutSocial": "Social", + "@aboutSocial": { + "description": "Section for social links" + }, + "aboutSupport": "Apoiar", + "@aboutSupport": { + "description": "Section for support/donation links" + }, + "aboutApp": "Aplicativo", + "@aboutApp": { + "description": "Section for app info" + }, + "aboutVersion": "Versão", + "@aboutVersion": { + "description": "Version info label" + }, + "aboutBinimumDesc": "O criador da API QQDL e HiFi. Sem esta API, os downloads Tidal não existiriam!", + "@aboutBinimumDesc": { + "description": "Credit description for binimum" + }, + "aboutSachinsenalDesc": "O criador original do projeto HiFi. A base da integração do Tidal!", + "@aboutSachinsenalDesc": { + "description": "Credit description for sachinsenal0x64" + }, + "aboutSjdonadoDesc": "Creator of I Don't Have Spotify (IDHS). The fallback link resolver that saves the day!", + "@aboutSjdonadoDesc": { + "description": "Credit description for sjdonado" + }, + "aboutDoubleDouble": "DoubleDouble", + "@aboutDoubleDouble": { + "description": "Name of Amazon API service - DO NOT TRANSLATE" + }, + "aboutDoubleDoubleDesc": "API incrível para downloads do Amazon Music. Obrigado por fazê-lo gratuitamente!", + "@aboutDoubleDoubleDesc": { + "description": "Credit for DoubleDouble API" + }, + "aboutDabMusic": "DAB Music", + "@aboutDabMusic": { + "description": "Name of Qobuz API service - DO NOT TRANSLATE" + }, + "aboutDabMusicDesc": "A melhor API de streaming do Qobuz. Downloads de alta resolução não seriam possíveis sem isso!", + "@aboutDabMusicDesc": { + "description": "Credit for DAB Music API" + }, + "aboutSpotiSaver": "SpotiSaver", + "@aboutSpotiSaver": { + "description": "Name of SpotiSaver API service - DO NOT TRANSLATE" + }, + "aboutSpotiSaverDesc": "Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!", + "@aboutSpotiSaverDesc": { + "description": "Credit for SpotiSaver API" + }, + "aboutAppDescription": "Baixe faixas do Spotify em qualidade sem perdas do Tidal, Qobuz e Amazon Music.", + "@aboutAppDescription": { + "description": "App description in header card" + }, + "albumTitle": "Álbum", + "@albumTitle": { + "description": "Album screen title" + }, + "albumTracks": "{count, plural, one {}=1{1 faixa} other{{count} faixas}}", + "@albumTracks": { + "description": "Album track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "albumDownloadAll": "Baixar Tudo", + "@albumDownloadAll": { + "description": "Button to download all tracks" + }, + "albumDownloadRemaining": "Downloads Restantes", + "@albumDownloadRemaining": { + "description": "Button to download remaining tracks" + }, + "playlistTitle": "Playlist", + "@playlistTitle": { + "description": "Playlist screen title" + }, + "artistTitle": "Artista", + "@artistTitle": { + "description": "Artist screen title" + }, + "artistAlbums": "Álbuns", + "@artistAlbums": { + "description": "Section header for artist albums" + }, + "artistSingles": "Singles e EPs", + "@artistSingles": { + "description": "Section header for singles/EPs" + }, + "artistCompilations": "Compilações", + "@artistCompilations": { + "description": "Section header for compilations" + }, + "artistReleases": "{count, plural, one {}=1{1 lançamento} other{{count} lançamentos}}", + "@artistReleases": { + "description": "Artist release count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "artistPopular": "Populares", + "@artistPopular": { + "description": "Section header for popular/top tracks" + }, + "artistMonthlyListeners": "{count} ouvintes mensais", + "@artistMonthlyListeners": { + "description": "Monthly listener count display", + "placeholders": { + "count": { + "type": "String", + "description": "Formatted listener count" + } + } + }, + "trackMetadataTitle": "Informações da Faixa", + "@trackMetadataTitle": { + "description": "Track metadata screen title" + }, + "trackMetadataArtist": "Artista", + "@trackMetadataArtist": { + "description": "Metadata field - artist name" + }, + "trackMetadataAlbum": "Álbum", + "@trackMetadataAlbum": { + "description": "Metadata field - album name" + }, + "trackMetadataDuration": "Duração", + "@trackMetadataDuration": { + "description": "Metadata field - track length" + }, + "trackMetadataQuality": "Qualidade", + "@trackMetadataQuality": { + "description": "Metadata field - audio quality" + }, + "trackMetadataPath": "Caminho do Arquivo", + "@trackMetadataPath": { + "description": "Metadata field - file location" + }, + "trackMetadataDownloadedAt": "Baixado", + "@trackMetadataDownloadedAt": { + "description": "Metadata field - download date" + }, + "trackMetadataService": "Serviço", + "@trackMetadataService": { + "description": "Metadata field - download service used" + }, + "trackMetadataPlay": "Reproduzir", + "@trackMetadataPlay": { + "description": "Action button - play track" + }, + "trackMetadataShare": "Compartilhar", + "@trackMetadataShare": { + "description": "Action button - share track" + }, + "trackMetadataDelete": "Apagar", + "@trackMetadataDelete": { + "description": "Action button - delete track" + }, + "trackMetadataRedownload": "Baixar Novamente", + "@trackMetadataRedownload": { + "description": "Action button - download again" + }, + "trackMetadataOpenFolder": "Abrir Pasta", + "@trackMetadataOpenFolder": { + "description": "Action button - open containing folder" + }, + "setupTitle": "Bem-vindo ao SpotiFLAC", + "@setupTitle": { + "description": "Setup wizard title" + }, + "setupSubtitle": "Vamos começar", + "@setupSubtitle": { + "description": "Setup wizard subtitle" + }, + "setupStoragePermission": "Permissão de Armazenamento", + "@setupStoragePermission": { + "description": "Storage permission step title" + }, + "setupStoragePermissionSubtitle": "Necessária para salvar arquivos baixados", + "@setupStoragePermissionSubtitle": { + "description": "Explanation for storage permission" + }, + "setupStoragePermissionGranted": "Permissão concedida", + "@setupStoragePermissionGranted": { + "description": "Status when permission granted" + }, + "setupStoragePermissionDenied": "Permissão negada", + "@setupStoragePermissionDenied": { + "description": "Status when permission denied" + }, + "setupGrantPermission": "Conceder Permissão", + "@setupGrantPermission": { + "description": "Button to request permission" + }, + "setupDownloadLocation": "Local do Download", + "@setupDownloadLocation": { + "description": "Download folder step title" + }, + "setupChooseFolder": "Selecionar Pasta", + "@setupChooseFolder": { + "description": "Button to pick folder" + }, + "setupContinue": "Continuar", + "@setupContinue": { + "description": "Continue to next step button" + }, + "setupSkip": "Ignorar por enquanto", + "@setupSkip": { + "description": "Skip current step button" + }, + "setupStorageAccessRequired": "Acesso ao Armazenamento Necessário", + "@setupStorageAccessRequired": { + "description": "Title when storage access needed" + }, + "setupStorageAccessMessage": "O SpotiFLAC precisa da permissão \"Acesso a todos os arquivos\" para salvar arquivos de música na sua pasta escolhida.", + "@setupStorageAccessMessage": { + "description": "Explanation for storage access" + }, + "setupStorageAccessMessageAndroid11": "O Android 11+ requer a permissão \"Acesso a Todos os Arquivos\" para salvar arquivos na pasta de download escolhida.", + "@setupStorageAccessMessageAndroid11": { + "description": "Android 11+ specific explanation" + }, + "setupOpenSettings": "Abrir Configurações", + "@setupOpenSettings": { + "description": "Button to open system settings" + }, + "setupPermissionDeniedMessage": "Permissão negada. Por favor, conceda todas as permissões para continuar.", + "@setupPermissionDeniedMessage": { + "description": "Error when permission denied" + }, + "setupPermissionRequired": "Permissão {permissionType} Necessária", + "@setupPermissionRequired": { + "description": "Generic permission required title", + "placeholders": { + "permissionType": { + "type": "String", + "description": "Type of permission (Storage/Notification)" + } + } + }, + "setupPermissionRequiredMessage": "A permissão {permissionType} é necessária para a melhor experiência. Você pode alterar isso mais tarde em Configurações.", + "@setupPermissionRequiredMessage": { + "description": "Generic permission required message", + "placeholders": { + "permissionType": { + "type": "String" + } + } + }, + "setupSelectDownloadFolder": "Escolher Pasta de Download", + "@setupSelectDownloadFolder": { + "description": "Folder selection step title" + }, + "setupUseDefaultFolder": "Usar Pasta Padrão?", + "@setupUseDefaultFolder": { + "description": "Dialog title for default folder" + }, + "setupNoFolderSelected": "Nenhuma pasta selecionada. Você gostaria de usar a pasta padrão de música?", + "@setupNoFolderSelected": { + "description": "Prompt when no folder selected" + }, + "setupUseDefault": "Usar Padrão", + "@setupUseDefault": { + "description": "Button to use default folder" + }, + "setupDownloadLocationTitle": "Local do Download", + "@setupDownloadLocationTitle": { + "description": "Download location dialog title" + }, + "setupDownloadLocationIosMessage": "No iOS, downloads são salvos na pasta Documentos do aplicativo. Você pode acessá-los através do app Arquivos.", + "@setupDownloadLocationIosMessage": { + "description": "iOS-specific folder info" + }, + "setupAppDocumentsFolder": "Pasta Documentos do App", + "@setupAppDocumentsFolder": { + "description": "iOS documents folder option" + }, + "setupAppDocumentsFolderSubtitle": "Recomendado - acessível através do aplicativo Arquivos", + "@setupAppDocumentsFolderSubtitle": { + "description": "Subtitle for documents folder" + }, + "setupChooseFromFiles": "Escolher dos Arquivos", + "@setupChooseFromFiles": { + "description": "iOS file picker option" + }, + "setupChooseFromFilesSubtitle": "Selecione o iCloud ou outro local", + "@setupChooseFromFilesSubtitle": { + "description": "Subtitle for file picker" + }, + "setupIosEmptyFolderWarning": "Limitação do iOS: Pastas vazias não podem ser selecionadas. Escolha uma pasta com pelo menos um arquivo.", + "@setupIosEmptyFolderWarning": { + "description": "iOS folder selection warning" + }, + "setupIcloudNotSupported": "iCloud Drive is not supported. Please use the app Documents folder.", + "@setupIcloudNotSupported": { + "description": "Error when user selects iCloud Drive on iOS" + }, + "setupDownloadInFlac": "Baixar faixas do Spotify em FLAC", + "@setupDownloadInFlac": { + "description": "App tagline in setup" + }, + "setupStepStorage": "Armazenamento", + "@setupStepStorage": { + "description": "Setup step indicator - storage" + }, + "setupStepNotification": "Notificação", + "@setupStepNotification": { + "description": "Setup step indicator - notification" + }, + "setupStepFolder": "Pasta", + "@setupStepFolder": { + "description": "Setup step indicator - folder" + }, + "setupStepSpotify": "Spotify", + "@setupStepSpotify": { + "description": "Setup step indicator - Spotify API" + }, + "setupStepPermission": "Permissão", + "@setupStepPermission": { + "description": "Setup step indicator - permission" + }, + "setupStorageGranted": "Permissão de Armazenamento Concedida!", + "@setupStorageGranted": { + "description": "Success message for storage permission" + }, + "setupStorageRequired": "Permissão de Armazenamento Necessária", + "@setupStorageRequired": { + "description": "Title when storage permission needed" + }, + "setupStorageDescription": "O SpotiFLAC precisa de permissão de armazenamento para salvar os seus arquivos de música baixados.", + "@setupStorageDescription": { + "description": "Explanation for storage permission" + }, + "setupNotificationGranted": "Permissão de Notificações Concedida!", + "@setupNotificationGranted": { + "description": "Success message for notification permission" + }, + "setupNotificationEnable": "Habilitar Notificações", + "@setupNotificationEnable": { + "description": "Button to enable notifications" + }, + "setupNotificationDescription": "Seja notificado quando os downloads completarem ou exigirem atenção.", + "@setupNotificationDescription": { + "description": "Explanation for notifications" + }, + "setupFolderSelected": "Pasta para Download Selecionada!", + "@setupFolderSelected": { + "description": "Success message for folder selection" + }, + "setupFolderChoose": "Escolher Pasta de Download", + "@setupFolderChoose": { + "description": "Button to choose folder" + }, + "setupFolderDescription": "Selecione uma pasta onde as suas músicas baixadas serão salvas.", + "@setupFolderDescription": { + "description": "Explanation for folder selection" + }, + "setupChangeFolder": "Alterar Pasta", + "@setupChangeFolder": { + "description": "Button to change selected folder" + }, + "setupSelectFolder": "Seleccionar Pasta", + "@setupSelectFolder": { + "description": "Button to select folder" + }, + "setupSpotifyApiOptional": "API do Spotify (opcional)", + "@setupSpotifyApiOptional": { + "description": "Spotify API step title" + }, + "setupSpotifyApiDescription": "Adicione as suas credenciais da API do Spotify para obter melhores resultados de busca e acesso a conteúdo exclusivo do Spotify.", + "@setupSpotifyApiDescription": { + "description": "Explanation for Spotify API" + }, + "setupUseSpotifyApi": "Usar API do Spotify", + "@setupUseSpotifyApi": { + "description": "Toggle to enable Spotify API" + }, + "setupEnterCredentialsBelow": "Insira as suas credenciais abaixo", + "@setupEnterCredentialsBelow": { + "description": "Prompt to enter credentials" + }, + "setupUsingDeezer": "Usando o Deezer (nenhuma conta necessária)", + "@setupUsingDeezer": { + "description": "Status when using Deezer" + }, + "setupEnterClientId": "Insira o Spotify Client ID", + "@setupEnterClientId": { + "description": "Placeholder for client ID field" + }, + "setupEnterClientSecret": "Insira o Spotify Client Secret", + "@setupEnterClientSecret": { + "description": "Placeholder for client secret field" + }, + "setupGetFreeCredentials": "Receba as suas credenciais de API gratuitas na Spotify Developer Dashboard.", + "@setupGetFreeCredentials": { + "description": "Info about getting Spotify credentials" + }, + "setupEnableNotifications": "Habilitar Notificações", + "@setupEnableNotifications": { + "description": "Button to enable notifications" + }, + "setupProceedToNextStep": "Você já pode prosseguir para o próximo passo.", + "@setupProceedToNextStep": { + "description": "Message after completing a step" + }, + "setupNotificationProgressDescription": "Você receberá notificações de progresso dos downloads.", + "@setupNotificationProgressDescription": { + "description": "Info about notification usage" + }, + "setupNotificationBackgroundDescription": "Seja notificado sobre o progresso e conclusão do download. Isso ajuda você a acompanhar os downloads quando o app estiver em segundo plano.", + "@setupNotificationBackgroundDescription": { + "description": "Detailed notification explanation" + }, + "setupSkipForNow": "Ignorar por enquanto", + "@setupSkipForNow": { + "description": "Skip button text" + }, + "setupBack": "Voltar", + "@setupBack": { + "description": "Back button text" + }, + "setupNext": "Próximo", + "@setupNext": { + "description": "Next button text" + }, + "setupGetStarted": "Começar", + "@setupGetStarted": { + "description": "Final setup button" + }, + "setupSkipAndStart": "Ignorar e Iniciar", + "@setupSkipAndStart": { + "description": "Skip setup and start app" + }, + "setupAllowAccessToManageFiles": "Por favor, habilite \"Permitir acesso para gerenciar todos os arquivos\" na próxima tela.", + "@setupAllowAccessToManageFiles": { + "description": "Instruction for file access permission" + }, + "setupGetCredentialsFromSpotify": "Obter credenciais do developer.spotify.com", + "@setupGetCredentialsFromSpotify": { + "description": "Link text for Spotify developer portal" + }, + "dialogCancel": "Cancelar", + "@dialogCancel": { + "description": "Dialog button - cancel action" + }, + "dialogOk": "OK", + "@dialogOk": { + "description": "Dialog button - confirm/acknowledge" + }, + "dialogSave": "Salvar", + "@dialogSave": { + "description": "Dialog button - save changes" + }, + "dialogDelete": "Apagar", + "@dialogDelete": { + "description": "Dialog button - delete item" + }, + "dialogRetry": "Tentar novamente", + "@dialogRetry": { + "description": "Dialog button - retry action" + }, + "dialogClose": "Fechar", + "@dialogClose": { + "description": "Dialog button - close dialog" + }, + "dialogYes": "Sim", + "@dialogYes": { + "description": "Dialog button - confirm yes" + }, + "dialogNo": "Não", + "@dialogNo": { + "description": "Dialog button - confirm no" + }, + "dialogClear": "Limpar", + "@dialogClear": { + "description": "Dialog button - clear items" + }, + "dialogConfirm": "Confirmar", + "@dialogConfirm": { + "description": "Dialog button - confirm action" + }, + "dialogDone": "Concluído", + "@dialogDone": { + "description": "Dialog button - action completed" + }, + "dialogImport": "Importar", + "@dialogImport": { + "description": "Dialog button - import data" + }, + "dialogDiscard": "Descartar", + "@dialogDiscard": { + "description": "Dialog button - discard changes" + }, + "dialogRemove": "Remover", + "@dialogRemove": { + "description": "Dialog button - remove item" + }, + "dialogUninstall": "Desinstalar", + "@dialogUninstall": { + "description": "Dialog button - uninstall extension" + }, + "dialogDiscardChanges": "Descartar Alterações?", + "@dialogDiscardChanges": { + "description": "Dialog title - unsaved changes warning" + }, + "dialogUnsavedChanges": "Você tem alterações não salvas. Deseja descartá-las?", + "@dialogUnsavedChanges": { + "description": "Dialog message - unsaved changes" + }, + "dialogDownloadFailed": "Download Falhou", + "@dialogDownloadFailed": { + "description": "Dialog title - download error" + }, + "dialogTrackLabel": "Faixa:", + "@dialogTrackLabel": { + "description": "Label for track name in error dialog" + }, + "dialogArtistLabel": "Artista:", + "@dialogArtistLabel": { + "description": "Label for artist name in error dialog" + }, + "dialogErrorLabel": "Erro:", + "@dialogErrorLabel": { + "description": "Label for error message" + }, + "dialogClearAll": "Limpar Tudo", + "@dialogClearAll": { + "description": "Dialog title - clear all items" + }, + "dialogClearAllDownloads": "Você tem certeza que deseja limpar todos os downloads?", + "@dialogClearAllDownloads": { + "description": "Dialog message - clear downloads confirmation" + }, + "dialogRemoveFromDevice": "Remover do dispositivo?", + "@dialogRemoveFromDevice": { + "description": "Dialog title - delete file confirmation" + }, + "dialogRemoveExtension": "Remover Extensão", + "@dialogRemoveExtension": { + "description": "Dialog title - uninstall extension" + }, + "dialogRemoveExtensionMessage": "Tem certeza de que deseja remover esta extensão? Isso não pode ser desfeito.", + "@dialogRemoveExtensionMessage": { + "description": "Dialog message - uninstall confirmation" + }, + "dialogUninstallExtension": "Desinstalar Extensão?", + "@dialogUninstallExtension": { + "description": "Dialog title - uninstall extension" + }, + "dialogUninstallExtensionMessage": "Tem certeza que deseja remover {extensionName}?", + "@dialogUninstallExtensionMessage": { + "description": "Dialog message - uninstall specific extension", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "dialogClearHistoryTitle": "Limpar Histórico", + "@dialogClearHistoryTitle": { + "description": "Dialog title - clear download history" + }, + "dialogClearHistoryMessage": "Tem certeza que deseja limpar todo o histórico de downloads? Isso não pode ser desfeito.", + "@dialogClearHistoryMessage": { + "description": "Dialog message - clear history confirmation" + }, + "dialogDeleteSelectedTitle": "Apagar Selecionados", + "@dialogDeleteSelectedTitle": { + "description": "Dialog title - delete selected items" + }, + "dialogDeleteSelectedMessage": "Apagar {count} {count, plural, one {}=1{faixa} other{faixas}} do histórico?\n\nIsso também apagará os arquivos do armazenamento.", + "@dialogDeleteSelectedMessage": { + "description": "Dialog message - delete selected tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogImportPlaylistTitle": "Importar Playlist", + "@dialogImportPlaylistTitle": { + "description": "Dialog title - import CSV playlist" + }, + "dialogImportPlaylistMessage": "{count} Faixas encontradas em CSV. Adicioná-las à lista de downloads?", + "csvImportTracks": "{count} faixas do CSV", + "@csvImportTracks": { + "description": "Label shown in quality picker for CSV import", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@dialogImportPlaylistMessage": { + "description": "Dialog message - import playlist confirmation", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedToQueue": "\"{trackName}\" adicionada à fila", + "@snackbarAddedToQueue": { + "description": "Snackbar - track added to download queue", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarAddedTracksToQueue": "{count} faixas adicionadas à fila", + "@snackbarAddedTracksToQueue": { + "description": "Snackbar - multiple tracks added to queue", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAlreadyDownloaded": "\"{trackName}\" já foi baixada", + "@snackbarAlreadyDownloaded": { + "description": "Snackbar - track already exists", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarAlreadyInLibrary": "\"{trackName}\" already exists in your library", + "@snackbarAlreadyInLibrary": { + "description": "Snackbar - track already exists in local library", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarHistoryCleared": "Histórico limpo", + "@snackbarHistoryCleared": { + "description": "Snackbar - history deleted" + }, + "snackbarCredentialsSaved": "Credenciais salvas", + "@snackbarCredentialsSaved": { + "description": "Snackbar - Spotify credentials saved" + }, + "snackbarCredentialsCleared": "Credenciais limpas", + "@snackbarCredentialsCleared": { + "description": "Snackbar - Spotify credentials removed" + }, + "snackbarDeletedTracks": "{count} {count, plural, one {}=1{faixa apagada} other{faixas apagadas}}", + "@snackbarDeletedTracks": { + "description": "Snackbar - tracks deleted", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarCannotOpenFile": "Não foi possível abrir o arquivo: {error}", + "@snackbarCannotOpenFile": { + "description": "Snackbar - file open error", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarFillAllFields": "Por favor, preencha todos os campos", + "@snackbarFillAllFields": { + "description": "Snackbar - validation error" + }, + "snackbarViewQueue": "Ver Fila", + "@snackbarViewQueue": { + "description": "Snackbar action - view download queue" + }, + "snackbarFailedToLoad": "Falha ao carregar: {error}", + "@snackbarFailedToLoad": { + "description": "Snackbar - loading error", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarUrlCopied": "URL do {platform} copiado para a área de transferência", + "@snackbarUrlCopied": { + "description": "Snackbar - URL copied", + "placeholders": { + "platform": { + "type": "String", + "description": "Platform name (Spotify/Deezer)" + } + } + }, + "snackbarFileNotFound": "Arquivo não encontrado", + "@snackbarFileNotFound": { + "description": "Snackbar - file doesn't exist" + }, + "snackbarSelectExtFile": "Por favor, selecione um arquivo .spotiflac-ext", + "@snackbarSelectExtFile": { + "description": "Snackbar - wrong file type selected" + }, + "snackbarProviderPrioritySaved": "Prioridade de provedor salva", + "@snackbarProviderPrioritySaved": { + "description": "Snackbar - provider order saved" + }, + "snackbarMetadataProviderSaved": "Prioridade do provedor de metadados salva", + "@snackbarMetadataProviderSaved": { + "description": "Snackbar - metadata provider order saved" + }, + "snackbarExtensionInstalled": "{extensionName} instalada.", + "@snackbarExtensionInstalled": { + "description": "Snackbar - extension installed successfully", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "snackbarExtensionUpdated": "{extensionName} atualizada.", + "@snackbarExtensionUpdated": { + "description": "Snackbar - extension updated successfully", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "snackbarFailedToInstall": "Falha ao instalar a extensão", + "@snackbarFailedToInstall": { + "description": "Snackbar - extension install error" + }, + "snackbarFailedToUpdate": "Falha ao atualizar a extensão", + "@snackbarFailedToUpdate": { + "description": "Snackbar - extension update error" + }, + "errorRateLimited": "Tráfico Limitado (Rate Limited)", + "@errorRateLimited": { + "description": "Error title - too many requests" + }, + "errorRateLimitedMessage": "Muitas solicitações. Por favor, aguarde um momento antes de pesquisar novamente.", + "@errorRateLimitedMessage": { + "description": "Error message - rate limit explanation" + }, + "errorFailedToLoad": "Falha ao carregar {item}", + "@errorFailedToLoad": { + "description": "Error message - loading failed", + "placeholders": { + "item": { + "type": "String", + "description": "Item that failed to load (album/playlist/etc)" + } + } + }, + "errorNoTracksFound": "Nenhuma faixa encontrada", + "@errorNoTracksFound": { + "description": "Error - search returned no results" + }, + "errorMissingExtensionSource": "Não é possível carregar {item}: faltando a fonte da extensão", + "@errorMissingExtensionSource": { + "description": "Error - extension source not available", + "placeholders": { + "item": { + "type": "String" + } + } + }, + "statusQueued": "Na Fila", + "@statusQueued": { + "description": "Download status - waiting in queue" + }, + "statusDownloading": "Baixando", + "@statusDownloading": { + "description": "Download status - in progress" + }, + "statusFinalizing": "Finalizando", + "@statusFinalizing": { + "description": "Download status - writing metadata" + }, + "statusCompleted": "Concluído", + "@statusCompleted": { + "description": "Download status - finished" + }, + "statusFailed": "Falhou", + "@statusFailed": { + "description": "Download status - error occurred" + }, + "statusSkipped": "Ignorado", + "@statusSkipped": { + "description": "Download status - already exists" + }, + "statusPaused": "Pausado", + "@statusPaused": { + "description": "Download status - paused" + }, + "actionPause": "Pausar", + "@actionPause": { + "description": "Action button - pause download" + }, + "actionResume": "Retomar", + "@actionResume": { + "description": "Action button - resume download" + }, + "actionCancel": "Cancelar", + "@actionCancel": { + "description": "Action button - cancel operation" + }, + "actionStop": "Parar", + "@actionStop": { + "description": "Action button - stop operation" + }, + "actionSelect": "Selecionar", + "@actionSelect": { + "description": "Action button - enter selection mode" + }, + "actionSelectAll": "Selecionar Tudo", + "@actionSelectAll": { + "description": "Action button - select all items" + }, + "actionDeselect": "Desselecionar", + "@actionDeselect": { + "description": "Action button - deselect all" + }, + "actionPaste": "Colar", + "@actionPaste": { + "description": "Action button - paste from clipboard" + }, + "actionImportCsv": "Importar CSV", + "@actionImportCsv": { + "description": "Action button - import CSV file" + }, + "actionRemoveCredentials": "Remover Credenciais", + "@actionRemoveCredentials": { + "description": "Action button - delete Spotify credentials" + }, + "actionSaveCredentials": "Salvar Credenciais", + "@actionSaveCredentials": { + "description": "Action button - save Spotify credentials" + }, + "selectionSelected": "{count} selecionado(s)", + "@selectionSelected": { + "description": "Selection count indicator", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionAllSelected": "Todas as faixas selecionadas", + "@selectionAllSelected": { + "description": "Status - all items selected" + }, + "selectionTapToSelect": "Toque nas faixas para selecionar", + "@selectionTapToSelect": { + "description": "Hint - how to select items" + }, + "selectionDeleteTracks": "Apagar {count} {count, plural, one {}=1{faixa} other{faixas}}", + "@selectionDeleteTracks": { + "description": "Delete button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionSelectToDelete": "Selecione as faixas para apagar", + "@selectionSelectToDelete": { + "description": "Placeholder when nothing selected" + }, + "progressFetchingMetadata": "Buscando metadados... {current}/{total}", + "@progressFetchingMetadata": { + "description": "Progress indicator - loading track info", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "progressReadingCsv": "Lendo CSV...", + "@progressReadingCsv": { + "description": "Progress indicator - parsing CSV file" + }, + "searchSongs": "Músicas", + "@searchSongs": { + "description": "Search result category - songs" + }, + "searchArtists": "Artistas", + "@searchArtists": { + "description": "Search result category - artists" + }, + "searchAlbums": "Álbuns", + "@searchAlbums": { + "description": "Search result category - albums" + }, + "searchPlaylists": "Playlists", + "@searchPlaylists": { + "description": "Search result category - playlists" + }, + "tooltipPlay": "Reproduzir", + "@tooltipPlay": { + "description": "Tooltip - play button" + }, + "tooltipCancel": "Cancelar", + "@tooltipCancel": { + "description": "Tooltip - cancel button" + }, + "tooltipStop": "Parar", + "@tooltipStop": { + "description": "Tooltip - stop button" + }, + "tooltipRetry": "Tentar Novamente", + "@tooltipRetry": { + "description": "Tooltip - retry button" + }, + "tooltipRemove": "Remover", + "@tooltipRemove": { + "description": "Tooltip - remove button" + }, + "tooltipClear": "Limpar", + "@tooltipClear": { + "description": "Tooltip - clear button" + }, + "tooltipPaste": "Colar", + "@tooltipPaste": { + "description": "Tooltip - paste button" + }, + "filenameFormat": "Formato do Nome do Arquivo", + "@filenameFormat": { + "description": "Setting title - filename pattern" + }, + "filenameFormatPreview": "Prévia: {preview}", + "@filenameFormatPreview": { + "description": "Preview of filename pattern", + "placeholders": { + "preview": { + "type": "String" + } + } + }, + "filenameAvailablePlaceholders": "Substituições permitidas:", + "@filenameAvailablePlaceholders": { + "description": "Label for placeholder list" + }, + "filenameHint": "{artist} - {title}", + "@filenameHint": { + "description": "Default filename format hint" + }, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, + "folderOrganization": "Organização de Pastas", + "@folderOrganization": { + "description": "Setting title - folder structure" + }, + "folderOrganizationNone": "Nenhuma organização", + "@folderOrganizationNone": { + "description": "Folder option - flat structure" + }, + "folderOrganizationByArtist": "Por Artista", + "@folderOrganizationByArtist": { + "description": "Folder option - artist folders" + }, + "folderOrganizationByAlbum": "Por Album", + "@folderOrganizationByAlbum": { + "description": "Folder option - album folders" + }, + "folderOrganizationByArtistAlbum": "Artista/Álbum", + "@folderOrganizationByArtistAlbum": { + "description": "Folder option - nested folders" + }, + "folderOrganizationDescription": "Organizar arquivos baixados em pastas", + "@folderOrganizationDescription": { + "description": "Folder organization sheet description" + }, + "folderOrganizationNoneSubtitle": "Todos os arquivos na pasta de download", + "@folderOrganizationNoneSubtitle": { + "description": "Subtitle for no organization option" + }, + "folderOrganizationByArtistSubtitle": "Pasta separada para cada artista", + "@folderOrganizationByArtistSubtitle": { + "description": "Subtitle for artist folder option" + }, + "folderOrganizationByAlbumSubtitle": "Pasta separada para cada álbum", + "@folderOrganizationByAlbumSubtitle": { + "description": "Subtitle for album folder option" + }, + "folderOrganizationByArtistAlbumSubtitle": "Pastas aninhadas para artista e álbum", + "@folderOrganizationByArtistAlbumSubtitle": { + "description": "Subtitle for nested folder option" + }, + "updateAvailable": "Atualização Disponível", + "@updateAvailable": { + "description": "Update dialog title" + }, + "updateNewVersion": "A versão {version} está disponível", + "@updateNewVersion": { + "description": "Update available message", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "updateDownload": "Baixar", + "@updateDownload": { + "description": "Update button - download update" + }, + "updateLater": "Depois", + "@updateLater": { + "description": "Update button - dismiss" + }, + "updateChangelog": "Lista de alterações", + "@updateChangelog": { + "description": "Link to changelog" + }, + "updateStartingDownload": "Iniciando download...", + "@updateStartingDownload": { + "description": "Update status - initializing" + }, + "updateDownloadFailed": "Download falhou", + "@updateDownloadFailed": { + "description": "Update error title" + }, + "updateFailedMessage": "Falha ao baixar a atualização", + "@updateFailedMessage": { + "description": "Update error message" + }, + "updateNewVersionReady": "Uma nova versão está pronta", + "@updateNewVersionReady": { + "description": "Update subtitle" + }, + "updateCurrent": "Atual", + "@updateCurrent": { + "description": "Label for current version" + }, + "updateNew": "Novo", + "@updateNew": { + "description": "Label for new version" + }, + "updateDownloading": "Baixando...", + "@updateDownloading": { + "description": "Update status - downloading" + }, + "updateWhatsNew": "Novidades", + "@updateWhatsNew": { + "description": "Changelog section title" + }, + "updateDownloadInstall": "Baixar e Instalar", + "@updateDownloadInstall": { + "description": "Update button - download and install" + }, + "updateDontRemind": "Não lembrar", + "@updateDontRemind": { + "description": "Update button - skip this version" + }, + "providerPriority": "Prioridade de Provedor", + "@providerPriority": { + "description": "Setting title - download provider order" + }, + "providerPrioritySubtitle": "Arraste para reordenar os provedores de download", + "@providerPrioritySubtitle": { + "description": "Subtitle for provider priority" + }, + "providerPriorityTitle": "Prioridade de Provedor", + "@providerPriorityTitle": { + "description": "Provider priority page title" + }, + "providerPriorityDescription": "Arraste para reordenar provedores de download. O aplicativo irá tentar provedores de cima para baixo ao baixar as faixas.", + "@providerPriorityDescription": { + "description": "Provider priority page description" + }, + "providerPriorityInfo": "Se uma faixa não estiver disponível no primeiro provedor, o aplicativo irá tentar automaticamente a próxima.", + "@providerPriorityInfo": { + "description": "Info tip about fallback behavior" + }, + "providerBuiltIn": "Embutido", + "@providerBuiltIn": { + "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + }, + "providerExtension": "Extensão", + "@providerExtension": { + "description": "Label for extension-provided providers" + }, + "metadataProviderPriority": "Prioridade de Provedor de Metadados", + "@metadataProviderPriority": { + "description": "Setting title - metadata provider order" + }, + "metadataProviderPrioritySubtitle": "Ordem usada para obter metadados de faixa", + "@metadataProviderPrioritySubtitle": { + "description": "Subtitle for metadata priority" + }, + "metadataProviderPriorityTitle": "Prioridade de Metadados", + "@metadataProviderPriorityTitle": { + "description": "Metadata priority page title" + }, + "metadataProviderPriorityDescription": "Arraste para reordenar provedores de metadados. O aplicativo tentará provedores de cima para baixo ao procurar por faixas e buscar metadados.", + "@metadataProviderPriorityDescription": { + "description": "Metadata priority page description" + }, + "metadataProviderPriorityInfo": "O Deezer não tem limites de taxa e é recomendado como principal. O Spotify pode limitar a taxa após muitas solicitações.", + "@metadataProviderPriorityInfo": { + "description": "Info tip about rate limits" + }, + "metadataNoRateLimits": "Sem limites de taxa", + "@metadataNoRateLimits": { + "description": "Deezer provider description" + }, + "metadataMayRateLimit": "Pode ter limites de taxa", + "@metadataMayRateLimit": { + "description": "Spotify provider description" + }, + "logTitle": "Registros", + "@logTitle": { + "description": "Logs screen title" + }, + "logCopy": "Copiar Registros", + "@logCopy": { + "description": "Action - copy logs to clipboard" + }, + "logClear": "Limpar Registros", + "@logClear": { + "description": "Action - delete all logs" + }, + "logShare": "Compartilhar Registros", + "@logShare": { + "description": "Action - share logs file" + }, + "logEmpty": "Ainda não há registros", + "@logEmpty": { + "description": "Empty state title" + }, + "logCopied": "Registros copiados para área de transferência", + "@logCopied": { + "description": "Snackbar - logs copied" + }, + "logSearchHint": "Pesquisar registros...", + "@logSearchHint": { + "description": "Log search placeholder" + }, + "logFilterLevel": "Nível", + "@logFilterLevel": { + "description": "Filter by log level" + }, + "logFilterSection": "Filtro", + "@logFilterSection": { + "description": "Filter section title" + }, + "logShareLogs": "Compartilhar registros", + "@logShareLogs": { + "description": "Share button tooltip" + }, + "logClearLogs": "Limpar registros", + "@logClearLogs": { + "description": "Clear button tooltip" + }, + "logClearLogsTitle": "Limpar Registros", + "@logClearLogsTitle": { + "description": "Clear logs dialog title" + }, + "logClearLogsMessage": "Tem certeza de que deseja limpar todos os registros?", + "@logClearLogsMessage": { + "description": "Clear logs confirmation message" + }, + "logIspBlocking": "BLOQUEIO DE ISP DETECTADO", + "@logIspBlocking": { + "description": "Error category - ISP blocking" + }, + "logRateLimited": "TAXA LIMITADA (RATELIMITED)", + "@logRateLimited": { + "description": "Error category - rate limiting" + }, + "logNetworkError": "ERRO DE REDE", + "@logNetworkError": { + "description": "Error category - network issues" + }, + "logTrackNotFound": "FAIXA NÃO ENCONTRADA", + "@logTrackNotFound": { + "description": "Error category - missing tracks" + }, + "logFilterBySeverity": "Filtrar registros por gravidade", + "@logFilterBySeverity": { + "description": "Filter dialog title" + }, + "logNoLogsYet": "Ainda não há registros", + "@logNoLogsYet": { + "description": "Empty state title" + }, + "logNoLogsYetSubtitle": "Os registros aparecerão aqui enquanto você usa o aplicativo", + "@logNoLogsYetSubtitle": { + "description": "Empty state subtitle" + }, + "logIssueSummary": "Resumo do Problemas", + "@logIssueSummary": { + "description": "Section header for error summary" + }, + "logIspBlockingDescription": "O seu provedor pode estar bloqueando o acesso aos serviços de download", + "@logIspBlockingDescription": { + "description": "ISP blocking explanation" + }, + "logIspBlockingSuggestion": "Tente usar uma VPN ou altere o DNS para 1.1.1 ou 8.8.8.8", + "@logIspBlockingSuggestion": { + "description": "ISP blocking fix suggestion" + }, + "logRateLimitedDescription": "Muitas solicitações ao serviço", + "@logRateLimitedDescription": { + "description": "Rate limit explanation" + }, + "logRateLimitedSuggestion": "Aguarde alguns minutos antes de tentar novamente", + "@logRateLimitedSuggestion": { + "description": "Rate limit fix suggestion" + }, + "logNetworkErrorDescription": "Problemas de conexão detectados", + "@logNetworkErrorDescription": { + "description": "Network error explanation" + }, + "logNetworkErrorSuggestion": "Verifique sua conexão de internet", + "@logNetworkErrorSuggestion": { + "description": "Network error fix suggestion" + }, + "logTrackNotFoundDescription": "Algumas faixas não foram encontradas nos serviços de download", + "@logTrackNotFoundDescription": { + "description": "Track not found explanation" + }, + "logTrackNotFoundSuggestion": "A faixa pode não estar disponível em qualidade sem perdas", + "@logTrackNotFoundSuggestion": { + "description": "Track not found explanation" + }, + "logTotalErrors": "Total de erros: {count}", + "@logTotalErrors": { + "description": "Error count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "logAffected": "Afetado(s): {domains}", + "@logAffected": { + "description": "Affected domains display", + "placeholders": { + "domains": { + "type": "String" + } + } + }, + "logEntriesFiltered": "Entradas ({count} filtradas)", + "@logEntriesFiltered": { + "description": "Log count with filter active", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "logEntries": "Entradas ({count})", + "@logEntries": { + "description": "Total log count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "credentialsTitle": "Credenciais do Spotify", + "@credentialsTitle": { + "description": "Credentials dialog title" + }, + "credentialsDescription": "Digite a sua Client ID e Secret para usar a sua própria cota de aplicativo do Spotify.", + "@credentialsDescription": { + "description": "Credentials dialog explanation" + }, + "credentialsClientId": "Client ID", + "@credentialsClientId": { + "description": "Client ID field label - DO NOT TRANSLATE" + }, + "credentialsClientIdHint": "Colar Client ID", + "@credentialsClientIdHint": { + "description": "Client ID placeholder" + }, + "credentialsClientSecret": "Client Secret", + "@credentialsClientSecret": { + "description": "Client Secret field label - DO NOT TRANSLATE" + }, + "credentialsClientSecretHint": "Colar Client Secret", + "@credentialsClientSecretHint": { + "description": "Client Secret placeholder" + }, + "channelStable": "Estável", + "@channelStable": { + "description": "Update channel - stable releases" + }, + "channelPreview": "Prévia", + "@channelPreview": { + "description": "Update channel - beta/preview releases" + }, + "sectionSearchSource": "Origem da Pesquisa", + "@sectionSearchSource": { + "description": "Settings section header" + }, + "sectionDownload": "Download", + "@sectionDownload": { + "description": "Settings section header" + }, + "sectionPerformance": "Desempenho", + "@sectionPerformance": { + "description": "Settings section header" + }, + "sectionApp": "Aplicativo", + "@sectionApp": { + "description": "Settings section header" + }, + "sectionData": "Dados", + "@sectionData": { + "description": "Settings section header" + }, + "sectionDebug": "Depuração", + "@sectionDebug": { + "description": "Settings section header" + }, + "sectionService": "Serviço", + "@sectionService": { + "description": "Settings section header" + }, + "sectionAudioQuality": "Qualidade de Áudio", + "@sectionAudioQuality": { + "description": "Settings section header" + }, + "sectionFileSettings": "Configurações de Arquivo", + "@sectionFileSettings": { + "description": "Settings section header" + }, + "sectionLyrics": "Letras", + "@sectionLyrics": { + "description": "Settings section header" + }, + "lyricsMode": "Modo de Letras", + "@lyricsMode": { + "description": "Setting - how to save lyrics" + }, + "lyricsModeDescription": "Escolha como as letras são salvas com os seus downloads", + "@lyricsModeDescription": { + "description": "Lyrics mode picker description" + }, + "lyricsModeEmbed": "Incorporar no arquivo", + "@lyricsModeEmbed": { + "description": "Lyrics mode option - embed in audio file" + }, + "lyricsModeEmbedSubtitle": "Letra armazenada nos metadados da FLAC", + "@lyricsModeEmbedSubtitle": { + "description": "Subtitle for embed option" + }, + "lyricsModeExternal": "Arquivo .lrc externo", + "@lyricsModeExternal": { + "description": "Lyrics mode option - separate LRC file" + }, + "lyricsModeExternalSubtitle": "Arquivo .lrc separado para reprodutores como o Samsung Music", + "@lyricsModeExternalSubtitle": { + "description": "Subtitle for external option" + }, + "lyricsModeBoth": "Ambos", + "@lyricsModeBoth": { + "description": "Lyrics mode option - embed and external" + }, + "lyricsModeBothSubtitle": "Incorporar e salvar arquivo .lrc", + "@lyricsModeBothSubtitle": { + "description": "Subtitle for both option" + }, + "sectionColor": "Cor", + "@sectionColor": { + "description": "Settings section header" + }, + "sectionTheme": "Tema", + "@sectionTheme": { + "description": "Settings section header" + }, + "sectionLayout": "Layout", + "@sectionLayout": { + "description": "Settings section header" + }, + "sectionLanguage": "Idioma", + "@sectionLanguage": { + "description": "Settings section header for language" + }, + "appearanceLanguage": "Idioma do aplicativo", + "@appearanceLanguage": { + "description": "Language setting title" + }, + "appearanceLanguageSubtitle": "Escolha o seu idioma preferido", + "@appearanceLanguageSubtitle": { + "description": "Language setting subtitle" + }, + "settingsAppearanceSubtitle": "Tema, cores, exibição", + "@settingsAppearanceSubtitle": { + "description": "Appearance settings description" + }, + "settingsDownloadSubtitle": "Serviço, qualidade, formato de nome de arquivo", + "@settingsDownloadSubtitle": { + "description": "Download settings description" + }, + "settingsOptionsSubtitle": "Fallback, letras, arte de capa, atualizações", + "@settingsOptionsSubtitle": { + "description": "Options settings description" + }, + "settingsExtensionsSubtitle": "Gerenciar provedores de download", + "@settingsExtensionsSubtitle": { + "description": "Extensions settings description" + }, + "settingsLogsSubtitle": "Ver logs do app para depuração", + "@settingsLogsSubtitle": { + "description": "Logs settings description" + }, + "loadingSharedLink": "Carregando link compartilhado...", + "@loadingSharedLink": { + "description": "Status when opening shared URL" + }, + "pressBackAgainToExit": "Pressione voltar novamente para sair", + "@pressBackAgainToExit": { + "description": "Exit confirmation message" + }, + "tracksHeader": "Faixas", + "@tracksHeader": { + "description": "Section header for track list" + }, + "downloadAllCount": "Baixar Todos ({count})", + "@downloadAllCount": { + "description": "Download all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "tracksCount": "{count, plural, one {}=1{1 faixa} other{{count} faixas}}", + "@tracksCount": { + "description": "Track count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "trackCopyFilePath": "Copiar caminho do arquivo", + "@trackCopyFilePath": { + "description": "Action - copy file path" + }, + "trackRemoveFromDevice": "Remover do dispositivo", + "@trackRemoveFromDevice": { + "description": "Action - delete downloaded file" + }, + "trackLoadLyrics": "Carregar Letras", + "@trackLoadLyrics": { + "description": "Action - fetch lyrics" + }, + "trackMetadata": "Metadados", + "@trackMetadata": { + "description": "Tab title - track metadata" + }, + "trackFileInfo": "Informações do Arquivo", + "@trackFileInfo": { + "description": "Tab title - file information" + }, + "trackLyrics": "Letras", + "@trackLyrics": { + "description": "Tab title - lyrics" + }, + "trackFileNotFound": "Arquivo não encontrado", + "@trackFileNotFound": { + "description": "Error - file doesn't exist" + }, + "trackOpenInDeezer": "Abrir no Deezer", + "@trackOpenInDeezer": { + "description": "Action - open track in Deezer app" + }, + "trackOpenInSpotify": "Abrir no Spotify", + "@trackOpenInSpotify": { + "description": "Action - open track in Spotify app" + }, + "trackTrackName": "Nome da faixa", + "@trackTrackName": { + "description": "Metadata label - track title" + }, + "trackArtist": "Artista", + "@trackArtist": { + "description": "Metadata label - artist name" + }, + "trackAlbumArtist": "Artista do álbum", + "@trackAlbumArtist": { + "description": "Metadata label - album artist" + }, + "trackAlbum": "Álbum", + "@trackAlbum": { + "description": "Metadata label - album name" + }, + "trackTrackNumber": "Número da faixa", + "@trackTrackNumber": { + "description": "Metadata label - track number" + }, + "trackDiscNumber": "Número do disco", + "@trackDiscNumber": { + "description": "Metadata label - disc number" + }, + "trackDuration": "Duração", + "@trackDuration": { + "description": "Metadata label - track length" + }, + "trackAudioQuality": "Qualidade de Áudio", + "@trackAudioQuality": { + "description": "Metadata label - audio quality" + }, + "trackReleaseDate": "Data de lançamento", + "@trackReleaseDate": { + "description": "Metadata label - release date" + }, + "trackGenre": "Género", + "@trackGenre": { + "description": "Metadata label - music genre" + }, + "trackLabel": "Gravadora", + "@trackLabel": { + "description": "Metadata label - record label" + }, + "trackCopyright": "Direitos Autorais", + "@trackCopyright": { + "description": "Metadata label - copyright information" + }, + "trackDownloaded": "Baixado", + "@trackDownloaded": { + "description": "Metadata label - download date" + }, + "trackCopyLyrics": "Copiar letra", + "@trackCopyLyrics": { + "description": "Action - copy lyrics to clipboard" + }, + "trackLyricsNotAvailable": "Letra não disponível para esta faixa", + "@trackLyricsNotAvailable": { + "description": "Message when lyrics not found" + }, + "trackLyricsTimeout": "A solicitação expirou. Tente novamente mais tarde.", + "@trackLyricsTimeout": { + "description": "Message when lyrics request times out" + }, + "trackLyricsLoadFailed": "Falha ao carregar a letra", + "@trackLyricsLoadFailed": { + "description": "Message when lyrics loading fails" + }, + "trackEmbedLyrics": "Incorporar Letras", + "@trackEmbedLyrics": { + "description": "Action - embed lyrics into audio file" + }, + "trackLyricsEmbedded": "Letras incorporadas com sucesso", + "@trackLyricsEmbedded": { + "description": "Snackbar - lyrics saved to file" + }, + "trackInstrumental": "Faixa de instrumentais", + "@trackInstrumental": { + "description": "Message when track is instrumental (no lyrics)" + }, + "trackCopiedToClipboard": "Copiado para a área de transferência", + "@trackCopiedToClipboard": { + "description": "Snackbar - content copied" + }, + "trackDeleteConfirmTitle": "Remover do dispositivo?", + "@trackDeleteConfirmTitle": { + "description": "Delete confirmation title" + }, + "trackDeleteConfirmMessage": "Isto irá excluir o arquivo baixado permanentemente e removê-lo do seu histórico.", + "@trackDeleteConfirmMessage": { + "description": "Delete confirmation message" + }, + "trackCannotOpen": "Não foi possível abrir: {message}", + "@trackCannotOpen": { + "description": "Error opening file", + "placeholders": { + "message": { + "type": "String" + } + } + }, + "dateToday": "Hoje", + "@dateToday": { + "description": "Relative date - today" + }, + "dateYesterday": "Ontem", + "@dateYesterday": { + "description": "Relative date - yesterday" + }, + "dateDaysAgo": "{count} dias atrás", + "@dateDaysAgo": { + "description": "Relative date - days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dateWeeksAgo": "{count} semanas atrás", + "@dateWeeksAgo": { + "description": "Relative date - weeks ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dateMonthsAgo": "{count} meses atrás", + "@dateMonthsAgo": { + "description": "Relative date - months ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "concurrentSequential": "Sequencial", + "@concurrentSequential": { + "description": "Download mode - one at a time" + }, + "concurrentParallel2": "2 Paralelos", + "@concurrentParallel2": { + "description": "Download mode - 2 simultaneous" + }, + "concurrentParallel3": "3 Paralelos", + "@concurrentParallel3": { + "description": "Download mode - 3 simultaneous" + }, + "tapToSeeError": "Toque para ver os detalhes do erro", + "@tapToSeeError": { + "description": "Tooltip for failed download" + }, + "storeFilterAll": "Tudo", + "@storeFilterAll": { + "description": "Store filter - all extensions" + }, + "storeFilterMetadata": "Metadados", + "@storeFilterMetadata": { + "description": "Store filter - metadata providers" + }, + "storeFilterDownload": "Download", + "@storeFilterDownload": { + "description": "Store filter - download providers" + }, + "storeFilterUtility": "Utilidade", + "@storeFilterUtility": { + "description": "Store filter - utility extensions" + }, + "storeFilterLyrics": "Letras", + "@storeFilterLyrics": { + "description": "Store filter - lyrics providers" + }, + "storeFilterIntegration": "Integração", + "@storeFilterIntegration": { + "description": "Store filter - integrations" + }, + "storeClearFilters": "Limpar filtros", + "@storeClearFilters": { + "description": "Button to clear all filters" + }, + "storeNoResults": "Nenhuma extensão encontrada", + "@storeNoResults": { + "description": "Empty state when no extensions match filters" + }, + "extensionProviderPriority": "Prioridade de Provedor", + "@extensionProviderPriority": { + "description": "Extension capability - provider priority" + }, + "extensionInstallButton": "Instalar Extensão", + "@extensionInstallButton": { + "description": "Button to install extension" + }, + "extensionDefaultProvider": "Padrão (Deezer/Spotify)", + "@extensionDefaultProvider": { + "description": "Default search provider option" + }, + "extensionDefaultProviderSubtitle": "Usar pesquisa integrada", + "@extensionDefaultProviderSubtitle": { + "description": "Subtitle for default provider" + }, + "extensionAuthor": "Autor", + "@extensionAuthor": { + "description": "Extension detail - author" + }, + "extensionId": "ID", + "@extensionId": { + "description": "Extension detail - unique ID" + }, + "extensionError": "Erro", + "@extensionError": { + "description": "Extension detail - error message" + }, + "extensionCapabilities": "Funcionalidades", + "@extensionCapabilities": { + "description": "Section header - extension features" + }, + "extensionMetadataProvider": "Provedor de Metadados", + "@extensionMetadataProvider": { + "description": "Capability - provides metadata" + }, + "extensionDownloadProvider": "Provedor de Download", + "@extensionDownloadProvider": { + "description": "Capability - provides downloads" + }, + "extensionLyricsProvider": "Provedor de Letras", + "@extensionLyricsProvider": { + "description": "Capability - provides lyrics" + }, + "extensionUrlHandler": "Gerenciador de URL", + "@extensionUrlHandler": { + "description": "Capability - handles URLs" + }, + "extensionQualityOptions": "Opções de Qualidade", + "@extensionQualityOptions": { + "description": "Capability - quality selection" + }, + "extensionPostProcessingHooks": "Ganchos de Pós-Processamento", + "@extensionPostProcessingHooks": { + "description": "Capability - post-processing" + }, + "extensionPermissions": "Permissões", + "@extensionPermissions": { + "description": "Section header - required permissions" + }, + "extensionSettings": "Configurações", + "@extensionSettings": { + "description": "Section header - extension settings" + }, + "extensionRemoveButton": "Remover Extensão", + "@extensionRemoveButton": { + "description": "Button to uninstall extension" + }, + "extensionUpdated": "Atualizado", + "@extensionUpdated": { + "description": "Extension detail - last update" + }, + "extensionMinAppVersion": "Versão Mínima do App", + "@extensionMinAppVersion": { + "description": "Extension detail - minimum app version" + }, + "extensionCustomTrackMatching": "Correspondência de Faixa Personalizada", + "@extensionCustomTrackMatching": { + "description": "Capability - custom track matching algorithm" + }, + "extensionPostProcessing": "Pós-Processamento", + "@extensionPostProcessing": { + "description": "Capability - post-download processing" + }, + "extensionHooksAvailable": "{count} gancho(s) disponíveis", + "@extensionHooksAvailable": { + "description": "Post-processing hooks count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "extensionPatternsCount": "{count} padrão(ões)", + "@extensionPatternsCount": { + "description": "URL patterns count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "extensionStrategy": "Estratégia: {strategy}", + "@extensionStrategy": { + "description": "Track matching strategy name", + "placeholders": { + "strategy": { + "type": "String" + } + } + }, + "extensionsProviderPrioritySection": "Prioridade de Provedor", + "@extensionsProviderPrioritySection": { + "description": "Section header - provider priority" + }, + "extensionsInstalledSection": "Extensões Instaladas", + "@extensionsInstalledSection": { + "description": "Section header - installed extensions" + }, + "extensionsNoExtensions": "Nenhuma extensão instalada", + "@extensionsNoExtensions": { + "description": "Empty state - no extensions" + }, + "extensionsNoExtensionsSubtitle": "Instale arquivos .spotiflac-ext para adicionar novos provedores", + "@extensionsNoExtensionsSubtitle": { + "description": "Empty state subtitle" + }, + "extensionsInstallButton": "Instalar Extensão", + "@extensionsInstallButton": { + "description": "Button to install extension from file" + }, + "extensionsInfoTip": "Extensões podem adicionar novos metadados e baixar provedores. Somente instale extensões a partir de fontes confiáveis.", + "@extensionsInfoTip": { + "description": "Security warning about extensions" + }, + "extensionsInstalledSuccess": "Extensão instalada com sucesso", + "@extensionsInstalledSuccess": { + "description": "Success message after install" + }, + "extensionsDownloadPriority": "Prioridade de Download", + "@extensionsDownloadPriority": { + "description": "Setting - download provider order" + }, + "extensionsDownloadPrioritySubtitle": "Definir ordem do serviço de download", + "@extensionsDownloadPrioritySubtitle": { + "description": "Subtitle for download priority" + }, + "extensionsNoDownloadProvider": "Nenhuma extensão com provedor de download", + "@extensionsNoDownloadProvider": { + "description": "Empty state - no download providers" + }, + "extensionsMetadataPriority": "Prioridade de Metadados", + "@extensionsMetadataPriority": { + "description": "Setting - metadata provider order" + }, + "extensionsMetadataPrioritySubtitle": "Definir ordem de origem de pesquisa e metadados", + "@extensionsMetadataPrioritySubtitle": { + "description": "Subtitle for metadata priority" + }, + "extensionsNoMetadataProvider": "Nenhuma extensão com provedor de metadados", + "@extensionsNoMetadataProvider": { + "description": "Empty state - no metadata providers" + }, + "extensionsSearchProvider": "Provedor de Pesquisa", + "@extensionsSearchProvider": { + "description": "Setting - search provider selection" + }, + "extensionsNoCustomSearch": "Nenhuma extensão com pesquisa personalizada", + "@extensionsNoCustomSearch": { + "description": "Empty state - no search providers" + }, + "extensionsSearchProviderDescription": "Escolha qual serviço utilizar para pesquisar faixas", + "@extensionsSearchProviderDescription": { + "description": "Search provider setting description" + }, + "extensionsCustomSearch": "Busca personalizada", + "@extensionsCustomSearch": { + "description": "Label for custom search provider" + }, + "extensionsErrorLoading": "Erro ao carregar extensão", + "@extensionsErrorLoading": { + "description": "Error message when extension fails to load" + }, + "qualityFlacLossless": "FLAC Lossless", + "@qualityFlacLossless": { + "description": "Quality option - CD quality FLAC" + }, + "qualityFlacLosslessSubtitle": "16-bit / 44.1kHz", + "@qualityFlacLosslessSubtitle": { + "description": "Technical spec for lossless" + }, + "qualityHiResFlac": "Hi-Res FLAC", + "@qualityHiResFlac": { + "description": "Quality option - high resolution FLAC" + }, + "qualityHiResFlacSubtitle": "24-bit / até 96kHz", + "@qualityHiResFlacSubtitle": { + "description": "Technical spec for hi-res" + }, + "qualityHiResFlacMax": "Hi-Res FLAC Max", + "@qualityHiResFlacMax": { + "description": "Quality option - maximum resolution FLAC" + }, + "qualityHiResFlacMaxSubtitle": "24-bit / até 192kHz", + "@qualityHiResFlacMaxSubtitle": { + "description": "Technical spec for hi-res max" + }, + "qualityLossy": "Lossy", + "@qualityLossy": { + "description": "Quality option - lossy format (MP3/Opus)" + }, + "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", + "@qualityLossyMp3Subtitle": { + "description": "Technical spec for lossy MP3" + }, + "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", + "@qualityLossyOpusSubtitle": { + "description": "Technical spec for lossy Opus" + }, + "enableLossyOption": "Enable Lossy Option", + "@enableLossyOption": { + "description": "Setting - enable lossy quality option" + }, + "enableLossyOptionSubtitleOn": "Lossy quality option is available", + "@enableLossyOptionSubtitleOn": { + "description": "Subtitle when lossy is enabled" + }, + "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", + "@enableLossyOptionSubtitleOff": { + "description": "Subtitle when lossy is disabled" + }, + "lossyFormat": "Lossy Format", + "@lossyFormat": { + "description": "Setting - choose lossy format" + }, + "lossyFormatDescription": "Choose the lossy format for conversion", + "@lossyFormatDescription": { + "description": "Description for lossy format picker" + }, + "lossyFormatMp3Subtitle": "320kbps, best compatibility", + "@lossyFormatMp3Subtitle": { + "description": "MP3 format description" + }, + "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", + "@lossyFormatOpusSubtitle": { + "description": "Opus format description" + }, + "qualityNote": "A qualidade real depende da faixa que estiver disponível no serviço", + "@qualityNote": { + "description": "Note about quality availability" + }, + "youtubeQualityNote": "YouTube provides lossy audio only. Not part of lossless fallback.", + "@youtubeQualityNote": { + "description": "Note for YouTube service explaining lossy-only quality" + }, + "downloadAskBeforeDownload": "Perguntar qualidade antes de baixar", + "@downloadAskBeforeDownload": { + "description": "Setting - show quality picker" + }, + "downloadDirectory": "Pasta de Download", + "@downloadDirectory": { + "description": "Setting - download folder" + }, + "downloadSeparateSinglesFolder": "Pasta de Singles Separada", + "@downloadSeparateSinglesFolder": { + "description": "Setting - separate folder for singles" + }, + "downloadAlbumFolderStructure": "Estrutura da Pasta de Álbum", + "@downloadAlbumFolderStructure": { + "description": "Setting - album folder organization" + }, + "downloadUseAlbumArtistForFolders": "Use Album Artist for folders", + "@downloadUseAlbumArtistForFolders": { + "description": "Setting - choose whether artist folders use Album Artist or Track Artist" + }, + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" + }, + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" + }, + "downloadUsePrimaryArtistOnly": "Primary artist only for folders", + "@downloadUsePrimaryArtistOnly": { + "description": "Setting - strip featured artists from folder name" + }, + "downloadUsePrimaryArtistOnlyEnabled": "Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)", + "@downloadUsePrimaryArtistOnlyEnabled": { + "description": "Subtitle when primary artist only is enabled" + }, + "downloadUsePrimaryArtistOnlyDisabled": "Full artist string used for folder name", + "@downloadUsePrimaryArtistOnlyDisabled": { + "description": "Subtitle when primary artist only is disabled" + }, + "downloadSaveFormat": "Formato para Salvar", + "@downloadSaveFormat": { + "description": "Setting - output file format" + }, + "downloadSelectService": "Selecionar Serviço", + "@downloadSelectService": { + "description": "Dialog title - choose download service" + }, + "downloadSelectQuality": "Selecionar Qualidade", + "@downloadSelectQuality": { + "description": "Dialog title - choose audio quality" + }, + "downloadFrom": "Baixar De", + "@downloadFrom": { + "description": "Label - download source" + }, + "downloadDefaultQualityLabel": "Qualidade Padrão", + "@downloadDefaultQualityLabel": { + "description": "Label - default quality setting" + }, + "downloadBestAvailable": "Melhor Disponível", + "@downloadBestAvailable": { + "description": "Quality option - highest available" + }, + "folderNone": "Nenhuma", + "@folderNone": { + "description": "Folder option - no organization" + }, + "folderNoneSubtitle": "Salvar todos os arquivos diretamente na pasta de download", + "@folderNoneSubtitle": { + "description": "Subtitle for no folder organization" + }, + "folderArtist": "Artista", + "@folderArtist": { + "description": "Folder option - by artist" + }, + "folderArtistSubtitle": "Nome do Artista/arquivo", + "@folderArtistSubtitle": { + "description": "Folder structure example" + }, + "folderAlbum": "Álbum", + "@folderAlbum": { + "description": "Folder option - by album" + }, + "folderAlbumSubtitle": "Nome do Álbum/arquivo", + "@folderAlbumSubtitle": { + "description": "Folder structure example" + }, + "folderArtistAlbum": "Artista/Álbum", + "@folderArtistAlbum": { + "description": "Folder option - nested" + }, + "folderArtistAlbumSubtitle": "Nome do Artista/Nome do Álbum/arquivo", + "@folderArtistAlbumSubtitle": { + "description": "Folder structure example" + }, + "serviceTidal": "Tidal", + "@serviceTidal": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceQobuz": "Qobuz", + "@serviceQobuz": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceAmazon": "Amazon", + "@serviceAmazon": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceDeezer": "Deezer", + "@serviceDeezer": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceSpotify": "Spotify", + "@serviceSpotify": { + "description": "Service name - DO NOT TRANSLATE" + }, + "appearanceAmoledDark": "Escuro AMOLED", + "@appearanceAmoledDark": { + "description": "Theme option - pure black" + }, + "appearanceAmoledDarkSubtitle": "Fundo preto puro", + "@appearanceAmoledDarkSubtitle": { + "description": "Subtitle for AMOLED dark" + }, + "appearanceChooseAccentColor": "Escolha a Cor de Destaque", + "@appearanceChooseAccentColor": { + "description": "Color picker dialog title" + }, + "appearanceChooseTheme": "Modo do Tema", + "@appearanceChooseTheme": { + "description": "Theme picker dialog title" + }, + "queueTitle": "Fila de Download", + "@queueTitle": { + "description": "Queue screen title" + }, + "queueClearAll": "Limpar Tudo", + "@queueClearAll": { + "description": "Button - clear all queue items" + }, + "queueClearAllMessage": "Você tem certeza que deseja limpar todos os downloads?", + "@queueClearAllMessage": { + "description": "Clear queue confirmation" + }, + "queueExportFailed": "Export", + "@queueExportFailed": { + "description": "Button - export failed downloads to TXT" + }, + "queueExportFailedSuccess": "Failed downloads exported to TXT file", + "@queueExportFailedSuccess": { + "description": "Success message after exporting failed downloads" + }, + "queueExportFailedClear": "Clear Failed", + "@queueExportFailedClear": { + "description": "Action to clear failed downloads after export" + }, + "queueExportFailedError": "Failed to export downloads", + "@queueExportFailedError": { + "description": "Error message when export fails" + }, + "settingsAutoExportFailed": "Auto-export failed downloads", + "@settingsAutoExportFailed": { + "description": "Setting toggle for auto-export" + }, + "settingsAutoExportFailedSubtitle": "Save failed downloads to TXT file automatically", + "@settingsAutoExportFailedSubtitle": { + "description": "Subtitle for auto-export setting" + }, + "settingsDownloadNetwork": "Download Network", + "@settingsDownloadNetwork": { + "description": "Setting for network type preference" + }, + "settingsDownloadNetworkAny": "WiFi + Mobile Data", + "@settingsDownloadNetworkAny": { + "description": "Network option - use any connection" + }, + "settingsDownloadNetworkWifiOnly": "WiFi Only", + "@settingsDownloadNetworkWifiOnly": { + "description": "Network option - only use WiFi" + }, + "settingsDownloadNetworkSubtitle": "Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.", + "@settingsDownloadNetworkSubtitle": { + "description": "Subtitle explaining network preference" + }, + "queueEmpty": "Nenhum download na fila", + "@queueEmpty": { + "description": "Empty queue state title" + }, + "queueEmptySubtitle": "Adicione faixas a partir da tela inicial", + "@queueEmptySubtitle": { + "description": "Empty queue state subtitle" + }, + "queueClearCompleted": "Limpar concluídos", + "@queueClearCompleted": { + "description": "Button - clear finished downloads" + }, + "queueDownloadFailed": "Download Falhou", + "@queueDownloadFailed": { + "description": "Error dialog title" + }, + "queueTrackLabel": "Faixa:", + "@queueTrackLabel": { + "description": "Label in error dialog" + }, + "queueArtistLabel": "Artista:", + "@queueArtistLabel": { + "description": "Label in error dialog" + }, + "queueErrorLabel": "Erro:", + "@queueErrorLabel": { + "description": "Label in error dialog" + }, + "queueUnknownError": "Erro desconhecido", + "@queueUnknownError": { + "description": "Fallback error message" + }, + "albumFolderArtistAlbum": "Artista / Álbum", + "@albumFolderArtistAlbum": { + "description": "Album folder option" + }, + "albumFolderArtistAlbumSubtitle": "Álbuns/Nome do Artista/Nome do Álbum/", + "@albumFolderArtistAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistYearAlbum": "Artista / [Ano] Álbum", + "@albumFolderArtistYearAlbum": { + "description": "Album folder option with year" + }, + "albumFolderArtistYearAlbumSubtitle": "Álbuns/Nome do Artista/[2005] Nome do Álbum/", + "@albumFolderArtistYearAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderAlbumOnly": "Somente Álbum", + "@albumFolderAlbumOnly": { + "description": "Album folder option" + }, + "albumFolderAlbumOnlySubtitle": "Albums/Nome do Álbum/", + "@albumFolderAlbumOnlySubtitle": { + "description": "Folder structure example" + }, + "albumFolderYearAlbum": "[Ano] Álbum", + "@albumFolderYearAlbum": { + "description": "Album folder option with year" + }, + "albumFolderYearAlbumSubtitle": "Álbuns/[2005] Nome do Álbum/", + "@albumFolderYearAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistAlbumSingles": "Artista / Álbum + Singles", + "@albumFolderArtistAlbumSingles": { + "description": "Album folder option with singles inside artist" + }, + "albumFolderArtistAlbumSinglesSubtitle": "Artista/Álbum/ e Artista/Singles/", + "@albumFolderArtistAlbumSinglesSubtitle": { + "description": "Folder structure example" + }, + "downloadedAlbumDeleteSelected": "Apagar Selecionados", + "@downloadedAlbumDeleteSelected": { + "description": "Button - delete selected tracks" + }, + "downloadedAlbumDeleteMessage": "Excluir {count} {count, plural, one {}=1{faixa} other{faixas}} deste álbum?\n\nIsso também excluirá os arquivos do armazenamento.", + "@downloadedAlbumDeleteMessage": { + "description": "Delete confirmation with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumTracksHeader": "Faixas", + "@downloadedAlbumTracksHeader": { + "description": "Section header for tracks" + }, + "downloadedAlbumDownloadedCount": "{count} baixado(s)", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumSelectedCount": "{count} selecionado(s)", + "@downloadedAlbumSelectedCount": { + "description": "Selection count indicator", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumAllSelected": "Todas as faixas selecionadas", + "@downloadedAlbumAllSelected": { + "description": "Status - all items selected" + }, + "downloadedAlbumTapToSelect": "Toque nas faixas para selecionar", + "@downloadedAlbumTapToSelect": { + "description": "Selection hint" + }, + "downloadedAlbumDeleteCount": "Apagar {count} {count, plural, one {}=1{faixa} other{faixas}}", + "@downloadedAlbumDeleteCount": { + "description": "Delete button text with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumSelectToDelete": "Selecione as faixas para apagar", + "@downloadedAlbumSelectToDelete": { + "description": "Placeholder when nothing selected" + }, + "downloadedAlbumDiscHeader": "Disco {discNumber}", + "@downloadedAlbumDiscHeader": { + "description": "Header for disc separator in multi-disc albums", + "placeholders": { + "discNumber": { + "type": "int", + "example": "1" + } + } + }, + "utilityFunctions": "Funções Utilitárias", + "@utilityFunctions": { + "description": "Extension capability - utility functions" + }, + "recentTypeArtist": "Artista", + "@recentTypeArtist": { + "description": "Recent access item type - artist" + }, + "recentTypeAlbum": "Álbum", + "@recentTypeAlbum": { + "description": "Recent access item type - album" + }, + "recentTypeSong": "Música", + "@recentTypeSong": { + "description": "Recent access item type - song/track" + }, + "recentTypePlaylist": "Playlist", + "@recentTypePlaylist": { + "description": "Recent access item type - playlist" + }, + "recentEmpty": "No recent items yet", + "@recentEmpty": { + "description": "Empty state text for recent access list" + }, + "recentShowAllDownloads": "Show All Downloads", + "@recentShowAllDownloads": { + "description": "Button label to unhide hidden downloads in recent access" + }, + "recentPlaylistInfo": "Playlist: {name}", + "@recentPlaylistInfo": { + "description": "Snackbar message when tapping playlist in recent access", + "placeholders": { + "name": { + "type": "String", + "description": "Playlist name" + } + } + }, + "errorGeneric": "Erro: {message}", + "@errorGeneric": { + "description": "Generic error message format", + "placeholders": { + "message": { + "type": "String", + "description": "Error message" + } + } + }, + "discographyDownload": "Baixar Discografia", + "@discographyDownload": { + "description": "Button - download artist discography" + }, + "discographyDownloadAll": "Baixar Tudo", + "@discographyDownloadAll": { + "description": "Option - download entire discography" + }, + "discographyDownloadAllSubtitle": "{count} faixas de {albumCount} lançamentos", + "@discographyDownloadAllSubtitle": { + "description": "Subtitle showing total tracks and albums", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographyAlbumsOnly": "Somente Álbuns", + "@discographyAlbumsOnly": { + "description": "Option - download only albums" + }, + "discographyAlbumsOnlySubtitle": "{count} faixas de {albumCount} álbuns", + "@discographyAlbumsOnlySubtitle": { + "description": "Subtitle showing album tracks count", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographySinglesOnly": "Somente Singles e EPs", + "@discographySinglesOnly": { + "description": "Option - download only singles" + }, + "discographySinglesOnlySubtitle": "{count} faixas de {albumCount} singles", + "@discographySinglesOnlySubtitle": { + "description": "Subtitle showing singles tracks count", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographySelectAlbums": "Selecione Álbuns...", + "@discographySelectAlbums": { + "description": "Option - manually select albums to download" + }, + "discographySelectAlbumsSubtitle": "Escolher álbuns ou singles específicos", + "@discographySelectAlbumsSubtitle": { + "description": "Subtitle for select albums option" + }, + "discographyFetchingTracks": "Buscando faixas...", + "@discographyFetchingTracks": { + "description": "Progress - fetching album tracks" + }, + "discographyFetchingAlbum": "Buscando {current} de {total}...", + "@discographyFetchingAlbum": { + "description": "Progress - fetching specific album", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "discographySelectedCount": "{count} selecionado(s)", + "@discographySelectedCount": { + "description": "Selection count badge", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discographyDownloadSelected": "Baixar Selecionados", + "@discographyDownloadSelected": { + "description": "Button - download selected albums" + }, + "discographyAddedToQueue": "{count} faixas adicionadas à fila", + "@discographyAddedToQueue": { + "description": "Snackbar - tracks added from discography", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discographySkippedDownloaded": "{added} adicionada(s), {skipped} já baixada(s)", + "@discographySkippedDownloaded": { + "description": "Snackbar - with skipped tracks count", + "placeholders": { + "added": { + "type": "int" + }, + "skipped": { + "type": "int" + } + } + }, + "discographyNoAlbums": "Nenhum álbum disponível", + "@discographyNoAlbums": { + "description": "Error - no albums found for artist" + }, + "discographyFailedToFetch": "Falha ao obter alguns álbuns", + "@discographyFailedToFetch": { + "description": "Error - some albums failed to load" + }, + "sectionStorageAccess": "Storage Access", + "@sectionStorageAccess": { + "description": "Section header for storage access settings" + }, + "allFilesAccess": "All Files Access", + "@allFilesAccess": { + "description": "Toggle for MANAGE_EXTERNAL_STORAGE permission" + }, + "allFilesAccessEnabledSubtitle": "Can write to any folder", + "@allFilesAccessEnabledSubtitle": { + "description": "Subtitle when all files access is enabled" + }, + "allFilesAccessDisabledSubtitle": "Limited to media folders only", + "@allFilesAccessDisabledSubtitle": { + "description": "Subtitle when all files access is disabled" + }, + "allFilesAccessDescription": "Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.", + "@allFilesAccessDescription": { + "description": "Description explaining when to enable all files access" + }, + "allFilesAccessDeniedMessage": "Permission was denied. Please enable 'All files access' manually in system settings.", + "@allFilesAccessDeniedMessage": { + "description": "Message when permission is permanently denied" + }, + "allFilesAccessDisabledMessage": "All Files Access disabled. The app will use limited storage access.", + "@allFilesAccessDisabledMessage": { + "description": "Snackbar message when user disables all files access" + }, + "settingsLocalLibrary": "Local Library", + "@settingsLocalLibrary": { + "description": "Settings menu item - local library" + }, + "settingsLocalLibrarySubtitle": "Scan music & detect duplicates", + "@settingsLocalLibrarySubtitle": { + "description": "Subtitle for local library settings" + }, + "settingsCache": "Storage & Cache", + "@settingsCache": { + "description": "Settings menu item - cache management" + }, + "settingsCacheSubtitle": "View size and clear cached data", + "@settingsCacheSubtitle": { + "description": "Subtitle for cache management menu" + }, + "libraryTitle": "Local Library", + "@libraryTitle": { + "description": "Library settings page title" + }, + "libraryStatus": "Library Status", + "@libraryStatus": { + "description": "Section header for library status" + }, + "libraryScanSettings": "Scan Settings", + "@libraryScanSettings": { + "description": "Section header for scan settings" + }, + "libraryEnableLocalLibrary": "Enable Local Library", + "@libraryEnableLocalLibrary": { + "description": "Toggle to enable library scanning" + }, + "libraryEnableLocalLibrarySubtitle": "Scan and track your existing music", + "@libraryEnableLocalLibrarySubtitle": { + "description": "Subtitle for enable toggle" + }, + "libraryFolder": "Library Folder", + "@libraryFolder": { + "description": "Folder selection setting" + }, + "libraryFolderHint": "Tap to select folder", + "@libraryFolderHint": { + "description": "Placeholder when no folder selected" + }, + "libraryShowDuplicateIndicator": "Show Duplicate Indicator", + "@libraryShowDuplicateIndicator": { + "description": "Toggle for duplicate indicator in search" + }, + "libraryShowDuplicateIndicatorSubtitle": "Show when searching for existing tracks", + "@libraryShowDuplicateIndicatorSubtitle": { + "description": "Subtitle for duplicate indicator toggle" + }, + "libraryActions": "Actions", + "@libraryActions": { + "description": "Section header for library actions" + }, + "libraryScan": "Scan Library", + "@libraryScan": { + "description": "Button to start library scan" + }, + "libraryScanSubtitle": "Scan for audio files", + "@libraryScanSubtitle": { + "description": "Subtitle for scan button" + }, + "libraryScanSelectFolderFirst": "Select a folder first", + "@libraryScanSelectFolderFirst": { + "description": "Message when trying to scan without folder" + }, + "libraryCleanupMissingFiles": "Cleanup Missing Files", + "@libraryCleanupMissingFiles": { + "description": "Button to remove entries for missing files" + }, + "libraryCleanupMissingFilesSubtitle": "Remove entries for files that no longer exist", + "@libraryCleanupMissingFilesSubtitle": { + "description": "Subtitle for cleanup button" + }, + "libraryClear": "Clear Library", + "@libraryClear": { + "description": "Button to clear all library entries" + }, + "libraryClearSubtitle": "Remove all scanned tracks", + "@libraryClearSubtitle": { + "description": "Subtitle for clear button" + }, + "libraryClearConfirmTitle": "Clear Library", + "@libraryClearConfirmTitle": { + "description": "Dialog title for clear confirmation" + }, + "libraryClearConfirmMessage": "This will remove all scanned tracks from your library. Your actual music files will not be deleted.", + "@libraryClearConfirmMessage": { + "description": "Dialog message for clear confirmation" + }, + "libraryAbout": "About Local Library", + "@libraryAbout": { + "description": "Section header for about info" + }, + "libraryAboutDescription": "Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.", + "@libraryAboutDescription": { + "description": "Description of local library feature" + }, + "libraryTracksCount": "{count} tracks", + "@libraryTracksCount": { + "description": "Track count in library", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "libraryLastScanned": "Last scanned: {time}", + "@libraryLastScanned": { + "description": "Last scan time display", + "placeholders": { + "time": { + "type": "String" + } + } + }, + "libraryLastScannedNever": "Never", + "@libraryLastScannedNever": { + "description": "Shown when library has never been scanned" + }, + "libraryScanning": "Scanning...", + "@libraryScanning": { + "description": "Status during scan" + }, + "libraryScanProgress": "{progress}% of {total} files", + "@libraryScanProgress": { + "description": "Scan progress display", + "placeholders": { + "progress": { + "type": "String" + }, + "total": { + "type": "int" + } + } + }, + "libraryInLibrary": "In Library", + "@libraryInLibrary": { + "description": "Badge shown on tracks that exist in local library" + }, + "libraryRemovedMissingFiles": "Removed {count} missing files from library", + "@libraryRemovedMissingFiles": { + "description": "Snackbar after cleanup", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "libraryCleared": "Library cleared", + "@libraryCleared": { + "description": "Snackbar after clearing library" + }, + "libraryStorageAccessRequired": "Storage Access Required", + "@libraryStorageAccessRequired": { + "description": "Dialog title for storage permission" + }, + "libraryStorageAccessMessage": "SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.", + "@libraryStorageAccessMessage": { + "description": "Dialog message for storage permission" + }, + "libraryFolderNotExist": "Selected folder does not exist", + "@libraryFolderNotExist": { + "description": "Error when folder doesn't exist" + }, + "librarySourceDownloaded": "Downloaded", + "@librarySourceDownloaded": { + "description": "Badge for tracks downloaded via SpotiFLAC" + }, + "librarySourceLocal": "Local", + "@librarySourceLocal": { + "description": "Badge for tracks from local library scan" + }, + "libraryFilterAll": "All", + "@libraryFilterAll": { + "description": "Filter chip - show all library items" + }, + "libraryFilterDownloaded": "Downloaded", + "@libraryFilterDownloaded": { + "description": "Filter chip - show only downloaded items" + }, + "libraryFilterLocal": "Local", + "@libraryFilterLocal": { + "description": "Filter chip - show only local library items" + }, + "libraryFilterTitle": "Filters", + "@libraryFilterTitle": { + "description": "Filter bottom sheet title" + }, + "libraryFilterReset": "Reset", + "@libraryFilterReset": { + "description": "Reset all filters button" + }, + "libraryFilterApply": "Apply", + "@libraryFilterApply": { + "description": "Apply filters button" + }, + "libraryFilterSource": "Source", + "@libraryFilterSource": { + "description": "Filter section - source type" + }, + "libraryFilterQuality": "Quality", + "@libraryFilterQuality": { + "description": "Filter section - audio quality" + }, + "libraryFilterQualityHiRes": "Hi-Res (24bit)", + "@libraryFilterQualityHiRes": { + "description": "Filter option - high resolution audio" + }, + "libraryFilterQualityCD": "CD (16bit)", + "@libraryFilterQualityCD": { + "description": "Filter option - CD quality audio" + }, + "libraryFilterQualityLossy": "Lossy", + "@libraryFilterQualityLossy": { + "description": "Filter option - lossy compressed audio" + }, + "libraryFilterFormat": "Format", + "@libraryFilterFormat": { + "description": "Filter section - file format" + }, + "libraryFilterDate": "Date Added", + "@libraryFilterDate": { + "description": "Filter section - date range" + }, + "libraryFilterDateToday": "Today", + "@libraryFilterDateToday": { + "description": "Filter option - today only" + }, + "libraryFilterDateWeek": "This Week", + "@libraryFilterDateWeek": { + "description": "Filter option - this week" + }, + "libraryFilterDateMonth": "This Month", + "@libraryFilterDateMonth": { + "description": "Filter option - this month" + }, + "libraryFilterDateYear": "This Year", + "@libraryFilterDateYear": { + "description": "Filter option - this year" + }, + "libraryFilterSort": "Sort", + "@libraryFilterSort": { + "description": "Filter section - sort order" + }, + "libraryFilterSortLatest": "Latest", + "@libraryFilterSortLatest": { + "description": "Sort option - newest first" + }, + "libraryFilterSortOldest": "Oldest", + "@libraryFilterSortOldest": { + "description": "Sort option - oldest first" + }, + "libraryFilterActive": "{count} filter(s) active", + "@libraryFilterActive": { + "description": "Badge showing number of active filters", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "timeJustNow": "Just now", + "@timeJustNow": { + "description": "Relative time - less than a minute ago" + }, + "timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}", + "@timeMinutesAgo": { + "description": "Relative time - minutes ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}", + "@timeHoursAgo": { + "description": "Relative time - hours ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "storageSwitchTitle": "Switch Storage Mode", + "@storageSwitchTitle": { + "description": "Dialog title when switching storage mode" + }, + "storageSwitchToSafTitle": "Switch to SAF Storage?", + "@storageSwitchToSafTitle": { + "description": "Dialog title when switching to SAF" + }, + "storageSwitchToAppTitle": "Switch to App Storage?", + "@storageSwitchToAppTitle": { + "description": "Dialog title when switching to app storage" + }, + "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", + "@storageSwitchToSafMessage": { + "description": "Explanation when switching to SAF" + }, + "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", + "@storageSwitchToAppMessage": { + "description": "Explanation when switching to app storage" + }, + "storageSwitchExistingDownloads": "Existing Downloads", + "@storageSwitchExistingDownloads": { + "description": "Section header for existing downloads info" + }, + "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", + "@storageSwitchExistingDownloadsInfo": { + "description": "Info about existing downloads count", + "placeholders": { + "count": { + "type": "int" + }, + "mode": { + "type": "String" + } + } + }, + "storageSwitchNewDownloads": "New Downloads", + "@storageSwitchNewDownloads": { + "description": "Section header for new downloads info" + }, + "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", + "@storageSwitchNewDownloadsLocation": { + "description": "Shows where new downloads will go", + "placeholders": { + "location": { + "type": "String" + } + } + }, + "storageSwitchContinue": "Continue", + "@storageSwitchContinue": { + "description": "Button to proceed with storage switch" + }, + "storageSwitchSelectFolder": "Select SAF Folder", + "@storageSwitchSelectFolder": { + "description": "Button to select SAF folder" + }, + "storageAppStorage": "App Storage", + "@storageAppStorage": { + "description": "Label for app storage mode" + }, + "storageSafStorage": "SAF Storage", + "@storageSafStorage": { + "description": "Label for SAF storage mode" + }, + "storageModeBadge": "Storage: {mode}", + "@storageModeBadge": { + "description": "Badge showing storage mode for a track", + "placeholders": { + "mode": { + "type": "String" + } + } + }, + "storageStatsTitle": "Storage Statistics", + "@storageStatsTitle": { + "description": "Section title for storage stats" + }, + "storageStatsAppCount": "{count} tracks in App Storage", + "@storageStatsAppCount": { + "description": "Count of tracks in app storage", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "storageStatsSafCount": "{count} tracks in SAF Storage", + "@storageStatsSafCount": { + "description": "Count of tracks in SAF storage", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "storageModeInfo": "Your files are stored in multiple locations", + "@storageModeInfo": { + "description": "Info when user has files in both storage modes" + }, + "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", + "@tutorialWelcomeTitle": { + "description": "Tutorial welcome page title" + }, + "tutorialWelcomeDesc": "Let's learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.", + "@tutorialWelcomeDesc": { + "description": "Tutorial welcome page description" + }, + "tutorialWelcomeTip1": "Download music from Spotify, Deezer, or paste any supported URL", + "@tutorialWelcomeTip1": { + "description": "Tutorial welcome tip 1" + }, + "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "@tutorialWelcomeTip2": { + "description": "Tutorial welcome tip 2" + }, + "tutorialWelcomeTip3": "Automatic metadata, cover art, and lyrics embedding", + "@tutorialWelcomeTip3": { + "description": "Tutorial welcome tip 3" + }, + "tutorialSearchTitle": "Finding Music", + "@tutorialSearchTitle": { + "description": "Tutorial search page title" + }, + "tutorialSearchDesc": "There are two easy ways to find music you want to download.", + "@tutorialSearchDesc": { + "description": "Tutorial search page description" + }, + "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", + "@tutorialSearchTip1": { + "description": "Tutorial search tip 1" + }, + "tutorialSearchTip2": "Or type the song name, artist, or album to search", + "@tutorialSearchTip2": { + "description": "Tutorial search tip 2" + }, + "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", + "@tutorialSearchTip3": { + "description": "Tutorial search tip 3" + }, + "tutorialDownloadTitle": "Downloading Music", + "@tutorialDownloadTitle": { + "description": "Tutorial download page title" + }, + "tutorialDownloadDesc": "Downloading music is simple and fast. Here's how it works.", + "@tutorialDownloadDesc": { + "description": "Tutorial download page description" + }, + "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", + "@tutorialDownloadTip1": { + "description": "Tutorial download tip 1" + }, + "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", + "@tutorialDownloadTip2": { + "description": "Tutorial download tip 2" + }, + "tutorialDownloadTip3": "Download entire albums or playlists with one tap", + "@tutorialDownloadTip3": { + "description": "Tutorial download tip 3" + }, + "tutorialLibraryTitle": "Your Library", + "@tutorialLibraryTitle": { + "description": "Tutorial library page title" + }, + "tutorialLibraryDesc": "All your downloaded music is organized in the Library tab.", + "@tutorialLibraryDesc": { + "description": "Tutorial library page description" + }, + "tutorialLibraryTip1": "View download progress and queue in the Library tab", + "@tutorialLibraryTip1": { + "description": "Tutorial library tip 1" + }, + "tutorialLibraryTip2": "Tap any track to play it with your music player", + "@tutorialLibraryTip2": { + "description": "Tutorial library tip 2" + }, + "tutorialLibraryTip3": "Switch between list and grid view for better browsing", + "@tutorialLibraryTip3": { + "description": "Tutorial library tip 3" + }, + "tutorialExtensionsTitle": "Extensions", + "@tutorialExtensionsTitle": { + "description": "Tutorial extensions page title" + }, + "tutorialExtensionsDesc": "Extend the app's capabilities with community extensions.", + "@tutorialExtensionsDesc": { + "description": "Tutorial extensions page description" + }, + "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "@tutorialExtensionsTip1": { + "description": "Tutorial extensions tip 1" + }, + "tutorialExtensionsTip2": "Add new download providers or search sources", + "@tutorialExtensionsTip2": { + "description": "Tutorial extensions tip 2" + }, + "tutorialExtensionsTip3": "Get lyrics, enhanced metadata, and more features", + "@tutorialExtensionsTip3": { + "description": "Tutorial extensions tip 3" + }, + "tutorialSettingsTitle": "Customize Your Experience", + "@tutorialSettingsTitle": { + "description": "Tutorial settings page title" + }, + "tutorialSettingsDesc": "Personalize the app in Settings to match your preferences.", + "@tutorialSettingsDesc": { + "description": "Tutorial settings page description" + }, + "tutorialSettingsTip1": "Change download location and folder organization", + "@tutorialSettingsTip1": { + "description": "Tutorial settings tip 1" + }, + "tutorialSettingsTip2": "Set default audio quality and format preferences", + "@tutorialSettingsTip2": { + "description": "Tutorial settings tip 2" + }, + "tutorialSettingsTip3": "Customize app theme and appearance", + "@tutorialSettingsTip3": { + "description": "Tutorial settings tip 3" + }, + "tutorialReadyMessage": "You're all set! Start downloading your favorite music now.", + "@tutorialReadyMessage": { + "description": "Tutorial completion message" + }, + "tutorialExample": "EXAMPLE", + "@tutorialExample": { + "description": "Example label in tutorial" + }, + "libraryForceFullScan": "Force Full Scan", + "@libraryForceFullScan": { + "description": "Button to force a complete rescan of library" + }, + "libraryForceFullScanSubtitle": "Rescan all files, ignoring cache", + "@libraryForceFullScanSubtitle": { + "description": "Subtitle for force full scan button" + }, + "cleanupOrphanedDownloads": "Cleanup Orphaned Downloads", + "@cleanupOrphanedDownloads": { + "description": "Button to remove history entries for deleted files" + }, + "cleanupOrphanedDownloadsSubtitle": "Remove history entries for files that no longer exist", + "@cleanupOrphanedDownloadsSubtitle": { + "description": "Subtitle for orphaned cleanup button" + }, + "cleanupOrphanedDownloadsResult": "Removed {count} orphaned entries from history", + "@cleanupOrphanedDownloadsResult": { + "description": "Snackbar after orphan cleanup", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cleanupOrphanedDownloadsNone": "No orphaned entries found", + "@cleanupOrphanedDownloadsNone": { + "description": "Snackbar when no orphans found" + }, + "cacheTitle": "Storage & Cache", + "@cacheTitle": { + "description": "Cache management page title" + }, + "cacheSummaryTitle": "Cache overview", + "@cacheSummaryTitle": { + "description": "Heading for cache summary card" + }, + "cacheSummarySubtitle": "Clearing cache will not remove downloaded music files.", + "@cacheSummarySubtitle": { + "description": "Helper text for cache summary card" + }, + "cacheEstimatedTotal": "Estimated cache usage: {size}", + "@cacheEstimatedTotal": { + "description": "Total cache size shown in summary", + "placeholders": { + "size": { + "type": "String" + } + } + }, + "cacheSectionStorage": "Cached Data", + "@cacheSectionStorage": { + "description": "Section header for cache entries" + }, + "cacheSectionMaintenance": "Maintenance", + "@cacheSectionMaintenance": { + "description": "Section header for cleanup actions" + }, + "cacheAppDirectory": "App cache directory", + "@cacheAppDirectory": { + "description": "Cache item title for app cache directory" + }, + "cacheAppDirectoryDesc": "HTTP responses, WebView data, and other temporary app data.", + "@cacheAppDirectoryDesc": { + "description": "Description of what app cache directory contains" + }, + "cacheTempDirectory": "Temporary directory", + "@cacheTempDirectory": { + "description": "Cache item title for temporary files directory" + }, + "cacheTempDirectoryDesc": "Temporary files from downloads and audio conversion.", + "@cacheTempDirectoryDesc": { + "description": "Description of what temporary directory contains" + }, + "cacheCoverImage": "Cover image cache", + "@cacheCoverImage": { + "description": "Cache item title for persistent cover images" + }, + "cacheCoverImageDesc": "Downloaded album and track cover art. Will re-download when viewed.", + "@cacheCoverImageDesc": { + "description": "Description of what cover image cache contains" + }, + "cacheLibraryCover": "Library cover cache", + "@cacheLibraryCover": { + "description": "Cache item title for local library cover art images" + }, + "cacheLibraryCoverDesc": "Cover art extracted from local music files. Will re-extract on next scan.", + "@cacheLibraryCoverDesc": { + "description": "Description of what library cover cache contains" + }, + "cacheExploreFeed": "Explore feed cache", + "@cacheExploreFeed": { + "description": "Cache item title for explore home feed cache" + }, + "cacheExploreFeedDesc": "Explore tab content (new releases, trending). Will refresh on next visit.", + "@cacheExploreFeedDesc": { + "description": "Description of what explore feed cache contains" + }, + "cacheTrackLookup": "Track lookup cache", + "@cacheTrackLookup": { + "description": "Cache item title for track ID lookup cache" + }, + "cacheTrackLookupDesc": "Spotify/Deezer track ID lookups. Clearing may slow next few searches.", + "@cacheTrackLookupDesc": { + "description": "Description of what track lookup cache contains" + }, + "cacheCleanupUnusedDesc": "Remove orphaned download history and library entries for missing files.", + "@cacheCleanupUnusedDesc": { + "description": "Description of what cleanup unused data does" + }, + "cacheNoData": "No cached data", + "@cacheNoData": { + "description": "Label when cache category has no data" + }, + "cacheSizeWithFiles": "{size} in {count} files", + "@cacheSizeWithFiles": { + "description": "Cache size and file count", + "placeholders": { + "size": { + "type": "String" + }, + "count": { + "type": "int" + } + } + }, + "cacheSizeOnly": "{size}", + "@cacheSizeOnly": { + "description": "Cache size only", + "placeholders": { + "size": { + "type": "String" + } + } + }, + "cacheEntries": "{count} entries", + "@cacheEntries": { + "description": "Track cache entry count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cacheClearSuccess": "Cleared: {target}", + "@cacheClearSuccess": { + "description": "Snackbar after clearing selected cache", + "placeholders": { + "target": { + "type": "String" + } + } + }, + "cacheClearConfirmTitle": "Clear cache?", + "@cacheClearConfirmTitle": { + "description": "Dialog title before clearing one cache category" + }, + "cacheClearConfirmMessage": "This will clear cached data for {target}. Downloaded music files will not be deleted.", + "@cacheClearConfirmMessage": { + "description": "Dialog message before clearing selected cache", + "placeholders": { + "target": { + "type": "String" + } + } + }, + "cacheClearAllConfirmTitle": "Clear all cache?", + "@cacheClearAllConfirmTitle": { + "description": "Dialog title before clearing all caches" + }, + "cacheClearAllConfirmMessage": "This will clear all cache categories on this page. Downloaded music files will not be deleted.", + "@cacheClearAllConfirmMessage": { + "description": "Dialog message before clearing all caches" + }, + "cacheClearAll": "Clear all cache", + "@cacheClearAll": { + "description": "Button label to clear all caches" + }, + "cacheCleanupUnused": "Cleanup unused data", + "@cacheCleanupUnused": { + "description": "Action title for cleaning unused entries" + }, + "cacheCleanupUnusedSubtitle": "Remove orphaned download history and missing library entries", + "@cacheCleanupUnusedSubtitle": { + "description": "Subtitle for cleanup unused data action" + }, + "cacheCleanupResult": "Cleanup completed: {downloadCount} orphaned downloads, {libraryCount} missing library entries", + "@cacheCleanupResult": { + "description": "Snackbar after unused data cleanup", + "placeholders": { + "downloadCount": { + "type": "int" + }, + "libraryCount": { + "type": "int" + } + } + }, + "cacheRefreshStats": "Refresh stats", + "@cacheRefreshStats": { + "description": "Button label to refresh cache statistics" + }, + "trackSaveCoverArt": "Save Cover Art", + "@trackSaveCoverArt": { + "description": "Menu action - save album cover art as file" + }, + "trackSaveCoverArtSubtitle": "Save album art as .jpg file", + "@trackSaveCoverArtSubtitle": { + "description": "Subtitle for save cover art action" + }, + "trackSaveLyrics": "Save Lyrics (.lrc)", + "@trackSaveLyrics": { + "description": "Menu action - save lyrics as .lrc file" + }, + "trackSaveLyricsSubtitle": "Fetch and save lyrics as .lrc file", + "@trackSaveLyricsSubtitle": { + "description": "Subtitle for save lyrics action" + }, + "trackSaveLyricsProgress": "Saving lyrics...", + "@trackSaveLyricsProgress": { + "description": "Snackbar while saving lyrics to file" + }, + "trackReEnrich": "Re-enrich Metadata", + "@trackReEnrich": { + "description": "Menu action - re-embed metadata into audio file" + }, + "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", + "@trackReEnrichSubtitle": { + "description": "Subtitle for re-enrich metadata action" + }, + "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", + "@trackReEnrichOnlineSubtitle": { + "description": "Subtitle for re-enrich metadata action for local items" + }, + "trackEditMetadata": "Edit Metadata", + "@trackEditMetadata": { + "description": "Menu action - edit embedded metadata" + }, + "trackCoverSaved": "Cover art saved to {fileName}", + "@trackCoverSaved": { + "description": "Snackbar after cover art saved", + "placeholders": { + "fileName": { + "type": "String" + } + } + }, + "trackCoverNoSource": "No cover art source available", + "@trackCoverNoSource": { + "description": "Snackbar when no cover art URL or embedded cover" + }, + "trackLyricsSaved": "Lyrics saved to {fileName}", + "@trackLyricsSaved": { + "description": "Snackbar after lyrics saved", + "placeholders": { + "fileName": { + "type": "String" + } + } + }, + "trackReEnrichProgress": "Re-enriching metadata...", + "@trackReEnrichProgress": { + "description": "Snackbar while re-enriching metadata" + }, + "trackReEnrichSearching": "Searching metadata online...", + "@trackReEnrichSearching": { + "description": "Snackbar while searching metadata from internet for local items" + }, + "trackReEnrichSuccess": "Metadata re-enriched successfully", + "@trackReEnrichSuccess": { + "description": "Snackbar after successful re-enrichment" + }, + "trackReEnrichFfmpegFailed": "FFmpeg metadata embed failed", + "@trackReEnrichFfmpegFailed": { + "description": "Snackbar when FFmpeg embed fails for MP3/Opus" + }, + "trackSaveFailed": "Failed: {error}", + "@trackSaveFailed": { + "description": "Snackbar when save operation fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "trackConvertFormat": "Convert Format", + "@trackConvertFormat": { + "description": "Menu item - convert audio format" + }, + "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "@trackConvertFormatSubtitle": { + "description": "Subtitle for convert format menu item" + }, + "trackConvertTitle": "Convert Audio", + "@trackConvertTitle": { + "description": "Title of convert bottom sheet" + }, + "trackConvertTargetFormat": "Target Format", + "@trackConvertTargetFormat": { + "description": "Label for format selection" + }, + "trackConvertBitrate": "Bitrate", + "@trackConvertBitrate": { + "description": "Label for bitrate selection" + }, + "trackConvertConfirmTitle": "Confirm Conversion", + "@trackConvertConfirmTitle": { + "description": "Confirmation dialog title" + }, + "trackConvertConfirmMessage": "Convert from {sourceFormat} to {targetFormat} at {bitrate}?\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessage": { + "description": "Confirmation dialog message", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "trackConvertConverting": "Converting audio...", + "@trackConvertConverting": { + "description": "Snackbar while converting" + }, + "trackConvertSuccess": "Converted to {format} successfully", + "@trackConvertSuccess": { + "description": "Snackbar after successful conversion", + "placeholders": { + "format": { + "type": "String" + } + } + }, + "trackConvertFailed": "Conversion failed", + "@trackConvertFailed": { + "description": "Snackbar when conversion fails" + } +} \ No newline at end of file From adbed63196224fe40a56a904d509bef70a543444 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:24 +0700 Subject: [PATCH 012/104] New translations app_en.arb (Russian) --- lib/l10n/arb/app_ru.arb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 2ca3c6bf..9db59bc7 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -1532,6 +1532,14 @@ "@filenameHint": { "description": "Default filename format hint" }, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, "folderOrganization": "Организация папок", "@folderOrganization": { "description": "Setting title - folder structure" From 1f52a6c9e0d83d7fbfb67def8df2de66fb13a54b Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:25 +0700 Subject: [PATCH 013/104] New translations app_en.arb (Chinese Simplified) --- lib/l10n/arb/app_zh_CN.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_zh_CN.arb b/lib/l10n/arb/app_zh_CN.arb index f200ce55..4457abbf 100644 --- a/lib/l10n/arb/app_zh_CN.arb +++ b/lib/l10n/arb/app_zh_CN.arb @@ -1,5 +1,5 @@ { - "@@locale": "zh_CN", + "@@locale": "zh-CN", "@@last_modified": "2026-01-16", "appName": "SpotiFLAC", "@appName": { @@ -1532,6 +1532,14 @@ "@filenameHint": { "description": "Default filename format hint" }, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, "folderOrganization": "Folder Organization", "@folderOrganization": { "description": "Setting title - folder structure" From a8dcf3113c02a09016340bdcfa0b4b7c713e05d2 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:26 +0700 Subject: [PATCH 014/104] New translations app_en.arb (Chinese Traditional) --- lib/l10n/arb/app_zh_TW.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_zh_TW.arb b/lib/l10n/arb/app_zh_TW.arb index 168506b3..e103b61f 100644 --- a/lib/l10n/arb/app_zh_TW.arb +++ b/lib/l10n/arb/app_zh_TW.arb @@ -1,5 +1,5 @@ { - "@@locale": "zh_TW", + "@@locale": "zh-TW", "@@last_modified": "2026-01-16", "appName": "SpotiFLAC", "@appName": { @@ -1532,6 +1532,14 @@ "@filenameHint": { "description": "Default filename format hint" }, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, "folderOrganization": "Folder Organization", "@folderOrganization": { "description": "Setting title - folder structure" From d2a183b52d5f94e89f1cb7d6f916467c851d95fb Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:27 +0700 Subject: [PATCH 015/104] New translations app_en.arb (Indonesian) --- lib/l10n/arb/app_id.arb | 7750 ++++++++++++++++++++------------------- 1 file changed, 3879 insertions(+), 3871 deletions(-) diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index b26b5441..c87ceedb 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -1,3872 +1,3880 @@ -{ - "@@locale": "id", - "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", - "@appName": { - "description": "App name - DO NOT TRANSLATE" - }, - "appDescription": "Unduh lagu Spotify dalam kualitas lossless dari Tidal, Qobuz, dan Amazon Music.", - "@appDescription": { - "description": "App description shown in about page" - }, - "navHome": "Beranda", - "@navHome": { - "description": "Bottom navigation - Home tab" - }, - "navLibrary": "Library", - "@navLibrary": { - "description": "Bottom navigation - Library tab" - }, - "navHistory": "Riwayat", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, - "navSettings": "Pengaturan", - "@navSettings": { - "description": "Bottom navigation - Settings tab" - }, - "navStore": "Toko", - "@navStore": { - "description": "Bottom navigation - Extension store tab" - }, - "homeTitle": "Beranda", - "@homeTitle": { - "description": "Home screen title" - }, - "homeSearchHint": "Tempel URL Spotify atau cari...", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "Cari dengan {extensionName}...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, - "homeSubtitle": "Tempel link Spotify atau cari berdasarkan nama", - "@homeSubtitle": { - "description": "Subtitle shown below search box" - }, - "homeSupports": "Mendukung: URL Track, Album, Playlist, Artis", - "@homeSupports": { - "description": "Info text about supported URL types" - }, - "homeRecent": "Terbaru", - "@homeRecent": { - "description": "Section header for recent searches" - }, - "historyTitle": "Riwayat", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "Mengunduh ({count})", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "Terunduh", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, - "historyFilterAll": "Semua", - "@historyFilterAll": { - "description": "Filter chip - show all items" - }, - "historyFilterAlbums": "Album", - "@historyFilterAlbums": { - "description": "Filter chip - show albums only" - }, - "historyFilterSingles": "Single", - "@historyFilterSingles": { - "description": "Filter chip - show singles only" - }, - "historyTracksCount": "{count, plural, =1{1 lagu} other{{count} lagu}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural, =1{1 album} other{{count} album}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "Tidak ada riwayat unduhan", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "Lagu yang diunduh akan muncul di sini", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "Tidak ada unduhan album", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "Unduh beberapa lagu dari album untuk melihatnya di sini", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "Tidak ada unduhan single", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "Unduhan lagu satuan akan muncul di sini", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, - "historySearchHint": "Search history...", - "@historySearchHint": { - "description": "Search bar placeholder in history" - }, - "settingsTitle": "Pengaturan", - "@settingsTitle": { - "description": "Settings screen title" - }, - "settingsDownload": "Unduhan", - "@settingsDownload": { - "description": "Settings section - download options" - }, - "settingsAppearance": "Tampilan", - "@settingsAppearance": { - "description": "Settings section - visual customization" - }, - "settingsOptions": "Opsi", - "@settingsOptions": { - "description": "Settings section - app options" - }, - "settingsExtensions": "Ekstensi", - "@settingsExtensions": { - "description": "Settings section - extension management" - }, - "settingsAbout": "Tentang", - "@settingsAbout": { - "description": "Settings section - app info" - }, - "downloadTitle": "Unduhan", - "@downloadTitle": { - "description": "Download settings page title" - }, - "downloadLocation": "Lokasi Unduhan", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "Pilih tempat menyimpan file", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "Lokasi default", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "Layanan Default", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "Layanan yang digunakan untuk unduhan", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "Kualitas Default", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "Tanya Kualitas Sebelum Unduh", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, - "downloadAskQualitySubtitle": "Tampilkan pemilih kualitas untuk setiap unduhan", - "@downloadAskQualitySubtitle": { - "description": "Subtitle for ask quality toggle" - }, - "downloadFilenameFormat": "Format Nama File", - "@downloadFilenameFormat": { - "description": "Setting for output filename pattern" - }, - "downloadFolderOrganization": "Organisasi Folder", - "@downloadFolderOrganization": { - "description": "Setting for folder structure" - }, - "downloadSeparateSingles": "Pisahkan Single", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "Letakkan lagu satuan di folder terpisah", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "Terbaik", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 kbps", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 kbps", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, - "appearanceTitle": "Tampilan", - "@appearanceTitle": { - "description": "Appearance settings page title" - }, - "appearanceTheme": "Tema", - "@appearanceTheme": { - "description": "Theme mode setting" - }, - "appearanceThemeSystem": "Sistem", - "@appearanceThemeSystem": { - "description": "Follow system theme" - }, - "appearanceThemeLight": "Terang", - "@appearanceThemeLight": { - "description": "Light theme" - }, - "appearanceThemeDark": "Gelap", - "@appearanceThemeDark": { - "description": "Dark theme" - }, - "appearanceDynamicColor": "Warna Dinamis", - "@appearanceDynamicColor": { - "description": "Material You dynamic colors" - }, - "appearanceDynamicColorSubtitle": "Gunakan warna dari wallpaper Anda", - "@appearanceDynamicColorSubtitle": { - "description": "Subtitle for dynamic color" - }, - "appearanceAccentColor": "Warna Aksen", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, - "appearanceHistoryView": "Tampilan Riwayat", - "@appearanceHistoryView": { - "description": "Layout style for history" - }, - "appearanceHistoryViewList": "Daftar", - "@appearanceHistoryViewList": { - "description": "List layout option" - }, - "appearanceHistoryViewGrid": "Grid", - "@appearanceHistoryViewGrid": { - "description": "Grid layout option" - }, - "optionsTitle": "Opsi", - "@optionsTitle": { - "description": "Options settings page title" - }, - "optionsSearchSource": "Sumber Pencarian", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, - "optionsPrimaryProvider": "Provider Utama", - "@optionsPrimaryProvider": { - "description": "Main search provider setting" - }, - "optionsPrimaryProviderSubtitle": "Layanan yang digunakan saat mencari berdasarkan nama lagu.", - "@optionsPrimaryProviderSubtitle": { - "description": "Subtitle for primary provider" - }, - "optionsUsingExtension": "Menggunakan ekstensi: {extensionName}", - "@optionsUsingExtension": { - "description": "Shows active extension name", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "optionsSwitchBack": "Ketuk Deezer atau Spotify untuk beralih dari ekstensi", - "@optionsSwitchBack": { - "description": "Hint to switch back to built-in providers" - }, - "optionsAutoFallback": "Auto Fallback", - "@optionsAutoFallback": { - "description": "Auto-retry with other services" - }, - "optionsAutoFallbackSubtitle": "Coba layanan lain jika unduhan gagal", - "@optionsAutoFallbackSubtitle": { - "description": "Subtitle for auto fallback" - }, - "optionsUseExtensionProviders": "Gunakan Provider Ekstensi", - "@optionsUseExtensionProviders": { - "description": "Enable extension download providers" - }, - "optionsUseExtensionProvidersOn": "Ekstensi akan dicoba terlebih dahulu", - "@optionsUseExtensionProvidersOn": { - "description": "Status when extension providers enabled" - }, - "optionsUseExtensionProvidersOff": "Hanya menggunakan provider bawaan", - "@optionsUseExtensionProvidersOff": { - "description": "Status when extension providers disabled" - }, - "optionsEmbedLyrics": "Sematkan Lirik", - "@optionsEmbedLyrics": { - "description": "Embed lyrics in audio files" - }, - "optionsEmbedLyricsSubtitle": "Sematkan lirik sinkron ke file FLAC", - "@optionsEmbedLyricsSubtitle": { - "description": "Subtitle for embed lyrics" - }, - "optionsMaxQualityCover": "Cover Kualitas Maksimal", - "@optionsMaxQualityCover": { - "description": "Download highest quality album art" - }, - "optionsMaxQualityCoverSubtitle": "Unduh cover art resolusi tertinggi", - "@optionsMaxQualityCoverSubtitle": { - "description": "Subtitle for max quality cover" - }, - "optionsConcurrentDownloads": "Unduhan Bersamaan", - "@optionsConcurrentDownloads": { - "description": "Number of parallel downloads" - }, - "optionsConcurrentSequential": "Berurutan (1 per waktu)", - "@optionsConcurrentSequential": { - "description": "Download one at a time" - }, - "optionsConcurrentParallel": "{count} unduhan paralel", - "@optionsConcurrentParallel": { - "description": "Multiple parallel downloads", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "optionsConcurrentWarning": "Unduhan paralel dapat memicu pembatasan rate", - "@optionsConcurrentWarning": { - "description": "Warning about rate limits" - }, - "optionsExtensionStore": "Toko Ekstensi", - "@optionsExtensionStore": { - "description": "Show/hide store tab" - }, - "optionsExtensionStoreSubtitle": "Tampilkan tab Toko di navigasi", - "@optionsExtensionStoreSubtitle": { - "description": "Subtitle for extension store toggle" - }, - "optionsCheckUpdates": "Periksa Pembaruan", - "@optionsCheckUpdates": { - "description": "Auto update check toggle" - }, - "optionsCheckUpdatesSubtitle": "Beritahu saat versi baru tersedia", - "@optionsCheckUpdatesSubtitle": { - "description": "Subtitle for update check" - }, - "optionsUpdateChannel": "Saluran Pembaruan", - "@optionsUpdateChannel": { - "description": "Stable vs preview releases" - }, - "optionsUpdateChannelStable": "Hanya rilis stabil", - "@optionsUpdateChannelStable": { - "description": "Only stable updates" - }, - "optionsUpdateChannelPreview": "Dapatkan rilis preview", - "@optionsUpdateChannelPreview": { - "description": "Include beta/preview updates" - }, - "optionsUpdateChannelWarning": "Preview mungkin mengandung bug atau fitur belum lengkap", - "@optionsUpdateChannelWarning": { - "description": "Warning about preview channel" - }, - "optionsClearHistory": "Hapus Riwayat Unduhan", - "@optionsClearHistory": { - "description": "Delete all download history" - }, - "optionsClearHistorySubtitle": "Hapus semua lagu dari riwayat", - "@optionsClearHistorySubtitle": { - "description": "Subtitle for clear history" - }, - "optionsDetailedLogging": "Log Detail", - "@optionsDetailedLogging": { - "description": "Enable verbose logs for debugging" - }, - "optionsDetailedLoggingOn": "Log detail sedang direkam", - "@optionsDetailedLoggingOn": { - "description": "Status when logging enabled" - }, - "optionsDetailedLoggingOff": "Aktifkan untuk laporan bug", - "@optionsDetailedLoggingOff": { - "description": "Status when logging disabled" - }, - "optionsSpotifyCredentials": "Kredensial Spotify", - "@optionsSpotifyCredentials": { - "description": "Spotify API credentials setting" - }, - "optionsSpotifyCredentialsConfigured": "Client ID: {clientId}...", - "@optionsSpotifyCredentialsConfigured": { - "description": "Shows configured client ID preview", - "placeholders": { - "clientId": { - "type": "String" - } - } - }, - "optionsSpotifyCredentialsRequired": "Diperlukan - ketuk untuk mengatur", - "@optionsSpotifyCredentialsRequired": { - "description": "Prompt to set up credentials" - }, - "optionsSpotifyWarning": "Spotify memerlukan kredensial API Anda sendiri. Dapatkan gratis dari developer.spotify.com", - "@optionsSpotifyWarning": { - "description": "Info about Spotify API requirement" - }, - "optionsSpotifyDeprecationWarning": "Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.", - "@optionsSpotifyDeprecationWarning": { - "description": "Warning about Spotify API deprecation" - }, - "extensionsTitle": "Ekstensi", - "@extensionsTitle": { - "description": "Extensions page title" - }, - "extensionsInstalled": "Ekstensi Terpasang", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "Tidak ada ekstensi terpasang", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "Pasang ekstensi dari tab Toko", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "Aktif", - "@extensionsEnabled": { - "description": "Extension status - active" - }, - "extensionsDisabled": "Nonaktif", - "@extensionsDisabled": { - "description": "Extension status - inactive" - }, - "extensionsVersion": "Versi {version}", - "@extensionsVersion": { - "description": "Extension version display", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "extensionsAuthor": "oleh {author}", - "@extensionsAuthor": { - "description": "Extension author credit", - "placeholders": { - "author": { - "type": "String" - } - } - }, - "extensionsUninstall": "Copot", - "@extensionsUninstall": { - "description": "Uninstall extension button" - }, - "extensionsSetAsSearch": "Jadikan Provider Pencarian", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, - "storeTitle": "Toko Ekstensi", - "@storeTitle": { - "description": "Store screen title" - }, - "storeSearch": "Cari ekstensi...", - "@storeSearch": { - "description": "Store search placeholder" - }, - "storeInstall": "Pasang", - "@storeInstall": { - "description": "Install extension button" - }, - "storeInstalled": "Terpasang", - "@storeInstalled": { - "description": "Already installed badge" - }, - "storeUpdate": "Perbarui", - "@storeUpdate": { - "description": "Update available button" - }, - "aboutTitle": "Tentang", - "@aboutTitle": { - "description": "About page title" - }, - "aboutContributors": "Kontributor", - "@aboutContributors": { - "description": "Section for contributors" - }, - "aboutMobileDeveloper": "Pengembang versi mobile", - "@aboutMobileDeveloper": { - "description": "Role description for mobile dev" - }, - "aboutOriginalCreator": "Pembuat SpotiFLAC asli", - "@aboutOriginalCreator": { - "description": "Role description for original creator" - }, - "aboutLogoArtist": "Seniman berbakat yang membuat logo aplikasi kita yang indah!", - "@aboutLogoArtist": { - "description": "Role description for logo artist" - }, - "aboutTranslators": "Translators", - "@aboutTranslators": { - "description": "Section for translators" - }, - "aboutSpecialThanks": "Terima Kasih Khusus", - "@aboutSpecialThanks": { - "description": "Section for special thanks" - }, - "aboutLinks": "Tautan", - "@aboutLinks": { - "description": "Section for external links" - }, - "aboutMobileSource": "Kode sumber mobile", - "@aboutMobileSource": { - "description": "Link to mobile GitHub repo" - }, - "aboutPCSource": "Kode sumber PC", - "@aboutPCSource": { - "description": "Link to PC GitHub repo" - }, - "aboutReportIssue": "Laporkan masalah", - "@aboutReportIssue": { - "description": "Link to report bugs" - }, - "aboutReportIssueSubtitle": "Laporkan masalah yang Anda temui", - "@aboutReportIssueSubtitle": { - "description": "Subtitle for report issue" - }, - "aboutFeatureRequest": "Permintaan fitur", - "@aboutFeatureRequest": { - "description": "Link to suggest features" - }, - "aboutFeatureRequestSubtitle": "Sarankan fitur baru untuk aplikasi", - "@aboutFeatureRequestSubtitle": { - "description": "Subtitle for feature request" - }, - "aboutTelegramChannel": "Telegram Channel", - "@aboutTelegramChannel": { - "description": "Link to Telegram channel" - }, - "aboutTelegramChannelSubtitle": "Announcements and updates", - "@aboutTelegramChannelSubtitle": { - "description": "Subtitle for Telegram channel" - }, - "aboutTelegramChat": "Telegram Community", - "@aboutTelegramChat": { - "description": "Link to Telegram chat group" - }, - "aboutTelegramChatSubtitle": "Chat with other users", - "@aboutTelegramChatSubtitle": { - "description": "Subtitle for Telegram chat" - }, - "aboutSocial": "Social", - "@aboutSocial": { - "description": "Section for social links" - }, - "aboutSupport": "Dukungan", - "@aboutSupport": { - "description": "Section for support/donation links" - }, - "aboutApp": "Aplikasi", - "@aboutApp": { - "description": "Section for app info" - }, - "aboutVersion": "Versi", - "@aboutVersion": { - "description": "Version info label" - }, - "aboutBinimumDesc": "Pembuat QQDL & HiFi API. Tanpa API ini, unduhan Tidal tidak akan ada!", - "@aboutBinimumDesc": { - "description": "Credit description for binimum" - }, - "aboutSachinsenalDesc": "Pembuat proyek HiFi asli. Fondasi dari integrasi Tidal!", - "@aboutSachinsenalDesc": { - "description": "Credit description for sachinsenal0x64" - }, - "aboutSjdonadoDesc": "Creator of I Don't Have Spotify (IDHS). The fallback link resolver that saves the day!", - "@aboutSjdonadoDesc": { - "description": "Credit description for sjdonado" - }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "API luar biasa untuk unduhan Amazon Music. Terima kasih sudah membuatnya gratis!", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, - "aboutDabMusic": "DAB Music", - "@aboutDabMusic": { - "description": "Name of Qobuz API service - DO NOT TRANSLATE" - }, - "aboutDabMusicDesc": "API streaming Qobuz terbaik. Unduhan Hi-Res tidak akan mungkin tanpa ini!", - "@aboutDabMusicDesc": { - "description": "Credit for DAB Music API" - }, - "aboutSpotiSaver": "SpotiSaver", - "@aboutSpotiSaver": { - "description": "Name of SpotiSaver API service - DO NOT TRANSLATE" - }, - "aboutSpotiSaverDesc": "Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!", - "@aboutSpotiSaverDesc": { - "description": "Credit for SpotiSaver API" - }, - "aboutAppDescription": "Unduh lagu Spotify dalam kualitas lossless dari Tidal, Qobuz, dan Amazon Music.", - "@aboutAppDescription": { - "description": "App description in header card" - }, - "albumTitle": "Album", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural, =1{1 lagu} other{{count} lagu}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "Unduh Semua", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "Unduh Sisanya", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "Playlist", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "Artis", - "@artistTitle": { - "description": "Artist screen title" - }, - "artistAlbums": "Album", - "@artistAlbums": { - "description": "Section header for artist albums" - }, - "artistSingles": "Single & EP", - "@artistSingles": { - "description": "Section header for singles/EPs" - }, - "artistCompilations": "Kompilasi", - "@artistCompilations": { - "description": "Section header for compilations" - }, - "artistReleases": "{count, plural, =1{1 rilis} other{{count} rilis}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "artistPopular": "Populer", - "@artistPopular": { - "description": "Section header for popular/top tracks" - }, - "artistMonthlyListeners": "{count} pendengar bulanan", - "@artistMonthlyListeners": { - "description": "Monthly listener count display", - "placeholders": { - "count": { - "type": "String", - "description": "Formatted listener count" - } - } - }, - "trackMetadataTitle": "Info Lagu", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "Artis", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "Album", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "Durasi", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "Kualitas", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "Lokasi File", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "Diunduh", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, - "trackMetadataService": "Layanan", - "@trackMetadataService": { - "description": "Metadata field - download service used" - }, - "trackMetadataPlay": "Putar", - "@trackMetadataPlay": { - "description": "Action button - play track" - }, - "trackMetadataShare": "Bagikan", - "@trackMetadataShare": { - "description": "Action button - share track" - }, - "trackMetadataDelete": "Hapus", - "@trackMetadataDelete": { - "description": "Action button - delete track" - }, - "trackMetadataRedownload": "Unduh ulang", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "Buka Folder", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "Selamat Datang di SpotiFLAC", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "Mari mulai pengaturan", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "Izin Penyimpanan", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "Diperlukan untuk menyimpan file unduhan", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "Izin diberikan", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "Izin ditolak", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, - "setupGrantPermission": "Berikan Izin", - "@setupGrantPermission": { - "description": "Button to request permission" - }, - "setupDownloadLocation": "Lokasi Unduhan", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "Pilih Folder", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "Lanjutkan", - "@setupContinue": { - "description": "Continue to next step button" - }, - "setupSkip": "Lewati untuk sekarang", - "@setupSkip": { - "description": "Skip current step button" - }, - "setupStorageAccessRequired": "Akses Penyimpanan Diperlukan", - "@setupStorageAccessRequired": { - "description": "Title when storage access needed" - }, - "setupStorageAccessMessage": "SpotiFLAC membutuhkan izin \"Akses semua file\" untuk menyimpan file musik ke folder pilihan Anda.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, - "setupStorageAccessMessageAndroid11": "Android 11+ memerlukan izin \"Akses semua file\" untuk menyimpan file ke folder unduhan pilihan Anda.", - "@setupStorageAccessMessageAndroid11": { - "description": "Android 11+ specific explanation" - }, - "setupOpenSettings": "Buka Pengaturan", - "@setupOpenSettings": { - "description": "Button to open system settings" - }, - "setupPermissionDeniedMessage": "Izin ditolak. Harap berikan semua izin untuk melanjutkan.", - "@setupPermissionDeniedMessage": { - "description": "Error when permission denied" - }, - "setupPermissionRequired": "Izin {permissionType} Diperlukan", - "@setupPermissionRequired": { - "description": "Generic permission required title", - "placeholders": { - "permissionType": { - "type": "String", - "description": "Type of permission (Storage/Notification)" - } - } - }, - "setupPermissionRequiredMessage": "Izin {permissionType} diperlukan untuk pengalaman terbaik. Anda dapat mengubahnya nanti di Pengaturan.", - "@setupPermissionRequiredMessage": { - "description": "Generic permission required message", - "placeholders": { - "permissionType": { - "type": "String" - } - } - }, - "setupSelectDownloadFolder": "Pilih Folder Unduhan", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, - "setupUseDefaultFolder": "Gunakan Folder Default?", - "@setupUseDefaultFolder": { - "description": "Dialog title for default folder" - }, - "setupNoFolderSelected": "Tidak ada folder dipilih. Apakah Anda ingin menggunakan folder Musik default?", - "@setupNoFolderSelected": { - "description": "Prompt when no folder selected" - }, - "setupUseDefault": "Gunakan Default", - "@setupUseDefault": { - "description": "Button to use default folder" - }, - "setupDownloadLocationTitle": "Lokasi Unduhan", - "@setupDownloadLocationTitle": { - "description": "Download location dialog title" - }, - "setupDownloadLocationIosMessage": "Di iOS, unduhan disimpan ke folder Documents aplikasi. Anda dapat mengaksesnya melalui aplikasi Files.", - "@setupDownloadLocationIosMessage": { - "description": "iOS-specific folder info" - }, - "setupAppDocumentsFolder": "Folder Documents Aplikasi", - "@setupAppDocumentsFolder": { - "description": "iOS documents folder option" - }, - "setupAppDocumentsFolderSubtitle": "Direkomendasikan - dapat diakses via aplikasi Files", - "@setupAppDocumentsFolderSubtitle": { - "description": "Subtitle for documents folder" - }, - "setupChooseFromFiles": "Pilih dari Files", - "@setupChooseFromFiles": { - "description": "iOS file picker option" - }, - "setupChooseFromFilesSubtitle": "Pilih lokasi iCloud atau lainnya", - "@setupChooseFromFilesSubtitle": { - "description": "Subtitle for file picker" - }, - "setupIosEmptyFolderWarning": "Batasan iOS: Folder kosong tidak dapat dipilih. Pilih folder dengan minimal satu file.", - "@setupIosEmptyFolderWarning": { - "description": "iOS folder selection warning" - }, - "setupIcloudNotSupported": "iCloud Drive is not supported. Please use the app Documents folder.", - "@setupIcloudNotSupported": { - "description": "Error when user selects iCloud Drive on iOS" - }, - "setupDownloadInFlac": "Unduh lagu Spotify dalam format FLAC", - "@setupDownloadInFlac": { - "description": "App tagline in setup" - }, - "setupStepStorage": "Penyimpanan", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "Notifikasi", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "Folder", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "Izin", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, - "setupStorageGranted": "Izin Penyimpanan Diberikan!", - "@setupStorageGranted": { - "description": "Success message for storage permission" - }, - "setupStorageRequired": "Izin Penyimpanan Diperlukan", - "@setupStorageRequired": { - "description": "Title when storage permission needed" - }, - "setupStorageDescription": "SpotiFLAC membutuhkan izin penyimpanan untuk menyimpan file musik yang diunduh.", - "@setupStorageDescription": { - "description": "Explanation for storage permission" - }, - "setupNotificationGranted": "Izin Notifikasi Diberikan!", - "@setupNotificationGranted": { - "description": "Success message for notification permission" - }, - "setupNotificationEnable": "Aktifkan Notifikasi", - "@setupNotificationEnable": { - "description": "Button to enable notifications" - }, - "setupNotificationDescription": "Dapatkan pemberitahuan saat unduhan selesai atau membutuhkan perhatian.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "Folder Unduhan Dipilih!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, - "setupFolderChoose": "Pilih Folder Unduhan", - "@setupFolderChoose": { - "description": "Button to choose folder" - }, - "setupFolderDescription": "Pilih folder tempat musik yang diunduh akan disimpan.", - "@setupFolderDescription": { - "description": "Explanation for folder selection" - }, - "setupChangeFolder": "Ubah Folder", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, - "setupSelectFolder": "Pilih Folder", - "@setupSelectFolder": { - "description": "Button to select folder" - }, - "setupSpotifyApiOptional": "Spotify API (Opsional)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Tambahkan kredensial Spotify API untuk hasil pencarian lebih baik dan akses ke konten eksklusif Spotify.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Gunakan Spotify API", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "Masukkan kredensial Anda di bawah", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Menggunakan Deezer (tidak perlu akun)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Masukkan Spotify Client ID", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Masukkan Spotify Client Secret", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Dapatkan kredensial API gratis dari Spotify Developer Dashboard.", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, - "setupEnableNotifications": "Aktifkan Notifikasi", - "@setupEnableNotifications": { - "description": "Button to enable notifications" - }, - "setupProceedToNextStep": "Anda dapat melanjutkan ke langkah berikutnya.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "Anda akan menerima notifikasi progres unduhan.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, - "setupNotificationBackgroundDescription": "Dapatkan notifikasi tentang progres dan penyelesaian unduhan. Ini membantu Anda melacak unduhan saat aplikasi di latar belakang.", - "@setupNotificationBackgroundDescription": { - "description": "Detailed notification explanation" - }, - "setupSkipForNow": "Lewati untuk sekarang", - "@setupSkipForNow": { - "description": "Skip button text" - }, - "setupBack": "Kembali", - "@setupBack": { - "description": "Back button text" - }, - "setupNext": "Lanjut", - "@setupNext": { - "description": "Next button text" - }, - "setupGetStarted": "Mulai", - "@setupGetStarted": { - "description": "Final setup button" - }, - "setupSkipAndStart": "Lewati & Mulai", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, - "setupAllowAccessToManageFiles": "Harap aktifkan \"Izinkan akses untuk mengelola semua file\" di layar berikutnya.", - "@setupAllowAccessToManageFiles": { - "description": "Instruction for file access permission" - }, - "setupGetCredentialsFromSpotify": "Dapatkan kredensial dari developer.spotify.com", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, - "dialogCancel": "Batal", - "@dialogCancel": { - "description": "Dialog button - cancel action" - }, - "dialogOk": "OK", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, - "dialogSave": "Simpan", - "@dialogSave": { - "description": "Dialog button - save changes" - }, - "dialogDelete": "Hapus", - "@dialogDelete": { - "description": "Dialog button - delete item" - }, - "dialogRetry": "Coba Lagi", - "@dialogRetry": { - "description": "Dialog button - retry action" - }, - "dialogClose": "Tutup", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "Ya", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "Tidak", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, - "dialogClear": "Hapus", - "@dialogClear": { - "description": "Dialog button - clear items" - }, - "dialogConfirm": "Konfirmasi", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, - "dialogDone": "Selesai", - "@dialogDone": { - "description": "Dialog button - action completed" - }, - "dialogImport": "Impor", - "@dialogImport": { - "description": "Dialog button - import data" - }, - "dialogDiscard": "Buang", - "@dialogDiscard": { - "description": "Dialog button - discard changes" - }, - "dialogRemove": "Hapus", - "@dialogRemove": { - "description": "Dialog button - remove item" - }, - "dialogUninstall": "Copot", - "@dialogUninstall": { - "description": "Dialog button - uninstall extension" - }, - "dialogDiscardChanges": "Buang Perubahan?", - "@dialogDiscardChanges": { - "description": "Dialog title - unsaved changes warning" - }, - "dialogUnsavedChanges": "Anda memiliki perubahan yang belum disimpan. Apakah Anda ingin membuangnya?", - "@dialogUnsavedChanges": { - "description": "Dialog message - unsaved changes" - }, - "dialogDownloadFailed": "Unduhan Gagal", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "Lagu:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "Artis:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "Error:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, - "dialogClearAll": "Hapus Semua", - "@dialogClearAll": { - "description": "Dialog title - clear all items" - }, - "dialogClearAllDownloads": "Apakah Anda yakin ingin menghapus semua unduhan?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "Hapus dari perangkat?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, - "dialogRemoveExtension": "Hapus Ekstensi", - "@dialogRemoveExtension": { - "description": "Dialog title - uninstall extension" - }, - "dialogRemoveExtensionMessage": "Apakah Anda yakin ingin menghapus ekstensi ini? Tindakan ini tidak dapat dibatalkan.", - "@dialogRemoveExtensionMessage": { - "description": "Dialog message - uninstall confirmation" - }, - "dialogUninstallExtension": "Copot Ekstensi?", - "@dialogUninstallExtension": { - "description": "Dialog title - uninstall extension" - }, - "dialogUninstallExtensionMessage": "Apakah Anda yakin ingin menghapus {extensionName}?", - "@dialogUninstallExtensionMessage": { - "description": "Dialog message - uninstall specific extension", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "dialogClearHistoryTitle": "Hapus Riwayat", - "@dialogClearHistoryTitle": { - "description": "Dialog title - clear download history" - }, - "dialogClearHistoryMessage": "Apakah Anda yakin ingin menghapus semua riwayat unduhan? Ini tidak dapat dibatalkan.", - "@dialogClearHistoryMessage": { - "description": "Dialog message - clear history confirmation" - }, - "dialogDeleteSelectedTitle": "Hapus yang Dipilih", - "@dialogDeleteSelectedTitle": { - "description": "Dialog title - delete selected items" - }, - "dialogDeleteSelectedMessage": "Hapus {count} {count, plural, =1{lagu} other{lagu}} dari riwayat?\n\nIni juga akan menghapus file dari penyimpanan.", - "@dialogDeleteSelectedMessage": { - "description": "Dialog message - delete selected tracks", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "dialogImportPlaylistTitle": "Impor Playlist", - "@dialogImportPlaylistTitle": { - "description": "Dialog title - import CSV playlist" - }, - "dialogImportPlaylistMessage": "Ditemukan {count} lagu di CSV. Tambahkan ke antrian unduhan?", - "csvImportTracks": "{count} tracks from CSV", - "@csvImportTracks": { - "description": "Label shown in quality picker for CSV import", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "@dialogImportPlaylistMessage": { - "description": "Dialog message - import playlist confirmation", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "snackbarAddedToQueue": "Menambahkan \"{trackName}\" ke antrian", - "@snackbarAddedToQueue": { - "description": "Snackbar - track added to download queue", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "snackbarAddedTracksToQueue": "Menambahkan {count} lagu ke antrian", - "@snackbarAddedTracksToQueue": { - "description": "Snackbar - multiple tracks added to queue", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "snackbarAlreadyDownloaded": "\"{trackName}\" sudah diunduh", - "@snackbarAlreadyDownloaded": { - "description": "Snackbar - track already exists", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "snackbarAlreadyInLibrary": "\"{trackName}\" already exists in your library", - "@snackbarAlreadyInLibrary": { - "description": "Snackbar - track already exists in local library", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "snackbarHistoryCleared": "Riwayat dihapus", - "@snackbarHistoryCleared": { - "description": "Snackbar - history deleted" - }, - "snackbarCredentialsSaved": "Kredensial disimpan", - "@snackbarCredentialsSaved": { - "description": "Snackbar - Spotify credentials saved" - }, - "snackbarCredentialsCleared": "Kredensial dihapus", - "@snackbarCredentialsCleared": { - "description": "Snackbar - Spotify credentials removed" - }, - "snackbarDeletedTracks": "Menghapus {count} {count, plural, =1{lagu} other{lagu}}", - "@snackbarDeletedTracks": { - "description": "Snackbar - tracks deleted", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "snackbarCannotOpenFile": "Tidak dapat membuka file: {error}", - "@snackbarCannotOpenFile": { - "description": "Snackbar - file open error", - "placeholders": { - "error": { - "type": "String" - } - } - }, - "snackbarFillAllFields": "Harap isi semua field", - "@snackbarFillAllFields": { - "description": "Snackbar - validation error" - }, - "snackbarViewQueue": "Lihat Antrian", - "@snackbarViewQueue": { - "description": "Snackbar action - view download queue" - }, - "snackbarFailedToLoad": "Gagal memuat: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, - "snackbarUrlCopied": "URL {platform} disalin ke clipboard", - "@snackbarUrlCopied": { - "description": "Snackbar - URL copied", - "placeholders": { - "platform": { - "type": "String", - "description": "Platform name (Spotify/Deezer)" - } - } - }, - "snackbarFileNotFound": "File tidak ditemukan", - "@snackbarFileNotFound": { - "description": "Snackbar - file doesn't exist" - }, - "snackbarSelectExtFile": "Harap pilih file .spotiflac-ext", - "@snackbarSelectExtFile": { - "description": "Snackbar - wrong file type selected" - }, - "snackbarProviderPrioritySaved": "Prioritas provider disimpan", - "@snackbarProviderPrioritySaved": { - "description": "Snackbar - provider order saved" - }, - "snackbarMetadataProviderSaved": "Prioritas provider metadata disimpan", - "@snackbarMetadataProviderSaved": { - "description": "Snackbar - metadata provider order saved" - }, - "snackbarExtensionInstalled": "{extensionName} terpasang.", - "@snackbarExtensionInstalled": { - "description": "Snackbar - extension installed successfully", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "snackbarExtensionUpdated": "{extensionName} diperbarui.", - "@snackbarExtensionUpdated": { - "description": "Snackbar - extension updated successfully", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "snackbarFailedToInstall": "Gagal memasang ekstensi", - "@snackbarFailedToInstall": { - "description": "Snackbar - extension install error" - }, - "snackbarFailedToUpdate": "Gagal memperbarui ekstensi", - "@snackbarFailedToUpdate": { - "description": "Snackbar - extension update error" - }, - "errorRateLimited": "Dibatasi", - "@errorRateLimited": { - "description": "Error title - too many requests" - }, - "errorRateLimitedMessage": "Terlalu banyak permintaan. Harap tunggu sebentar sebelum mencari lagi.", - "@errorRateLimitedMessage": { - "description": "Error message - rate limit explanation" - }, - "errorFailedToLoad": "Gagal memuat {item}", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, - "errorNoTracksFound": "Tidak ada lagu ditemukan", - "@errorNoTracksFound": { - "description": "Error - search returned no results" - }, - "errorMissingExtensionSource": "Tidak dapat memuat {item}: sumber ekstensi tidak ada", - "@errorMissingExtensionSource": { - "description": "Error - extension source not available", - "placeholders": { - "item": { - "type": "String" - } - } - }, - "statusQueued": "Mengantri", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "Mengunduh", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "Menyelesaikan", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "Selesai", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "Gagal", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "Dilewati", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "Dijeda", - "@statusPaused": { - "description": "Download status - paused" - }, - "actionPause": "Jeda", - "@actionPause": { - "description": "Action button - pause download" - }, - "actionResume": "Lanjutkan", - "@actionResume": { - "description": "Action button - resume download" - }, - "actionCancel": "Batal", - "@actionCancel": { - "description": "Action button - cancel operation" - }, - "actionStop": "Hentikan", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "Pilih", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, - "actionSelectAll": "Pilih Semua", - "@actionSelectAll": { - "description": "Action button - select all items" - }, - "actionDeselect": "Batal Pilih", - "@actionDeselect": { - "description": "Action button - deselect all" - }, - "actionPaste": "Tempel", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "Impor CSV", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, - "actionRemoveCredentials": "Hapus Kredensial", - "@actionRemoveCredentials": { - "description": "Action button - delete Spotify credentials" - }, - "actionSaveCredentials": "Simpan Kredensial", - "@actionSaveCredentials": { - "description": "Action button - save Spotify credentials" - }, - "selectionSelected": "{count} dipilih", - "@selectionSelected": { - "description": "Selection count indicator", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "selectionAllSelected": "Semua lagu dipilih", - "@selectionAllSelected": { - "description": "Status - all items selected" - }, - "selectionTapToSelect": "Ketuk lagu untuk memilih", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "Hapus {count} {count, plural, =1{lagu} other{lagu}}", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "selectionSelectToDelete": "Pilih lagu untuk dihapus", - "@selectionSelectToDelete": { - "description": "Placeholder when nothing selected" - }, - "progressFetchingMetadata": "Mengambil metadata... {current}/{total}", - "@progressFetchingMetadata": { - "description": "Progress indicator - loading track info", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } - }, - "progressReadingCsv": "Membaca CSV...", - "@progressReadingCsv": { - "description": "Progress indicator - parsing CSV file" - }, - "searchSongs": "Lagu", - "@searchSongs": { - "description": "Search result category - songs" - }, - "searchArtists": "Artis", - "@searchArtists": { - "description": "Search result category - artists" - }, - "searchAlbums": "Album", - "@searchAlbums": { - "description": "Search result category - albums" - }, - "searchPlaylists": "Playlist", - "@searchPlaylists": { - "description": "Search result category - playlists" - }, - "tooltipPlay": "Putar", - "@tooltipPlay": { - "description": "Tooltip - play button" - }, - "tooltipCancel": "Batal", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "Hentikan", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "Coba Lagi", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "Hapus", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "Hapus", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "Tempel", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, - "filenameFormat": "Format Nama File", - "@filenameFormat": { - "description": "Setting title - filename pattern" - }, - "filenameFormatPreview": "Pratinjau: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "Placeholder yang tersedia:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, - "folderOrganization": "Organisasi Folder", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, - "folderOrganizationNone": "Tidak ada", - "@folderOrganizationNone": { - "description": "Folder option - flat structure" - }, - "folderOrganizationByArtist": "Berdasarkan Artis", - "@folderOrganizationByArtist": { - "description": "Folder option - artist folders" - }, - "folderOrganizationByAlbum": "Berdasarkan Album", - "@folderOrganizationByAlbum": { - "description": "Folder option - album folders" - }, - "folderOrganizationByArtistAlbum": "Berdasarkan Artis & Album", - "@folderOrganizationByArtistAlbum": { - "description": "Folder option - nested folders" - }, - "folderOrganizationDescription": "Atur file yang diunduh ke dalam folder", - "@folderOrganizationDescription": { - "description": "Folder organization sheet description" - }, - "folderOrganizationNoneSubtitle": "Semua file di folder unduhan", - "@folderOrganizationNoneSubtitle": { - "description": "Subtitle for no organization option" - }, - "folderOrganizationByArtistSubtitle": "Folder terpisah untuk setiap artis", - "@folderOrganizationByArtistSubtitle": { - "description": "Subtitle for artist folder option" - }, - "folderOrganizationByAlbumSubtitle": "Folder terpisah untuk setiap album", - "@folderOrganizationByAlbumSubtitle": { - "description": "Subtitle for album folder option" - }, - "folderOrganizationByArtistAlbumSubtitle": "Folder bersarang untuk artis dan album", - "@folderOrganizationByArtistAlbumSubtitle": { - "description": "Subtitle for nested folder option" - }, - "updateAvailable": "Pembaruan Tersedia", - "@updateAvailable": { - "description": "Update dialog title" - }, - "updateNewVersion": "Versi {version} tersedia", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "Unduh", - "@updateDownload": { - "description": "Update button - download update" - }, - "updateLater": "Nanti", - "@updateLater": { - "description": "Update button - dismiss" - }, - "updateChangelog": "Log Perubahan", - "@updateChangelog": { - "description": "Link to changelog" - }, - "updateStartingDownload": "Memulai unduhan...", - "@updateStartingDownload": { - "description": "Update status - initializing" - }, - "updateDownloadFailed": "Unduhan gagal", - "@updateDownloadFailed": { - "description": "Update error title" - }, - "updateFailedMessage": "Gagal mengunduh pembaruan", - "@updateFailedMessage": { - "description": "Update error message" - }, - "updateNewVersionReady": "Versi baru sudah siap", - "@updateNewVersionReady": { - "description": "Update subtitle" - }, - "updateCurrent": "Saat ini", - "@updateCurrent": { - "description": "Label for current version" - }, - "updateNew": "Baru", - "@updateNew": { - "description": "Label for new version" - }, - "updateDownloading": "Mengunduh...", - "@updateDownloading": { - "description": "Update status - downloading" - }, - "updateWhatsNew": "Yang Baru", - "@updateWhatsNew": { - "description": "Changelog section title" - }, - "updateDownloadInstall": "Unduh & Pasang", - "@updateDownloadInstall": { - "description": "Update button - download and install" - }, - "updateDontRemind": "Jangan ingatkan", - "@updateDontRemind": { - "description": "Update button - skip this version" - }, - "providerPriority": "Prioritas Provider", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "Seret untuk mengatur ulang provider unduhan", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, - "providerPriorityTitle": "Prioritas Provider", - "@providerPriorityTitle": { - "description": "Provider priority page title" - }, - "providerPriorityDescription": "Seret untuk mengatur ulang urutan provider unduhan. Aplikasi akan mencoba provider dari atas ke bawah saat mengunduh lagu.", - "@providerPriorityDescription": { - "description": "Provider priority page description" - }, - "providerPriorityInfo": "Jika lagu tidak tersedia di provider pertama, aplikasi akan otomatis mencoba yang berikutnya.", - "@providerPriorityInfo": { - "description": "Info tip about fallback behavior" - }, - "providerBuiltIn": "Bawaan", - "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" - }, - "providerExtension": "Ekstensi", - "@providerExtension": { - "description": "Label for extension-provided providers" - }, - "metadataProviderPriority": "Prioritas Provider Metadata", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Urutan yang digunakan saat mengambil metadata lagu", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, - "metadataProviderPriorityTitle": "Prioritas Metadata", - "@metadataProviderPriorityTitle": { - "description": "Metadata priority page title" - }, - "metadataProviderPriorityDescription": "Seret untuk mengatur ulang urutan provider metadata. Aplikasi akan mencoba provider dari atas ke bawah saat mencari lagu dan mengambil metadata.", - "@metadataProviderPriorityDescription": { - "description": "Metadata priority page description" - }, - "metadataProviderPriorityInfo": "Deezer tidak memiliki batas rate dan direkomendasikan sebagai utama. Spotify mungkin membatasi rate setelah banyak permintaan.", - "@metadataProviderPriorityInfo": { - "description": "Info tip about rate limits" - }, - "metadataNoRateLimits": "Tidak ada batas rate", - "@metadataNoRateLimits": { - "description": "Deezer provider description" - }, - "metadataMayRateLimit": "Mungkin dibatasi rate", - "@metadataMayRateLimit": { - "description": "Spotify provider description" - }, - "logTitle": "Log", - "@logTitle": { - "description": "Logs screen title" - }, - "logCopy": "Salin Log", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "Hapus Log", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "Bagikan Log", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "Belum ada log", - "@logEmpty": { - "description": "Empty state title" - }, - "logCopied": "Log disalin ke clipboard", - "@logCopied": { - "description": "Snackbar - logs copied" - }, - "logSearchHint": "Cari log...", - "@logSearchHint": { - "description": "Log search placeholder" - }, - "logFilterLevel": "Level", - "@logFilterLevel": { - "description": "Filter by log level" - }, - "logFilterSection": "Filter", - "@logFilterSection": { - "description": "Filter section title" - }, - "logShareLogs": "Bagikan log", - "@logShareLogs": { - "description": "Share button tooltip" - }, - "logClearLogs": "Hapus log", - "@logClearLogs": { - "description": "Clear button tooltip" - }, - "logClearLogsTitle": "Hapus Log", - "@logClearLogsTitle": { - "description": "Clear logs dialog title" - }, - "logClearLogsMessage": "Apakah Anda yakin ingin menghapus semua log?", - "@logClearLogsMessage": { - "description": "Clear logs confirmation message" - }, - "logIspBlocking": "PEMBLOKIRAN ISP TERDETEKSI", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "DIBATASI", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "ERROR JARINGAN", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "LAGU TIDAK DITEMUKAN", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, - "logFilterBySeverity": "Filter log berdasarkan tingkat keparahan", - "@logFilterBySeverity": { - "description": "Filter dialog title" - }, - "logNoLogsYet": "Belum ada log", - "@logNoLogsYet": { - "description": "Empty state title" - }, - "logNoLogsYetSubtitle": "Log akan muncul di sini saat Anda menggunakan aplikasi", - "@logNoLogsYetSubtitle": { - "description": "Empty state subtitle" - }, - "logIssueSummary": "Ringkasan Masalah", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "ISP Anda mungkin memblokir akses ke layanan unduhan", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "Coba gunakan VPN atau ubah DNS ke 1.1.1.1 atau 8.8.8.8", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "Terlalu banyak permintaan ke layanan", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "Tunggu beberapa menit sebelum mencoba lagi", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "Masalah koneksi terdeteksi", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "Periksa koneksi internet Anda", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "Beberapa lagu tidak dapat ditemukan di layanan unduhan", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "Lagu mungkin tidak tersedia dalam kualitas lossless", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "Total error: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "Terpengaruh: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, - "logEntriesFiltered": "Entri ({count} difilter)", - "@logEntriesFiltered": { - "description": "Log count with filter active", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logEntries": "Entri ({count})", - "@logEntries": { - "description": "Total log count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "credentialsTitle": "Kredensial Spotify", - "@credentialsTitle": { - "description": "Credentials dialog title" - }, - "credentialsDescription": "Masukkan Client ID dan Secret Anda untuk menggunakan kuota aplikasi Spotify Anda sendiri.", - "@credentialsDescription": { - "description": "Credentials dialog explanation" - }, - "credentialsClientId": "Client ID", - "@credentialsClientId": { - "description": "Client ID field label - DO NOT TRANSLATE" - }, - "credentialsClientIdHint": "Tempel Client ID", - "@credentialsClientIdHint": { - "description": "Client ID placeholder" - }, - "credentialsClientSecret": "Client Secret", - "@credentialsClientSecret": { - "description": "Client Secret field label - DO NOT TRANSLATE" - }, - "credentialsClientSecretHint": "Tempel Client Secret", - "@credentialsClientSecretHint": { - "description": "Client Secret placeholder" - }, - "channelStable": "Stabil", - "@channelStable": { - "description": "Update channel - stable releases" - }, - "channelPreview": "Preview", - "@channelPreview": { - "description": "Update channel - beta/preview releases" - }, - "sectionSearchSource": "Sumber Pencarian", - "@sectionSearchSource": { - "description": "Settings section header" - }, - "sectionDownload": "Unduhan", - "@sectionDownload": { - "description": "Settings section header" - }, - "sectionPerformance": "Performa", - "@sectionPerformance": { - "description": "Settings section header" - }, - "sectionApp": "Aplikasi", - "@sectionApp": { - "description": "Settings section header" - }, - "sectionData": "Data", - "@sectionData": { - "description": "Settings section header" - }, - "sectionDebug": "Debug", - "@sectionDebug": { - "description": "Settings section header" - }, - "sectionService": "Layanan", - "@sectionService": { - "description": "Settings section header" - }, - "sectionAudioQuality": "Kualitas Audio", - "@sectionAudioQuality": { - "description": "Settings section header" - }, - "sectionFileSettings": "Pengaturan File", - "@sectionFileSettings": { - "description": "Settings section header" - }, - "sectionLyrics": "Lyrics", - "@sectionLyrics": { - "description": "Settings section header" - }, - "lyricsMode": "Lyrics Mode", - "@lyricsMode": { - "description": "Setting - how to save lyrics" - }, - "lyricsModeDescription": "Choose how lyrics are saved with your downloads", - "@lyricsModeDescription": { - "description": "Lyrics mode picker description" - }, - "lyricsModeEmbed": "Embed in file", - "@lyricsModeEmbed": { - "description": "Lyrics mode option - embed in audio file" - }, - "lyricsModeEmbedSubtitle": "Lyrics stored inside FLAC metadata", - "@lyricsModeEmbedSubtitle": { - "description": "Subtitle for embed option" - }, - "lyricsModeExternal": "External .lrc file", - "@lyricsModeExternal": { - "description": "Lyrics mode option - separate LRC file" - }, - "lyricsModeExternalSubtitle": "Separate .lrc file for players like Samsung Music", - "@lyricsModeExternalSubtitle": { - "description": "Subtitle for external option" - }, - "lyricsModeBoth": "Both", - "@lyricsModeBoth": { - "description": "Lyrics mode option - embed and external" - }, - "lyricsModeBothSubtitle": "Embed and save .lrc file", - "@lyricsModeBothSubtitle": { - "description": "Subtitle for both option" - }, - "sectionColor": "Warna", - "@sectionColor": { - "description": "Settings section header" - }, - "sectionTheme": "Tema", - "@sectionTheme": { - "description": "Settings section header" - }, - "sectionLayout": "Tata Letak", - "@sectionLayout": { - "description": "Settings section header" - }, - "sectionLanguage": "Bahasa", - "@sectionLanguage": { - "description": "Settings section header for language" - }, - "appearanceLanguage": "Bahasa Aplikasi", - "@appearanceLanguage": { - "description": "Language setting title" - }, - "appearanceLanguageSubtitle": "Pilih bahasa yang kamu inginkan", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, - "settingsAppearanceSubtitle": "Tema, warna, tampilan", - "@settingsAppearanceSubtitle": { - "description": "Appearance settings description" - }, - "settingsDownloadSubtitle": "Layanan, kualitas, format nama file", - "@settingsDownloadSubtitle": { - "description": "Download settings description" - }, - "settingsOptionsSubtitle": "Fallback, lirik, cover art, pembaruan", - "@settingsOptionsSubtitle": { - "description": "Options settings description" - }, - "settingsExtensionsSubtitle": "Kelola provider unduhan", - "@settingsExtensionsSubtitle": { - "description": "Extensions settings description" - }, - "settingsLogsSubtitle": "Lihat log aplikasi untuk debugging", - "@settingsLogsSubtitle": { - "description": "Logs settings description" - }, - "loadingSharedLink": "Memuat link yang dibagikan...", - "@loadingSharedLink": { - "description": "Status when opening shared URL" - }, - "pressBackAgainToExit": "Tekan kembali sekali lagi untuk keluar", - "@pressBackAgainToExit": { - "description": "Exit confirmation message" - }, - "tracksHeader": "Lagu", - "@tracksHeader": { - "description": "Section header for track list" - }, - "downloadAllCount": "Unduh Semua ({count})", - "@downloadAllCount": { - "description": "Download all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "tracksCount": "{count, plural, =1{1 lagu} other{{count} lagu}}", - "@tracksCount": { - "description": "Track count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "trackCopyFilePath": "Salin lokasi file", - "@trackCopyFilePath": { - "description": "Action - copy file path" - }, - "trackRemoveFromDevice": "Hapus dari perangkat", - "@trackRemoveFromDevice": { - "description": "Action - delete downloaded file" - }, - "trackLoadLyrics": "Muat Lirik", - "@trackLoadLyrics": { - "description": "Action - fetch lyrics" - }, - "trackMetadata": "Metadata", - "@trackMetadata": { - "description": "Tab title - track metadata" - }, - "trackFileInfo": "Info File", - "@trackFileInfo": { - "description": "Tab title - file information" - }, - "trackLyrics": "Lirik", - "@trackLyrics": { - "description": "Tab title - lyrics" - }, - "trackFileNotFound": "File tidak ditemukan", - "@trackFileNotFound": { - "description": "Error - file doesn't exist" - }, - "trackOpenInDeezer": "Buka di Deezer", - "@trackOpenInDeezer": { - "description": "Action - open track in Deezer app" - }, - "trackOpenInSpotify": "Buka di Spotify", - "@trackOpenInSpotify": { - "description": "Action - open track in Spotify app" - }, - "trackTrackName": "Nama lagu", - "@trackTrackName": { - "description": "Metadata label - track title" - }, - "trackArtist": "Artis", - "@trackArtist": { - "description": "Metadata label - artist name" - }, - "trackAlbumArtist": "Artis album", - "@trackAlbumArtist": { - "description": "Metadata label - album artist" - }, - "trackAlbum": "Album", - "@trackAlbum": { - "description": "Metadata label - album name" - }, - "trackTrackNumber": "Nomor lagu", - "@trackTrackNumber": { - "description": "Metadata label - track number" - }, - "trackDiscNumber": "Nomor disc", - "@trackDiscNumber": { - "description": "Metadata label - disc number" - }, - "trackDuration": "Durasi", - "@trackDuration": { - "description": "Metadata label - track length" - }, - "trackAudioQuality": "Kualitas audio", - "@trackAudioQuality": { - "description": "Metadata label - audio quality" - }, - "trackReleaseDate": "Tanggal rilis", - "@trackReleaseDate": { - "description": "Metadata label - release date" - }, - "trackGenre": "Genre", - "@trackGenre": { - "description": "Metadata label - music genre" - }, - "trackLabel": "Label", - "@trackLabel": { - "description": "Metadata label - record label" - }, - "trackCopyright": "Copyright", - "@trackCopyright": { - "description": "Metadata label - copyright information" - }, - "trackDownloaded": "Diunduh", - "@trackDownloaded": { - "description": "Metadata label - download date" - }, - "trackCopyLyrics": "Salin lirik", - "@trackCopyLyrics": { - "description": "Action - copy lyrics to clipboard" - }, - "trackLyricsNotAvailable": "Lirik tidak tersedia untuk lagu ini", - "@trackLyricsNotAvailable": { - "description": "Message when lyrics not found" - }, - "trackLyricsTimeout": "Permintaan timeout. Coba lagi nanti.", - "@trackLyricsTimeout": { - "description": "Message when lyrics request times out" - }, - "trackLyricsLoadFailed": "Gagal memuat lirik", - "@trackLyricsLoadFailed": { - "description": "Message when lyrics loading fails" - }, - "trackEmbedLyrics": "Embed Lyrics", - "@trackEmbedLyrics": { - "description": "Action - embed lyrics into audio file" - }, - "trackLyricsEmbedded": "Lyrics embedded successfully", - "@trackLyricsEmbedded": { - "description": "Snackbar - lyrics saved to file" - }, - "trackInstrumental": "Instrumental track", - "@trackInstrumental": { - "description": "Message when track is instrumental (no lyrics)" - }, - "trackCopiedToClipboard": "Disalin ke clipboard", - "@trackCopiedToClipboard": { - "description": "Snackbar - content copied" - }, - "trackDeleteConfirmTitle": "Hapus dari perangkat?", - "@trackDeleteConfirmTitle": { - "description": "Delete confirmation title" - }, - "trackDeleteConfirmMessage": "Ini akan menghapus file unduhan secara permanen dan menghapusnya dari riwayat Anda.", - "@trackDeleteConfirmMessage": { - "description": "Delete confirmation message" - }, - "trackCannotOpen": "Tidak dapat membuka: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, - "dateToday": "Hari ini", - "@dateToday": { - "description": "Relative date - today" - }, - "dateYesterday": "Kemarin", - "@dateYesterday": { - "description": "Relative date - yesterday" - }, - "dateDaysAgo": "{count} hari lalu", - "@dateDaysAgo": { - "description": "Relative date - days ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "dateWeeksAgo": "{count} minggu lalu", - "@dateWeeksAgo": { - "description": "Relative date - weeks ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "dateMonthsAgo": "{count} bulan lalu", - "@dateMonthsAgo": { - "description": "Relative date - months ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "concurrentSequential": "Berurutan", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 Paralel", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 Paralel", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "Ketuk untuk melihat detail error", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, - "storeFilterAll": "Semua", - "@storeFilterAll": { - "description": "Store filter - all extensions" - }, - "storeFilterMetadata": "Metadata", - "@storeFilterMetadata": { - "description": "Store filter - metadata providers" - }, - "storeFilterDownload": "Unduhan", - "@storeFilterDownload": { - "description": "Store filter - download providers" - }, - "storeFilterUtility": "Utilitas", - "@storeFilterUtility": { - "description": "Store filter - utility extensions" - }, - "storeFilterLyrics": "Lirik", - "@storeFilterLyrics": { - "description": "Store filter - lyrics providers" - }, - "storeFilterIntegration": "Integrasi", - "@storeFilterIntegration": { - "description": "Store filter - integrations" - }, - "storeClearFilters": "Hapus filter", - "@storeClearFilters": { - "description": "Button to clear all filters" - }, - "storeNoResults": "Tidak ada ekstensi ditemukan", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "Prioritas Provider", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "Pasang Ekstensi", - "@extensionInstallButton": { - "description": "Button to install extension" - }, - "extensionDefaultProvider": "Default (Deezer/Spotify)", - "@extensionDefaultProvider": { - "description": "Default search provider option" - }, - "extensionDefaultProviderSubtitle": "Gunakan pencarian bawaan", - "@extensionDefaultProviderSubtitle": { - "description": "Subtitle for default provider" - }, - "extensionAuthor": "Pembuat", - "@extensionAuthor": { - "description": "Extension detail - author" - }, - "extensionId": "ID", - "@extensionId": { - "description": "Extension detail - unique ID" - }, - "extensionError": "Error", - "@extensionError": { - "description": "Extension detail - error message" - }, - "extensionCapabilities": "Kemampuan", - "@extensionCapabilities": { - "description": "Section header - extension features" - }, - "extensionMetadataProvider": "Provider Metadata", - "@extensionMetadataProvider": { - "description": "Capability - provides metadata" - }, - "extensionDownloadProvider": "Provider Unduhan", - "@extensionDownloadProvider": { - "description": "Capability - provides downloads" - }, - "extensionLyricsProvider": "Provider Lirik", - "@extensionLyricsProvider": { - "description": "Capability - provides lyrics" - }, - "extensionUrlHandler": "Penanganan URL", - "@extensionUrlHandler": { - "description": "Capability - handles URLs" - }, - "extensionQualityOptions": "Opsi Kualitas", - "@extensionQualityOptions": { - "description": "Capability - quality selection" - }, - "extensionPostProcessingHooks": "Hook Pasca-Pemrosesan", - "@extensionPostProcessingHooks": { - "description": "Capability - post-processing" - }, - "extensionPermissions": "Izin", - "@extensionPermissions": { - "description": "Section header - required permissions" - }, - "extensionSettings": "Pengaturan", - "@extensionSettings": { - "description": "Section header - extension settings" - }, - "extensionRemoveButton": "Hapus Ekstensi", - "@extensionRemoveButton": { - "description": "Button to uninstall extension" - }, - "extensionUpdated": "Diperbarui", - "@extensionUpdated": { - "description": "Extension detail - last update" - }, - "extensionMinAppVersion": "Versi App Minimum", - "@extensionMinAppVersion": { - "description": "Extension detail - minimum app version" - }, - "extensionCustomTrackMatching": "Pencocokan Lagu Kustom", - "@extensionCustomTrackMatching": { - "description": "Capability - custom track matching algorithm" - }, - "extensionPostProcessing": "Pasca-Pemrosesan", - "@extensionPostProcessing": { - "description": "Capability - post-download processing" - }, - "extensionHooksAvailable": "{count} hook tersedia", - "@extensionHooksAvailable": { - "description": "Post-processing hooks count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "extensionPatternsCount": "{count} pola", - "@extensionPatternsCount": { - "description": "URL patterns count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "extensionStrategy": "Strategi: {strategy}", - "@extensionStrategy": { - "description": "Track matching strategy name", - "placeholders": { - "strategy": { - "type": "String" - } - } - }, - "extensionsProviderPrioritySection": "Prioritas Provider", - "@extensionsProviderPrioritySection": { - "description": "Section header - provider priority" - }, - "extensionsInstalledSection": "Ekstensi Terpasang", - "@extensionsInstalledSection": { - "description": "Section header - installed extensions" - }, - "extensionsNoExtensions": "Tidak ada ekstensi terpasang", - "@extensionsNoExtensions": { - "description": "Empty state - no extensions" - }, - "extensionsNoExtensionsSubtitle": "Pasang file .spotiflac-ext untuk menambahkan provider baru", - "@extensionsNoExtensionsSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsInstallButton": "Pasang Ekstensi", - "@extensionsInstallButton": { - "description": "Button to install extension from file" - }, - "extensionsInfoTip": "Ekstensi dapat menambahkan provider metadata dan unduhan baru. Hanya pasang ekstensi dari sumber terpercaya.", - "@extensionsInfoTip": { - "description": "Security warning about extensions" - }, - "extensionsInstalledSuccess": "Ekstensi berhasil dipasang", - "@extensionsInstalledSuccess": { - "description": "Success message after install" - }, - "extensionsDownloadPriority": "Prioritas Unduhan", - "@extensionsDownloadPriority": { - "description": "Setting - download provider order" - }, - "extensionsDownloadPrioritySubtitle": "Atur urutan layanan unduhan", - "@extensionsDownloadPrioritySubtitle": { - "description": "Subtitle for download priority" - }, - "extensionsNoDownloadProvider": "Tidak ada ekstensi dengan provider unduhan", - "@extensionsNoDownloadProvider": { - "description": "Empty state - no download providers" - }, - "extensionsMetadataPriority": "Prioritas Metadata", - "@extensionsMetadataPriority": { - "description": "Setting - metadata provider order" - }, - "extensionsMetadataPrioritySubtitle": "Atur urutan sumber pencarian & metadata", - "@extensionsMetadataPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, - "extensionsNoMetadataProvider": "Tidak ada ekstensi dengan provider metadata", - "@extensionsNoMetadataProvider": { - "description": "Empty state - no metadata providers" - }, - "extensionsSearchProvider": "Provider Pencarian", - "@extensionsSearchProvider": { - "description": "Setting - search provider selection" - }, - "extensionsNoCustomSearch": "Tidak ada ekstensi dengan pencarian kustom", - "@extensionsNoCustomSearch": { - "description": "Empty state - no search providers" - }, - "extensionsSearchProviderDescription": "Pilih layanan yang digunakan untuk mencari lagu", - "@extensionsSearchProviderDescription": { - "description": "Search provider setting description" - }, - "extensionsCustomSearch": "Pencarian kustom", - "@extensionsCustomSearch": { - "description": "Label for custom search provider" - }, - "extensionsErrorLoading": "Error memuat ekstensi", - "@extensionsErrorLoading": { - "description": "Error message when extension fails to load" - }, - "qualityFlacLossless": "FLAC Lossless", - "@qualityFlacLossless": { - "description": "Quality option - CD quality FLAC" - }, - "qualityFlacLosslessSubtitle": "16-bit / 44.1kHz", - "@qualityFlacLosslessSubtitle": { - "description": "Technical spec for lossless" - }, - "qualityHiResFlac": "Hi-Res FLAC", - "@qualityHiResFlac": { - "description": "Quality option - high resolution FLAC" - }, - "qualityHiResFlacSubtitle": "24-bit / hingga 96kHz", - "@qualityHiResFlacSubtitle": { - "description": "Technical spec for hi-res" - }, - "qualityHiResFlacMax": "Hi-Res FLAC Max", - "@qualityHiResFlacMax": { - "description": "Quality option - maximum resolution FLAC" - }, - "qualityHiResFlacMaxSubtitle": "24-bit / hingga 192kHz", - "@qualityHiResFlacMaxSubtitle": { - "description": "Technical spec for hi-res max" - }, - "qualityLossy": "Lossy", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Enable Lossy Option", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "Lossy quality option is available", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Lossy Format", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Choose the lossy format for conversion", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320kbps, best compatibility", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, - "qualityNote": "Kualitas sebenarnya tergantung ketersediaan lagu dari layanan", - "@qualityNote": { - "description": "Note about quality availability" - }, - "youtubeQualityNote": "YouTube provides lossy audio only. Not part of lossless fallback.", - "@youtubeQualityNote": { - "description": "Note for YouTube service explaining lossy-only quality" - }, - "downloadAskBeforeDownload": "Tanya Sebelum Unduh", - "@downloadAskBeforeDownload": { - "description": "Setting - show quality picker" - }, - "downloadDirectory": "Direktori Unduhan", - "@downloadDirectory": { - "description": "Setting - download folder" - }, - "downloadSeparateSinglesFolder": "Folder Singles Terpisah", - "@downloadSeparateSinglesFolder": { - "description": "Setting - separate folder for singles" - }, - "downloadAlbumFolderStructure": "Struktur Folder Album", - "@downloadAlbumFolderStructure": { - "description": "Setting - album folder organization" - }, - "downloadUseAlbumArtistForFolders": "Use Album Artist for folders", - "@downloadUseAlbumArtistForFolders": { - "description": "Setting - choose whether artist folders use Album Artist or Track Artist" - }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, - "downloadUsePrimaryArtistOnly": "Primary artist only for folders", - "@downloadUsePrimaryArtistOnly": { - "description": "Setting - strip featured artists from folder name" - }, - "downloadUsePrimaryArtistOnlyEnabled": "Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)", - "@downloadUsePrimaryArtistOnlyEnabled": { - "description": "Subtitle when primary artist only is enabled" - }, - "downloadUsePrimaryArtistOnlyDisabled": "Full artist string used for folder name", - "@downloadUsePrimaryArtistOnlyDisabled": { - "description": "Subtitle when primary artist only is disabled" - }, - "downloadSaveFormat": "Simpan Format", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "Pilih Layanan", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, - "downloadSelectQuality": "Pilih Kualitas", - "@downloadSelectQuality": { - "description": "Dialog title - choose audio quality" - }, - "downloadFrom": "Unduh Dari", - "@downloadFrom": { - "description": "Label - download source" - }, - "downloadDefaultQualityLabel": "Kualitas Default", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "Terbaik tersedia", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "Tidak ada", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "Simpan semua file langsung ke folder unduhan", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "Artis", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "Nama Artis/namafile", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "Album", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "Nama Album/namafile", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "Artis/Album", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "Nama Artis/Nama Album/namafile", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, - "appearanceAmoledDark": "AMOLED Gelap", - "@appearanceAmoledDark": { - "description": "Theme option - pure black" - }, - "appearanceAmoledDarkSubtitle": "Latar belakang hitam murni", - "@appearanceAmoledDarkSubtitle": { - "description": "Subtitle for AMOLED dark" - }, - "appearanceChooseAccentColor": "Pilih Warna Aksen", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "Mode Tema", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "Antrian Unduhan", - "@queueTitle": { - "description": "Queue screen title" - }, - "queueClearAll": "Hapus Semua", - "@queueClearAll": { - "description": "Button - clear all queue items" - }, - "queueClearAllMessage": "Apakah Anda yakin ingin menghapus semua unduhan?", - "@queueClearAllMessage": { - "description": "Clear queue confirmation" - }, - "queueExportFailed": "Export", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Failed downloads exported to TXT file", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "Clear Failed", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "Failed to export downloads", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, - "settingsAutoExportFailed": "Auto-export failed downloads", - "@settingsAutoExportFailed": { - "description": "Setting toggle for auto-export" - }, - "settingsAutoExportFailedSubtitle": "Save failed downloads to TXT file automatically", - "@settingsAutoExportFailedSubtitle": { - "description": "Subtitle for auto-export setting" - }, - "settingsDownloadNetwork": "Download Network", - "@settingsDownloadNetwork": { - "description": "Setting for network type preference" - }, - "settingsDownloadNetworkAny": "WiFi + Mobile Data", - "@settingsDownloadNetworkAny": { - "description": "Network option - use any connection" - }, - "settingsDownloadNetworkWifiOnly": "WiFi Only", - "@settingsDownloadNetworkWifiOnly": { - "description": "Network option - only use WiFi" - }, - "settingsDownloadNetworkSubtitle": "Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.", - "@settingsDownloadNetworkSubtitle": { - "description": "Subtitle explaining network preference" - }, - "queueEmpty": "Tidak ada unduhan dalam antrian", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "Tambahkan lagu dari layar beranda", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "Hapus yang selesai", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "Unduhan Gagal", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "Lagu:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "Artis:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "Error:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "Error tidak diketahui", - "@queueUnknownError": { - "description": "Fallback error message" - }, - "albumFolderArtistAlbum": "Artis / Album", - "@albumFolderArtistAlbum": { - "description": "Album folder option" - }, - "albumFolderArtistAlbumSubtitle": "Albums/Nama Artis/Nama Album/", - "@albumFolderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "albumFolderArtistYearAlbum": "Artis / [Tahun] Album", - "@albumFolderArtistYearAlbum": { - "description": "Album folder option with year" - }, - "albumFolderArtistYearAlbumSubtitle": "Albums/Nama Artis/[2005] Nama Album/", - "@albumFolderArtistYearAlbumSubtitle": { - "description": "Folder structure example" - }, - "albumFolderAlbumOnly": "Album Saja", - "@albumFolderAlbumOnly": { - "description": "Album folder option" - }, - "albumFolderAlbumOnlySubtitle": "Albums/Nama Album/", - "@albumFolderAlbumOnlySubtitle": { - "description": "Folder structure example" - }, - "albumFolderYearAlbum": "[Tahun] Album", - "@albumFolderYearAlbum": { - "description": "Album folder option with year" - }, - "albumFolderYearAlbumSubtitle": "Albums/[2005] Nama Album/", - "@albumFolderYearAlbumSubtitle": { - "description": "Folder structure example" - }, - "albumFolderArtistAlbumSingles": "Artist / Album + Singles", - "@albumFolderArtistAlbumSingles": { - "description": "Album folder option with singles inside artist" - }, - "albumFolderArtistAlbumSinglesSubtitle": "Artist/Album/ and Artist/Singles/", - "@albumFolderArtistAlbumSinglesSubtitle": { - "description": "Folder structure example" - }, - "downloadedAlbumDeleteSelected": "Hapus yang Dipilih", - "@downloadedAlbumDeleteSelected": { - "description": "Button - delete selected tracks" - }, - "downloadedAlbumDeleteMessage": "Hapus {count} {count, plural, =1{lagu} other{lagu}} dari album ini?\n\nIni juga akan menghapus file dari penyimpanan.", - "@downloadedAlbumDeleteMessage": { - "description": "Delete confirmation with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadedAlbumTracksHeader": "Lagu", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} diunduh", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadedAlbumSelectedCount": "{count} dipilih", - "@downloadedAlbumSelectedCount": { - "description": "Selection count indicator", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadedAlbumAllSelected": "Semua lagu dipilih", - "@downloadedAlbumAllSelected": { - "description": "Status - all items selected" - }, - "downloadedAlbumTapToSelect": "Ketuk lagu untuk memilih", - "@downloadedAlbumTapToSelect": { - "description": "Selection hint" - }, - "downloadedAlbumDeleteCount": "Hapus {count} {count, plural, =1{lagu} other{lagu}}", - "@downloadedAlbumDeleteCount": { - "description": "Delete button text with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadedAlbumSelectToDelete": "Pilih lagu untuk dihapus", - "@downloadedAlbumSelectToDelete": { - "description": "Placeholder when nothing selected" - }, - "downloadedAlbumDiscHeader": "Disc {discNumber}", - "@downloadedAlbumDiscHeader": { - "description": "Header for disc separator in multi-disc albums", - "placeholders": { - "discNumber": { - "type": "int", - "example": "1" - } - } - }, - "utilityFunctions": "Fungsi Utilitas", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, - "recentTypeArtist": "Artis", - "@recentTypeArtist": { - "description": "Recent access item type - artist" - }, - "recentTypeAlbum": "Album", - "@recentTypeAlbum": { - "description": "Recent access item type - album" - }, - "recentTypeSong": "Lagu", - "@recentTypeSong": { - "description": "Recent access item type - song/track" - }, - "recentTypePlaylist": "Playlist", - "@recentTypePlaylist": { - "description": "Recent access item type - playlist" - }, - "recentEmpty": "No recent items yet", - "@recentEmpty": { - "description": "Empty state text for recent access list" - }, - "recentShowAllDownloads": "Show All Downloads", - "@recentShowAllDownloads": { - "description": "Button label to unhide hidden downloads in recent access" - }, - "recentPlaylistInfo": "Playlist: {name}", - "@recentPlaylistInfo": { - "description": "Snackbar message when tapping playlist in recent access", - "placeholders": { - "name": { - "type": "String", - "description": "Playlist name" - } - } - }, - "errorGeneric": "Error: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, - "discographyDownload": "Download Discography", - "@discographyDownload": { - "description": "Button - download artist discography" - }, - "discographyDownloadAll": "Unduh Semua", - "@discographyDownloadAll": { - "description": "Option - download entire discography" - }, - "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", - "@discographyDownloadAllSubtitle": { - "description": "Subtitle showing total tracks and albums", - "placeholders": { - "count": { - "type": "int" - }, - "albumCount": { - "type": "int" - } - } - }, - "discographyAlbumsOnly": "Albums Only", - "@discographyAlbumsOnly": { - "description": "Option - download only albums" - }, - "discographyAlbumsOnlySubtitle": "{count} tracks from {albumCount} albums", - "@discographyAlbumsOnlySubtitle": { - "description": "Subtitle showing album tracks count", - "placeholders": { - "count": { - "type": "int" - }, - "albumCount": { - "type": "int" - } - } - }, - "discographySinglesOnly": "Singles & EPs Only", - "@discographySinglesOnly": { - "description": "Option - download only singles" - }, - "discographySinglesOnlySubtitle": "{count} tracks from {albumCount} singles", - "@discographySinglesOnlySubtitle": { - "description": "Subtitle showing singles tracks count", - "placeholders": { - "count": { - "type": "int" - }, - "albumCount": { - "type": "int" - } - } - }, - "discographySelectAlbums": "Select Albums...", - "@discographySelectAlbums": { - "description": "Option - manually select albums to download" - }, - "discographySelectAlbumsSubtitle": "Choose specific albums or singles", - "@discographySelectAlbumsSubtitle": { - "description": "Subtitle for select albums option" - }, - "discographyFetchingTracks": "Fetching tracks...", - "@discographyFetchingTracks": { - "description": "Progress - fetching album tracks" - }, - "discographyFetchingAlbum": "Fetching {current} of {total}...", - "@discographyFetchingAlbum": { - "description": "Progress - fetching specific album", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } - }, - "discographySelectedCount": "{count} selected", - "@discographySelectedCount": { - "description": "Selection count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "discographyDownloadSelected": "Download Selected", - "@discographyDownloadSelected": { - "description": "Button - download selected albums" - }, - "discographyAddedToQueue": "Added {count} tracks to queue", - "@discographyAddedToQueue": { - "description": "Snackbar - tracks added from discography", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "discographySkippedDownloaded": "{added} added, {skipped} already downloaded", - "@discographySkippedDownloaded": { - "description": "Snackbar - with skipped tracks count", - "placeholders": { - "added": { - "type": "int" - }, - "skipped": { - "type": "int" - } - } - }, - "discographyNoAlbums": "No albums available", - "@discographyNoAlbums": { - "description": "Error - no albums found for artist" - }, - "discographyFailedToFetch": "Failed to fetch some albums", - "@discographyFailedToFetch": { - "description": "Error - some albums failed to load" - }, - "sectionStorageAccess": "Storage Access", - "@sectionStorageAccess": { - "description": "Section header for storage access settings" - }, - "allFilesAccess": "All Files Access", - "@allFilesAccess": { - "description": "Toggle for MANAGE_EXTERNAL_STORAGE permission" - }, - "allFilesAccessEnabledSubtitle": "Can write to any folder", - "@allFilesAccessEnabledSubtitle": { - "description": "Subtitle when all files access is enabled" - }, - "allFilesAccessDisabledSubtitle": "Limited to media folders only", - "@allFilesAccessDisabledSubtitle": { - "description": "Subtitle when all files access is disabled" - }, - "allFilesAccessDescription": "Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.", - "@allFilesAccessDescription": { - "description": "Description explaining when to enable all files access" - }, - "allFilesAccessDeniedMessage": "Permission was denied. Please enable 'All files access' manually in system settings.", - "@allFilesAccessDeniedMessage": { - "description": "Message when permission is permanently denied" - }, - "allFilesAccessDisabledMessage": "All Files Access disabled. The app will use limited storage access.", - "@allFilesAccessDisabledMessage": { - "description": "Snackbar message when user disables all files access" - }, - "settingsLocalLibrary": "Local Library", - "@settingsLocalLibrary": { - "description": "Settings menu item - local library" - }, - "settingsLocalLibrarySubtitle": "Scan music & detect duplicates", - "@settingsLocalLibrarySubtitle": { - "description": "Subtitle for local library settings" - }, - "settingsCache": "Storage & Cache", - "@settingsCache": { - "description": "Settings menu item - cache management" - }, - "settingsCacheSubtitle": "View size and clear cached data", - "@settingsCacheSubtitle": { - "description": "Subtitle for cache management menu" - }, - "libraryTitle": "Local Library", - "@libraryTitle": { - "description": "Library settings page title" - }, - "libraryStatus": "Library Status", - "@libraryStatus": { - "description": "Section header for library status" - }, - "libraryScanSettings": "Scan Settings", - "@libraryScanSettings": { - "description": "Section header for scan settings" - }, - "libraryEnableLocalLibrary": "Enable Local Library", - "@libraryEnableLocalLibrary": { - "description": "Toggle to enable library scanning" - }, - "libraryEnableLocalLibrarySubtitle": "Scan and track your existing music", - "@libraryEnableLocalLibrarySubtitle": { - "description": "Subtitle for enable toggle" - }, - "libraryFolder": "Library Folder", - "@libraryFolder": { - "description": "Folder selection setting" - }, - "libraryFolderHint": "Tap to select folder", - "@libraryFolderHint": { - "description": "Placeholder when no folder selected" - }, - "libraryShowDuplicateIndicator": "Show Duplicate Indicator", - "@libraryShowDuplicateIndicator": { - "description": "Toggle for duplicate indicator in search" - }, - "libraryShowDuplicateIndicatorSubtitle": "Show when searching for existing tracks", - "@libraryShowDuplicateIndicatorSubtitle": { - "description": "Subtitle for duplicate indicator toggle" - }, - "libraryActions": "Actions", - "@libraryActions": { - "description": "Section header for library actions" - }, - "libraryScan": "Scan Library", - "@libraryScan": { - "description": "Button to start library scan" - }, - "libraryScanSubtitle": "Scan for audio files", - "@libraryScanSubtitle": { - "description": "Subtitle for scan button" - }, - "libraryScanSelectFolderFirst": "Select a folder first", - "@libraryScanSelectFolderFirst": { - "description": "Message when trying to scan without folder" - }, - "libraryCleanupMissingFiles": "Cleanup Missing Files", - "@libraryCleanupMissingFiles": { - "description": "Button to remove entries for missing files" - }, - "libraryCleanupMissingFilesSubtitle": "Remove entries for files that no longer exist", - "@libraryCleanupMissingFilesSubtitle": { - "description": "Subtitle for cleanup button" - }, - "libraryClear": "Clear Library", - "@libraryClear": { - "description": "Button to clear all library entries" - }, - "libraryClearSubtitle": "Remove all scanned tracks", - "@libraryClearSubtitle": { - "description": "Subtitle for clear button" - }, - "libraryClearConfirmTitle": "Clear Library", - "@libraryClearConfirmTitle": { - "description": "Dialog title for clear confirmation" - }, - "libraryClearConfirmMessage": "This will remove all scanned tracks from your library. Your actual music files will not be deleted.", - "@libraryClearConfirmMessage": { - "description": "Dialog message for clear confirmation" - }, - "libraryAbout": "About Local Library", - "@libraryAbout": { - "description": "Section header for about info" - }, - "libraryAboutDescription": "Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.", - "@libraryAboutDescription": { - "description": "Description of local library feature" - }, - "libraryTracksCount": "{count} tracks", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "libraryLastScanned": "Last scanned: {time}", - "@libraryLastScanned": { - "description": "Last scan time display", - "placeholders": { - "time": { - "type": "String" - } - } - }, - "libraryLastScannedNever": "Never", - "@libraryLastScannedNever": { - "description": "Shown when library has never been scanned" - }, - "libraryScanning": "Scanning...", - "@libraryScanning": { - "description": "Status during scan" - }, - "libraryScanProgress": "{progress}% of {total} files", - "@libraryScanProgress": { - "description": "Scan progress display", - "placeholders": { - "progress": { - "type": "String" - }, - "total": { - "type": "int" - } - } - }, - "libraryInLibrary": "In Library", - "@libraryInLibrary": { - "description": "Badge shown on tracks that exist in local library" - }, - "libraryRemovedMissingFiles": "Removed {count} missing files from library", - "@libraryRemovedMissingFiles": { - "description": "Snackbar after cleanup", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "libraryCleared": "Library cleared", - "@libraryCleared": { - "description": "Snackbar after clearing library" - }, - "libraryStorageAccessRequired": "Storage Access Required", - "@libraryStorageAccessRequired": { - "description": "Dialog title for storage permission" - }, - "libraryStorageAccessMessage": "SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.", - "@libraryStorageAccessMessage": { - "description": "Dialog message for storage permission" - }, - "libraryFolderNotExist": "Selected folder does not exist", - "@libraryFolderNotExist": { - "description": "Error when folder doesn't exist" - }, - "librarySourceDownloaded": "Downloaded", - "@librarySourceDownloaded": { - "description": "Badge for tracks downloaded via SpotiFLAC" - }, - "librarySourceLocal": "Local", - "@librarySourceLocal": { - "description": "Badge for tracks from local library scan" - }, - "libraryFilterAll": "All", - "@libraryFilterAll": { - "description": "Filter chip - show all library items" - }, - "libraryFilterDownloaded": "Downloaded", - "@libraryFilterDownloaded": { - "description": "Filter chip - show only downloaded items" - }, - "libraryFilterLocal": "Local", - "@libraryFilterLocal": { - "description": "Filter chip - show only local library items" - }, - "libraryFilterTitle": "Filters", - "@libraryFilterTitle": { - "description": "Filter bottom sheet title" - }, - "libraryFilterReset": "Reset", - "@libraryFilterReset": { - "description": "Reset all filters button" - }, - "libraryFilterApply": "Apply", - "@libraryFilterApply": { - "description": "Apply filters button" - }, - "libraryFilterSource": "Source", - "@libraryFilterSource": { - "description": "Filter section - source type" - }, - "libraryFilterQuality": "Quality", - "@libraryFilterQuality": { - "description": "Filter section - audio quality" - }, - "libraryFilterQualityHiRes": "Hi-Res (24bit)", - "@libraryFilterQualityHiRes": { - "description": "Filter option - high resolution audio" - }, - "libraryFilterQualityCD": "CD (16bit)", - "@libraryFilterQualityCD": { - "description": "Filter option - CD quality audio" - }, - "libraryFilterQualityLossy": "Lossy", - "@libraryFilterQualityLossy": { - "description": "Filter option - lossy compressed audio" - }, - "libraryFilterFormat": "Format", - "@libraryFilterFormat": { - "description": "Filter section - file format" - }, - "libraryFilterDate": "Date Added", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "Today", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "This Week", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "This Month", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "This Year", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, - "libraryFilterSort": "Sort", - "@libraryFilterSort": { - "description": "Filter section - sort order" - }, - "libraryFilterSortLatest": "Latest", - "@libraryFilterSortLatest": { - "description": "Sort option - newest first" - }, - "libraryFilterSortOldest": "Oldest", - "@libraryFilterSortOldest": { - "description": "Sort option - oldest first" - }, - "libraryFilterActive": "{count} filter(s) active", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "timeJustNow": "Just now", - "@timeJustNow": { - "description": "Relative time - less than a minute ago" - }, - "timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}", - "@timeMinutesAgo": { - "description": "Relative time - minutes ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}", - "@timeHoursAgo": { - "description": "Relative time - hours ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageSwitchTitle": "Switch Storage Mode", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "Switch to SAF Storage?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "Switch to App Storage?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Existing Downloads", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "New Downloads", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "Continue", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "Select SAF Folder", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "App Storage", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "SAF Storage", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "Storage: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "Storage Statistics", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "{count} tracks in App Storage", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "{count} tracks in SAF Storage", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Your files are stored in multiple locations", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, - "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", - "@tutorialWelcomeTitle": { - "description": "Tutorial welcome page title" - }, - "tutorialWelcomeDesc": "Let's learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.", - "@tutorialWelcomeDesc": { - "description": "Tutorial welcome page description" - }, - "tutorialWelcomeTip1": "Download music from Spotify, Deezer, or paste any supported URL", - "@tutorialWelcomeTip1": { - "description": "Tutorial welcome tip 1" - }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", - "@tutorialWelcomeTip2": { - "description": "Tutorial welcome tip 2" - }, - "tutorialWelcomeTip3": "Automatic metadata, cover art, and lyrics embedding", - "@tutorialWelcomeTip3": { - "description": "Tutorial welcome tip 3" - }, - "tutorialSearchTitle": "Finding Music", - "@tutorialSearchTitle": { - "description": "Tutorial search page title" - }, - "tutorialSearchDesc": "There are two easy ways to find music you want to download.", - "@tutorialSearchDesc": { - "description": "Tutorial search page description" - }, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, - "tutorialDownloadTitle": "Downloading Music", - "@tutorialDownloadTitle": { - "description": "Tutorial download page title" - }, - "tutorialDownloadDesc": "Downloading music is simple and fast. Here's how it works.", - "@tutorialDownloadDesc": { - "description": "Tutorial download page description" - }, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, - "tutorialLibraryTitle": "Your Library", - "@tutorialLibraryTitle": { - "description": "Tutorial library page title" - }, - "tutorialLibraryDesc": "All your downloaded music is organized in the Library tab.", - "@tutorialLibraryDesc": { - "description": "Tutorial library page description" - }, - "tutorialLibraryTip1": "View download progress and queue in the Library tab", - "@tutorialLibraryTip1": { - "description": "Tutorial library tip 1" - }, - "tutorialLibraryTip2": "Tap any track to play it with your music player", - "@tutorialLibraryTip2": { - "description": "Tutorial library tip 2" - }, - "tutorialLibraryTip3": "Switch between list and grid view for better browsing", - "@tutorialLibraryTip3": { - "description": "Tutorial library tip 3" - }, - "tutorialExtensionsTitle": "Extensions", - "@tutorialExtensionsTitle": { - "description": "Tutorial extensions page title" - }, - "tutorialExtensionsDesc": "Extend the app's capabilities with community extensions.", - "@tutorialExtensionsDesc": { - "description": "Tutorial extensions page description" - }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", - "@tutorialExtensionsTip1": { - "description": "Tutorial extensions tip 1" - }, - "tutorialExtensionsTip2": "Add new download providers or search sources", - "@tutorialExtensionsTip2": { - "description": "Tutorial extensions tip 2" - }, - "tutorialExtensionsTip3": "Get lyrics, enhanced metadata, and more features", - "@tutorialExtensionsTip3": { - "description": "Tutorial extensions tip 3" - }, - "tutorialSettingsTitle": "Customize Your Experience", - "@tutorialSettingsTitle": { - "description": "Tutorial settings page title" - }, - "tutorialSettingsDesc": "Personalize the app in Settings to match your preferences.", - "@tutorialSettingsDesc": { - "description": "Tutorial settings page description" - }, - "tutorialSettingsTip1": "Change download location and folder organization", - "@tutorialSettingsTip1": { - "description": "Tutorial settings tip 1" - }, - "tutorialSettingsTip2": "Set default audio quality and format preferences", - "@tutorialSettingsTip2": { - "description": "Tutorial settings tip 2" - }, - "tutorialSettingsTip3": "Customize app theme and appearance", - "@tutorialSettingsTip3": { - "description": "Tutorial settings tip 3" - }, - "tutorialReadyMessage": "You're all set! Start downloading your favorite music now.", - "@tutorialReadyMessage": { - "description": "Tutorial completion message" - }, - "tutorialExample": "EXAMPLE", - "@tutorialExample": { - "description": "Example label in tutorial" - }, - "libraryForceFullScan": "Force Full Scan", - "@libraryForceFullScan": { - "description": "Button to force a complete rescan of library" - }, - "libraryForceFullScanSubtitle": "Rescan all files, ignoring cache", - "@libraryForceFullScanSubtitle": { - "description": "Subtitle for force full scan button" - }, - "cleanupOrphanedDownloads": "Cleanup Orphaned Downloads", - "@cleanupOrphanedDownloads": { - "description": "Button to remove history entries for deleted files" - }, - "cleanupOrphanedDownloadsSubtitle": "Remove history entries for files that no longer exist", - "@cleanupOrphanedDownloadsSubtitle": { - "description": "Subtitle for orphaned cleanup button" - }, - "cleanupOrphanedDownloadsResult": "Removed {count} orphaned entries from history", - "@cleanupOrphanedDownloadsResult": { - "description": "Snackbar after orphan cleanup", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "cleanupOrphanedDownloadsNone": "No orphaned entries found", - "@cleanupOrphanedDownloadsNone": { - "description": "Snackbar when no orphans found" - }, - "cacheTitle": "Storage & Cache", - "@cacheTitle": { - "description": "Cache management page title" - }, - "cacheSummaryTitle": "Cache overview", - "@cacheSummaryTitle": { - "description": "Heading for cache summary card" - }, - "cacheSummarySubtitle": "Clearing cache will not remove downloaded music files.", - "@cacheSummarySubtitle": { - "description": "Helper text for cache summary card" - }, - "cacheEstimatedTotal": "Estimated cache usage: {size}", - "@cacheEstimatedTotal": { - "description": "Total cache size shown in summary", - "placeholders": { - "size": { - "type": "String" - } - } - }, - "cacheSectionStorage": "Cached Data", - "@cacheSectionStorage": { - "description": "Section header for cache entries" - }, - "cacheSectionMaintenance": "Maintenance", - "@cacheSectionMaintenance": { - "description": "Section header for cleanup actions" - }, - "cacheAppDirectory": "App cache directory", - "@cacheAppDirectory": { - "description": "Cache item title for app cache directory" - }, - "cacheAppDirectoryDesc": "HTTP responses, WebView data, and other temporary app data.", - "@cacheAppDirectoryDesc": { - "description": "Description of what app cache directory contains" - }, - "cacheTempDirectory": "Temporary directory", - "@cacheTempDirectory": { - "description": "Cache item title for temporary files directory" - }, - "cacheTempDirectoryDesc": "Temporary files from downloads and audio conversion.", - "@cacheTempDirectoryDesc": { - "description": "Description of what temporary directory contains" - }, - "cacheCoverImage": "Cover image cache", - "@cacheCoverImage": { - "description": "Cache item title for persistent cover images" - }, - "cacheCoverImageDesc": "Downloaded album and track cover art. Will re-download when viewed.", - "@cacheCoverImageDesc": { - "description": "Description of what cover image cache contains" - }, - "cacheLibraryCover": "Library cover cache", - "@cacheLibraryCover": { - "description": "Cache item title for local library cover art images" - }, - "cacheLibraryCoverDesc": "Cover art extracted from local music files. Will re-extract on next scan.", - "@cacheLibraryCoverDesc": { - "description": "Description of what library cover cache contains" - }, - "cacheExploreFeed": "Explore feed cache", - "@cacheExploreFeed": { - "description": "Cache item title for explore home feed cache" - }, - "cacheExploreFeedDesc": "Explore tab content (new releases, trending). Will refresh on next visit.", - "@cacheExploreFeedDesc": { - "description": "Description of what explore feed cache contains" - }, - "cacheTrackLookup": "Track lookup cache", - "@cacheTrackLookup": { - "description": "Cache item title for track ID lookup cache" - }, - "cacheTrackLookupDesc": "Spotify/Deezer track ID lookups. Clearing may slow next few searches.", - "@cacheTrackLookupDesc": { - "description": "Description of what track lookup cache contains" - }, - "cacheCleanupUnusedDesc": "Remove orphaned download history and library entries for missing files.", - "@cacheCleanupUnusedDesc": { - "description": "Description of what cleanup unused data does" - }, - "cacheNoData": "No cached data", - "@cacheNoData": { - "description": "Label when cache category has no data" - }, - "cacheSizeWithFiles": "{size} in {count} files", - "@cacheSizeWithFiles": { - "description": "Cache size and file count", - "placeholders": { - "size": { - "type": "String" - }, - "count": { - "type": "int" - } - } - }, - "cacheSizeOnly": "{size}", - "@cacheSizeOnly": { - "description": "Cache size only", - "placeholders": { - "size": { - "type": "String" - } - } - }, - "cacheEntries": "{count} entries", - "@cacheEntries": { - "description": "Track cache entry count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "cacheClearSuccess": "Cleared: {target}", - "@cacheClearSuccess": { - "description": "Snackbar after clearing selected cache", - "placeholders": { - "target": { - "type": "String" - } - } - }, - "cacheClearConfirmTitle": "Clear cache?", - "@cacheClearConfirmTitle": { - "description": "Dialog title before clearing one cache category" - }, - "cacheClearConfirmMessage": "This will clear cached data for {target}. Downloaded music files will not be deleted.", - "@cacheClearConfirmMessage": { - "description": "Dialog message before clearing selected cache", - "placeholders": { - "target": { - "type": "String" - } - } - }, - "cacheClearAllConfirmTitle": "Clear all cache?", - "@cacheClearAllConfirmTitle": { - "description": "Dialog title before clearing all caches" - }, - "cacheClearAllConfirmMessage": "This will clear all cache categories on this page. Downloaded music files will not be deleted.", - "@cacheClearAllConfirmMessage": { - "description": "Dialog message before clearing all caches" - }, - "cacheClearAll": "Clear all cache", - "@cacheClearAll": { - "description": "Button label to clear all caches" - }, - "cacheCleanupUnused": "Cleanup unused data", - "@cacheCleanupUnused": { - "description": "Action title for cleaning unused entries" - }, - "cacheCleanupUnusedSubtitle": "Remove orphaned download history and missing library entries", - "@cacheCleanupUnusedSubtitle": { - "description": "Subtitle for cleanup unused data action" - }, - "cacheCleanupResult": "Cleanup completed: {downloadCount} orphaned downloads, {libraryCount} missing library entries", - "@cacheCleanupResult": { - "description": "Snackbar after unused data cleanup", - "placeholders": { - "downloadCount": { - "type": "int" - }, - "libraryCount": { - "type": "int" - } - } - }, - "cacheRefreshStats": "Refresh stats", - "@cacheRefreshStats": { - "description": "Button label to refresh cache statistics" - }, - "trackSaveCoverArt": "Save Cover Art", - "@trackSaveCoverArt": { - "description": "Menu action - save album cover art as file" - }, - "trackSaveCoverArtSubtitle": "Save album art as .jpg file", - "@trackSaveCoverArtSubtitle": { - "description": "Subtitle for save cover art action" - }, - "trackSaveLyrics": "Save Lyrics (.lrc)", - "@trackSaveLyrics": { - "description": "Menu action - save lyrics as .lrc file" - }, - "trackSaveLyricsSubtitle": "Fetch and save lyrics as .lrc file", - "@trackSaveLyricsSubtitle": { - "description": "Subtitle for save lyrics action" - }, - "trackSaveLyricsProgress": "Saving lyrics...", - "@trackSaveLyricsProgress": { - "description": "Snackbar while saving lyrics to file" - }, - "trackReEnrich": "Re-enrich Metadata", - "@trackReEnrich": { - "description": "Menu action - re-embed metadata into audio file" - }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, - "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", - "@trackReEnrichOnlineSubtitle": { - "description": "Subtitle for re-enrich metadata action for local items" - }, - "trackEditMetadata": "Edit Metadata", - "@trackEditMetadata": { - "description": "Menu action - edit embedded metadata" - }, - "trackCoverSaved": "Cover art saved to {fileName}", - "@trackCoverSaved": { - "description": "Snackbar after cover art saved", - "placeholders": { - "fileName": { - "type": "String" - } - } - }, - "trackCoverNoSource": "No cover art source available", - "@trackCoverNoSource": { - "description": "Snackbar when no cover art URL or embedded cover" - }, - "trackLyricsSaved": "Lyrics saved to {fileName}", - "@trackLyricsSaved": { - "description": "Snackbar after lyrics saved", - "placeholders": { - "fileName": { - "type": "String" - } - } - }, - "trackReEnrichProgress": "Re-enriching metadata...", - "@trackReEnrichProgress": { - "description": "Snackbar while re-enriching metadata" - }, - "trackReEnrichSearching": "Searching metadata online...", - "@trackReEnrichSearching": { - "description": "Snackbar while searching metadata from internet for local items" - }, - "trackReEnrichSuccess": "Metadata re-enriched successfully", - "@trackReEnrichSuccess": { - "description": "Snackbar after successful re-enrichment" - }, - "trackReEnrichFfmpegFailed": "FFmpeg metadata embed failed", - "@trackReEnrichFfmpegFailed": { - "description": "Snackbar when FFmpeg embed fails for MP3/Opus" - }, - "trackSaveFailed": "Failed: {error}", - "@trackSaveFailed": { - "description": "Snackbar when save operation fails", - "placeholders": { - "error": { - "type": "String" - } - } - }, - "trackConvertFormat": "Convert Format", - "@trackConvertFormat": { - "description": "Menu item - convert audio format" - }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", - "@trackConvertFormatSubtitle": { - "description": "Subtitle for convert format menu item" - }, - "trackConvertTitle": "Convert Audio", - "@trackConvertTitle": { - "description": "Title of convert bottom sheet" - }, - "trackConvertTargetFormat": "Target Format", - "@trackConvertTargetFormat": { - "description": "Label for format selection" - }, - "trackConvertBitrate": "Bitrate", - "@trackConvertBitrate": { - "description": "Label for bitrate selection" - }, - "trackConvertConfirmTitle": "Confirm Conversion", - "@trackConvertConfirmTitle": { - "description": "Confirmation dialog title" - }, - "trackConvertConfirmMessage": "Convert from {sourceFormat} to {targetFormat} at {bitrate}?\n\nThe original file will be deleted after conversion.", - "@trackConvertConfirmMessage": { - "description": "Confirmation dialog message", - "placeholders": { - "sourceFormat": { - "type": "String" - }, - "targetFormat": { - "type": "String" - }, - "bitrate": { - "type": "String" - } - } - }, - "trackConvertConverting": "Converting audio...", - "@trackConvertConverting": { - "description": "Snackbar while converting" - }, - "trackConvertSuccess": "Converted to {format} successfully", - "@trackConvertSuccess": { - "description": "Snackbar after successful conversion", - "placeholders": { - "format": { - "type": "String" - } - } - }, - "trackConvertFailed": "Conversion failed", - "@trackConvertFailed": { - "description": "Snackbar when conversion fails" - } +{ + "@@locale": "id", + "@@last_modified": "2026-01-16", + "appName": "SpotiFLAC", + "@appName": { + "description": "App name - DO NOT TRANSLATE" + }, + "appDescription": "Unduh lagu Spotify dalam kualitas lossless dari Tidal, Qobuz, dan Amazon Music.", + "@appDescription": { + "description": "App description shown in about page" + }, + "navHome": "Beranda", + "@navHome": { + "description": "Bottom navigation - Home tab" + }, + "navLibrary": "Library", + "@navLibrary": { + "description": "Bottom navigation - Library tab" + }, + "navHistory": "Riwayat", + "@navHistory": { + "description": "Bottom navigation - History tab (legacy)" + }, + "navSettings": "Pengaturan", + "@navSettings": { + "description": "Bottom navigation - Settings tab" + }, + "navStore": "Toko", + "@navStore": { + "description": "Bottom navigation - Extension store tab" + }, + "homeTitle": "Beranda", + "@homeTitle": { + "description": "Home screen title" + }, + "homeSearchHint": "Tempel URL Spotify atau cari...", + "@homeSearchHint": { + "description": "Placeholder text in search box" + }, + "homeSearchHintExtension": "Cari dengan {extensionName}...", + "@homeSearchHintExtension": { + "description": "Placeholder when extension search is active", + "placeholders": { + "extensionName": { + "type": "String", + "description": "Name of the active extension" + } + } + }, + "homeSubtitle": "Tempel link Spotify atau cari berdasarkan nama", + "@homeSubtitle": { + "description": "Subtitle shown below search box" + }, + "homeSupports": "Mendukung: URL Track, Album, Playlist, Artis", + "@homeSupports": { + "description": "Info text about supported URL types" + }, + "homeRecent": "Terbaru", + "@homeRecent": { + "description": "Section header for recent searches" + }, + "historyTitle": "Riwayat", + "@historyTitle": { + "description": "History screen title" + }, + "historyDownloading": "Mengunduh ({count})", + "@historyDownloading": { + "description": "Tab showing active downloads count", + "placeholders": { + "count": { + "type": "int", + "description": "Number of active downloads" + } + } + }, + "historyDownloaded": "Terunduh", + "@historyDownloaded": { + "description": "Tab showing completed downloads" + }, + "historyFilterAll": "Semua", + "@historyFilterAll": { + "description": "Filter chip - show all items" + }, + "historyFilterAlbums": "Album", + "@historyFilterAlbums": { + "description": "Filter chip - show albums only" + }, + "historyFilterSingles": "Single", + "@historyFilterSingles": { + "description": "Filter chip - show singles only" + }, + "historyTracksCount": "{count, plural, =1{1 lagu} other{{count} lagu}}", + "@historyTracksCount": { + "description": "Track count with plural form", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "historyAlbumsCount": "{count, plural, =1{1 album} other{{count} album}}", + "@historyAlbumsCount": { + "description": "Album count with plural form", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "historyNoDownloads": "Tidak ada riwayat unduhan", + "@historyNoDownloads": { + "description": "Empty state title" + }, + "historyNoDownloadsSubtitle": "Lagu yang diunduh akan muncul di sini", + "@historyNoDownloadsSubtitle": { + "description": "Empty state subtitle" + }, + "historyNoAlbums": "Tidak ada unduhan album", + "@historyNoAlbums": { + "description": "Empty state when filtering albums" + }, + "historyNoAlbumsSubtitle": "Unduh beberapa lagu dari album untuk melihatnya di sini", + "@historyNoAlbumsSubtitle": { + "description": "Empty state subtitle for albums filter" + }, + "historyNoSingles": "Tidak ada unduhan single", + "@historyNoSingles": { + "description": "Empty state when filtering singles" + }, + "historyNoSinglesSubtitle": "Unduhan lagu satuan akan muncul di sini", + "@historyNoSinglesSubtitle": { + "description": "Empty state subtitle for singles filter" + }, + "historySearchHint": "Search history...", + "@historySearchHint": { + "description": "Search bar placeholder in history" + }, + "settingsTitle": "Pengaturan", + "@settingsTitle": { + "description": "Settings screen title" + }, + "settingsDownload": "Unduhan", + "@settingsDownload": { + "description": "Settings section - download options" + }, + "settingsAppearance": "Tampilan", + "@settingsAppearance": { + "description": "Settings section - visual customization" + }, + "settingsOptions": "Opsi", + "@settingsOptions": { + "description": "Settings section - app options" + }, + "settingsExtensions": "Ekstensi", + "@settingsExtensions": { + "description": "Settings section - extension management" + }, + "settingsAbout": "Tentang", + "@settingsAbout": { + "description": "Settings section - app info" + }, + "downloadTitle": "Unduhan", + "@downloadTitle": { + "description": "Download settings page title" + }, + "downloadLocation": "Lokasi Unduhan", + "@downloadLocation": { + "description": "Setting for download folder" + }, + "downloadLocationSubtitle": "Pilih tempat menyimpan file", + "@downloadLocationSubtitle": { + "description": "Subtitle for download location" + }, + "downloadLocationDefault": "Lokasi default", + "@downloadLocationDefault": { + "description": "Shown when using default folder" + }, + "downloadDefaultService": "Layanan Default", + "@downloadDefaultService": { + "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" + }, + "downloadDefaultServiceSubtitle": "Layanan yang digunakan untuk unduhan", + "@downloadDefaultServiceSubtitle": { + "description": "Subtitle for default service" + }, + "downloadDefaultQuality": "Kualitas Default", + "@downloadDefaultQuality": { + "description": "Setting for audio quality" + }, + "downloadAskQuality": "Tanya Kualitas Sebelum Unduh", + "@downloadAskQuality": { + "description": "Toggle to show quality picker" + }, + "downloadAskQualitySubtitle": "Tampilkan pemilih kualitas untuk setiap unduhan", + "@downloadAskQualitySubtitle": { + "description": "Subtitle for ask quality toggle" + }, + "downloadFilenameFormat": "Format Nama File", + "@downloadFilenameFormat": { + "description": "Setting for output filename pattern" + }, + "downloadFolderOrganization": "Organisasi Folder", + "@downloadFolderOrganization": { + "description": "Setting for folder structure" + }, + "downloadSeparateSingles": "Pisahkan Single", + "@downloadSeparateSingles": { + "description": "Toggle to separate single tracks" + }, + "downloadSeparateSinglesSubtitle": "Letakkan lagu satuan di folder terpisah", + "@downloadSeparateSinglesSubtitle": { + "description": "Subtitle for separate singles toggle" + }, + "qualityBest": "Terbaik", + "@qualityBest": { + "description": "Audio quality option - highest available" + }, + "qualityFlac": "FLAC", + "@qualityFlac": { + "description": "Audio quality option - FLAC lossless" + }, + "quality320": "320 kbps", + "@quality320": { + "description": "Audio quality option - 320kbps MP3" + }, + "quality128": "128 kbps", + "@quality128": { + "description": "Audio quality option - 128kbps MP3" + }, + "appearanceTitle": "Tampilan", + "@appearanceTitle": { + "description": "Appearance settings page title" + }, + "appearanceTheme": "Tema", + "@appearanceTheme": { + "description": "Theme mode setting" + }, + "appearanceThemeSystem": "Sistem", + "@appearanceThemeSystem": { + "description": "Follow system theme" + }, + "appearanceThemeLight": "Terang", + "@appearanceThemeLight": { + "description": "Light theme" + }, + "appearanceThemeDark": "Gelap", + "@appearanceThemeDark": { + "description": "Dark theme" + }, + "appearanceDynamicColor": "Warna Dinamis", + "@appearanceDynamicColor": { + "description": "Material You dynamic colors" + }, + "appearanceDynamicColorSubtitle": "Gunakan warna dari wallpaper Anda", + "@appearanceDynamicColorSubtitle": { + "description": "Subtitle for dynamic color" + }, + "appearanceAccentColor": "Warna Aksen", + "@appearanceAccentColor": { + "description": "Custom accent color picker" + }, + "appearanceHistoryView": "Tampilan Riwayat", + "@appearanceHistoryView": { + "description": "Layout style for history" + }, + "appearanceHistoryViewList": "Daftar", + "@appearanceHistoryViewList": { + "description": "List layout option" + }, + "appearanceHistoryViewGrid": "Grid", + "@appearanceHistoryViewGrid": { + "description": "Grid layout option" + }, + "optionsTitle": "Opsi", + "@optionsTitle": { + "description": "Options settings page title" + }, + "optionsSearchSource": "Sumber Pencarian", + "@optionsSearchSource": { + "description": "Section for search provider settings" + }, + "optionsPrimaryProvider": "Provider Utama", + "@optionsPrimaryProvider": { + "description": "Main search provider setting" + }, + "optionsPrimaryProviderSubtitle": "Layanan yang digunakan saat mencari berdasarkan nama lagu.", + "@optionsPrimaryProviderSubtitle": { + "description": "Subtitle for primary provider" + }, + "optionsUsingExtension": "Menggunakan ekstensi: {extensionName}", + "@optionsUsingExtension": { + "description": "Shows active extension name", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "optionsSwitchBack": "Ketuk Deezer atau Spotify untuk beralih dari ekstensi", + "@optionsSwitchBack": { + "description": "Hint to switch back to built-in providers" + }, + "optionsAutoFallback": "Auto Fallback", + "@optionsAutoFallback": { + "description": "Auto-retry with other services" + }, + "optionsAutoFallbackSubtitle": "Coba layanan lain jika unduhan gagal", + "@optionsAutoFallbackSubtitle": { + "description": "Subtitle for auto fallback" + }, + "optionsUseExtensionProviders": "Gunakan Provider Ekstensi", + "@optionsUseExtensionProviders": { + "description": "Enable extension download providers" + }, + "optionsUseExtensionProvidersOn": "Ekstensi akan dicoba terlebih dahulu", + "@optionsUseExtensionProvidersOn": { + "description": "Status when extension providers enabled" + }, + "optionsUseExtensionProvidersOff": "Hanya menggunakan provider bawaan", + "@optionsUseExtensionProvidersOff": { + "description": "Status when extension providers disabled" + }, + "optionsEmbedLyrics": "Sematkan Lirik", + "@optionsEmbedLyrics": { + "description": "Embed lyrics in audio files" + }, + "optionsEmbedLyricsSubtitle": "Sematkan lirik sinkron ke file FLAC", + "@optionsEmbedLyricsSubtitle": { + "description": "Subtitle for embed lyrics" + }, + "optionsMaxQualityCover": "Cover Kualitas Maksimal", + "@optionsMaxQualityCover": { + "description": "Download highest quality album art" + }, + "optionsMaxQualityCoverSubtitle": "Unduh cover art resolusi tertinggi", + "@optionsMaxQualityCoverSubtitle": { + "description": "Subtitle for max quality cover" + }, + "optionsConcurrentDownloads": "Unduhan Bersamaan", + "@optionsConcurrentDownloads": { + "description": "Number of parallel downloads" + }, + "optionsConcurrentSequential": "Berurutan (1 per waktu)", + "@optionsConcurrentSequential": { + "description": "Download one at a time" + }, + "optionsConcurrentParallel": "{count} unduhan paralel", + "@optionsConcurrentParallel": { + "description": "Multiple parallel downloads", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "optionsConcurrentWarning": "Unduhan paralel dapat memicu pembatasan rate", + "@optionsConcurrentWarning": { + "description": "Warning about rate limits" + }, + "optionsExtensionStore": "Toko Ekstensi", + "@optionsExtensionStore": { + "description": "Show/hide store tab" + }, + "optionsExtensionStoreSubtitle": "Tampilkan tab Toko di navigasi", + "@optionsExtensionStoreSubtitle": { + "description": "Subtitle for extension store toggle" + }, + "optionsCheckUpdates": "Periksa Pembaruan", + "@optionsCheckUpdates": { + "description": "Auto update check toggle" + }, + "optionsCheckUpdatesSubtitle": "Beritahu saat versi baru tersedia", + "@optionsCheckUpdatesSubtitle": { + "description": "Subtitle for update check" + }, + "optionsUpdateChannel": "Saluran Pembaruan", + "@optionsUpdateChannel": { + "description": "Stable vs preview releases" + }, + "optionsUpdateChannelStable": "Hanya rilis stabil", + "@optionsUpdateChannelStable": { + "description": "Only stable updates" + }, + "optionsUpdateChannelPreview": "Dapatkan rilis preview", + "@optionsUpdateChannelPreview": { + "description": "Include beta/preview updates" + }, + "optionsUpdateChannelWarning": "Preview mungkin mengandung bug atau fitur belum lengkap", + "@optionsUpdateChannelWarning": { + "description": "Warning about preview channel" + }, + "optionsClearHistory": "Hapus Riwayat Unduhan", + "@optionsClearHistory": { + "description": "Delete all download history" + }, + "optionsClearHistorySubtitle": "Hapus semua lagu dari riwayat", + "@optionsClearHistorySubtitle": { + "description": "Subtitle for clear history" + }, + "optionsDetailedLogging": "Log Detail", + "@optionsDetailedLogging": { + "description": "Enable verbose logs for debugging" + }, + "optionsDetailedLoggingOn": "Log detail sedang direkam", + "@optionsDetailedLoggingOn": { + "description": "Status when logging enabled" + }, + "optionsDetailedLoggingOff": "Aktifkan untuk laporan bug", + "@optionsDetailedLoggingOff": { + "description": "Status when logging disabled" + }, + "optionsSpotifyCredentials": "Kredensial Spotify", + "@optionsSpotifyCredentials": { + "description": "Spotify API credentials setting" + }, + "optionsSpotifyCredentialsConfigured": "Client ID: {clientId}...", + "@optionsSpotifyCredentialsConfigured": { + "description": "Shows configured client ID preview", + "placeholders": { + "clientId": { + "type": "String" + } + } + }, + "optionsSpotifyCredentialsRequired": "Diperlukan - ketuk untuk mengatur", + "@optionsSpotifyCredentialsRequired": { + "description": "Prompt to set up credentials" + }, + "optionsSpotifyWarning": "Spotify memerlukan kredensial API Anda sendiri. Dapatkan gratis dari developer.spotify.com", + "@optionsSpotifyWarning": { + "description": "Info about Spotify API requirement" + }, + "optionsSpotifyDeprecationWarning": "Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.", + "@optionsSpotifyDeprecationWarning": { + "description": "Warning about Spotify API deprecation" + }, + "extensionsTitle": "Ekstensi", + "@extensionsTitle": { + "description": "Extensions page title" + }, + "extensionsInstalled": "Ekstensi Terpasang", + "@extensionsInstalled": { + "description": "Section header for installed extensions" + }, + "extensionsNone": "Tidak ada ekstensi terpasang", + "@extensionsNone": { + "description": "Empty state title" + }, + "extensionsNoneSubtitle": "Pasang ekstensi dari tab Toko", + "@extensionsNoneSubtitle": { + "description": "Empty state subtitle" + }, + "extensionsEnabled": "Aktif", + "@extensionsEnabled": { + "description": "Extension status - active" + }, + "extensionsDisabled": "Nonaktif", + "@extensionsDisabled": { + "description": "Extension status - inactive" + }, + "extensionsVersion": "Versi {version}", + "@extensionsVersion": { + "description": "Extension version display", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "extensionsAuthor": "oleh {author}", + "@extensionsAuthor": { + "description": "Extension author credit", + "placeholders": { + "author": { + "type": "String" + } + } + }, + "extensionsUninstall": "Copot", + "@extensionsUninstall": { + "description": "Uninstall extension button" + }, + "extensionsSetAsSearch": "Jadikan Provider Pencarian", + "@extensionsSetAsSearch": { + "description": "Use extension for search" + }, + "storeTitle": "Toko Ekstensi", + "@storeTitle": { + "description": "Store screen title" + }, + "storeSearch": "Cari ekstensi...", + "@storeSearch": { + "description": "Store search placeholder" + }, + "storeInstall": "Pasang", + "@storeInstall": { + "description": "Install extension button" + }, + "storeInstalled": "Terpasang", + "@storeInstalled": { + "description": "Already installed badge" + }, + "storeUpdate": "Perbarui", + "@storeUpdate": { + "description": "Update available button" + }, + "aboutTitle": "Tentang", + "@aboutTitle": { + "description": "About page title" + }, + "aboutContributors": "Kontributor", + "@aboutContributors": { + "description": "Section for contributors" + }, + "aboutMobileDeveloper": "Pengembang versi mobile", + "@aboutMobileDeveloper": { + "description": "Role description for mobile dev" + }, + "aboutOriginalCreator": "Pembuat SpotiFLAC asli", + "@aboutOriginalCreator": { + "description": "Role description for original creator" + }, + "aboutLogoArtist": "Seniman berbakat yang membuat logo aplikasi kita yang indah!", + "@aboutLogoArtist": { + "description": "Role description for logo artist" + }, + "aboutTranslators": "Translators", + "@aboutTranslators": { + "description": "Section for translators" + }, + "aboutSpecialThanks": "Terima Kasih Khusus", + "@aboutSpecialThanks": { + "description": "Section for special thanks" + }, + "aboutLinks": "Tautan", + "@aboutLinks": { + "description": "Section for external links" + }, + "aboutMobileSource": "Kode sumber mobile", + "@aboutMobileSource": { + "description": "Link to mobile GitHub repo" + }, + "aboutPCSource": "Kode sumber PC", + "@aboutPCSource": { + "description": "Link to PC GitHub repo" + }, + "aboutReportIssue": "Laporkan masalah", + "@aboutReportIssue": { + "description": "Link to report bugs" + }, + "aboutReportIssueSubtitle": "Laporkan masalah yang Anda temui", + "@aboutReportIssueSubtitle": { + "description": "Subtitle for report issue" + }, + "aboutFeatureRequest": "Permintaan fitur", + "@aboutFeatureRequest": { + "description": "Link to suggest features" + }, + "aboutFeatureRequestSubtitle": "Sarankan fitur baru untuk aplikasi", + "@aboutFeatureRequestSubtitle": { + "description": "Subtitle for feature request" + }, + "aboutTelegramChannel": "Telegram Channel", + "@aboutTelegramChannel": { + "description": "Link to Telegram channel" + }, + "aboutTelegramChannelSubtitle": "Announcements and updates", + "@aboutTelegramChannelSubtitle": { + "description": "Subtitle for Telegram channel" + }, + "aboutTelegramChat": "Telegram Community", + "@aboutTelegramChat": { + "description": "Link to Telegram chat group" + }, + "aboutTelegramChatSubtitle": "Chat with other users", + "@aboutTelegramChatSubtitle": { + "description": "Subtitle for Telegram chat" + }, + "aboutSocial": "Social", + "@aboutSocial": { + "description": "Section for social links" + }, + "aboutSupport": "Dukungan", + "@aboutSupport": { + "description": "Section for support/donation links" + }, + "aboutApp": "Aplikasi", + "@aboutApp": { + "description": "Section for app info" + }, + "aboutVersion": "Versi", + "@aboutVersion": { + "description": "Version info label" + }, + "aboutBinimumDesc": "Pembuat QQDL & HiFi API. Tanpa API ini, unduhan Tidal tidak akan ada!", + "@aboutBinimumDesc": { + "description": "Credit description for binimum" + }, + "aboutSachinsenalDesc": "Pembuat proyek HiFi asli. Fondasi dari integrasi Tidal!", + "@aboutSachinsenalDesc": { + "description": "Credit description for sachinsenal0x64" + }, + "aboutSjdonadoDesc": "Creator of I Don't Have Spotify (IDHS). The fallback link resolver that saves the day!", + "@aboutSjdonadoDesc": { + "description": "Credit description for sjdonado" + }, + "aboutDoubleDouble": "DoubleDouble", + "@aboutDoubleDouble": { + "description": "Name of Amazon API service - DO NOT TRANSLATE" + }, + "aboutDoubleDoubleDesc": "API luar biasa untuk unduhan Amazon Music. Terima kasih sudah membuatnya gratis!", + "@aboutDoubleDoubleDesc": { + "description": "Credit for DoubleDouble API" + }, + "aboutDabMusic": "DAB Music", + "@aboutDabMusic": { + "description": "Name of Qobuz API service - DO NOT TRANSLATE" + }, + "aboutDabMusicDesc": "API streaming Qobuz terbaik. Unduhan Hi-Res tidak akan mungkin tanpa ini!", + "@aboutDabMusicDesc": { + "description": "Credit for DAB Music API" + }, + "aboutSpotiSaver": "SpotiSaver", + "@aboutSpotiSaver": { + "description": "Name of SpotiSaver API service - DO NOT TRANSLATE" + }, + "aboutSpotiSaverDesc": "Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!", + "@aboutSpotiSaverDesc": { + "description": "Credit for SpotiSaver API" + }, + "aboutAppDescription": "Unduh lagu Spotify dalam kualitas lossless dari Tidal, Qobuz, dan Amazon Music.", + "@aboutAppDescription": { + "description": "App description in header card" + }, + "albumTitle": "Album", + "@albumTitle": { + "description": "Album screen title" + }, + "albumTracks": "{count, plural, =1{1 lagu} other{{count} lagu}}", + "@albumTracks": { + "description": "Album track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "albumDownloadAll": "Unduh Semua", + "@albumDownloadAll": { + "description": "Button to download all tracks" + }, + "albumDownloadRemaining": "Unduh Sisanya", + "@albumDownloadRemaining": { + "description": "Button to download remaining tracks" + }, + "playlistTitle": "Playlist", + "@playlistTitle": { + "description": "Playlist screen title" + }, + "artistTitle": "Artis", + "@artistTitle": { + "description": "Artist screen title" + }, + "artistAlbums": "Album", + "@artistAlbums": { + "description": "Section header for artist albums" + }, + "artistSingles": "Single & EP", + "@artistSingles": { + "description": "Section header for singles/EPs" + }, + "artistCompilations": "Kompilasi", + "@artistCompilations": { + "description": "Section header for compilations" + }, + "artistReleases": "{count, plural, =1{1 rilis} other{{count} rilis}}", + "@artistReleases": { + "description": "Artist release count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "artistPopular": "Populer", + "@artistPopular": { + "description": "Section header for popular/top tracks" + }, + "artistMonthlyListeners": "{count} pendengar bulanan", + "@artistMonthlyListeners": { + "description": "Monthly listener count display", + "placeholders": { + "count": { + "type": "String", + "description": "Formatted listener count" + } + } + }, + "trackMetadataTitle": "Info Lagu", + "@trackMetadataTitle": { + "description": "Track metadata screen title" + }, + "trackMetadataArtist": "Artis", + "@trackMetadataArtist": { + "description": "Metadata field - artist name" + }, + "trackMetadataAlbum": "Album", + "@trackMetadataAlbum": { + "description": "Metadata field - album name" + }, + "trackMetadataDuration": "Durasi", + "@trackMetadataDuration": { + "description": "Metadata field - track length" + }, + "trackMetadataQuality": "Kualitas", + "@trackMetadataQuality": { + "description": "Metadata field - audio quality" + }, + "trackMetadataPath": "Lokasi File", + "@trackMetadataPath": { + "description": "Metadata field - file location" + }, + "trackMetadataDownloadedAt": "Diunduh", + "@trackMetadataDownloadedAt": { + "description": "Metadata field - download date" + }, + "trackMetadataService": "Layanan", + "@trackMetadataService": { + "description": "Metadata field - download service used" + }, + "trackMetadataPlay": "Putar", + "@trackMetadataPlay": { + "description": "Action button - play track" + }, + "trackMetadataShare": "Bagikan", + "@trackMetadataShare": { + "description": "Action button - share track" + }, + "trackMetadataDelete": "Hapus", + "@trackMetadataDelete": { + "description": "Action button - delete track" + }, + "trackMetadataRedownload": "Unduh ulang", + "@trackMetadataRedownload": { + "description": "Action button - download again" + }, + "trackMetadataOpenFolder": "Buka Folder", + "@trackMetadataOpenFolder": { + "description": "Action button - open containing folder" + }, + "setupTitle": "Selamat Datang di SpotiFLAC", + "@setupTitle": { + "description": "Setup wizard title" + }, + "setupSubtitle": "Mari mulai pengaturan", + "@setupSubtitle": { + "description": "Setup wizard subtitle" + }, + "setupStoragePermission": "Izin Penyimpanan", + "@setupStoragePermission": { + "description": "Storage permission step title" + }, + "setupStoragePermissionSubtitle": "Diperlukan untuk menyimpan file unduhan", + "@setupStoragePermissionSubtitle": { + "description": "Explanation for storage permission" + }, + "setupStoragePermissionGranted": "Izin diberikan", + "@setupStoragePermissionGranted": { + "description": "Status when permission granted" + }, + "setupStoragePermissionDenied": "Izin ditolak", + "@setupStoragePermissionDenied": { + "description": "Status when permission denied" + }, + "setupGrantPermission": "Berikan Izin", + "@setupGrantPermission": { + "description": "Button to request permission" + }, + "setupDownloadLocation": "Lokasi Unduhan", + "@setupDownloadLocation": { + "description": "Download folder step title" + }, + "setupChooseFolder": "Pilih Folder", + "@setupChooseFolder": { + "description": "Button to pick folder" + }, + "setupContinue": "Lanjutkan", + "@setupContinue": { + "description": "Continue to next step button" + }, + "setupSkip": "Lewati untuk sekarang", + "@setupSkip": { + "description": "Skip current step button" + }, + "setupStorageAccessRequired": "Akses Penyimpanan Diperlukan", + "@setupStorageAccessRequired": { + "description": "Title when storage access needed" + }, + "setupStorageAccessMessage": "SpotiFLAC membutuhkan izin \"Akses semua file\" untuk menyimpan file musik ke folder pilihan Anda.", + "@setupStorageAccessMessage": { + "description": "Explanation for storage access" + }, + "setupStorageAccessMessageAndroid11": "Android 11+ memerlukan izin \"Akses semua file\" untuk menyimpan file ke folder unduhan pilihan Anda.", + "@setupStorageAccessMessageAndroid11": { + "description": "Android 11+ specific explanation" + }, + "setupOpenSettings": "Buka Pengaturan", + "@setupOpenSettings": { + "description": "Button to open system settings" + }, + "setupPermissionDeniedMessage": "Izin ditolak. Harap berikan semua izin untuk melanjutkan.", + "@setupPermissionDeniedMessage": { + "description": "Error when permission denied" + }, + "setupPermissionRequired": "Izin {permissionType} Diperlukan", + "@setupPermissionRequired": { + "description": "Generic permission required title", + "placeholders": { + "permissionType": { + "type": "String", + "description": "Type of permission (Storage/Notification)" + } + } + }, + "setupPermissionRequiredMessage": "Izin {permissionType} diperlukan untuk pengalaman terbaik. Anda dapat mengubahnya nanti di Pengaturan.", + "@setupPermissionRequiredMessage": { + "description": "Generic permission required message", + "placeholders": { + "permissionType": { + "type": "String" + } + } + }, + "setupSelectDownloadFolder": "Pilih Folder Unduhan", + "@setupSelectDownloadFolder": { + "description": "Folder selection step title" + }, + "setupUseDefaultFolder": "Gunakan Folder Default?", + "@setupUseDefaultFolder": { + "description": "Dialog title for default folder" + }, + "setupNoFolderSelected": "Tidak ada folder dipilih. Apakah Anda ingin menggunakan folder Musik default?", + "@setupNoFolderSelected": { + "description": "Prompt when no folder selected" + }, + "setupUseDefault": "Gunakan Default", + "@setupUseDefault": { + "description": "Button to use default folder" + }, + "setupDownloadLocationTitle": "Lokasi Unduhan", + "@setupDownloadLocationTitle": { + "description": "Download location dialog title" + }, + "setupDownloadLocationIosMessage": "Di iOS, unduhan disimpan ke folder Documents aplikasi. Anda dapat mengaksesnya melalui aplikasi Files.", + "@setupDownloadLocationIosMessage": { + "description": "iOS-specific folder info" + }, + "setupAppDocumentsFolder": "Folder Documents Aplikasi", + "@setupAppDocumentsFolder": { + "description": "iOS documents folder option" + }, + "setupAppDocumentsFolderSubtitle": "Direkomendasikan - dapat diakses via aplikasi Files", + "@setupAppDocumentsFolderSubtitle": { + "description": "Subtitle for documents folder" + }, + "setupChooseFromFiles": "Pilih dari Files", + "@setupChooseFromFiles": { + "description": "iOS file picker option" + }, + "setupChooseFromFilesSubtitle": "Pilih lokasi iCloud atau lainnya", + "@setupChooseFromFilesSubtitle": { + "description": "Subtitle for file picker" + }, + "setupIosEmptyFolderWarning": "Batasan iOS: Folder kosong tidak dapat dipilih. Pilih folder dengan minimal satu file.", + "@setupIosEmptyFolderWarning": { + "description": "iOS folder selection warning" + }, + "setupIcloudNotSupported": "iCloud Drive is not supported. Please use the app Documents folder.", + "@setupIcloudNotSupported": { + "description": "Error when user selects iCloud Drive on iOS" + }, + "setupDownloadInFlac": "Unduh lagu Spotify dalam format FLAC", + "@setupDownloadInFlac": { + "description": "App tagline in setup" + }, + "setupStepStorage": "Penyimpanan", + "@setupStepStorage": { + "description": "Setup step indicator - storage" + }, + "setupStepNotification": "Notifikasi", + "@setupStepNotification": { + "description": "Setup step indicator - notification" + }, + "setupStepFolder": "Folder", + "@setupStepFolder": { + "description": "Setup step indicator - folder" + }, + "setupStepSpotify": "Spotify", + "@setupStepSpotify": { + "description": "Setup step indicator - Spotify API" + }, + "setupStepPermission": "Izin", + "@setupStepPermission": { + "description": "Setup step indicator - permission" + }, + "setupStorageGranted": "Izin Penyimpanan Diberikan!", + "@setupStorageGranted": { + "description": "Success message for storage permission" + }, + "setupStorageRequired": "Izin Penyimpanan Diperlukan", + "@setupStorageRequired": { + "description": "Title when storage permission needed" + }, + "setupStorageDescription": "SpotiFLAC membutuhkan izin penyimpanan untuk menyimpan file musik yang diunduh.", + "@setupStorageDescription": { + "description": "Explanation for storage permission" + }, + "setupNotificationGranted": "Izin Notifikasi Diberikan!", + "@setupNotificationGranted": { + "description": "Success message for notification permission" + }, + "setupNotificationEnable": "Aktifkan Notifikasi", + "@setupNotificationEnable": { + "description": "Button to enable notifications" + }, + "setupNotificationDescription": "Dapatkan pemberitahuan saat unduhan selesai atau membutuhkan perhatian.", + "@setupNotificationDescription": { + "description": "Explanation for notifications" + }, + "setupFolderSelected": "Folder Unduhan Dipilih!", + "@setupFolderSelected": { + "description": "Success message for folder selection" + }, + "setupFolderChoose": "Pilih Folder Unduhan", + "@setupFolderChoose": { + "description": "Button to choose folder" + }, + "setupFolderDescription": "Pilih folder tempat musik yang diunduh akan disimpan.", + "@setupFolderDescription": { + "description": "Explanation for folder selection" + }, + "setupChangeFolder": "Ubah Folder", + "@setupChangeFolder": { + "description": "Button to change selected folder" + }, + "setupSelectFolder": "Pilih Folder", + "@setupSelectFolder": { + "description": "Button to select folder" + }, + "setupSpotifyApiOptional": "Spotify API (Opsional)", + "@setupSpotifyApiOptional": { + "description": "Spotify API step title" + }, + "setupSpotifyApiDescription": "Tambahkan kredensial Spotify API untuk hasil pencarian lebih baik dan akses ke konten eksklusif Spotify.", + "@setupSpotifyApiDescription": { + "description": "Explanation for Spotify API" + }, + "setupUseSpotifyApi": "Gunakan Spotify API", + "@setupUseSpotifyApi": { + "description": "Toggle to enable Spotify API" + }, + "setupEnterCredentialsBelow": "Masukkan kredensial Anda di bawah", + "@setupEnterCredentialsBelow": { + "description": "Prompt to enter credentials" + }, + "setupUsingDeezer": "Menggunakan Deezer (tidak perlu akun)", + "@setupUsingDeezer": { + "description": "Status when using Deezer" + }, + "setupEnterClientId": "Masukkan Spotify Client ID", + "@setupEnterClientId": { + "description": "Placeholder for client ID field" + }, + "setupEnterClientSecret": "Masukkan Spotify Client Secret", + "@setupEnterClientSecret": { + "description": "Placeholder for client secret field" + }, + "setupGetFreeCredentials": "Dapatkan kredensial API gratis dari Spotify Developer Dashboard.", + "@setupGetFreeCredentials": { + "description": "Info about getting Spotify credentials" + }, + "setupEnableNotifications": "Aktifkan Notifikasi", + "@setupEnableNotifications": { + "description": "Button to enable notifications" + }, + "setupProceedToNextStep": "Anda dapat melanjutkan ke langkah berikutnya.", + "@setupProceedToNextStep": { + "description": "Message after completing a step" + }, + "setupNotificationProgressDescription": "Anda akan menerima notifikasi progres unduhan.", + "@setupNotificationProgressDescription": { + "description": "Info about notification usage" + }, + "setupNotificationBackgroundDescription": "Dapatkan notifikasi tentang progres dan penyelesaian unduhan. Ini membantu Anda melacak unduhan saat aplikasi di latar belakang.", + "@setupNotificationBackgroundDescription": { + "description": "Detailed notification explanation" + }, + "setupSkipForNow": "Lewati untuk sekarang", + "@setupSkipForNow": { + "description": "Skip button text" + }, + "setupBack": "Kembali", + "@setupBack": { + "description": "Back button text" + }, + "setupNext": "Lanjut", + "@setupNext": { + "description": "Next button text" + }, + "setupGetStarted": "Mulai", + "@setupGetStarted": { + "description": "Final setup button" + }, + "setupSkipAndStart": "Lewati & Mulai", + "@setupSkipAndStart": { + "description": "Skip setup and start app" + }, + "setupAllowAccessToManageFiles": "Harap aktifkan \"Izinkan akses untuk mengelola semua file\" di layar berikutnya.", + "@setupAllowAccessToManageFiles": { + "description": "Instruction for file access permission" + }, + "setupGetCredentialsFromSpotify": "Dapatkan kredensial dari developer.spotify.com", + "@setupGetCredentialsFromSpotify": { + "description": "Link text for Spotify developer portal" + }, + "dialogCancel": "Batal", + "@dialogCancel": { + "description": "Dialog button - cancel action" + }, + "dialogOk": "OK", + "@dialogOk": { + "description": "Dialog button - confirm/acknowledge" + }, + "dialogSave": "Simpan", + "@dialogSave": { + "description": "Dialog button - save changes" + }, + "dialogDelete": "Hapus", + "@dialogDelete": { + "description": "Dialog button - delete item" + }, + "dialogRetry": "Coba Lagi", + "@dialogRetry": { + "description": "Dialog button - retry action" + }, + "dialogClose": "Tutup", + "@dialogClose": { + "description": "Dialog button - close dialog" + }, + "dialogYes": "Ya", + "@dialogYes": { + "description": "Dialog button - confirm yes" + }, + "dialogNo": "Tidak", + "@dialogNo": { + "description": "Dialog button - confirm no" + }, + "dialogClear": "Hapus", + "@dialogClear": { + "description": "Dialog button - clear items" + }, + "dialogConfirm": "Konfirmasi", + "@dialogConfirm": { + "description": "Dialog button - confirm action" + }, + "dialogDone": "Selesai", + "@dialogDone": { + "description": "Dialog button - action completed" + }, + "dialogImport": "Impor", + "@dialogImport": { + "description": "Dialog button - import data" + }, + "dialogDiscard": "Buang", + "@dialogDiscard": { + "description": "Dialog button - discard changes" + }, + "dialogRemove": "Hapus", + "@dialogRemove": { + "description": "Dialog button - remove item" + }, + "dialogUninstall": "Copot", + "@dialogUninstall": { + "description": "Dialog button - uninstall extension" + }, + "dialogDiscardChanges": "Buang Perubahan?", + "@dialogDiscardChanges": { + "description": "Dialog title - unsaved changes warning" + }, + "dialogUnsavedChanges": "Anda memiliki perubahan yang belum disimpan. Apakah Anda ingin membuangnya?", + "@dialogUnsavedChanges": { + "description": "Dialog message - unsaved changes" + }, + "dialogDownloadFailed": "Unduhan Gagal", + "@dialogDownloadFailed": { + "description": "Dialog title - download error" + }, + "dialogTrackLabel": "Lagu:", + "@dialogTrackLabel": { + "description": "Label for track name in error dialog" + }, + "dialogArtistLabel": "Artis:", + "@dialogArtistLabel": { + "description": "Label for artist name in error dialog" + }, + "dialogErrorLabel": "Error:", + "@dialogErrorLabel": { + "description": "Label for error message" + }, + "dialogClearAll": "Hapus Semua", + "@dialogClearAll": { + "description": "Dialog title - clear all items" + }, + "dialogClearAllDownloads": "Apakah Anda yakin ingin menghapus semua unduhan?", + "@dialogClearAllDownloads": { + "description": "Dialog message - clear downloads confirmation" + }, + "dialogRemoveFromDevice": "Hapus dari perangkat?", + "@dialogRemoveFromDevice": { + "description": "Dialog title - delete file confirmation" + }, + "dialogRemoveExtension": "Hapus Ekstensi", + "@dialogRemoveExtension": { + "description": "Dialog title - uninstall extension" + }, + "dialogRemoveExtensionMessage": "Apakah Anda yakin ingin menghapus ekstensi ini? Tindakan ini tidak dapat dibatalkan.", + "@dialogRemoveExtensionMessage": { + "description": "Dialog message - uninstall confirmation" + }, + "dialogUninstallExtension": "Copot Ekstensi?", + "@dialogUninstallExtension": { + "description": "Dialog title - uninstall extension" + }, + "dialogUninstallExtensionMessage": "Apakah Anda yakin ingin menghapus {extensionName}?", + "@dialogUninstallExtensionMessage": { + "description": "Dialog message - uninstall specific extension", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "dialogClearHistoryTitle": "Hapus Riwayat", + "@dialogClearHistoryTitle": { + "description": "Dialog title - clear download history" + }, + "dialogClearHistoryMessage": "Apakah Anda yakin ingin menghapus semua riwayat unduhan? Ini tidak dapat dibatalkan.", + "@dialogClearHistoryMessage": { + "description": "Dialog message - clear history confirmation" + }, + "dialogDeleteSelectedTitle": "Hapus yang Dipilih", + "@dialogDeleteSelectedTitle": { + "description": "Dialog title - delete selected items" + }, + "dialogDeleteSelectedMessage": "Hapus {count} {count, plural, =1{lagu} other{lagu}} dari riwayat?\n\nIni juga akan menghapus file dari penyimpanan.", + "@dialogDeleteSelectedMessage": { + "description": "Dialog message - delete selected tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogImportPlaylistTitle": "Impor Playlist", + "@dialogImportPlaylistTitle": { + "description": "Dialog title - import CSV playlist" + }, + "dialogImportPlaylistMessage": "Ditemukan {count} lagu di CSV. Tambahkan ke antrian unduhan?", + "csvImportTracks": "{count} tracks from CSV", + "@csvImportTracks": { + "description": "Label shown in quality picker for CSV import", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@dialogImportPlaylistMessage": { + "description": "Dialog message - import playlist confirmation", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedToQueue": "Menambahkan \"{trackName}\" ke antrian", + "@snackbarAddedToQueue": { + "description": "Snackbar - track added to download queue", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarAddedTracksToQueue": "Menambahkan {count} lagu ke antrian", + "@snackbarAddedTracksToQueue": { + "description": "Snackbar - multiple tracks added to queue", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAlreadyDownloaded": "\"{trackName}\" sudah diunduh", + "@snackbarAlreadyDownloaded": { + "description": "Snackbar - track already exists", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarAlreadyInLibrary": "\"{trackName}\" already exists in your library", + "@snackbarAlreadyInLibrary": { + "description": "Snackbar - track already exists in local library", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarHistoryCleared": "Riwayat dihapus", + "@snackbarHistoryCleared": { + "description": "Snackbar - history deleted" + }, + "snackbarCredentialsSaved": "Kredensial disimpan", + "@snackbarCredentialsSaved": { + "description": "Snackbar - Spotify credentials saved" + }, + "snackbarCredentialsCleared": "Kredensial dihapus", + "@snackbarCredentialsCleared": { + "description": "Snackbar - Spotify credentials removed" + }, + "snackbarDeletedTracks": "Menghapus {count} {count, plural, =1{lagu} other{lagu}}", + "@snackbarDeletedTracks": { + "description": "Snackbar - tracks deleted", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarCannotOpenFile": "Tidak dapat membuka file: {error}", + "@snackbarCannotOpenFile": { + "description": "Snackbar - file open error", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarFillAllFields": "Harap isi semua field", + "@snackbarFillAllFields": { + "description": "Snackbar - validation error" + }, + "snackbarViewQueue": "Lihat Antrian", + "@snackbarViewQueue": { + "description": "Snackbar action - view download queue" + }, + "snackbarFailedToLoad": "Gagal memuat: {error}", + "@snackbarFailedToLoad": { + "description": "Snackbar - loading error", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarUrlCopied": "URL {platform} disalin ke clipboard", + "@snackbarUrlCopied": { + "description": "Snackbar - URL copied", + "placeholders": { + "platform": { + "type": "String", + "description": "Platform name (Spotify/Deezer)" + } + } + }, + "snackbarFileNotFound": "File tidak ditemukan", + "@snackbarFileNotFound": { + "description": "Snackbar - file doesn't exist" + }, + "snackbarSelectExtFile": "Harap pilih file .spotiflac-ext", + "@snackbarSelectExtFile": { + "description": "Snackbar - wrong file type selected" + }, + "snackbarProviderPrioritySaved": "Prioritas provider disimpan", + "@snackbarProviderPrioritySaved": { + "description": "Snackbar - provider order saved" + }, + "snackbarMetadataProviderSaved": "Prioritas provider metadata disimpan", + "@snackbarMetadataProviderSaved": { + "description": "Snackbar - metadata provider order saved" + }, + "snackbarExtensionInstalled": "{extensionName} terpasang.", + "@snackbarExtensionInstalled": { + "description": "Snackbar - extension installed successfully", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "snackbarExtensionUpdated": "{extensionName} diperbarui.", + "@snackbarExtensionUpdated": { + "description": "Snackbar - extension updated successfully", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "snackbarFailedToInstall": "Gagal memasang ekstensi", + "@snackbarFailedToInstall": { + "description": "Snackbar - extension install error" + }, + "snackbarFailedToUpdate": "Gagal memperbarui ekstensi", + "@snackbarFailedToUpdate": { + "description": "Snackbar - extension update error" + }, + "errorRateLimited": "Dibatasi", + "@errorRateLimited": { + "description": "Error title - too many requests" + }, + "errorRateLimitedMessage": "Terlalu banyak permintaan. Harap tunggu sebentar sebelum mencari lagi.", + "@errorRateLimitedMessage": { + "description": "Error message - rate limit explanation" + }, + "errorFailedToLoad": "Gagal memuat {item}", + "@errorFailedToLoad": { + "description": "Error message - loading failed", + "placeholders": { + "item": { + "type": "String", + "description": "Item that failed to load (album/playlist/etc)" + } + } + }, + "errorNoTracksFound": "Tidak ada lagu ditemukan", + "@errorNoTracksFound": { + "description": "Error - search returned no results" + }, + "errorMissingExtensionSource": "Tidak dapat memuat {item}: sumber ekstensi tidak ada", + "@errorMissingExtensionSource": { + "description": "Error - extension source not available", + "placeholders": { + "item": { + "type": "String" + } + } + }, + "statusQueued": "Mengantri", + "@statusQueued": { + "description": "Download status - waiting in queue" + }, + "statusDownloading": "Mengunduh", + "@statusDownloading": { + "description": "Download status - in progress" + }, + "statusFinalizing": "Menyelesaikan", + "@statusFinalizing": { + "description": "Download status - writing metadata" + }, + "statusCompleted": "Selesai", + "@statusCompleted": { + "description": "Download status - finished" + }, + "statusFailed": "Gagal", + "@statusFailed": { + "description": "Download status - error occurred" + }, + "statusSkipped": "Dilewati", + "@statusSkipped": { + "description": "Download status - already exists" + }, + "statusPaused": "Dijeda", + "@statusPaused": { + "description": "Download status - paused" + }, + "actionPause": "Jeda", + "@actionPause": { + "description": "Action button - pause download" + }, + "actionResume": "Lanjutkan", + "@actionResume": { + "description": "Action button - resume download" + }, + "actionCancel": "Batal", + "@actionCancel": { + "description": "Action button - cancel operation" + }, + "actionStop": "Hentikan", + "@actionStop": { + "description": "Action button - stop operation" + }, + "actionSelect": "Pilih", + "@actionSelect": { + "description": "Action button - enter selection mode" + }, + "actionSelectAll": "Pilih Semua", + "@actionSelectAll": { + "description": "Action button - select all items" + }, + "actionDeselect": "Batal Pilih", + "@actionDeselect": { + "description": "Action button - deselect all" + }, + "actionPaste": "Tempel", + "@actionPaste": { + "description": "Action button - paste from clipboard" + }, + "actionImportCsv": "Impor CSV", + "@actionImportCsv": { + "description": "Action button - import CSV file" + }, + "actionRemoveCredentials": "Hapus Kredensial", + "@actionRemoveCredentials": { + "description": "Action button - delete Spotify credentials" + }, + "actionSaveCredentials": "Simpan Kredensial", + "@actionSaveCredentials": { + "description": "Action button - save Spotify credentials" + }, + "selectionSelected": "{count} dipilih", + "@selectionSelected": { + "description": "Selection count indicator", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionAllSelected": "Semua lagu dipilih", + "@selectionAllSelected": { + "description": "Status - all items selected" + }, + "selectionTapToSelect": "Ketuk lagu untuk memilih", + "@selectionTapToSelect": { + "description": "Hint - how to select items" + }, + "selectionDeleteTracks": "Hapus {count} {count, plural, =1{lagu} other{lagu}}", + "@selectionDeleteTracks": { + "description": "Delete button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionSelectToDelete": "Pilih lagu untuk dihapus", + "@selectionSelectToDelete": { + "description": "Placeholder when nothing selected" + }, + "progressFetchingMetadata": "Mengambil metadata... {current}/{total}", + "@progressFetchingMetadata": { + "description": "Progress indicator - loading track info", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "progressReadingCsv": "Membaca CSV...", + "@progressReadingCsv": { + "description": "Progress indicator - parsing CSV file" + }, + "searchSongs": "Lagu", + "@searchSongs": { + "description": "Search result category - songs" + }, + "searchArtists": "Artis", + "@searchArtists": { + "description": "Search result category - artists" + }, + "searchAlbums": "Album", + "@searchAlbums": { + "description": "Search result category - albums" + }, + "searchPlaylists": "Playlist", + "@searchPlaylists": { + "description": "Search result category - playlists" + }, + "tooltipPlay": "Putar", + "@tooltipPlay": { + "description": "Tooltip - play button" + }, + "tooltipCancel": "Batal", + "@tooltipCancel": { + "description": "Tooltip - cancel button" + }, + "tooltipStop": "Hentikan", + "@tooltipStop": { + "description": "Tooltip - stop button" + }, + "tooltipRetry": "Coba Lagi", + "@tooltipRetry": { + "description": "Tooltip - retry button" + }, + "tooltipRemove": "Hapus", + "@tooltipRemove": { + "description": "Tooltip - remove button" + }, + "tooltipClear": "Hapus", + "@tooltipClear": { + "description": "Tooltip - clear button" + }, + "tooltipPaste": "Tempel", + "@tooltipPaste": { + "description": "Tooltip - paste button" + }, + "filenameFormat": "Format Nama File", + "@filenameFormat": { + "description": "Setting title - filename pattern" + }, + "filenameFormatPreview": "Pratinjau: {preview}", + "@filenameFormatPreview": { + "description": "Preview of filename pattern", + "placeholders": { + "preview": { + "type": "String" + } + } + }, + "filenameAvailablePlaceholders": "Placeholder yang tersedia:", + "@filenameAvailablePlaceholders": { + "description": "Label for placeholder list" + }, + "filenameHint": "{artist} - {title}", + "@filenameHint": { + "description": "Default filename format hint" + }, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, + "folderOrganization": "Organisasi Folder", + "@folderOrganization": { + "description": "Setting title - folder structure" + }, + "folderOrganizationNone": "Tidak ada", + "@folderOrganizationNone": { + "description": "Folder option - flat structure" + }, + "folderOrganizationByArtist": "Berdasarkan Artis", + "@folderOrganizationByArtist": { + "description": "Folder option - artist folders" + }, + "folderOrganizationByAlbum": "Berdasarkan Album", + "@folderOrganizationByAlbum": { + "description": "Folder option - album folders" + }, + "folderOrganizationByArtistAlbum": "Berdasarkan Artis & Album", + "@folderOrganizationByArtistAlbum": { + "description": "Folder option - nested folders" + }, + "folderOrganizationDescription": "Atur file yang diunduh ke dalam folder", + "@folderOrganizationDescription": { + "description": "Folder organization sheet description" + }, + "folderOrganizationNoneSubtitle": "Semua file di folder unduhan", + "@folderOrganizationNoneSubtitle": { + "description": "Subtitle for no organization option" + }, + "folderOrganizationByArtistSubtitle": "Folder terpisah untuk setiap artis", + "@folderOrganizationByArtistSubtitle": { + "description": "Subtitle for artist folder option" + }, + "folderOrganizationByAlbumSubtitle": "Folder terpisah untuk setiap album", + "@folderOrganizationByAlbumSubtitle": { + "description": "Subtitle for album folder option" + }, + "folderOrganizationByArtistAlbumSubtitle": "Folder bersarang untuk artis dan album", + "@folderOrganizationByArtistAlbumSubtitle": { + "description": "Subtitle for nested folder option" + }, + "updateAvailable": "Pembaruan Tersedia", + "@updateAvailable": { + "description": "Update dialog title" + }, + "updateNewVersion": "Versi {version} tersedia", + "@updateNewVersion": { + "description": "Update available message", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "updateDownload": "Unduh", + "@updateDownload": { + "description": "Update button - download update" + }, + "updateLater": "Nanti", + "@updateLater": { + "description": "Update button - dismiss" + }, + "updateChangelog": "Log Perubahan", + "@updateChangelog": { + "description": "Link to changelog" + }, + "updateStartingDownload": "Memulai unduhan...", + "@updateStartingDownload": { + "description": "Update status - initializing" + }, + "updateDownloadFailed": "Unduhan gagal", + "@updateDownloadFailed": { + "description": "Update error title" + }, + "updateFailedMessage": "Gagal mengunduh pembaruan", + "@updateFailedMessage": { + "description": "Update error message" + }, + "updateNewVersionReady": "Versi baru sudah siap", + "@updateNewVersionReady": { + "description": "Update subtitle" + }, + "updateCurrent": "Saat ini", + "@updateCurrent": { + "description": "Label for current version" + }, + "updateNew": "Baru", + "@updateNew": { + "description": "Label for new version" + }, + "updateDownloading": "Mengunduh...", + "@updateDownloading": { + "description": "Update status - downloading" + }, + "updateWhatsNew": "Yang Baru", + "@updateWhatsNew": { + "description": "Changelog section title" + }, + "updateDownloadInstall": "Unduh & Pasang", + "@updateDownloadInstall": { + "description": "Update button - download and install" + }, + "updateDontRemind": "Jangan ingatkan", + "@updateDontRemind": { + "description": "Update button - skip this version" + }, + "providerPriority": "Prioritas Provider", + "@providerPriority": { + "description": "Setting title - download provider order" + }, + "providerPrioritySubtitle": "Seret untuk mengatur ulang provider unduhan", + "@providerPrioritySubtitle": { + "description": "Subtitle for provider priority" + }, + "providerPriorityTitle": "Prioritas Provider", + "@providerPriorityTitle": { + "description": "Provider priority page title" + }, + "providerPriorityDescription": "Seret untuk mengatur ulang urutan provider unduhan. Aplikasi akan mencoba provider dari atas ke bawah saat mengunduh lagu.", + "@providerPriorityDescription": { + "description": "Provider priority page description" + }, + "providerPriorityInfo": "Jika lagu tidak tersedia di provider pertama, aplikasi akan otomatis mencoba yang berikutnya.", + "@providerPriorityInfo": { + "description": "Info tip about fallback behavior" + }, + "providerBuiltIn": "Bawaan", + "@providerBuiltIn": { + "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + }, + "providerExtension": "Ekstensi", + "@providerExtension": { + "description": "Label for extension-provided providers" + }, + "metadataProviderPriority": "Prioritas Provider Metadata", + "@metadataProviderPriority": { + "description": "Setting title - metadata provider order" + }, + "metadataProviderPrioritySubtitle": "Urutan yang digunakan saat mengambil metadata lagu", + "@metadataProviderPrioritySubtitle": { + "description": "Subtitle for metadata priority" + }, + "metadataProviderPriorityTitle": "Prioritas Metadata", + "@metadataProviderPriorityTitle": { + "description": "Metadata priority page title" + }, + "metadataProviderPriorityDescription": "Seret untuk mengatur ulang urutan provider metadata. Aplikasi akan mencoba provider dari atas ke bawah saat mencari lagu dan mengambil metadata.", + "@metadataProviderPriorityDescription": { + "description": "Metadata priority page description" + }, + "metadataProviderPriorityInfo": "Deezer tidak memiliki batas rate dan direkomendasikan sebagai utama. Spotify mungkin membatasi rate setelah banyak permintaan.", + "@metadataProviderPriorityInfo": { + "description": "Info tip about rate limits" + }, + "metadataNoRateLimits": "Tidak ada batas rate", + "@metadataNoRateLimits": { + "description": "Deezer provider description" + }, + "metadataMayRateLimit": "Mungkin dibatasi rate", + "@metadataMayRateLimit": { + "description": "Spotify provider description" + }, + "logTitle": "Log", + "@logTitle": { + "description": "Logs screen title" + }, + "logCopy": "Salin Log", + "@logCopy": { + "description": "Action - copy logs to clipboard" + }, + "logClear": "Hapus Log", + "@logClear": { + "description": "Action - delete all logs" + }, + "logShare": "Bagikan Log", + "@logShare": { + "description": "Action - share logs file" + }, + "logEmpty": "Belum ada log", + "@logEmpty": { + "description": "Empty state title" + }, + "logCopied": "Log disalin ke clipboard", + "@logCopied": { + "description": "Snackbar - logs copied" + }, + "logSearchHint": "Cari log...", + "@logSearchHint": { + "description": "Log search placeholder" + }, + "logFilterLevel": "Level", + "@logFilterLevel": { + "description": "Filter by log level" + }, + "logFilterSection": "Filter", + "@logFilterSection": { + "description": "Filter section title" + }, + "logShareLogs": "Bagikan log", + "@logShareLogs": { + "description": "Share button tooltip" + }, + "logClearLogs": "Hapus log", + "@logClearLogs": { + "description": "Clear button tooltip" + }, + "logClearLogsTitle": "Hapus Log", + "@logClearLogsTitle": { + "description": "Clear logs dialog title" + }, + "logClearLogsMessage": "Apakah Anda yakin ingin menghapus semua log?", + "@logClearLogsMessage": { + "description": "Clear logs confirmation message" + }, + "logIspBlocking": "PEMBLOKIRAN ISP TERDETEKSI", + "@logIspBlocking": { + "description": "Error category - ISP blocking" + }, + "logRateLimited": "DIBATASI", + "@logRateLimited": { + "description": "Error category - rate limiting" + }, + "logNetworkError": "ERROR JARINGAN", + "@logNetworkError": { + "description": "Error category - network issues" + }, + "logTrackNotFound": "LAGU TIDAK DITEMUKAN", + "@logTrackNotFound": { + "description": "Error category - missing tracks" + }, + "logFilterBySeverity": "Filter log berdasarkan tingkat keparahan", + "@logFilterBySeverity": { + "description": "Filter dialog title" + }, + "logNoLogsYet": "Belum ada log", + "@logNoLogsYet": { + "description": "Empty state title" + }, + "logNoLogsYetSubtitle": "Log akan muncul di sini saat Anda menggunakan aplikasi", + "@logNoLogsYetSubtitle": { + "description": "Empty state subtitle" + }, + "logIssueSummary": "Ringkasan Masalah", + "@logIssueSummary": { + "description": "Section header for error summary" + }, + "logIspBlockingDescription": "ISP Anda mungkin memblokir akses ke layanan unduhan", + "@logIspBlockingDescription": { + "description": "ISP blocking explanation" + }, + "logIspBlockingSuggestion": "Coba gunakan VPN atau ubah DNS ke 1.1.1.1 atau 8.8.8.8", + "@logIspBlockingSuggestion": { + "description": "ISP blocking fix suggestion" + }, + "logRateLimitedDescription": "Terlalu banyak permintaan ke layanan", + "@logRateLimitedDescription": { + "description": "Rate limit explanation" + }, + "logRateLimitedSuggestion": "Tunggu beberapa menit sebelum mencoba lagi", + "@logRateLimitedSuggestion": { + "description": "Rate limit fix suggestion" + }, + "logNetworkErrorDescription": "Masalah koneksi terdeteksi", + "@logNetworkErrorDescription": { + "description": "Network error explanation" + }, + "logNetworkErrorSuggestion": "Periksa koneksi internet Anda", + "@logNetworkErrorSuggestion": { + "description": "Network error fix suggestion" + }, + "logTrackNotFoundDescription": "Beberapa lagu tidak dapat ditemukan di layanan unduhan", + "@logTrackNotFoundDescription": { + "description": "Track not found explanation" + }, + "logTrackNotFoundSuggestion": "Lagu mungkin tidak tersedia dalam kualitas lossless", + "@logTrackNotFoundSuggestion": { + "description": "Track not found explanation" + }, + "logTotalErrors": "Total error: {count}", + "@logTotalErrors": { + "description": "Error count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "logAffected": "Terpengaruh: {domains}", + "@logAffected": { + "description": "Affected domains display", + "placeholders": { + "domains": { + "type": "String" + } + } + }, + "logEntriesFiltered": "Entri ({count} difilter)", + "@logEntriesFiltered": { + "description": "Log count with filter active", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "logEntries": "Entri ({count})", + "@logEntries": { + "description": "Total log count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "credentialsTitle": "Kredensial Spotify", + "@credentialsTitle": { + "description": "Credentials dialog title" + }, + "credentialsDescription": "Masukkan Client ID dan Secret Anda untuk menggunakan kuota aplikasi Spotify Anda sendiri.", + "@credentialsDescription": { + "description": "Credentials dialog explanation" + }, + "credentialsClientId": "Client ID", + "@credentialsClientId": { + "description": "Client ID field label - DO NOT TRANSLATE" + }, + "credentialsClientIdHint": "Tempel Client ID", + "@credentialsClientIdHint": { + "description": "Client ID placeholder" + }, + "credentialsClientSecret": "Client Secret", + "@credentialsClientSecret": { + "description": "Client Secret field label - DO NOT TRANSLATE" + }, + "credentialsClientSecretHint": "Tempel Client Secret", + "@credentialsClientSecretHint": { + "description": "Client Secret placeholder" + }, + "channelStable": "Stabil", + "@channelStable": { + "description": "Update channel - stable releases" + }, + "channelPreview": "Preview", + "@channelPreview": { + "description": "Update channel - beta/preview releases" + }, + "sectionSearchSource": "Sumber Pencarian", + "@sectionSearchSource": { + "description": "Settings section header" + }, + "sectionDownload": "Unduhan", + "@sectionDownload": { + "description": "Settings section header" + }, + "sectionPerformance": "Performa", + "@sectionPerformance": { + "description": "Settings section header" + }, + "sectionApp": "Aplikasi", + "@sectionApp": { + "description": "Settings section header" + }, + "sectionData": "Data", + "@sectionData": { + "description": "Settings section header" + }, + "sectionDebug": "Debug", + "@sectionDebug": { + "description": "Settings section header" + }, + "sectionService": "Layanan", + "@sectionService": { + "description": "Settings section header" + }, + "sectionAudioQuality": "Kualitas Audio", + "@sectionAudioQuality": { + "description": "Settings section header" + }, + "sectionFileSettings": "Pengaturan File", + "@sectionFileSettings": { + "description": "Settings section header" + }, + "sectionLyrics": "Lyrics", + "@sectionLyrics": { + "description": "Settings section header" + }, + "lyricsMode": "Lyrics Mode", + "@lyricsMode": { + "description": "Setting - how to save lyrics" + }, + "lyricsModeDescription": "Choose how lyrics are saved with your downloads", + "@lyricsModeDescription": { + "description": "Lyrics mode picker description" + }, + "lyricsModeEmbed": "Embed in file", + "@lyricsModeEmbed": { + "description": "Lyrics mode option - embed in audio file" + }, + "lyricsModeEmbedSubtitle": "Lyrics stored inside FLAC metadata", + "@lyricsModeEmbedSubtitle": { + "description": "Subtitle for embed option" + }, + "lyricsModeExternal": "External .lrc file", + "@lyricsModeExternal": { + "description": "Lyrics mode option - separate LRC file" + }, + "lyricsModeExternalSubtitle": "Separate .lrc file for players like Samsung Music", + "@lyricsModeExternalSubtitle": { + "description": "Subtitle for external option" + }, + "lyricsModeBoth": "Both", + "@lyricsModeBoth": { + "description": "Lyrics mode option - embed and external" + }, + "lyricsModeBothSubtitle": "Embed and save .lrc file", + "@lyricsModeBothSubtitle": { + "description": "Subtitle for both option" + }, + "sectionColor": "Warna", + "@sectionColor": { + "description": "Settings section header" + }, + "sectionTheme": "Tema", + "@sectionTheme": { + "description": "Settings section header" + }, + "sectionLayout": "Tata Letak", + "@sectionLayout": { + "description": "Settings section header" + }, + "sectionLanguage": "Bahasa", + "@sectionLanguage": { + "description": "Settings section header for language" + }, + "appearanceLanguage": "Bahasa Aplikasi", + "@appearanceLanguage": { + "description": "Language setting title" + }, + "appearanceLanguageSubtitle": "Pilih bahasa yang kamu inginkan", + "@appearanceLanguageSubtitle": { + "description": "Language setting subtitle" + }, + "settingsAppearanceSubtitle": "Tema, warna, tampilan", + "@settingsAppearanceSubtitle": { + "description": "Appearance settings description" + }, + "settingsDownloadSubtitle": "Layanan, kualitas, format nama file", + "@settingsDownloadSubtitle": { + "description": "Download settings description" + }, + "settingsOptionsSubtitle": "Fallback, lirik, cover art, pembaruan", + "@settingsOptionsSubtitle": { + "description": "Options settings description" + }, + "settingsExtensionsSubtitle": "Kelola provider unduhan", + "@settingsExtensionsSubtitle": { + "description": "Extensions settings description" + }, + "settingsLogsSubtitle": "Lihat log aplikasi untuk debugging", + "@settingsLogsSubtitle": { + "description": "Logs settings description" + }, + "loadingSharedLink": "Memuat link yang dibagikan...", + "@loadingSharedLink": { + "description": "Status when opening shared URL" + }, + "pressBackAgainToExit": "Tekan kembali sekali lagi untuk keluar", + "@pressBackAgainToExit": { + "description": "Exit confirmation message" + }, + "tracksHeader": "Lagu", + "@tracksHeader": { + "description": "Section header for track list" + }, + "downloadAllCount": "Unduh Semua ({count})", + "@downloadAllCount": { + "description": "Download all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "tracksCount": "{count, plural, =1{1 lagu} other{{count} lagu}}", + "@tracksCount": { + "description": "Track count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "trackCopyFilePath": "Salin lokasi file", + "@trackCopyFilePath": { + "description": "Action - copy file path" + }, + "trackRemoveFromDevice": "Hapus dari perangkat", + "@trackRemoveFromDevice": { + "description": "Action - delete downloaded file" + }, + "trackLoadLyrics": "Muat Lirik", + "@trackLoadLyrics": { + "description": "Action - fetch lyrics" + }, + "trackMetadata": "Metadata", + "@trackMetadata": { + "description": "Tab title - track metadata" + }, + "trackFileInfo": "Info File", + "@trackFileInfo": { + "description": "Tab title - file information" + }, + "trackLyrics": "Lirik", + "@trackLyrics": { + "description": "Tab title - lyrics" + }, + "trackFileNotFound": "File tidak ditemukan", + "@trackFileNotFound": { + "description": "Error - file doesn't exist" + }, + "trackOpenInDeezer": "Buka di Deezer", + "@trackOpenInDeezer": { + "description": "Action - open track in Deezer app" + }, + "trackOpenInSpotify": "Buka di Spotify", + "@trackOpenInSpotify": { + "description": "Action - open track in Spotify app" + }, + "trackTrackName": "Nama lagu", + "@trackTrackName": { + "description": "Metadata label - track title" + }, + "trackArtist": "Artis", + "@trackArtist": { + "description": "Metadata label - artist name" + }, + "trackAlbumArtist": "Artis album", + "@trackAlbumArtist": { + "description": "Metadata label - album artist" + }, + "trackAlbum": "Album", + "@trackAlbum": { + "description": "Metadata label - album name" + }, + "trackTrackNumber": "Nomor lagu", + "@trackTrackNumber": { + "description": "Metadata label - track number" + }, + "trackDiscNumber": "Nomor disc", + "@trackDiscNumber": { + "description": "Metadata label - disc number" + }, + "trackDuration": "Durasi", + "@trackDuration": { + "description": "Metadata label - track length" + }, + "trackAudioQuality": "Kualitas audio", + "@trackAudioQuality": { + "description": "Metadata label - audio quality" + }, + "trackReleaseDate": "Tanggal rilis", + "@trackReleaseDate": { + "description": "Metadata label - release date" + }, + "trackGenre": "Genre", + "@trackGenre": { + "description": "Metadata label - music genre" + }, + "trackLabel": "Label", + "@trackLabel": { + "description": "Metadata label - record label" + }, + "trackCopyright": "Copyright", + "@trackCopyright": { + "description": "Metadata label - copyright information" + }, + "trackDownloaded": "Diunduh", + "@trackDownloaded": { + "description": "Metadata label - download date" + }, + "trackCopyLyrics": "Salin lirik", + "@trackCopyLyrics": { + "description": "Action - copy lyrics to clipboard" + }, + "trackLyricsNotAvailable": "Lirik tidak tersedia untuk lagu ini", + "@trackLyricsNotAvailable": { + "description": "Message when lyrics not found" + }, + "trackLyricsTimeout": "Permintaan timeout. Coba lagi nanti.", + "@trackLyricsTimeout": { + "description": "Message when lyrics request times out" + }, + "trackLyricsLoadFailed": "Gagal memuat lirik", + "@trackLyricsLoadFailed": { + "description": "Message when lyrics loading fails" + }, + "trackEmbedLyrics": "Embed Lyrics", + "@trackEmbedLyrics": { + "description": "Action - embed lyrics into audio file" + }, + "trackLyricsEmbedded": "Lyrics embedded successfully", + "@trackLyricsEmbedded": { + "description": "Snackbar - lyrics saved to file" + }, + "trackInstrumental": "Instrumental track", + "@trackInstrumental": { + "description": "Message when track is instrumental (no lyrics)" + }, + "trackCopiedToClipboard": "Disalin ke clipboard", + "@trackCopiedToClipboard": { + "description": "Snackbar - content copied" + }, + "trackDeleteConfirmTitle": "Hapus dari perangkat?", + "@trackDeleteConfirmTitle": { + "description": "Delete confirmation title" + }, + "trackDeleteConfirmMessage": "Ini akan menghapus file unduhan secara permanen dan menghapusnya dari riwayat Anda.", + "@trackDeleteConfirmMessage": { + "description": "Delete confirmation message" + }, + "trackCannotOpen": "Tidak dapat membuka: {message}", + "@trackCannotOpen": { + "description": "Error opening file", + "placeholders": { + "message": { + "type": "String" + } + } + }, + "dateToday": "Hari ini", + "@dateToday": { + "description": "Relative date - today" + }, + "dateYesterday": "Kemarin", + "@dateYesterday": { + "description": "Relative date - yesterday" + }, + "dateDaysAgo": "{count} hari lalu", + "@dateDaysAgo": { + "description": "Relative date - days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dateWeeksAgo": "{count} minggu lalu", + "@dateWeeksAgo": { + "description": "Relative date - weeks ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dateMonthsAgo": "{count} bulan lalu", + "@dateMonthsAgo": { + "description": "Relative date - months ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "concurrentSequential": "Berurutan", + "@concurrentSequential": { + "description": "Download mode - one at a time" + }, + "concurrentParallel2": "2 Paralel", + "@concurrentParallel2": { + "description": "Download mode - 2 simultaneous" + }, + "concurrentParallel3": "3 Paralel", + "@concurrentParallel3": { + "description": "Download mode - 3 simultaneous" + }, + "tapToSeeError": "Ketuk untuk melihat detail error", + "@tapToSeeError": { + "description": "Tooltip for failed download" + }, + "storeFilterAll": "Semua", + "@storeFilterAll": { + "description": "Store filter - all extensions" + }, + "storeFilterMetadata": "Metadata", + "@storeFilterMetadata": { + "description": "Store filter - metadata providers" + }, + "storeFilterDownload": "Unduhan", + "@storeFilterDownload": { + "description": "Store filter - download providers" + }, + "storeFilterUtility": "Utilitas", + "@storeFilterUtility": { + "description": "Store filter - utility extensions" + }, + "storeFilterLyrics": "Lirik", + "@storeFilterLyrics": { + "description": "Store filter - lyrics providers" + }, + "storeFilterIntegration": "Integrasi", + "@storeFilterIntegration": { + "description": "Store filter - integrations" + }, + "storeClearFilters": "Hapus filter", + "@storeClearFilters": { + "description": "Button to clear all filters" + }, + "storeNoResults": "Tidak ada ekstensi ditemukan", + "@storeNoResults": { + "description": "Empty state when no extensions match filters" + }, + "extensionProviderPriority": "Prioritas Provider", + "@extensionProviderPriority": { + "description": "Extension capability - provider priority" + }, + "extensionInstallButton": "Pasang Ekstensi", + "@extensionInstallButton": { + "description": "Button to install extension" + }, + "extensionDefaultProvider": "Default (Deezer/Spotify)", + "@extensionDefaultProvider": { + "description": "Default search provider option" + }, + "extensionDefaultProviderSubtitle": "Gunakan pencarian bawaan", + "@extensionDefaultProviderSubtitle": { + "description": "Subtitle for default provider" + }, + "extensionAuthor": "Pembuat", + "@extensionAuthor": { + "description": "Extension detail - author" + }, + "extensionId": "ID", + "@extensionId": { + "description": "Extension detail - unique ID" + }, + "extensionError": "Error", + "@extensionError": { + "description": "Extension detail - error message" + }, + "extensionCapabilities": "Kemampuan", + "@extensionCapabilities": { + "description": "Section header - extension features" + }, + "extensionMetadataProvider": "Provider Metadata", + "@extensionMetadataProvider": { + "description": "Capability - provides metadata" + }, + "extensionDownloadProvider": "Provider Unduhan", + "@extensionDownloadProvider": { + "description": "Capability - provides downloads" + }, + "extensionLyricsProvider": "Provider Lirik", + "@extensionLyricsProvider": { + "description": "Capability - provides lyrics" + }, + "extensionUrlHandler": "Penanganan URL", + "@extensionUrlHandler": { + "description": "Capability - handles URLs" + }, + "extensionQualityOptions": "Opsi Kualitas", + "@extensionQualityOptions": { + "description": "Capability - quality selection" + }, + "extensionPostProcessingHooks": "Hook Pasca-Pemrosesan", + "@extensionPostProcessingHooks": { + "description": "Capability - post-processing" + }, + "extensionPermissions": "Izin", + "@extensionPermissions": { + "description": "Section header - required permissions" + }, + "extensionSettings": "Pengaturan", + "@extensionSettings": { + "description": "Section header - extension settings" + }, + "extensionRemoveButton": "Hapus Ekstensi", + "@extensionRemoveButton": { + "description": "Button to uninstall extension" + }, + "extensionUpdated": "Diperbarui", + "@extensionUpdated": { + "description": "Extension detail - last update" + }, + "extensionMinAppVersion": "Versi App Minimum", + "@extensionMinAppVersion": { + "description": "Extension detail - minimum app version" + }, + "extensionCustomTrackMatching": "Pencocokan Lagu Kustom", + "@extensionCustomTrackMatching": { + "description": "Capability - custom track matching algorithm" + }, + "extensionPostProcessing": "Pasca-Pemrosesan", + "@extensionPostProcessing": { + "description": "Capability - post-download processing" + }, + "extensionHooksAvailable": "{count} hook tersedia", + "@extensionHooksAvailable": { + "description": "Post-processing hooks count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "extensionPatternsCount": "{count} pola", + "@extensionPatternsCount": { + "description": "URL patterns count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "extensionStrategy": "Strategi: {strategy}", + "@extensionStrategy": { + "description": "Track matching strategy name", + "placeholders": { + "strategy": { + "type": "String" + } + } + }, + "extensionsProviderPrioritySection": "Prioritas Provider", + "@extensionsProviderPrioritySection": { + "description": "Section header - provider priority" + }, + "extensionsInstalledSection": "Ekstensi Terpasang", + "@extensionsInstalledSection": { + "description": "Section header - installed extensions" + }, + "extensionsNoExtensions": "Tidak ada ekstensi terpasang", + "@extensionsNoExtensions": { + "description": "Empty state - no extensions" + }, + "extensionsNoExtensionsSubtitle": "Pasang file .spotiflac-ext untuk menambahkan provider baru", + "@extensionsNoExtensionsSubtitle": { + "description": "Empty state subtitle" + }, + "extensionsInstallButton": "Pasang Ekstensi", + "@extensionsInstallButton": { + "description": "Button to install extension from file" + }, + "extensionsInfoTip": "Ekstensi dapat menambahkan provider metadata dan unduhan baru. Hanya pasang ekstensi dari sumber terpercaya.", + "@extensionsInfoTip": { + "description": "Security warning about extensions" + }, + "extensionsInstalledSuccess": "Ekstensi berhasil dipasang", + "@extensionsInstalledSuccess": { + "description": "Success message after install" + }, + "extensionsDownloadPriority": "Prioritas Unduhan", + "@extensionsDownloadPriority": { + "description": "Setting - download provider order" + }, + "extensionsDownloadPrioritySubtitle": "Atur urutan layanan unduhan", + "@extensionsDownloadPrioritySubtitle": { + "description": "Subtitle for download priority" + }, + "extensionsNoDownloadProvider": "Tidak ada ekstensi dengan provider unduhan", + "@extensionsNoDownloadProvider": { + "description": "Empty state - no download providers" + }, + "extensionsMetadataPriority": "Prioritas Metadata", + "@extensionsMetadataPriority": { + "description": "Setting - metadata provider order" + }, + "extensionsMetadataPrioritySubtitle": "Atur urutan sumber pencarian & metadata", + "@extensionsMetadataPrioritySubtitle": { + "description": "Subtitle for metadata priority" + }, + "extensionsNoMetadataProvider": "Tidak ada ekstensi dengan provider metadata", + "@extensionsNoMetadataProvider": { + "description": "Empty state - no metadata providers" + }, + "extensionsSearchProvider": "Provider Pencarian", + "@extensionsSearchProvider": { + "description": "Setting - search provider selection" + }, + "extensionsNoCustomSearch": "Tidak ada ekstensi dengan pencarian kustom", + "@extensionsNoCustomSearch": { + "description": "Empty state - no search providers" + }, + "extensionsSearchProviderDescription": "Pilih layanan yang digunakan untuk mencari lagu", + "@extensionsSearchProviderDescription": { + "description": "Search provider setting description" + }, + "extensionsCustomSearch": "Pencarian kustom", + "@extensionsCustomSearch": { + "description": "Label for custom search provider" + }, + "extensionsErrorLoading": "Error memuat ekstensi", + "@extensionsErrorLoading": { + "description": "Error message when extension fails to load" + }, + "qualityFlacLossless": "FLAC Lossless", + "@qualityFlacLossless": { + "description": "Quality option - CD quality FLAC" + }, + "qualityFlacLosslessSubtitle": "16-bit / 44.1kHz", + "@qualityFlacLosslessSubtitle": { + "description": "Technical spec for lossless" + }, + "qualityHiResFlac": "Hi-Res FLAC", + "@qualityHiResFlac": { + "description": "Quality option - high resolution FLAC" + }, + "qualityHiResFlacSubtitle": "24-bit / hingga 96kHz", + "@qualityHiResFlacSubtitle": { + "description": "Technical spec for hi-res" + }, + "qualityHiResFlacMax": "Hi-Res FLAC Max", + "@qualityHiResFlacMax": { + "description": "Quality option - maximum resolution FLAC" + }, + "qualityHiResFlacMaxSubtitle": "24-bit / hingga 192kHz", + "@qualityHiResFlacMaxSubtitle": { + "description": "Technical spec for hi-res max" + }, + "qualityLossy": "Lossy", + "@qualityLossy": { + "description": "Quality option - lossy format (MP3/Opus)" + }, + "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", + "@qualityLossyMp3Subtitle": { + "description": "Technical spec for lossy MP3" + }, + "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", + "@qualityLossyOpusSubtitle": { + "description": "Technical spec for lossy Opus" + }, + "enableLossyOption": "Enable Lossy Option", + "@enableLossyOption": { + "description": "Setting - enable lossy quality option" + }, + "enableLossyOptionSubtitleOn": "Lossy quality option is available", + "@enableLossyOptionSubtitleOn": { + "description": "Subtitle when lossy is enabled" + }, + "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", + "@enableLossyOptionSubtitleOff": { + "description": "Subtitle when lossy is disabled" + }, + "lossyFormat": "Lossy Format", + "@lossyFormat": { + "description": "Setting - choose lossy format" + }, + "lossyFormatDescription": "Choose the lossy format for conversion", + "@lossyFormatDescription": { + "description": "Description for lossy format picker" + }, + "lossyFormatMp3Subtitle": "320kbps, best compatibility", + "@lossyFormatMp3Subtitle": { + "description": "MP3 format description" + }, + "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", + "@lossyFormatOpusSubtitle": { + "description": "Opus format description" + }, + "qualityNote": "Kualitas sebenarnya tergantung ketersediaan lagu dari layanan", + "@qualityNote": { + "description": "Note about quality availability" + }, + "youtubeQualityNote": "YouTube provides lossy audio only. Not part of lossless fallback.", + "@youtubeQualityNote": { + "description": "Note for YouTube service explaining lossy-only quality" + }, + "downloadAskBeforeDownload": "Tanya Sebelum Unduh", + "@downloadAskBeforeDownload": { + "description": "Setting - show quality picker" + }, + "downloadDirectory": "Direktori Unduhan", + "@downloadDirectory": { + "description": "Setting - download folder" + }, + "downloadSeparateSinglesFolder": "Folder Singles Terpisah", + "@downloadSeparateSinglesFolder": { + "description": "Setting - separate folder for singles" + }, + "downloadAlbumFolderStructure": "Struktur Folder Album", + "@downloadAlbumFolderStructure": { + "description": "Setting - album folder organization" + }, + "downloadUseAlbumArtistForFolders": "Use Album Artist for folders", + "@downloadUseAlbumArtistForFolders": { + "description": "Setting - choose whether artist folders use Album Artist or Track Artist" + }, + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" + }, + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" + }, + "downloadUsePrimaryArtistOnly": "Primary artist only for folders", + "@downloadUsePrimaryArtistOnly": { + "description": "Setting - strip featured artists from folder name" + }, + "downloadUsePrimaryArtistOnlyEnabled": "Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)", + "@downloadUsePrimaryArtistOnlyEnabled": { + "description": "Subtitle when primary artist only is enabled" + }, + "downloadUsePrimaryArtistOnlyDisabled": "Full artist string used for folder name", + "@downloadUsePrimaryArtistOnlyDisabled": { + "description": "Subtitle when primary artist only is disabled" + }, + "downloadSaveFormat": "Simpan Format", + "@downloadSaveFormat": { + "description": "Setting - output file format" + }, + "downloadSelectService": "Pilih Layanan", + "@downloadSelectService": { + "description": "Dialog title - choose download service" + }, + "downloadSelectQuality": "Pilih Kualitas", + "@downloadSelectQuality": { + "description": "Dialog title - choose audio quality" + }, + "downloadFrom": "Unduh Dari", + "@downloadFrom": { + "description": "Label - download source" + }, + "downloadDefaultQualityLabel": "Kualitas Default", + "@downloadDefaultQualityLabel": { + "description": "Label - default quality setting" + }, + "downloadBestAvailable": "Terbaik tersedia", + "@downloadBestAvailable": { + "description": "Quality option - highest available" + }, + "folderNone": "Tidak ada", + "@folderNone": { + "description": "Folder option - no organization" + }, + "folderNoneSubtitle": "Simpan semua file langsung ke folder unduhan", + "@folderNoneSubtitle": { + "description": "Subtitle for no folder organization" + }, + "folderArtist": "Artis", + "@folderArtist": { + "description": "Folder option - by artist" + }, + "folderArtistSubtitle": "Nama Artis/namafile", + "@folderArtistSubtitle": { + "description": "Folder structure example" + }, + "folderAlbum": "Album", + "@folderAlbum": { + "description": "Folder option - by album" + }, + "folderAlbumSubtitle": "Nama Album/namafile", + "@folderAlbumSubtitle": { + "description": "Folder structure example" + }, + "folderArtistAlbum": "Artis/Album", + "@folderArtistAlbum": { + "description": "Folder option - nested" + }, + "folderArtistAlbumSubtitle": "Nama Artis/Nama Album/namafile", + "@folderArtistAlbumSubtitle": { + "description": "Folder structure example" + }, + "serviceTidal": "Tidal", + "@serviceTidal": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceQobuz": "Qobuz", + "@serviceQobuz": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceAmazon": "Amazon", + "@serviceAmazon": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceDeezer": "Deezer", + "@serviceDeezer": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceSpotify": "Spotify", + "@serviceSpotify": { + "description": "Service name - DO NOT TRANSLATE" + }, + "appearanceAmoledDark": "AMOLED Gelap", + "@appearanceAmoledDark": { + "description": "Theme option - pure black" + }, + "appearanceAmoledDarkSubtitle": "Latar belakang hitam murni", + "@appearanceAmoledDarkSubtitle": { + "description": "Subtitle for AMOLED dark" + }, + "appearanceChooseAccentColor": "Pilih Warna Aksen", + "@appearanceChooseAccentColor": { + "description": "Color picker dialog title" + }, + "appearanceChooseTheme": "Mode Tema", + "@appearanceChooseTheme": { + "description": "Theme picker dialog title" + }, + "queueTitle": "Antrian Unduhan", + "@queueTitle": { + "description": "Queue screen title" + }, + "queueClearAll": "Hapus Semua", + "@queueClearAll": { + "description": "Button - clear all queue items" + }, + "queueClearAllMessage": "Apakah Anda yakin ingin menghapus semua unduhan?", + "@queueClearAllMessage": { + "description": "Clear queue confirmation" + }, + "queueExportFailed": "Export", + "@queueExportFailed": { + "description": "Button - export failed downloads to TXT" + }, + "queueExportFailedSuccess": "Failed downloads exported to TXT file", + "@queueExportFailedSuccess": { + "description": "Success message after exporting failed downloads" + }, + "queueExportFailedClear": "Clear Failed", + "@queueExportFailedClear": { + "description": "Action to clear failed downloads after export" + }, + "queueExportFailedError": "Failed to export downloads", + "@queueExportFailedError": { + "description": "Error message when export fails" + }, + "settingsAutoExportFailed": "Auto-export failed downloads", + "@settingsAutoExportFailed": { + "description": "Setting toggle for auto-export" + }, + "settingsAutoExportFailedSubtitle": "Save failed downloads to TXT file automatically", + "@settingsAutoExportFailedSubtitle": { + "description": "Subtitle for auto-export setting" + }, + "settingsDownloadNetwork": "Download Network", + "@settingsDownloadNetwork": { + "description": "Setting for network type preference" + }, + "settingsDownloadNetworkAny": "WiFi + Mobile Data", + "@settingsDownloadNetworkAny": { + "description": "Network option - use any connection" + }, + "settingsDownloadNetworkWifiOnly": "WiFi Only", + "@settingsDownloadNetworkWifiOnly": { + "description": "Network option - only use WiFi" + }, + "settingsDownloadNetworkSubtitle": "Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.", + "@settingsDownloadNetworkSubtitle": { + "description": "Subtitle explaining network preference" + }, + "queueEmpty": "Tidak ada unduhan dalam antrian", + "@queueEmpty": { + "description": "Empty queue state title" + }, + "queueEmptySubtitle": "Tambahkan lagu dari layar beranda", + "@queueEmptySubtitle": { + "description": "Empty queue state subtitle" + }, + "queueClearCompleted": "Hapus yang selesai", + "@queueClearCompleted": { + "description": "Button - clear finished downloads" + }, + "queueDownloadFailed": "Unduhan Gagal", + "@queueDownloadFailed": { + "description": "Error dialog title" + }, + "queueTrackLabel": "Lagu:", + "@queueTrackLabel": { + "description": "Label in error dialog" + }, + "queueArtistLabel": "Artis:", + "@queueArtistLabel": { + "description": "Label in error dialog" + }, + "queueErrorLabel": "Error:", + "@queueErrorLabel": { + "description": "Label in error dialog" + }, + "queueUnknownError": "Error tidak diketahui", + "@queueUnknownError": { + "description": "Fallback error message" + }, + "albumFolderArtistAlbum": "Artis / Album", + "@albumFolderArtistAlbum": { + "description": "Album folder option" + }, + "albumFolderArtistAlbumSubtitle": "Albums/Nama Artis/Nama Album/", + "@albumFolderArtistAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistYearAlbum": "Artis / [Tahun] Album", + "@albumFolderArtistYearAlbum": { + "description": "Album folder option with year" + }, + "albumFolderArtistYearAlbumSubtitle": "Albums/Nama Artis/[2005] Nama Album/", + "@albumFolderArtistYearAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderAlbumOnly": "Album Saja", + "@albumFolderAlbumOnly": { + "description": "Album folder option" + }, + "albumFolderAlbumOnlySubtitle": "Albums/Nama Album/", + "@albumFolderAlbumOnlySubtitle": { + "description": "Folder structure example" + }, + "albumFolderYearAlbum": "[Tahun] Album", + "@albumFolderYearAlbum": { + "description": "Album folder option with year" + }, + "albumFolderYearAlbumSubtitle": "Albums/[2005] Nama Album/", + "@albumFolderYearAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistAlbumSingles": "Artist / Album + Singles", + "@albumFolderArtistAlbumSingles": { + "description": "Album folder option with singles inside artist" + }, + "albumFolderArtistAlbumSinglesSubtitle": "Artist/Album/ and Artist/Singles/", + "@albumFolderArtistAlbumSinglesSubtitle": { + "description": "Folder structure example" + }, + "downloadedAlbumDeleteSelected": "Hapus yang Dipilih", + "@downloadedAlbumDeleteSelected": { + "description": "Button - delete selected tracks" + }, + "downloadedAlbumDeleteMessage": "Hapus {count} {count, plural, =1{lagu} other{lagu}} dari album ini?\n\nIni juga akan menghapus file dari penyimpanan.", + "@downloadedAlbumDeleteMessage": { + "description": "Delete confirmation with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumTracksHeader": "Lagu", + "@downloadedAlbumTracksHeader": { + "description": "Section header for tracks" + }, + "downloadedAlbumDownloadedCount": "{count} diunduh", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumSelectedCount": "{count} dipilih", + "@downloadedAlbumSelectedCount": { + "description": "Selection count indicator", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumAllSelected": "Semua lagu dipilih", + "@downloadedAlbumAllSelected": { + "description": "Status - all items selected" + }, + "downloadedAlbumTapToSelect": "Ketuk lagu untuk memilih", + "@downloadedAlbumTapToSelect": { + "description": "Selection hint" + }, + "downloadedAlbumDeleteCount": "Hapus {count} {count, plural, =1{lagu} other{lagu}}", + "@downloadedAlbumDeleteCount": { + "description": "Delete button text with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumSelectToDelete": "Pilih lagu untuk dihapus", + "@downloadedAlbumSelectToDelete": { + "description": "Placeholder when nothing selected" + }, + "downloadedAlbumDiscHeader": "Disc {discNumber}", + "@downloadedAlbumDiscHeader": { + "description": "Header for disc separator in multi-disc albums", + "placeholders": { + "discNumber": { + "type": "int", + "example": "1" + } + } + }, + "utilityFunctions": "Fungsi Utilitas", + "@utilityFunctions": { + "description": "Extension capability - utility functions" + }, + "recentTypeArtist": "Artis", + "@recentTypeArtist": { + "description": "Recent access item type - artist" + }, + "recentTypeAlbum": "Album", + "@recentTypeAlbum": { + "description": "Recent access item type - album" + }, + "recentTypeSong": "Lagu", + "@recentTypeSong": { + "description": "Recent access item type - song/track" + }, + "recentTypePlaylist": "Playlist", + "@recentTypePlaylist": { + "description": "Recent access item type - playlist" + }, + "recentEmpty": "No recent items yet", + "@recentEmpty": { + "description": "Empty state text for recent access list" + }, + "recentShowAllDownloads": "Show All Downloads", + "@recentShowAllDownloads": { + "description": "Button label to unhide hidden downloads in recent access" + }, + "recentPlaylistInfo": "Playlist: {name}", + "@recentPlaylistInfo": { + "description": "Snackbar message when tapping playlist in recent access", + "placeholders": { + "name": { + "type": "String", + "description": "Playlist name" + } + } + }, + "errorGeneric": "Error: {message}", + "@errorGeneric": { + "description": "Generic error message format", + "placeholders": { + "message": { + "type": "String", + "description": "Error message" + } + } + }, + "discographyDownload": "Download Discography", + "@discographyDownload": { + "description": "Button - download artist discography" + }, + "discographyDownloadAll": "Unduh Semua", + "@discographyDownloadAll": { + "description": "Option - download entire discography" + }, + "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", + "@discographyDownloadAllSubtitle": { + "description": "Subtitle showing total tracks and albums", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographyAlbumsOnly": "Albums Only", + "@discographyAlbumsOnly": { + "description": "Option - download only albums" + }, + "discographyAlbumsOnlySubtitle": "{count} tracks from {albumCount} albums", + "@discographyAlbumsOnlySubtitle": { + "description": "Subtitle showing album tracks count", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographySinglesOnly": "Singles & EPs Only", + "@discographySinglesOnly": { + "description": "Option - download only singles" + }, + "discographySinglesOnlySubtitle": "{count} tracks from {albumCount} singles", + "@discographySinglesOnlySubtitle": { + "description": "Subtitle showing singles tracks count", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographySelectAlbums": "Select Albums...", + "@discographySelectAlbums": { + "description": "Option - manually select albums to download" + }, + "discographySelectAlbumsSubtitle": "Choose specific albums or singles", + "@discographySelectAlbumsSubtitle": { + "description": "Subtitle for select albums option" + }, + "discographyFetchingTracks": "Fetching tracks...", + "@discographyFetchingTracks": { + "description": "Progress - fetching album tracks" + }, + "discographyFetchingAlbum": "Fetching {current} of {total}...", + "@discographyFetchingAlbum": { + "description": "Progress - fetching specific album", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "discographySelectedCount": "{count} selected", + "@discographySelectedCount": { + "description": "Selection count badge", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discographyDownloadSelected": "Download Selected", + "@discographyDownloadSelected": { + "description": "Button - download selected albums" + }, + "discographyAddedToQueue": "Added {count} tracks to queue", + "@discographyAddedToQueue": { + "description": "Snackbar - tracks added from discography", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discographySkippedDownloaded": "{added} added, {skipped} already downloaded", + "@discographySkippedDownloaded": { + "description": "Snackbar - with skipped tracks count", + "placeholders": { + "added": { + "type": "int" + }, + "skipped": { + "type": "int" + } + } + }, + "discographyNoAlbums": "No albums available", + "@discographyNoAlbums": { + "description": "Error - no albums found for artist" + }, + "discographyFailedToFetch": "Failed to fetch some albums", + "@discographyFailedToFetch": { + "description": "Error - some albums failed to load" + }, + "sectionStorageAccess": "Storage Access", + "@sectionStorageAccess": { + "description": "Section header for storage access settings" + }, + "allFilesAccess": "All Files Access", + "@allFilesAccess": { + "description": "Toggle for MANAGE_EXTERNAL_STORAGE permission" + }, + "allFilesAccessEnabledSubtitle": "Can write to any folder", + "@allFilesAccessEnabledSubtitle": { + "description": "Subtitle when all files access is enabled" + }, + "allFilesAccessDisabledSubtitle": "Limited to media folders only", + "@allFilesAccessDisabledSubtitle": { + "description": "Subtitle when all files access is disabled" + }, + "allFilesAccessDescription": "Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.", + "@allFilesAccessDescription": { + "description": "Description explaining when to enable all files access" + }, + "allFilesAccessDeniedMessage": "Permission was denied. Please enable 'All files access' manually in system settings.", + "@allFilesAccessDeniedMessage": { + "description": "Message when permission is permanently denied" + }, + "allFilesAccessDisabledMessage": "All Files Access disabled. The app will use limited storage access.", + "@allFilesAccessDisabledMessage": { + "description": "Snackbar message when user disables all files access" + }, + "settingsLocalLibrary": "Local Library", + "@settingsLocalLibrary": { + "description": "Settings menu item - local library" + }, + "settingsLocalLibrarySubtitle": "Scan music & detect duplicates", + "@settingsLocalLibrarySubtitle": { + "description": "Subtitle for local library settings" + }, + "settingsCache": "Storage & Cache", + "@settingsCache": { + "description": "Settings menu item - cache management" + }, + "settingsCacheSubtitle": "View size and clear cached data", + "@settingsCacheSubtitle": { + "description": "Subtitle for cache management menu" + }, + "libraryTitle": "Local Library", + "@libraryTitle": { + "description": "Library settings page title" + }, + "libraryStatus": "Library Status", + "@libraryStatus": { + "description": "Section header for library status" + }, + "libraryScanSettings": "Scan Settings", + "@libraryScanSettings": { + "description": "Section header for scan settings" + }, + "libraryEnableLocalLibrary": "Enable Local Library", + "@libraryEnableLocalLibrary": { + "description": "Toggle to enable library scanning" + }, + "libraryEnableLocalLibrarySubtitle": "Scan and track your existing music", + "@libraryEnableLocalLibrarySubtitle": { + "description": "Subtitle for enable toggle" + }, + "libraryFolder": "Library Folder", + "@libraryFolder": { + "description": "Folder selection setting" + }, + "libraryFolderHint": "Tap to select folder", + "@libraryFolderHint": { + "description": "Placeholder when no folder selected" + }, + "libraryShowDuplicateIndicator": "Show Duplicate Indicator", + "@libraryShowDuplicateIndicator": { + "description": "Toggle for duplicate indicator in search" + }, + "libraryShowDuplicateIndicatorSubtitle": "Show when searching for existing tracks", + "@libraryShowDuplicateIndicatorSubtitle": { + "description": "Subtitle for duplicate indicator toggle" + }, + "libraryActions": "Actions", + "@libraryActions": { + "description": "Section header for library actions" + }, + "libraryScan": "Scan Library", + "@libraryScan": { + "description": "Button to start library scan" + }, + "libraryScanSubtitle": "Scan for audio files", + "@libraryScanSubtitle": { + "description": "Subtitle for scan button" + }, + "libraryScanSelectFolderFirst": "Select a folder first", + "@libraryScanSelectFolderFirst": { + "description": "Message when trying to scan without folder" + }, + "libraryCleanupMissingFiles": "Cleanup Missing Files", + "@libraryCleanupMissingFiles": { + "description": "Button to remove entries for missing files" + }, + "libraryCleanupMissingFilesSubtitle": "Remove entries for files that no longer exist", + "@libraryCleanupMissingFilesSubtitle": { + "description": "Subtitle for cleanup button" + }, + "libraryClear": "Clear Library", + "@libraryClear": { + "description": "Button to clear all library entries" + }, + "libraryClearSubtitle": "Remove all scanned tracks", + "@libraryClearSubtitle": { + "description": "Subtitle for clear button" + }, + "libraryClearConfirmTitle": "Clear Library", + "@libraryClearConfirmTitle": { + "description": "Dialog title for clear confirmation" + }, + "libraryClearConfirmMessage": "This will remove all scanned tracks from your library. Your actual music files will not be deleted.", + "@libraryClearConfirmMessage": { + "description": "Dialog message for clear confirmation" + }, + "libraryAbout": "About Local Library", + "@libraryAbout": { + "description": "Section header for about info" + }, + "libraryAboutDescription": "Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.", + "@libraryAboutDescription": { + "description": "Description of local library feature" + }, + "libraryTracksCount": "{count} tracks", + "@libraryTracksCount": { + "description": "Track count in library", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "libraryLastScanned": "Last scanned: {time}", + "@libraryLastScanned": { + "description": "Last scan time display", + "placeholders": { + "time": { + "type": "String" + } + } + }, + "libraryLastScannedNever": "Never", + "@libraryLastScannedNever": { + "description": "Shown when library has never been scanned" + }, + "libraryScanning": "Scanning...", + "@libraryScanning": { + "description": "Status during scan" + }, + "libraryScanProgress": "{progress}% of {total} files", + "@libraryScanProgress": { + "description": "Scan progress display", + "placeholders": { + "progress": { + "type": "String" + }, + "total": { + "type": "int" + } + } + }, + "libraryInLibrary": "In Library", + "@libraryInLibrary": { + "description": "Badge shown on tracks that exist in local library" + }, + "libraryRemovedMissingFiles": "Removed {count} missing files from library", + "@libraryRemovedMissingFiles": { + "description": "Snackbar after cleanup", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "libraryCleared": "Library cleared", + "@libraryCleared": { + "description": "Snackbar after clearing library" + }, + "libraryStorageAccessRequired": "Storage Access Required", + "@libraryStorageAccessRequired": { + "description": "Dialog title for storage permission" + }, + "libraryStorageAccessMessage": "SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.", + "@libraryStorageAccessMessage": { + "description": "Dialog message for storage permission" + }, + "libraryFolderNotExist": "Selected folder does not exist", + "@libraryFolderNotExist": { + "description": "Error when folder doesn't exist" + }, + "librarySourceDownloaded": "Downloaded", + "@librarySourceDownloaded": { + "description": "Badge for tracks downloaded via SpotiFLAC" + }, + "librarySourceLocal": "Local", + "@librarySourceLocal": { + "description": "Badge for tracks from local library scan" + }, + "libraryFilterAll": "All", + "@libraryFilterAll": { + "description": "Filter chip - show all library items" + }, + "libraryFilterDownloaded": "Downloaded", + "@libraryFilterDownloaded": { + "description": "Filter chip - show only downloaded items" + }, + "libraryFilterLocal": "Local", + "@libraryFilterLocal": { + "description": "Filter chip - show only local library items" + }, + "libraryFilterTitle": "Filters", + "@libraryFilterTitle": { + "description": "Filter bottom sheet title" + }, + "libraryFilterReset": "Reset", + "@libraryFilterReset": { + "description": "Reset all filters button" + }, + "libraryFilterApply": "Apply", + "@libraryFilterApply": { + "description": "Apply filters button" + }, + "libraryFilterSource": "Source", + "@libraryFilterSource": { + "description": "Filter section - source type" + }, + "libraryFilterQuality": "Quality", + "@libraryFilterQuality": { + "description": "Filter section - audio quality" + }, + "libraryFilterQualityHiRes": "Hi-Res (24bit)", + "@libraryFilterQualityHiRes": { + "description": "Filter option - high resolution audio" + }, + "libraryFilterQualityCD": "CD (16bit)", + "@libraryFilterQualityCD": { + "description": "Filter option - CD quality audio" + }, + "libraryFilterQualityLossy": "Lossy", + "@libraryFilterQualityLossy": { + "description": "Filter option - lossy compressed audio" + }, + "libraryFilterFormat": "Format", + "@libraryFilterFormat": { + "description": "Filter section - file format" + }, + "libraryFilterDate": "Date Added", + "@libraryFilterDate": { + "description": "Filter section - date range" + }, + "libraryFilterDateToday": "Today", + "@libraryFilterDateToday": { + "description": "Filter option - today only" + }, + "libraryFilterDateWeek": "This Week", + "@libraryFilterDateWeek": { + "description": "Filter option - this week" + }, + "libraryFilterDateMonth": "This Month", + "@libraryFilterDateMonth": { + "description": "Filter option - this month" + }, + "libraryFilterDateYear": "This Year", + "@libraryFilterDateYear": { + "description": "Filter option - this year" + }, + "libraryFilterSort": "Sort", + "@libraryFilterSort": { + "description": "Filter section - sort order" + }, + "libraryFilterSortLatest": "Latest", + "@libraryFilterSortLatest": { + "description": "Sort option - newest first" + }, + "libraryFilterSortOldest": "Oldest", + "@libraryFilterSortOldest": { + "description": "Sort option - oldest first" + }, + "libraryFilterActive": "{count} filter(s) active", + "@libraryFilterActive": { + "description": "Badge showing number of active filters", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "timeJustNow": "Just now", + "@timeJustNow": { + "description": "Relative time - less than a minute ago" + }, + "timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}", + "@timeMinutesAgo": { + "description": "Relative time - minutes ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}", + "@timeHoursAgo": { + "description": "Relative time - hours ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "storageSwitchTitle": "Switch Storage Mode", + "@storageSwitchTitle": { + "description": "Dialog title when switching storage mode" + }, + "storageSwitchToSafTitle": "Switch to SAF Storage?", + "@storageSwitchToSafTitle": { + "description": "Dialog title when switching to SAF" + }, + "storageSwitchToAppTitle": "Switch to App Storage?", + "@storageSwitchToAppTitle": { + "description": "Dialog title when switching to app storage" + }, + "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", + "@storageSwitchToSafMessage": { + "description": "Explanation when switching to SAF" + }, + "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", + "@storageSwitchToAppMessage": { + "description": "Explanation when switching to app storage" + }, + "storageSwitchExistingDownloads": "Existing Downloads", + "@storageSwitchExistingDownloads": { + "description": "Section header for existing downloads info" + }, + "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", + "@storageSwitchExistingDownloadsInfo": { + "description": "Info about existing downloads count", + "placeholders": { + "count": { + "type": "int" + }, + "mode": { + "type": "String" + } + } + }, + "storageSwitchNewDownloads": "New Downloads", + "@storageSwitchNewDownloads": { + "description": "Section header for new downloads info" + }, + "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", + "@storageSwitchNewDownloadsLocation": { + "description": "Shows where new downloads will go", + "placeholders": { + "location": { + "type": "String" + } + } + }, + "storageSwitchContinue": "Continue", + "@storageSwitchContinue": { + "description": "Button to proceed with storage switch" + }, + "storageSwitchSelectFolder": "Select SAF Folder", + "@storageSwitchSelectFolder": { + "description": "Button to select SAF folder" + }, + "storageAppStorage": "App Storage", + "@storageAppStorage": { + "description": "Label for app storage mode" + }, + "storageSafStorage": "SAF Storage", + "@storageSafStorage": { + "description": "Label for SAF storage mode" + }, + "storageModeBadge": "Storage: {mode}", + "@storageModeBadge": { + "description": "Badge showing storage mode for a track", + "placeholders": { + "mode": { + "type": "String" + } + } + }, + "storageStatsTitle": "Storage Statistics", + "@storageStatsTitle": { + "description": "Section title for storage stats" + }, + "storageStatsAppCount": "{count} tracks in App Storage", + "@storageStatsAppCount": { + "description": "Count of tracks in app storage", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "storageStatsSafCount": "{count} tracks in SAF Storage", + "@storageStatsSafCount": { + "description": "Count of tracks in SAF storage", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "storageModeInfo": "Your files are stored in multiple locations", + "@storageModeInfo": { + "description": "Info when user has files in both storage modes" + }, + "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", + "@tutorialWelcomeTitle": { + "description": "Tutorial welcome page title" + }, + "tutorialWelcomeDesc": "Let's learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.", + "@tutorialWelcomeDesc": { + "description": "Tutorial welcome page description" + }, + "tutorialWelcomeTip1": "Download music from Spotify, Deezer, or paste any supported URL", + "@tutorialWelcomeTip1": { + "description": "Tutorial welcome tip 1" + }, + "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "@tutorialWelcomeTip2": { + "description": "Tutorial welcome tip 2" + }, + "tutorialWelcomeTip3": "Automatic metadata, cover art, and lyrics embedding", + "@tutorialWelcomeTip3": { + "description": "Tutorial welcome tip 3" + }, + "tutorialSearchTitle": "Finding Music", + "@tutorialSearchTitle": { + "description": "Tutorial search page title" + }, + "tutorialSearchDesc": "There are two easy ways to find music you want to download.", + "@tutorialSearchDesc": { + "description": "Tutorial search page description" + }, + "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", + "@tutorialSearchTip1": { + "description": "Tutorial search tip 1" + }, + "tutorialSearchTip2": "Or type the song name, artist, or album to search", + "@tutorialSearchTip2": { + "description": "Tutorial search tip 2" + }, + "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", + "@tutorialSearchTip3": { + "description": "Tutorial search tip 3" + }, + "tutorialDownloadTitle": "Downloading Music", + "@tutorialDownloadTitle": { + "description": "Tutorial download page title" + }, + "tutorialDownloadDesc": "Downloading music is simple and fast. Here's how it works.", + "@tutorialDownloadDesc": { + "description": "Tutorial download page description" + }, + "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", + "@tutorialDownloadTip1": { + "description": "Tutorial download tip 1" + }, + "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", + "@tutorialDownloadTip2": { + "description": "Tutorial download tip 2" + }, + "tutorialDownloadTip3": "Download entire albums or playlists with one tap", + "@tutorialDownloadTip3": { + "description": "Tutorial download tip 3" + }, + "tutorialLibraryTitle": "Your Library", + "@tutorialLibraryTitle": { + "description": "Tutorial library page title" + }, + "tutorialLibraryDesc": "All your downloaded music is organized in the Library tab.", + "@tutorialLibraryDesc": { + "description": "Tutorial library page description" + }, + "tutorialLibraryTip1": "View download progress and queue in the Library tab", + "@tutorialLibraryTip1": { + "description": "Tutorial library tip 1" + }, + "tutorialLibraryTip2": "Tap any track to play it with your music player", + "@tutorialLibraryTip2": { + "description": "Tutorial library tip 2" + }, + "tutorialLibraryTip3": "Switch between list and grid view for better browsing", + "@tutorialLibraryTip3": { + "description": "Tutorial library tip 3" + }, + "tutorialExtensionsTitle": "Extensions", + "@tutorialExtensionsTitle": { + "description": "Tutorial extensions page title" + }, + "tutorialExtensionsDesc": "Extend the app's capabilities with community extensions.", + "@tutorialExtensionsDesc": { + "description": "Tutorial extensions page description" + }, + "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "@tutorialExtensionsTip1": { + "description": "Tutorial extensions tip 1" + }, + "tutorialExtensionsTip2": "Add new download providers or search sources", + "@tutorialExtensionsTip2": { + "description": "Tutorial extensions tip 2" + }, + "tutorialExtensionsTip3": "Get lyrics, enhanced metadata, and more features", + "@tutorialExtensionsTip3": { + "description": "Tutorial extensions tip 3" + }, + "tutorialSettingsTitle": "Customize Your Experience", + "@tutorialSettingsTitle": { + "description": "Tutorial settings page title" + }, + "tutorialSettingsDesc": "Personalize the app in Settings to match your preferences.", + "@tutorialSettingsDesc": { + "description": "Tutorial settings page description" + }, + "tutorialSettingsTip1": "Change download location and folder organization", + "@tutorialSettingsTip1": { + "description": "Tutorial settings tip 1" + }, + "tutorialSettingsTip2": "Set default audio quality and format preferences", + "@tutorialSettingsTip2": { + "description": "Tutorial settings tip 2" + }, + "tutorialSettingsTip3": "Customize app theme and appearance", + "@tutorialSettingsTip3": { + "description": "Tutorial settings tip 3" + }, + "tutorialReadyMessage": "You're all set! Start downloading your favorite music now.", + "@tutorialReadyMessage": { + "description": "Tutorial completion message" + }, + "tutorialExample": "EXAMPLE", + "@tutorialExample": { + "description": "Example label in tutorial" + }, + "libraryForceFullScan": "Force Full Scan", + "@libraryForceFullScan": { + "description": "Button to force a complete rescan of library" + }, + "libraryForceFullScanSubtitle": "Rescan all files, ignoring cache", + "@libraryForceFullScanSubtitle": { + "description": "Subtitle for force full scan button" + }, + "cleanupOrphanedDownloads": "Cleanup Orphaned Downloads", + "@cleanupOrphanedDownloads": { + "description": "Button to remove history entries for deleted files" + }, + "cleanupOrphanedDownloadsSubtitle": "Remove history entries for files that no longer exist", + "@cleanupOrphanedDownloadsSubtitle": { + "description": "Subtitle for orphaned cleanup button" + }, + "cleanupOrphanedDownloadsResult": "Removed {count} orphaned entries from history", + "@cleanupOrphanedDownloadsResult": { + "description": "Snackbar after orphan cleanup", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cleanupOrphanedDownloadsNone": "No orphaned entries found", + "@cleanupOrphanedDownloadsNone": { + "description": "Snackbar when no orphans found" + }, + "cacheTitle": "Storage & Cache", + "@cacheTitle": { + "description": "Cache management page title" + }, + "cacheSummaryTitle": "Cache overview", + "@cacheSummaryTitle": { + "description": "Heading for cache summary card" + }, + "cacheSummarySubtitle": "Clearing cache will not remove downloaded music files.", + "@cacheSummarySubtitle": { + "description": "Helper text for cache summary card" + }, + "cacheEstimatedTotal": "Estimated cache usage: {size}", + "@cacheEstimatedTotal": { + "description": "Total cache size shown in summary", + "placeholders": { + "size": { + "type": "String" + } + } + }, + "cacheSectionStorage": "Cached Data", + "@cacheSectionStorage": { + "description": "Section header for cache entries" + }, + "cacheSectionMaintenance": "Maintenance", + "@cacheSectionMaintenance": { + "description": "Section header for cleanup actions" + }, + "cacheAppDirectory": "App cache directory", + "@cacheAppDirectory": { + "description": "Cache item title for app cache directory" + }, + "cacheAppDirectoryDesc": "HTTP responses, WebView data, and other temporary app data.", + "@cacheAppDirectoryDesc": { + "description": "Description of what app cache directory contains" + }, + "cacheTempDirectory": "Temporary directory", + "@cacheTempDirectory": { + "description": "Cache item title for temporary files directory" + }, + "cacheTempDirectoryDesc": "Temporary files from downloads and audio conversion.", + "@cacheTempDirectoryDesc": { + "description": "Description of what temporary directory contains" + }, + "cacheCoverImage": "Cover image cache", + "@cacheCoverImage": { + "description": "Cache item title for persistent cover images" + }, + "cacheCoverImageDesc": "Downloaded album and track cover art. Will re-download when viewed.", + "@cacheCoverImageDesc": { + "description": "Description of what cover image cache contains" + }, + "cacheLibraryCover": "Library cover cache", + "@cacheLibraryCover": { + "description": "Cache item title for local library cover art images" + }, + "cacheLibraryCoverDesc": "Cover art extracted from local music files. Will re-extract on next scan.", + "@cacheLibraryCoverDesc": { + "description": "Description of what library cover cache contains" + }, + "cacheExploreFeed": "Explore feed cache", + "@cacheExploreFeed": { + "description": "Cache item title for explore home feed cache" + }, + "cacheExploreFeedDesc": "Explore tab content (new releases, trending). Will refresh on next visit.", + "@cacheExploreFeedDesc": { + "description": "Description of what explore feed cache contains" + }, + "cacheTrackLookup": "Track lookup cache", + "@cacheTrackLookup": { + "description": "Cache item title for track ID lookup cache" + }, + "cacheTrackLookupDesc": "Spotify/Deezer track ID lookups. Clearing may slow next few searches.", + "@cacheTrackLookupDesc": { + "description": "Description of what track lookup cache contains" + }, + "cacheCleanupUnusedDesc": "Remove orphaned download history and library entries for missing files.", + "@cacheCleanupUnusedDesc": { + "description": "Description of what cleanup unused data does" + }, + "cacheNoData": "No cached data", + "@cacheNoData": { + "description": "Label when cache category has no data" + }, + "cacheSizeWithFiles": "{size} in {count} files", + "@cacheSizeWithFiles": { + "description": "Cache size and file count", + "placeholders": { + "size": { + "type": "String" + }, + "count": { + "type": "int" + } + } + }, + "cacheSizeOnly": "{size}", + "@cacheSizeOnly": { + "description": "Cache size only", + "placeholders": { + "size": { + "type": "String" + } + } + }, + "cacheEntries": "{count} entries", + "@cacheEntries": { + "description": "Track cache entry count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cacheClearSuccess": "Cleared: {target}", + "@cacheClearSuccess": { + "description": "Snackbar after clearing selected cache", + "placeholders": { + "target": { + "type": "String" + } + } + }, + "cacheClearConfirmTitle": "Clear cache?", + "@cacheClearConfirmTitle": { + "description": "Dialog title before clearing one cache category" + }, + "cacheClearConfirmMessage": "This will clear cached data for {target}. Downloaded music files will not be deleted.", + "@cacheClearConfirmMessage": { + "description": "Dialog message before clearing selected cache", + "placeholders": { + "target": { + "type": "String" + } + } + }, + "cacheClearAllConfirmTitle": "Clear all cache?", + "@cacheClearAllConfirmTitle": { + "description": "Dialog title before clearing all caches" + }, + "cacheClearAllConfirmMessage": "This will clear all cache categories on this page. Downloaded music files will not be deleted.", + "@cacheClearAllConfirmMessage": { + "description": "Dialog message before clearing all caches" + }, + "cacheClearAll": "Clear all cache", + "@cacheClearAll": { + "description": "Button label to clear all caches" + }, + "cacheCleanupUnused": "Cleanup unused data", + "@cacheCleanupUnused": { + "description": "Action title for cleaning unused entries" + }, + "cacheCleanupUnusedSubtitle": "Remove orphaned download history and missing library entries", + "@cacheCleanupUnusedSubtitle": { + "description": "Subtitle for cleanup unused data action" + }, + "cacheCleanupResult": "Cleanup completed: {downloadCount} orphaned downloads, {libraryCount} missing library entries", + "@cacheCleanupResult": { + "description": "Snackbar after unused data cleanup", + "placeholders": { + "downloadCount": { + "type": "int" + }, + "libraryCount": { + "type": "int" + } + } + }, + "cacheRefreshStats": "Refresh stats", + "@cacheRefreshStats": { + "description": "Button label to refresh cache statistics" + }, + "trackSaveCoverArt": "Save Cover Art", + "@trackSaveCoverArt": { + "description": "Menu action - save album cover art as file" + }, + "trackSaveCoverArtSubtitle": "Save album art as .jpg file", + "@trackSaveCoverArtSubtitle": { + "description": "Subtitle for save cover art action" + }, + "trackSaveLyrics": "Save Lyrics (.lrc)", + "@trackSaveLyrics": { + "description": "Menu action - save lyrics as .lrc file" + }, + "trackSaveLyricsSubtitle": "Fetch and save lyrics as .lrc file", + "@trackSaveLyricsSubtitle": { + "description": "Subtitle for save lyrics action" + }, + "trackSaveLyricsProgress": "Saving lyrics...", + "@trackSaveLyricsProgress": { + "description": "Snackbar while saving lyrics to file" + }, + "trackReEnrich": "Re-enrich Metadata", + "@trackReEnrich": { + "description": "Menu action - re-embed metadata into audio file" + }, + "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", + "@trackReEnrichSubtitle": { + "description": "Subtitle for re-enrich metadata action" + }, + "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", + "@trackReEnrichOnlineSubtitle": { + "description": "Subtitle for re-enrich metadata action for local items" + }, + "trackEditMetadata": "Edit Metadata", + "@trackEditMetadata": { + "description": "Menu action - edit embedded metadata" + }, + "trackCoverSaved": "Cover art saved to {fileName}", + "@trackCoverSaved": { + "description": "Snackbar after cover art saved", + "placeholders": { + "fileName": { + "type": "String" + } + } + }, + "trackCoverNoSource": "No cover art source available", + "@trackCoverNoSource": { + "description": "Snackbar when no cover art URL or embedded cover" + }, + "trackLyricsSaved": "Lyrics saved to {fileName}", + "@trackLyricsSaved": { + "description": "Snackbar after lyrics saved", + "placeholders": { + "fileName": { + "type": "String" + } + } + }, + "trackReEnrichProgress": "Re-enriching metadata...", + "@trackReEnrichProgress": { + "description": "Snackbar while re-enriching metadata" + }, + "trackReEnrichSearching": "Searching metadata online...", + "@trackReEnrichSearching": { + "description": "Snackbar while searching metadata from internet for local items" + }, + "trackReEnrichSuccess": "Metadata re-enriched successfully", + "@trackReEnrichSuccess": { + "description": "Snackbar after successful re-enrichment" + }, + "trackReEnrichFfmpegFailed": "FFmpeg metadata embed failed", + "@trackReEnrichFfmpegFailed": { + "description": "Snackbar when FFmpeg embed fails for MP3/Opus" + }, + "trackSaveFailed": "Failed: {error}", + "@trackSaveFailed": { + "description": "Snackbar when save operation fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "trackConvertFormat": "Convert Format", + "@trackConvertFormat": { + "description": "Menu item - convert audio format" + }, + "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "@trackConvertFormatSubtitle": { + "description": "Subtitle for convert format menu item" + }, + "trackConvertTitle": "Convert Audio", + "@trackConvertTitle": { + "description": "Title of convert bottom sheet" + }, + "trackConvertTargetFormat": "Target Format", + "@trackConvertTargetFormat": { + "description": "Label for format selection" + }, + "trackConvertBitrate": "Bitrate", + "@trackConvertBitrate": { + "description": "Label for bitrate selection" + }, + "trackConvertConfirmTitle": "Confirm Conversion", + "@trackConvertConfirmTitle": { + "description": "Confirmation dialog title" + }, + "trackConvertConfirmMessage": "Convert from {sourceFormat} to {targetFormat} at {bitrate}?\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessage": { + "description": "Confirmation dialog message", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "trackConvertConverting": "Converting audio...", + "@trackConvertConverting": { + "description": "Snackbar while converting" + }, + "trackConvertSuccess": "Converted to {format} successfully", + "@trackConvertSuccess": { + "description": "Snackbar after successful conversion", + "placeholders": { + "format": { + "type": "String" + } + } + }, + "trackConvertFailed": "Conversion failed", + "@trackConvertFailed": { + "description": "Snackbar when conversion fails" + } } \ No newline at end of file From f4110204b11ab7f0a3e18a443a0b63b833d770b8 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:28 +0700 Subject: [PATCH 016/104] New translations app_en.arb (Hindi) --- lib/l10n/arb/app_hi.arb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/l10n/arb/app_hi.arb b/lib/l10n/arb/app_hi.arb index 8f6ab38f..e69d363e 100644 --- a/lib/l10n/arb/app_hi.arb +++ b/lib/l10n/arb/app_hi.arb @@ -1532,6 +1532,14 @@ "@filenameHint": { "description": "Default filename format hint" }, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, "folderOrganization": "Folder Organization", "@folderOrganization": { "description": "Setting title - folder structure" From 17ba1713add89615ec8b73c14ab88fcdda76504d Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:29 +0700 Subject: [PATCH 017/104] New translations app_en.arb (Turkish) --- lib/l10n/arb/app_tr-TR.arb | 3880 ++++++++++++++++++++++++++++++++++++ 1 file changed, 3880 insertions(+) create mode 100644 lib/l10n/arb/app_tr-TR.arb diff --git a/lib/l10n/arb/app_tr-TR.arb b/lib/l10n/arb/app_tr-TR.arb new file mode 100644 index 00000000..0ab6a6c3 --- /dev/null +++ b/lib/l10n/arb/app_tr-TR.arb @@ -0,0 +1,3880 @@ +{ + "@@locale": "tr", + "@@last_modified": "2026-01-16", + "appName": "SpotiFLAC", + "@appName": { + "description": "App name - DO NOT TRANSLATE" + }, + "appDescription": "Spotify şarkılarını Tidal, Qobuz ve Amazon Music'den yüksek kalitede indir.", + "@appDescription": { + "description": "App description shown in about page" + }, + "navHome": "Ara", + "@navHome": { + "description": "Bottom navigation - Home tab" + }, + "navLibrary": "Library", + "@navLibrary": { + "description": "Bottom navigation - Library tab" + }, + "navHistory": "Geçmiş", + "@navHistory": { + "description": "Bottom navigation - History tab (legacy)" + }, + "navSettings": "Ayarlar", + "@navSettings": { + "description": "Bottom navigation - Settings tab" + }, + "navStore": "Dükkan", + "@navStore": { + "description": "Bottom navigation - Extension store tab" + }, + "homeTitle": "Ara", + "@homeTitle": { + "description": "Home screen title" + }, + "homeSearchHint": "Spotify URL'i yapıştır veya ara...", + "@homeSearchHint": { + "description": "Placeholder text in search box" + }, + "homeSearchHintExtension": "{extensionName} ile arat...", + "@homeSearchHintExtension": { + "description": "Placeholder when extension search is active", + "placeholders": { + "extensionName": { + "type": "String", + "description": "Name of the active extension" + } + } + }, + "homeSubtitle": "Spotify linki yapıştır veya isimle arat", + "@homeSubtitle": { + "description": "Subtitle shown below search box" + }, + "homeSupports": "Desteklenen linkler: Şarkı, Albüm, Çalma Listesi, Sanatçı linkleri", + "@homeSupports": { + "description": "Info text about supported URL types" + }, + "homeRecent": "En son", + "@homeRecent": { + "description": "Section header for recent searches" + }, + "historyTitle": "Geçmiş", + "@historyTitle": { + "description": "History screen title" + }, + "historyDownloading": "({count}) tane indiriliyor", + "@historyDownloading": { + "description": "Tab showing active downloads count", + "placeholders": { + "count": { + "type": "int", + "description": "Number of active downloads" + } + } + }, + "historyDownloaded": "İndirildi", + "@historyDownloaded": { + "description": "Tab showing completed downloads" + }, + "historyFilterAll": "Tümü", + "@historyFilterAll": { + "description": "Filter chip - show all items" + }, + "historyFilterAlbums": "Albümler", + "@historyFilterAlbums": { + "description": "Filter chip - show albums only" + }, + "historyFilterSingles": "Single'lar", + "@historyFilterSingles": { + "description": "Filter chip - show singles only" + }, + "historyTracksCount": "{count, plural, one {}=1{1 şarkı} other{{count} şarkı}}", + "@historyTracksCount": { + "description": "Track count with plural form", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "historyAlbumsCount": "{count, plural, one {}=1{1 albüm} other{{count} albüm}}", + "@historyAlbumsCount": { + "description": "Album count with plural form", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "historyNoDownloads": "İndirme geçmişi yok", + "@historyNoDownloads": { + "description": "Empty state title" + }, + "historyNoDownloadsSubtitle": "İndirilen şarkılar burada gözükecek", + "@historyNoDownloadsSubtitle": { + "description": "Empty state subtitle" + }, + "historyNoAlbums": "İndirilen albüm yok", + "@historyNoAlbums": { + "description": "Empty state when filtering albums" + }, + "historyNoAlbumsSubtitle": "Albümleri burada görmek için bir albümden birden fazla şarkı indir", + "@historyNoAlbumsSubtitle": { + "description": "Empty state subtitle for albums filter" + }, + "historyNoSingles": "Single indirilmemiş", + "@historyNoSingles": { + "description": "Empty state when filtering singles" + }, + "historyNoSinglesSubtitle": "Single şarkılar burada gözükecek", + "@historyNoSinglesSubtitle": { + "description": "Empty state subtitle for singles filter" + }, + "historySearchHint": "Arama geçmişi...", + "@historySearchHint": { + "description": "Search bar placeholder in history" + }, + "settingsTitle": "Ayarlar", + "@settingsTitle": { + "description": "Settings screen title" + }, + "settingsDownload": "İndirme", + "@settingsDownload": { + "description": "Settings section - download options" + }, + "settingsAppearance": "Görünüm", + "@settingsAppearance": { + "description": "Settings section - visual customization" + }, + "settingsOptions": "Seçenekler", + "@settingsOptions": { + "description": "Settings section - app options" + }, + "settingsExtensions": "Eklentiler", + "@settingsExtensions": { + "description": "Settings section - extension management" + }, + "settingsAbout": "Hakkında", + "@settingsAbout": { + "description": "Settings section - app info" + }, + "downloadTitle": "İndirme", + "@downloadTitle": { + "description": "Download settings page title" + }, + "downloadLocation": "İndirme Konumu", + "@downloadLocation": { + "description": "Setting for download folder" + }, + "downloadLocationSubtitle": "Dosyaları nereye kaydedeceğinizi seçin", + "@downloadLocationSubtitle": { + "description": "Subtitle for download location" + }, + "downloadLocationDefault": "Varsayılan dizin", + "@downloadLocationDefault": { + "description": "Shown when using default folder" + }, + "downloadDefaultService": "Varsayılan Hizmet", + "@downloadDefaultService": { + "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" + }, + "downloadDefaultServiceSubtitle": "İndirmeler için kullanılan hizmet", + "@downloadDefaultServiceSubtitle": { + "description": "Subtitle for default service" + }, + "downloadDefaultQuality": "Varsayılan Kalite", + "@downloadDefaultQuality": { + "description": "Setting for audio quality" + }, + "downloadAskQuality": "İndirmeden Önce Kaliteyi Sor", + "@downloadAskQuality": { + "description": "Toggle to show quality picker" + }, + "downloadAskQualitySubtitle": "Her indirmeden önce kalite seçim ekranını göster", + "@downloadAskQualitySubtitle": { + "description": "Subtitle for ask quality toggle" + }, + "downloadFilenameFormat": "Dosya adı formatı", + "@downloadFilenameFormat": { + "description": "Setting for output filename pattern" + }, + "downloadFolderOrganization": "Dosya Organizasyonu", + "@downloadFolderOrganization": { + "description": "Setting for folder structure" + }, + "downloadSeparateSingles": "Single'ları Ayır", + "@downloadSeparateSingles": { + "description": "Toggle to separate single tracks" + }, + "downloadSeparateSinglesSubtitle": "Single şarkıları ayrı dosyaya koy", + "@downloadSeparateSinglesSubtitle": { + "description": "Subtitle for separate singles toggle" + }, + "qualityBest": "Mevcut en iyi", + "@qualityBest": { + "description": "Audio quality option - highest available" + }, + "qualityFlac": "FLAC", + "@qualityFlac": { + "description": "Audio quality option - FLAC lossless" + }, + "quality320": "320 kbps", + "@quality320": { + "description": "Audio quality option - 320kbps MP3" + }, + "quality128": "128 kbps", + "@quality128": { + "description": "Audio quality option - 128kbps MP3" + }, + "appearanceTitle": "Görünüm", + "@appearanceTitle": { + "description": "Appearance settings page title" + }, + "appearanceTheme": "Tema", + "@appearanceTheme": { + "description": "Theme mode setting" + }, + "appearanceThemeSystem": "Sistem", + "@appearanceThemeSystem": { + "description": "Follow system theme" + }, + "appearanceThemeLight": "Açık", + "@appearanceThemeLight": { + "description": "Light theme" + }, + "appearanceThemeDark": "Koyu", + "@appearanceThemeDark": { + "description": "Dark theme" + }, + "appearanceDynamicColor": "Dinamik Renk", + "@appearanceDynamicColor": { + "description": "Material You dynamic colors" + }, + "appearanceDynamicColorSubtitle": "Duvar kağıdının renklerini kullan", + "@appearanceDynamicColorSubtitle": { + "description": "Subtitle for dynamic color" + }, + "appearanceAccentColor": "Vurgu Rengi", + "@appearanceAccentColor": { + "description": "Custom accent color picker" + }, + "appearanceHistoryView": "Geçmiş Düzeni", + "@appearanceHistoryView": { + "description": "Layout style for history" + }, + "appearanceHistoryViewList": "Liste", + "@appearanceHistoryViewList": { + "description": "List layout option" + }, + "appearanceHistoryViewGrid": "Izgara", + "@appearanceHistoryViewGrid": { + "description": "Grid layout option" + }, + "optionsTitle": "Seçenekler", + "@optionsTitle": { + "description": "Options settings page title" + }, + "optionsSearchSource": "Arama Kaynağı", + "@optionsSearchSource": { + "description": "Section for search provider settings" + }, + "optionsPrimaryProvider": "Ana Kaynek", + "@optionsPrimaryProvider": { + "description": "Main search provider setting" + }, + "optionsPrimaryProviderSubtitle": "Şarkı ismi aratılırken kullanılan kaynak.", + "@optionsPrimaryProviderSubtitle": { + "description": "Subtitle for primary provider" + }, + "optionsUsingExtension": "Kullanılan eklenti: {extensionName}", + "@optionsUsingExtension": { + "description": "Shows active extension name", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "optionsSwitchBack": "Dahili kaynaklara dönmek için Deezer veya Spotify'a tıkla", + "@optionsSwitchBack": { + "description": "Hint to switch back to built-in providers" + }, + "optionsAutoFallback": "Diğerlerini dene", + "@optionsAutoFallback": { + "description": "Auto-retry with other services" + }, + "optionsAutoFallbackSubtitle": "İndirme başarısız olursa diğer hizmetleri dene", + "@optionsAutoFallbackSubtitle": { + "description": "Subtitle for auto fallback" + }, + "optionsUseExtensionProviders": "Eklenti sağlayıcılarını kullan", + "@optionsUseExtensionProviders": { + "description": "Enable extension download providers" + }, + "optionsUseExtensionProvidersOn": "Eklentiler ilk denenecek", + "@optionsUseExtensionProvidersOn": { + "description": "Status when extension providers enabled" + }, + "optionsUseExtensionProvidersOff": "Sadece dahili sağlayıcıları kullan", + "@optionsUseExtensionProvidersOff": { + "description": "Status when extension providers disabled" + }, + "optionsEmbedLyrics": "Şarkı Sözlerini Göm", + "@optionsEmbedLyrics": { + "description": "Embed lyrics in audio files" + }, + "optionsEmbedLyricsSubtitle": "Senkronize şarkı sözlerini FLAC dosyalarına göm", + "@optionsEmbedLyricsSubtitle": { + "description": "Subtitle for embed lyrics" + }, + "optionsMaxQualityCover": "En Yüksek Kapak Kalitesi", + "@optionsMaxQualityCover": { + "description": "Download highest quality album art" + }, + "optionsMaxQualityCoverSubtitle": "En yüksek kalitedeki albüm kapaklarını indir", + "@optionsMaxQualityCoverSubtitle": { + "description": "Subtitle for max quality cover" + }, + "optionsConcurrentDownloads": "Eş Zamanlı İndirmeler", + "@optionsConcurrentDownloads": { + "description": "Number of parallel downloads" + }, + "optionsConcurrentSequential": "Sıralı (Birer birer)", + "@optionsConcurrentSequential": { + "description": "Download one at a time" + }, + "optionsConcurrentParallel": "Aynı anda {count} indirme", + "@optionsConcurrentParallel": { + "description": "Multiple parallel downloads", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "optionsConcurrentWarning": "Aynı anda birden fazla indirme sınırlamaya takılabilir", + "@optionsConcurrentWarning": { + "description": "Warning about rate limits" + }, + "optionsExtensionStore": "Eklenti Dükkanı", + "@optionsExtensionStore": { + "description": "Show/hide store tab" + }, + "optionsExtensionStoreSubtitle": "Dükkan sekmesini altta göster", + "@optionsExtensionStoreSubtitle": { + "description": "Subtitle for extension store toggle" + }, + "optionsCheckUpdates": "Güncelleştirmeleri Denetle", + "@optionsCheckUpdates": { + "description": "Auto update check toggle" + }, + "optionsCheckUpdatesSubtitle": "Yeni sürüm çıktığında bildir", + "@optionsCheckUpdatesSubtitle": { + "description": "Subtitle for update check" + }, + "optionsUpdateChannel": "Güncelleme Kanalı", + "@optionsUpdateChannel": { + "description": "Stable vs preview releases" + }, + "optionsUpdateChannelStable": "Sadece stabil sürümler", + "@optionsUpdateChannelStable": { + "description": "Only stable updates" + }, + "optionsUpdateChannelPreview": "Önizleme sürümlerini al", + "@optionsUpdateChannelPreview": { + "description": "Include beta/preview updates" + }, + "optionsUpdateChannelWarning": "Önizleme sürümleri hatalar veya tamamlanmamış özellikler içerebilir", + "@optionsUpdateChannelWarning": { + "description": "Warning about preview channel" + }, + "optionsClearHistory": "İndirme Geçmişini Temizle", + "@optionsClearHistory": { + "description": "Delete all download history" + }, + "optionsClearHistorySubtitle": "İndirilen bütün şarkıları geçmişten temizle", + "@optionsClearHistorySubtitle": { + "description": "Subtitle for clear history" + }, + "optionsDetailedLogging": "Detaylı Günlükleme", + "@optionsDetailedLogging": { + "description": "Enable verbose logs for debugging" + }, + "optionsDetailedLoggingOn": "Detaylı günlük kayıt ediliyor", + "@optionsDetailedLoggingOn": { + "description": "Status when logging enabled" + }, + "optionsDetailedLoggingOff": "Hata bildirmek için aç", + "@optionsDetailedLoggingOff": { + "description": "Status when logging disabled" + }, + "optionsSpotifyCredentials": "Spotify Kimlik Bilgileri", + "@optionsSpotifyCredentials": { + "description": "Spotify API credentials setting" + }, + "optionsSpotifyCredentialsConfigured": "Client ID: {clientId}...", + "@optionsSpotifyCredentialsConfigured": { + "description": "Shows configured client ID preview", + "placeholders": { + "clientId": { + "type": "String" + } + } + }, + "optionsSpotifyCredentialsRequired": "Zorunlu - değiştirmek için tıkla", + "@optionsSpotifyCredentialsRequired": { + "description": "Prompt to set up credentials" + }, + "optionsSpotifyWarning": "Spotify'ın senin API kimlik bilgilerine ihtiyacı var. Onları developer.spotify.com'dan alabilirsin", + "@optionsSpotifyWarning": { + "description": "Info about Spotify API requirement" + }, + "optionsSpotifyDeprecationWarning": "Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.", + "@optionsSpotifyDeprecationWarning": { + "description": "Warning about Spotify API deprecation" + }, + "extensionsTitle": "Eklentiler", + "@extensionsTitle": { + "description": "Extensions page title" + }, + "extensionsInstalled": "Kurulu Eklentiler", + "@extensionsInstalled": { + "description": "Section header for installed extensions" + }, + "extensionsNone": "Hiçbir eklenti kurulmamış", + "@extensionsNone": { + "description": "Empty state title" + }, + "extensionsNoneSubtitle": "Dükkan sekmesinden eklenti indir", + "@extensionsNoneSubtitle": { + "description": "Empty state subtitle" + }, + "extensionsEnabled": "Etkin", + "@extensionsEnabled": { + "description": "Extension status - active" + }, + "extensionsDisabled": "Devre Dışı", + "@extensionsDisabled": { + "description": "Extension status - inactive" + }, + "extensionsVersion": "Versiyon {version}", + "@extensionsVersion": { + "description": "Extension version display", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "extensionsAuthor": "{author} tarafından", + "@extensionsAuthor": { + "description": "Extension author credit", + "placeholders": { + "author": { + "type": "String" + } + } + }, + "extensionsUninstall": "Kaldır", + "@extensionsUninstall": { + "description": "Uninstall extension button" + }, + "extensionsSetAsSearch": "Arama Sağlayıcı olarak Ayarla", + "@extensionsSetAsSearch": { + "description": "Use extension for search" + }, + "storeTitle": "Eklenti Dükkanı", + "@storeTitle": { + "description": "Store screen title" + }, + "storeSearch": "Eklenti ara...", + "@storeSearch": { + "description": "Store search placeholder" + }, + "storeInstall": "Kur", + "@storeInstall": { + "description": "Install extension button" + }, + "storeInstalled": "Kuruldu", + "@storeInstalled": { + "description": "Already installed badge" + }, + "storeUpdate": "Güncelle", + "@storeUpdate": { + "description": "Update available button" + }, + "aboutTitle": "Hakkında", + "@aboutTitle": { + "description": "About page title" + }, + "aboutContributors": "Katkıda Bulunanlar", + "@aboutContributors": { + "description": "Section for contributors" + }, + "aboutMobileDeveloper": "Mobil versiyon geliştiricisi", + "@aboutMobileDeveloper": { + "description": "Role description for mobile dev" + }, + "aboutOriginalCreator": "Orijinal SpotiFLAC'ın kurucusu", + "@aboutOriginalCreator": { + "description": "Role description for original creator" + }, + "aboutLogoArtist": "Uygulama logomuzu yaratmış yetenekli sanatçımız!", + "@aboutLogoArtist": { + "description": "Role description for logo artist" + }, + "aboutTranslators": "Çevirmenler", + "@aboutTranslators": { + "description": "Section for translators" + }, + "aboutSpecialThanks": "Özel teşekkür", + "@aboutSpecialThanks": { + "description": "Section for special thanks" + }, + "aboutLinks": "Linkler", + "@aboutLinks": { + "description": "Section for external links" + }, + "aboutMobileSource": "Mobil kaynak kodu", + "@aboutMobileSource": { + "description": "Link to mobile GitHub repo" + }, + "aboutPCSource": "PC kaynak kodu", + "@aboutPCSource": { + "description": "Link to PC GitHub repo" + }, + "aboutReportIssue": "Sorun bildir", + "@aboutReportIssue": { + "description": "Link to report bugs" + }, + "aboutReportIssueSubtitle": "Karşılaştığın herhangi bir problemi bildir", + "@aboutReportIssueSubtitle": { + "description": "Subtitle for report issue" + }, + "aboutFeatureRequest": "Özellik isteği", + "@aboutFeatureRequest": { + "description": "Link to suggest features" + }, + "aboutFeatureRequestSubtitle": "Uygulama için yeni özellikler isteyin", + "@aboutFeatureRequestSubtitle": { + "description": "Subtitle for feature request" + }, + "aboutTelegramChannel": "Telegram Kanalı", + "@aboutTelegramChannel": { + "description": "Link to Telegram channel" + }, + "aboutTelegramChannelSubtitle": "Duyurular ve güncellemeler", + "@aboutTelegramChannelSubtitle": { + "description": "Subtitle for Telegram channel" + }, + "aboutTelegramChat": "Telegram Grubu", + "@aboutTelegramChat": { + "description": "Link to Telegram chat group" + }, + "aboutTelegramChatSubtitle": "Diğer kullanıcılarla sohbet et", + "@aboutTelegramChatSubtitle": { + "description": "Subtitle for Telegram chat" + }, + "aboutSocial": "Sosyal ağlar", + "@aboutSocial": { + "description": "Section for social links" + }, + "aboutSupport": "Destek", + "@aboutSupport": { + "description": "Section for support/donation links" + }, + "aboutApp": "Uygulama", + "@aboutApp": { + "description": "Section for app info" + }, + "aboutVersion": "Versiyon", + "@aboutVersion": { + "description": "Version info label" + }, + "aboutBinimumDesc": "QQDL ve HiFi API'ın kurucusu. Bu API olmadan, Tidal indirmeleri olmazdı!", + "@aboutBinimumDesc": { + "description": "Credit description for binimum" + }, + "aboutSachinsenalDesc": "Orijinal HiFi projesi kurucusu. Tidal entegrasyonun temeli!", + "@aboutSachinsenalDesc": { + "description": "Credit description for sachinsenal0x64" + }, + "aboutSjdonadoDesc": "Creator of I Don't Have Spotify (IDHS). The fallback link resolver that saves the day!", + "@aboutSjdonadoDesc": { + "description": "Credit description for sjdonado" + }, + "aboutDoubleDouble": "DoubleDouble", + "@aboutDoubleDouble": { + "description": "Name of Amazon API service - DO NOT TRANSLATE" + }, + "aboutDoubleDoubleDesc": "Amazom Music indirmeleri için harika bir API. Ücretsiz yaptığın için teşekkürler!", + "@aboutDoubleDoubleDesc": { + "description": "Credit for DoubleDouble API" + }, + "aboutDabMusic": "DAB Music", + "@aboutDabMusic": { + "description": "Name of Qobuz API service - DO NOT TRANSLATE" + }, + "aboutDabMusicDesc": "En iyi Qobuz streaming API'ı. Yüksek kalite indirmeler bunun sayesinde!", + "@aboutDabMusicDesc": { + "description": "Credit for DAB Music API" + }, + "aboutSpotiSaver": "SpotiSaver", + "@aboutSpotiSaver": { + "description": "Name of SpotiSaver API service - DO NOT TRANSLATE" + }, + "aboutSpotiSaverDesc": "Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!", + "@aboutSpotiSaverDesc": { + "description": "Credit for SpotiSaver API" + }, + "aboutAppDescription": "Spotify şarkılarını Tidal, Qobuz ve Amazon Music'den yüksek kalitede indir.", + "@aboutAppDescription": { + "description": "App description in header card" + }, + "albumTitle": "Albüm", + "@albumTitle": { + "description": "Album screen title" + }, + "albumTracks": "{count, plural, one {}=1{1 şarkı} other{{count} şarkı}}", + "@albumTracks": { + "description": "Album track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "albumDownloadAll": "Tümünü İndir", + "@albumDownloadAll": { + "description": "Button to download all tracks" + }, + "albumDownloadRemaining": "Kalanını İndir", + "@albumDownloadRemaining": { + "description": "Button to download remaining tracks" + }, + "playlistTitle": "Çalma Listesi", + "@playlistTitle": { + "description": "Playlist screen title" + }, + "artistTitle": "Sanatçı", + "@artistTitle": { + "description": "Artist screen title" + }, + "artistAlbums": "Albümler", + "@artistAlbums": { + "description": "Section header for artist albums" + }, + "artistSingles": "Single'lar ve EP'ler", + "@artistSingles": { + "description": "Section header for singles/EPs" + }, + "artistCompilations": "Derlemeler", + "@artistCompilations": { + "description": "Section header for compilations" + }, + "artistReleases": "{count, plural, =1{1 release} other{{count} releases}}", + "@artistReleases": { + "description": "Artist release count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "artistPopular": "Popüler", + "@artistPopular": { + "description": "Section header for popular/top tracks" + }, + "artistMonthlyListeners": "Aylık {count} dinleyici", + "@artistMonthlyListeners": { + "description": "Monthly listener count display", + "placeholders": { + "count": { + "type": "String", + "description": "Formatted listener count" + } + } + }, + "trackMetadataTitle": "Şarkı Bilgisi", + "@trackMetadataTitle": { + "description": "Track metadata screen title" + }, + "trackMetadataArtist": "Sanatçı", + "@trackMetadataArtist": { + "description": "Metadata field - artist name" + }, + "trackMetadataAlbum": "Albüm", + "@trackMetadataAlbum": { + "description": "Metadata field - album name" + }, + "trackMetadataDuration": "Süre", + "@trackMetadataDuration": { + "description": "Metadata field - track length" + }, + "trackMetadataQuality": "Kalite", + "@trackMetadataQuality": { + "description": "Metadata field - audio quality" + }, + "trackMetadataPath": "Dosya Yolu", + "@trackMetadataPath": { + "description": "Metadata field - file location" + }, + "trackMetadataDownloadedAt": "İndirme tarihi", + "@trackMetadataDownloadedAt": { + "description": "Metadata field - download date" + }, + "trackMetadataService": "Hizmet", + "@trackMetadataService": { + "description": "Metadata field - download service used" + }, + "trackMetadataPlay": "Oynat", + "@trackMetadataPlay": { + "description": "Action button - play track" + }, + "trackMetadataShare": "Paylaş", + "@trackMetadataShare": { + "description": "Action button - share track" + }, + "trackMetadataDelete": "Sil", + "@trackMetadataDelete": { + "description": "Action button - delete track" + }, + "trackMetadataRedownload": "Yeniden İndir", + "@trackMetadataRedownload": { + "description": "Action button - download again" + }, + "trackMetadataOpenFolder": "Klasörü Aç", + "@trackMetadataOpenFolder": { + "description": "Action button - open containing folder" + }, + "setupTitle": "SpotiFLAC'e Hoşgeldiniz", + "@setupTitle": { + "description": "Setup wizard title" + }, + "setupSubtitle": "Hadi başlayalım", + "@setupSubtitle": { + "description": "Setup wizard subtitle" + }, + "setupStoragePermission": "Depolama İzni", + "@setupStoragePermission": { + "description": "Storage permission step title" + }, + "setupStoragePermissionSubtitle": "İndirilen dosyaları kaydetmek için gerekli", + "@setupStoragePermissionSubtitle": { + "description": "Explanation for storage permission" + }, + "setupStoragePermissionGranted": "İzin verildi", + "@setupStoragePermissionGranted": { + "description": "Status when permission granted" + }, + "setupStoragePermissionDenied": "İzin reddedildi", + "@setupStoragePermissionDenied": { + "description": "Status when permission denied" + }, + "setupGrantPermission": "İzin Ver", + "@setupGrantPermission": { + "description": "Button to request permission" + }, + "setupDownloadLocation": "İndirme Konumu", + "@setupDownloadLocation": { + "description": "Download folder step title" + }, + "setupChooseFolder": "Klasör Seç", + "@setupChooseFolder": { + "description": "Button to pick folder" + }, + "setupContinue": "Devam", + "@setupContinue": { + "description": "Continue to next step button" + }, + "setupSkip": "Şimdilik atla", + "@setupSkip": { + "description": "Skip current step button" + }, + "setupStorageAccessRequired": "Depolama Erişimi Gerekli", + "@setupStorageAccessRequired": { + "description": "Title when storage access needed" + }, + "setupStorageAccessMessage": "SpotiFLAC'ın şarkıları seçili klasörünüze kaydetmek için \"Bütün dosyalara eriş\" iznine ihtiyacı var.", + "@setupStorageAccessMessage": { + "description": "Explanation for storage access" + }, + "setupStorageAccessMessageAndroid11": "Android 11 ve sonrasında şarkıların seçili klasörünüze kaydedilebilmesi için \"Bütün dosyalara eriş\" iznine ihtiyaç var.", + "@setupStorageAccessMessageAndroid11": { + "description": "Android 11+ specific explanation" + }, + "setupOpenSettings": "Ayarları Aç", + "@setupOpenSettings": { + "description": "Button to open system settings" + }, + "setupPermissionDeniedMessage": "İzin reddedildi. Devam etmek için lütfen bütün izinleri verin.", + "@setupPermissionDeniedMessage": { + "description": "Error when permission denied" + }, + "setupPermissionRequired": "{permissionType} İzni Zorunlu", + "@setupPermissionRequired": { + "description": "Generic permission required title", + "placeholders": { + "permissionType": { + "type": "String", + "description": "Type of permission (Storage/Notification)" + } + } + }, + "setupPermissionRequiredMessage": "En iyi deneyim için {permissionType} izni zorunludur. Bunu ayarlardan daha sonra değiştirebilirsiniz.", + "@setupPermissionRequiredMessage": { + "description": "Generic permission required message", + "placeholders": { + "permissionType": { + "type": "String" + } + } + }, + "setupSelectDownloadFolder": "İndirilecek Klasörü Seç", + "@setupSelectDownloadFolder": { + "description": "Folder selection step title" + }, + "setupUseDefaultFolder": "Varsayılan Klasörü Kullan?", + "@setupUseDefaultFolder": { + "description": "Dialog title for default folder" + }, + "setupNoFolderSelected": "Klasör seçilmedi. Varsayılan \"Music\" klasörünü kullanmak ister misiniz?", + "@setupNoFolderSelected": { + "description": "Prompt when no folder selected" + }, + "setupUseDefault": "Varsayılanı Kullan", + "@setupUseDefault": { + "description": "Button to use default folder" + }, + "setupDownloadLocationTitle": "İndirme Konumu", + "@setupDownloadLocationTitle": { + "description": "Download location dialog title" + }, + "setupDownloadLocationIosMessage": "iOS'ta indirilenler uygulamanın \"Documents\" dosyasına kaydedilir. Onlara Dosyalar uygulamasından erişebilirsiniz.", + "@setupDownloadLocationIosMessage": { + "description": "iOS-specific folder info" + }, + "setupAppDocumentsFolder": "App Documents Folder", + "@setupAppDocumentsFolder": { + "description": "iOS documents folder option" + }, + "setupAppDocumentsFolderSubtitle": "Tavsiye edilen - Dosyalar uygulamasından erişilebilir", + "@setupAppDocumentsFolderSubtitle": { + "description": "Subtitle for documents folder" + }, + "setupChooseFromFiles": "Dosyalar'dan Seç", + "@setupChooseFromFiles": { + "description": "iOS file picker option" + }, + "setupChooseFromFilesSubtitle": "iCloud veya başka konum seç", + "@setupChooseFromFilesSubtitle": { + "description": "Subtitle for file picker" + }, + "setupIosEmptyFolderWarning": "iOS'un sınırlaması: Boş klasörler seçilemiyor. İçinde en az bir dosya bulunan bir klasör seçin.", + "@setupIosEmptyFolderWarning": { + "description": "iOS folder selection warning" + }, + "setupIcloudNotSupported": "iCloud Drive is not supported. Please use the app Documents folder.", + "@setupIcloudNotSupported": { + "description": "Error when user selects iCloud Drive on iOS" + }, + "setupDownloadInFlac": "Spotify şarkılarını FLAC olarak indirin", + "@setupDownloadInFlac": { + "description": "App tagline in setup" + }, + "setupStepStorage": "Depolama", + "@setupStepStorage": { + "description": "Setup step indicator - storage" + }, + "setupStepNotification": "Bildirim", + "@setupStepNotification": { + "description": "Setup step indicator - notification" + }, + "setupStepFolder": "Klasör", + "@setupStepFolder": { + "description": "Setup step indicator - folder" + }, + "setupStepSpotify": "Spotify", + "@setupStepSpotify": { + "description": "Setup step indicator - Spotify API" + }, + "setupStepPermission": "İzin", + "@setupStepPermission": { + "description": "Setup step indicator - permission" + }, + "setupStorageGranted": "Depolama İzni Verildi!", + "@setupStorageGranted": { + "description": "Success message for storage permission" + }, + "setupStorageRequired": "Depolama İzni Gerekli", + "@setupStorageRequired": { + "description": "Title when storage permission needed" + }, + "setupStorageDescription": "SpotiFLAC'ın şarkılarınızı kaydetmek için depolama iznine ihtiyacı var.", + "@setupStorageDescription": { + "description": "Explanation for storage permission" + }, + "setupNotificationGranted": "Bildirim İzni Verildi!", + "@setupNotificationGranted": { + "description": "Success message for notification permission" + }, + "setupNotificationEnable": "Bildirimleri Etkinleştir", + "@setupNotificationEnable": { + "description": "Button to enable notifications" + }, + "setupNotificationDescription": "İndirmeler bittiğinde veya bakılması gereken bir şey olduğunda haberdar olun.", + "@setupNotificationDescription": { + "description": "Explanation for notifications" + }, + "setupFolderSelected": "İndirilecek Klasör Seçildi!", + "@setupFolderSelected": { + "description": "Success message for folder selection" + }, + "setupFolderChoose": "İndirilecek Klasörü Seç", + "@setupFolderChoose": { + "description": "Button to choose folder" + }, + "setupFolderDescription": "İndirdiğin şarkıların kaydedileceği klasörü seç.", + "@setupFolderDescription": { + "description": "Explanation for folder selection" + }, + "setupChangeFolder": "Klasörü Değiştir", + "@setupChangeFolder": { + "description": "Button to change selected folder" + }, + "setupSelectFolder": "Klasör Seç", + "@setupSelectFolder": { + "description": "Button to select folder" + }, + "setupSpotifyApiOptional": "Spotify API (İsteğe Bağlı)", + "@setupSpotifyApiOptional": { + "description": "Spotify API step title" + }, + "setupSpotifyApiDescription": "Daha iyi arama sonuçları ve Spotify'a özel içeriklere erişmek için Spotify API kimlik bilgilerini gir.", + "@setupSpotifyApiDescription": { + "description": "Explanation for Spotify API" + }, + "setupUseSpotifyApi": "Spotify API'ı kullan", + "@setupUseSpotifyApi": { + "description": "Toggle to enable Spotify API" + }, + "setupEnterCredentialsBelow": "Kimlik bilgilerini aşağıya gir", + "@setupEnterCredentialsBelow": { + "description": "Prompt to enter credentials" + }, + "setupUsingDeezer": "Deezer kullanılıyor (hesap gerekli değil)", + "@setupUsingDeezer": { + "description": "Status when using Deezer" + }, + "setupEnterClientId": "Spotify Client ID gir", + "@setupEnterClientId": { + "description": "Placeholder for client ID field" + }, + "setupEnterClientSecret": "Spotify Client Secret gir", + "@setupEnterClientSecret": { + "description": "Placeholder for client secret field" + }, + "setupGetFreeCredentials": "Spotify Developer Dashboard'tan API kimlik bilgilerini ücretsiz alın.", + "@setupGetFreeCredentials": { + "description": "Info about getting Spotify credentials" + }, + "setupEnableNotifications": "Bildirimleri Etkinleştir", + "@setupEnableNotifications": { + "description": "Button to enable notifications" + }, + "setupProceedToNextStep": "Bir sonraki adıma geçebilirsin.", + "@setupProceedToNextStep": { + "description": "Message after completing a step" + }, + "setupNotificationProgressDescription": "İndirme ilerlemelerinin bildirimlerini alacaksın.", + "@setupNotificationProgressDescription": { + "description": "Info about notification usage" + }, + "setupNotificationBackgroundDescription": "İndirmelerin durumu hakkında bildirim al. Bunu açmak uygulama arka plandayken indirmelerinizi takip etmenizi sağlar.", + "@setupNotificationBackgroundDescription": { + "description": "Detailed notification explanation" + }, + "setupSkipForNow": "Şimdilik atla", + "@setupSkipForNow": { + "description": "Skip button text" + }, + "setupBack": "Geri", + "@setupBack": { + "description": "Back button text" + }, + "setupNext": "Sıradaki", + "@setupNext": { + "description": "Next button text" + }, + "setupGetStarted": "Başla", + "@setupGetStarted": { + "description": "Final setup button" + }, + "setupSkipAndStart": "Kurulumu atla", + "@setupSkipAndStart": { + "description": "Skip setup and start app" + }, + "setupAllowAccessToManageFiles": "Lütfen bir sonraki ekranda \"Bütün dosyalara eriş\" iznini sağlayın.", + "@setupAllowAccessToManageFiles": { + "description": "Instruction for file access permission" + }, + "setupGetCredentialsFromSpotify": "Kimlik bilgilerini developer.spotify.com'dan alın", + "@setupGetCredentialsFromSpotify": { + "description": "Link text for Spotify developer portal" + }, + "dialogCancel": "İptal", + "@dialogCancel": { + "description": "Dialog button - cancel action" + }, + "dialogOk": "Tamam", + "@dialogOk": { + "description": "Dialog button - confirm/acknowledge" + }, + "dialogSave": "Kaydet", + "@dialogSave": { + "description": "Dialog button - save changes" + }, + "dialogDelete": "Sil", + "@dialogDelete": { + "description": "Dialog button - delete item" + }, + "dialogRetry": "Yeniden dene", + "@dialogRetry": { + "description": "Dialog button - retry action" + }, + "dialogClose": "Kapat", + "@dialogClose": { + "description": "Dialog button - close dialog" + }, + "dialogYes": "Evet", + "@dialogYes": { + "description": "Dialog button - confirm yes" + }, + "dialogNo": "Hayır", + "@dialogNo": { + "description": "Dialog button - confirm no" + }, + "dialogClear": "Temizle", + "@dialogClear": { + "description": "Dialog button - clear items" + }, + "dialogConfirm": "Onayla", + "@dialogConfirm": { + "description": "Dialog button - confirm action" + }, + "dialogDone": "Tamamlandı", + "@dialogDone": { + "description": "Dialog button - action completed" + }, + "dialogImport": "İçe aktar", + "@dialogImport": { + "description": "Dialog button - import data" + }, + "dialogDiscard": "Vazgeç", + "@dialogDiscard": { + "description": "Dialog button - discard changes" + }, + "dialogRemove": "Kaldır", + "@dialogRemove": { + "description": "Dialog button - remove item" + }, + "dialogUninstall": "Kaldır", + "@dialogUninstall": { + "description": "Dialog button - uninstall extension" + }, + "dialogDiscardChanges": "Değişiklikleri İptal Et?", + "@dialogDiscardChanges": { + "description": "Dialog title - unsaved changes warning" + }, + "dialogUnsavedChanges": "Kaydedilmeyen değişiklikler mevcut. Bu değişiklikleri iptal etmek istiyor musunuz?", + "@dialogUnsavedChanges": { + "description": "Dialog message - unsaved changes" + }, + "dialogDownloadFailed": "İndirme Başarısız", + "@dialogDownloadFailed": { + "description": "Dialog title - download error" + }, + "dialogTrackLabel": "Şarkı:", + "@dialogTrackLabel": { + "description": "Label for track name in error dialog" + }, + "dialogArtistLabel": "Sanatçı:", + "@dialogArtistLabel": { + "description": "Label for artist name in error dialog" + }, + "dialogErrorLabel": "Hata:", + "@dialogErrorLabel": { + "description": "Label for error message" + }, + "dialogClearAll": "Tümünü Temizle", + "@dialogClearAll": { + "description": "Dialog title - clear all items" + }, + "dialogClearAllDownloads": "Bütün indirmeleri temizlemek istediğinize emin misiniz?", + "@dialogClearAllDownloads": { + "description": "Dialog message - clear downloads confirmation" + }, + "dialogRemoveFromDevice": "Cihazdan kaldır?", + "@dialogRemoveFromDevice": { + "description": "Dialog title - delete file confirmation" + }, + "dialogRemoveExtension": "Eklentiyi Kaldır", + "@dialogRemoveExtension": { + "description": "Dialog title - uninstall extension" + }, + "dialogRemoveExtensionMessage": "Bu eklentiyi kaldırmak istediğine emin misin? Bu işlem geri alınamaz.", + "@dialogRemoveExtensionMessage": { + "description": "Dialog message - uninstall confirmation" + }, + "dialogUninstallExtension": "Eklentiyi Kaldır?", + "@dialogUninstallExtension": { + "description": "Dialog title - uninstall extension" + }, + "dialogUninstallExtensionMessage": "{extensionName} eklentisini kaldırmak istediğine emin misin?", + "@dialogUninstallExtensionMessage": { + "description": "Dialog message - uninstall specific extension", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "dialogClearHistoryTitle": "Geçmişi Temizle", + "@dialogClearHistoryTitle": { + "description": "Dialog title - clear download history" + }, + "dialogClearHistoryMessage": "Tüm indirme geçmişini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "@dialogClearHistoryMessage": { + "description": "Dialog message - clear history confirmation" + }, + "dialogDeleteSelectedTitle": "Seçileni Sil", + "@dialogDeleteSelectedTitle": { + "description": "Dialog title - delete selected items" + }, + "dialogDeleteSelectedMessage": "{count} {count, plural, one {}=1{şarkıyı} other{şarkıyı}} geçmişten silmeye emin misiniz?\n\nBu işlem seçilenleri cihazınızdan da silecektir.", + "@dialogDeleteSelectedMessage": { + "description": "Dialog message - delete selected tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogImportPlaylistTitle": "Çalma listesini içe aktar", + "@dialogImportPlaylistTitle": { + "description": "Dialog title - import CSV playlist" + }, + "dialogImportPlaylistMessage": "CSV'de {count} şarkı bulundu. İndirme kuyruğuna ekle?", + "csvImportTracks": "CSV'den {count} şarkı", + "@csvImportTracks": { + "description": "Label shown in quality picker for CSV import", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@dialogImportPlaylistMessage": { + "description": "Dialog message - import playlist confirmation", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedToQueue": "\"{trackName}\" kuyruğa eklendi", + "@snackbarAddedToQueue": { + "description": "Snackbar - track added to download queue", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarAddedTracksToQueue": "{count} şarkı kuyruğa eklendi", + "@snackbarAddedTracksToQueue": { + "description": "Snackbar - multiple tracks added to queue", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAlreadyDownloaded": "\"{trackName}\" zaten indirilmiş", + "@snackbarAlreadyDownloaded": { + "description": "Snackbar - track already exists", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarAlreadyInLibrary": "\"{trackName}\" already exists in your library", + "@snackbarAlreadyInLibrary": { + "description": "Snackbar - track already exists in local library", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarHistoryCleared": "Geçmiş temizlendi", + "@snackbarHistoryCleared": { + "description": "Snackbar - history deleted" + }, + "snackbarCredentialsSaved": "Kimlik bilgileri kaydedildi", + "@snackbarCredentialsSaved": { + "description": "Snackbar - Spotify credentials saved" + }, + "snackbarCredentialsCleared": "Kimlik bilgileri temizlendi", + "@snackbarCredentialsCleared": { + "description": "Snackbar - Spotify credentials removed" + }, + "snackbarDeletedTracks": "{count} {count, plural, one {}=1{şarkı} other{şarkı}} silindi", + "@snackbarDeletedTracks": { + "description": "Snackbar - tracks deleted", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarCannotOpenFile": "Dosya açılamadı: {error}", + "@snackbarCannotOpenFile": { + "description": "Snackbar - file open error", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarFillAllFields": "Lütfen tüm alanları doldurun", + "@snackbarFillAllFields": { + "description": "Snackbar - validation error" + }, + "snackbarViewQueue": "Kuyruğu Görüntüle", + "@snackbarViewQueue": { + "description": "Snackbar action - view download queue" + }, + "snackbarFailedToLoad": "Yüklenemedi: {error}", + "@snackbarFailedToLoad": { + "description": "Snackbar - loading error", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarUrlCopied": "{platform} Bağlantı panoya kopyalandı", + "@snackbarUrlCopied": { + "description": "Snackbar - URL copied", + "placeholders": { + "platform": { + "type": "String", + "description": "Platform name (Spotify/Deezer)" + } + } + }, + "snackbarFileNotFound": "Dosya bulunamadı", + "@snackbarFileNotFound": { + "description": "Snackbar - file doesn't exist" + }, + "snackbarSelectExtFile": "Lütfen .spotiflac-ext dosyasını seçin", + "@snackbarSelectExtFile": { + "description": "Snackbar - wrong file type selected" + }, + "snackbarProviderPrioritySaved": "Provider priority saved", + "@snackbarProviderPrioritySaved": { + "description": "Snackbar - provider order saved" + }, + "snackbarMetadataProviderSaved": "Metadata provider priority saved", + "@snackbarMetadataProviderSaved": { + "description": "Snackbar - metadata provider order saved" + }, + "snackbarExtensionInstalled": "{extensionName} yüklendi.", + "@snackbarExtensionInstalled": { + "description": "Snackbar - extension installed successfully", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "snackbarExtensionUpdated": "{extensionName} güncellendi.", + "@snackbarExtensionUpdated": { + "description": "Snackbar - extension updated successfully", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "snackbarFailedToInstall": "Eklenti yüklenirken hata oluştu", + "@snackbarFailedToInstall": { + "description": "Snackbar - extension install error" + }, + "snackbarFailedToUpdate": "Eklenti güncellenirken hata oluştu", + "@snackbarFailedToUpdate": { + "description": "Snackbar - extension update error" + }, + "errorRateLimited": "Aşırı istek gönderildi", + "@errorRateLimited": { + "description": "Error title - too many requests" + }, + "errorRateLimitedMessage": "Çok fazla istek. Lütfen arama yapmadan önce biraz bekleyin.", + "@errorRateLimitedMessage": { + "description": "Error message - rate limit explanation" + }, + "errorFailedToLoad": "{item} yüklenirken hata oluştu", + "@errorFailedToLoad": { + "description": "Error message - loading failed", + "placeholders": { + "item": { + "type": "String", + "description": "Item that failed to load (album/playlist/etc)" + } + } + }, + "errorNoTracksFound": "Parça bulunamadı", + "@errorNoTracksFound": { + "description": "Error - search returned no results" + }, + "errorMissingExtensionSource": "{item} yüklenemedi: Eksik eklenti kaynağı", + "@errorMissingExtensionSource": { + "description": "Error - extension source not available", + "placeholders": { + "item": { + "type": "String" + } + } + }, + "statusQueued": "Sıraya alındı", + "@statusQueued": { + "description": "Download status - waiting in queue" + }, + "statusDownloading": "İndiriliyor", + "@statusDownloading": { + "description": "Download status - in progress" + }, + "statusFinalizing": "Tamamlanıyor", + "@statusFinalizing": { + "description": "Download status - writing metadata" + }, + "statusCompleted": "Tamamlandı", + "@statusCompleted": { + "description": "Download status - finished" + }, + "statusFailed": "Başarısız", + "@statusFailed": { + "description": "Download status - error occurred" + }, + "statusSkipped": "Atlandı", + "@statusSkipped": { + "description": "Download status - already exists" + }, + "statusPaused": "Durduruldu", + "@statusPaused": { + "description": "Download status - paused" + }, + "actionPause": "Duraklat", + "@actionPause": { + "description": "Action button - pause download" + }, + "actionResume": "Devam et", + "@actionResume": { + "description": "Action button - resume download" + }, + "actionCancel": "Vazgeç", + "@actionCancel": { + "description": "Action button - cancel operation" + }, + "actionStop": "Durdur", + "@actionStop": { + "description": "Action button - stop operation" + }, + "actionSelect": "Seç", + "@actionSelect": { + "description": "Action button - enter selection mode" + }, + "actionSelectAll": "Tümünü Seç", + "@actionSelectAll": { + "description": "Action button - select all items" + }, + "actionDeselect": "Seçimi kaldır", + "@actionDeselect": { + "description": "Action button - deselect all" + }, + "actionPaste": "Yapıştır", + "@actionPaste": { + "description": "Action button - paste from clipboard" + }, + "actionImportCsv": "CSV İçe Aktar", + "@actionImportCsv": { + "description": "Action button - import CSV file" + }, + "actionRemoveCredentials": "Özellikleri kaldır", + "@actionRemoveCredentials": { + "description": "Action button - delete Spotify credentials" + }, + "actionSaveCredentials": "Özellikleri kaydet", + "@actionSaveCredentials": { + "description": "Action button - save Spotify credentials" + }, + "selectionSelected": "{count} seçildi", + "@selectionSelected": { + "description": "Selection count indicator", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionAllSelected": "Tüm parçalar seçildi", + "@selectionAllSelected": { + "description": "Status - all items selected" + }, + "selectionTapToSelect": "Seçmek için parçalara dokunun", + "@selectionTapToSelect": { + "description": "Hint - how to select items" + }, + "selectionDeleteTracks": "Delete {count} {count, plural, =1{track} other{tracks}}", + "@selectionDeleteTracks": { + "description": "Delete button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionSelectToDelete": "Silinecek parçaları seçin", + "@selectionSelectToDelete": { + "description": "Placeholder when nothing selected" + }, + "progressFetchingMetadata": "Meta verileri alınıyor... {current}/{total}", + "@progressFetchingMetadata": { + "description": "Progress indicator - loading track info", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "progressReadingCsv": "CSV okunuyor...", + "@progressReadingCsv": { + "description": "Progress indicator - parsing CSV file" + }, + "searchSongs": "Şarkılar", + "@searchSongs": { + "description": "Search result category - songs" + }, + "searchArtists": "Sanatçılar", + "@searchArtists": { + "description": "Search result category - artists" + }, + "searchAlbums": "Albümler", + "@searchAlbums": { + "description": "Search result category - albums" + }, + "searchPlaylists": "Çalma Listeleri", + "@searchPlaylists": { + "description": "Search result category - playlists" + }, + "tooltipPlay": "Oynat", + "@tooltipPlay": { + "description": "Tooltip - play button" + }, + "tooltipCancel": "Vazgeç", + "@tooltipCancel": { + "description": "Tooltip - cancel button" + }, + "tooltipStop": "Durdur", + "@tooltipStop": { + "description": "Tooltip - stop button" + }, + "tooltipRetry": "Yeniden dene", + "@tooltipRetry": { + "description": "Tooltip - retry button" + }, + "tooltipRemove": "Kaldır", + "@tooltipRemove": { + "description": "Tooltip - remove button" + }, + "tooltipClear": "Temizle", + "@tooltipClear": { + "description": "Tooltip - clear button" + }, + "tooltipPaste": "Yapıştır", + "@tooltipPaste": { + "description": "Tooltip - paste button" + }, + "filenameFormat": "Dosya adı formatı", + "@filenameFormat": { + "description": "Setting title - filename pattern" + }, + "filenameFormatPreview": "Önizleme: {preview}", + "@filenameFormatPreview": { + "description": "Preview of filename pattern", + "placeholders": { + "preview": { + "type": "String" + } + } + }, + "filenameAvailablePlaceholders": "Kullanılabilir yer tutucular:", + "@filenameAvailablePlaceholders": { + "description": "Label for placeholder list" + }, + "filenameHint": "{artist} - {title}", + "@filenameHint": { + "description": "Default filename format hint" + }, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, + "folderOrganization": "Klasör Organizasyonu", + "@folderOrganization": { + "description": "Setting title - folder structure" + }, + "folderOrganizationNone": "Organizasyon yok", + "@folderOrganizationNone": { + "description": "Folder option - flat structure" + }, + "folderOrganizationByArtist": "Sanatçıya Göre", + "@folderOrganizationByArtist": { + "description": "Folder option - artist folders" + }, + "folderOrganizationByAlbum": "Albüme Göre", + "@folderOrganizationByAlbum": { + "description": "Folder option - album folders" + }, + "folderOrganizationByArtistAlbum": "Sanatçı/Albüm", + "@folderOrganizationByArtistAlbum": { + "description": "Folder option - nested folders" + }, + "folderOrganizationDescription": "İndirilenleri klasörlerle organize et", + "@folderOrganizationDescription": { + "description": "Folder organization sheet description" + }, + "folderOrganizationNoneSubtitle": "Her şey indirilen dosyasına kaydedilecek", + "@folderOrganizationNoneSubtitle": { + "description": "Subtitle for no organization option" + }, + "folderOrganizationByArtistSubtitle": "Her sanatçı için ayrı klasör", + "@folderOrganizationByArtistSubtitle": { + "description": "Subtitle for artist folder option" + }, + "folderOrganizationByAlbumSubtitle": "Her albüm için ayrı klasör", + "@folderOrganizationByAlbumSubtitle": { + "description": "Subtitle for album folder option" + }, + "folderOrganizationByArtistAlbumSubtitle": "Sanatçı klasörlerinin içinde Albüm klasörleri", + "@folderOrganizationByArtistAlbumSubtitle": { + "description": "Subtitle for nested folder option" + }, + "updateAvailable": "Güncelleme Mevcut", + "@updateAvailable": { + "description": "Update dialog title" + }, + "updateNewVersion": "{version} sürümü mevcut", + "@updateNewVersion": { + "description": "Update available message", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "updateDownload": "İndir", + "@updateDownload": { + "description": "Update button - download update" + }, + "updateLater": "Daha Sonra", + "@updateLater": { + "description": "Update button - dismiss" + }, + "updateChangelog": "Değişiklikler", + "@updateChangelog": { + "description": "Link to changelog" + }, + "updateStartingDownload": "İndirme başlıyor...", + "@updateStartingDownload": { + "description": "Update status - initializing" + }, + "updateDownloadFailed": "İndirme başarısız", + "@updateDownloadFailed": { + "description": "Update error title" + }, + "updateFailedMessage": "Güncelleme indirilemedi", + "@updateFailedMessage": { + "description": "Update error message" + }, + "updateNewVersionReady": "Yeni bir sürüm hazır", + "@updateNewVersionReady": { + "description": "Update subtitle" + }, + "updateCurrent": "Şimdiki", + "@updateCurrent": { + "description": "Label for current version" + }, + "updateNew": "Yeni", + "@updateNew": { + "description": "Label for new version" + }, + "updateDownloading": "İndiriliyor...", + "@updateDownloading": { + "description": "Update status - downloading" + }, + "updateWhatsNew": "Yenilikler", + "@updateWhatsNew": { + "description": "Changelog section title" + }, + "updateDownloadInstall": "İndir & Yükle", + "@updateDownloadInstall": { + "description": "Update button - download and install" + }, + "updateDontRemind": "Bir daha sorma", + "@updateDontRemind": { + "description": "Update button - skip this version" + }, + "providerPriority": "İndirme hizmetleri öncelik sırası", + "@providerPriority": { + "description": "Setting title - download provider order" + }, + "providerPrioritySubtitle": "İndirme hizmetlerini sıralamak için kaydır", + "@providerPrioritySubtitle": { + "description": "Subtitle for provider priority" + }, + "providerPriorityTitle": "İndirme hizmetleri öncelik sırası", + "@providerPriorityTitle": { + "description": "Provider priority page title" + }, + "providerPriorityDescription": "İndirme hizmetlerini sıralamak için kaydır. Uygulama şarkı indirirken hizmetleri yukarıdan aşağıya doğru deneyecektir.", + "@providerPriorityDescription": { + "description": "Provider priority page description" + }, + "providerPriorityInfo": "Eğer bir şarkı ilk hizmette mevcut değilse uygulama otomatik olarak bir sonrakini deneyecektir.", + "@providerPriorityInfo": { + "description": "Info tip about fallback behavior" + }, + "providerBuiltIn": "Dahili", + "@providerBuiltIn": { + "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + }, + "providerExtension": "Eklenti", + "@providerExtension": { + "description": "Label for extension-provided providers" + }, + "metadataProviderPriority": "Metadata Provider Priority", + "@metadataProviderPriority": { + "description": "Setting title - metadata provider order" + }, + "metadataProviderPrioritySubtitle": "Order used when fetching track metadata", + "@metadataProviderPrioritySubtitle": { + "description": "Subtitle for metadata priority" + }, + "metadataProviderPriorityTitle": "Metadata Priority", + "@metadataProviderPriorityTitle": { + "description": "Metadata priority page title" + }, + "metadataProviderPriorityDescription": "Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.", + "@metadataProviderPriorityDescription": { + "description": "Metadata priority page description" + }, + "metadataProviderPriorityInfo": "Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.", + "@metadataProviderPriorityInfo": { + "description": "Info tip about rate limits" + }, + "metadataNoRateLimits": "No rate limits", + "@metadataNoRateLimits": { + "description": "Deezer provider description" + }, + "metadataMayRateLimit": "May rate limit", + "@metadataMayRateLimit": { + "description": "Spotify provider description" + }, + "logTitle": "Kayıtlar", + "@logTitle": { + "description": "Logs screen title" + }, + "logCopy": "Copy Logs", + "@logCopy": { + "description": "Action - copy logs to clipboard" + }, + "logClear": "Kayıtları temizle", + "@logClear": { + "description": "Action - delete all logs" + }, + "logShare": "Kayıtları Paylaş", + "@logShare": { + "description": "Action - share logs file" + }, + "logEmpty": "Henüz kayıt yok", + "@logEmpty": { + "description": "Empty state title" + }, + "logCopied": "Kayıtlar panoya kopyalandı", + "@logCopied": { + "description": "Snackbar - logs copied" + }, + "logSearchHint": "Kayıtları Ara...", + "@logSearchHint": { + "description": "Log search placeholder" + }, + "logFilterLevel": "Seviye", + "@logFilterLevel": { + "description": "Filter by log level" + }, + "logFilterSection": "Filtre", + "@logFilterSection": { + "description": "Filter section title" + }, + "logShareLogs": "Kayıtları paylaş", + "@logShareLogs": { + "description": "Share button tooltip" + }, + "logClearLogs": "Kayıtları temizle", + "@logClearLogs": { + "description": "Clear button tooltip" + }, + "logClearLogsTitle": "Kayıtları temizle", + "@logClearLogsTitle": { + "description": "Clear logs dialog title" + }, + "logClearLogsMessage": "Tüm kayıtları temizlemek istediğinize emin misiniz?", + "@logClearLogsMessage": { + "description": "Clear logs confirmation message" + }, + "logIspBlocking": "ISP BLOCKING DETECTED", + "@logIspBlocking": { + "description": "Error category - ISP blocking" + }, + "logRateLimited": "RATE LIMITED", + "@logRateLimited": { + "description": "Error category - rate limiting" + }, + "logNetworkError": "NETWORK ERROR", + "@logNetworkError": { + "description": "Error category - network issues" + }, + "logTrackNotFound": "TRACK NOT FOUND", + "@logTrackNotFound": { + "description": "Error category - missing tracks" + }, + "logFilterBySeverity": "Filter logs by severity", + "@logFilterBySeverity": { + "description": "Filter dialog title" + }, + "logNoLogsYet": "No logs yet", + "@logNoLogsYet": { + "description": "Empty state title" + }, + "logNoLogsYetSubtitle": "Logs will appear here as you use the app", + "@logNoLogsYetSubtitle": { + "description": "Empty state subtitle" + }, + "logIssueSummary": "Issue Summary", + "@logIssueSummary": { + "description": "Section header for error summary" + }, + "logIspBlockingDescription": "Your ISP may be blocking access to download services", + "@logIspBlockingDescription": { + "description": "ISP blocking explanation" + }, + "logIspBlockingSuggestion": "Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8", + "@logIspBlockingSuggestion": { + "description": "ISP blocking fix suggestion" + }, + "logRateLimitedDescription": "Too many requests to the service", + "@logRateLimitedDescription": { + "description": "Rate limit explanation" + }, + "logRateLimitedSuggestion": "Wait a few minutes before trying again", + "@logRateLimitedSuggestion": { + "description": "Rate limit fix suggestion" + }, + "logNetworkErrorDescription": "Connection issues detected", + "@logNetworkErrorDescription": { + "description": "Network error explanation" + }, + "logNetworkErrorSuggestion": "Check your internet connection", + "@logNetworkErrorSuggestion": { + "description": "Network error fix suggestion" + }, + "logTrackNotFoundDescription": "Some tracks could not be found on download services", + "@logTrackNotFoundDescription": { + "description": "Track not found explanation" + }, + "logTrackNotFoundSuggestion": "The track may not be available in lossless quality", + "@logTrackNotFoundSuggestion": { + "description": "Track not found explanation" + }, + "logTotalErrors": "Total errors: {count}", + "@logTotalErrors": { + "description": "Error count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "logAffected": "Affected: {domains}", + "@logAffected": { + "description": "Affected domains display", + "placeholders": { + "domains": { + "type": "String" + } + } + }, + "logEntriesFiltered": "Entries ({count} filtered)", + "@logEntriesFiltered": { + "description": "Log count with filter active", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "logEntries": "Entries ({count})", + "@logEntries": { + "description": "Total log count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "credentialsTitle": "Spotify Credentials", + "@credentialsTitle": { + "description": "Credentials dialog title" + }, + "credentialsDescription": "Enter your Client ID and Secret to use your own Spotify application quota.", + "@credentialsDescription": { + "description": "Credentials dialog explanation" + }, + "credentialsClientId": "Client ID", + "@credentialsClientId": { + "description": "Client ID field label - DO NOT TRANSLATE" + }, + "credentialsClientIdHint": "Paste Client ID", + "@credentialsClientIdHint": { + "description": "Client ID placeholder" + }, + "credentialsClientSecret": "Client Secret", + "@credentialsClientSecret": { + "description": "Client Secret field label - DO NOT TRANSLATE" + }, + "credentialsClientSecretHint": "Paste Client Secret", + "@credentialsClientSecretHint": { + "description": "Client Secret placeholder" + }, + "channelStable": "Stable", + "@channelStable": { + "description": "Update channel - stable releases" + }, + "channelPreview": "Preview", + "@channelPreview": { + "description": "Update channel - beta/preview releases" + }, + "sectionSearchSource": "Search Source", + "@sectionSearchSource": { + "description": "Settings section header" + }, + "sectionDownload": "Download", + "@sectionDownload": { + "description": "Settings section header" + }, + "sectionPerformance": "Performance", + "@sectionPerformance": { + "description": "Settings section header" + }, + "sectionApp": "App", + "@sectionApp": { + "description": "Settings section header" + }, + "sectionData": "Data", + "@sectionData": { + "description": "Settings section header" + }, + "sectionDebug": "Debug", + "@sectionDebug": { + "description": "Settings section header" + }, + "sectionService": "Service", + "@sectionService": { + "description": "Settings section header" + }, + "sectionAudioQuality": "Audio Quality", + "@sectionAudioQuality": { + "description": "Settings section header" + }, + "sectionFileSettings": "File Settings", + "@sectionFileSettings": { + "description": "Settings section header" + }, + "sectionLyrics": "Lyrics", + "@sectionLyrics": { + "description": "Settings section header" + }, + "lyricsMode": "Lyrics Mode", + "@lyricsMode": { + "description": "Setting - how to save lyrics" + }, + "lyricsModeDescription": "Choose how lyrics are saved with your downloads", + "@lyricsModeDescription": { + "description": "Lyrics mode picker description" + }, + "lyricsModeEmbed": "Embed in file", + "@lyricsModeEmbed": { + "description": "Lyrics mode option - embed in audio file" + }, + "lyricsModeEmbedSubtitle": "Lyrics stored inside FLAC metadata", + "@lyricsModeEmbedSubtitle": { + "description": "Subtitle for embed option" + }, + "lyricsModeExternal": "External .lrc file", + "@lyricsModeExternal": { + "description": "Lyrics mode option - separate LRC file" + }, + "lyricsModeExternalSubtitle": "Separate .lrc file for players like Samsung Music", + "@lyricsModeExternalSubtitle": { + "description": "Subtitle for external option" + }, + "lyricsModeBoth": "Both", + "@lyricsModeBoth": { + "description": "Lyrics mode option - embed and external" + }, + "lyricsModeBothSubtitle": "Embed and save .lrc file", + "@lyricsModeBothSubtitle": { + "description": "Subtitle for both option" + }, + "sectionColor": "Color", + "@sectionColor": { + "description": "Settings section header" + }, + "sectionTheme": "Theme", + "@sectionTheme": { + "description": "Settings section header" + }, + "sectionLayout": "Layout", + "@sectionLayout": { + "description": "Settings section header" + }, + "sectionLanguage": "Language", + "@sectionLanguage": { + "description": "Settings section header for language" + }, + "appearanceLanguage": "App Language", + "@appearanceLanguage": { + "description": "Language setting title" + }, + "appearanceLanguageSubtitle": "Choose your preferred language", + "@appearanceLanguageSubtitle": { + "description": "Language setting subtitle" + }, + "settingsAppearanceSubtitle": "Theme, colors, display", + "@settingsAppearanceSubtitle": { + "description": "Appearance settings description" + }, + "settingsDownloadSubtitle": "Service, quality, filename format", + "@settingsDownloadSubtitle": { + "description": "Download settings description" + }, + "settingsOptionsSubtitle": "Fallback, lyrics, cover art, updates", + "@settingsOptionsSubtitle": { + "description": "Options settings description" + }, + "settingsExtensionsSubtitle": "Manage download providers", + "@settingsExtensionsSubtitle": { + "description": "Extensions settings description" + }, + "settingsLogsSubtitle": "View app logs for debugging", + "@settingsLogsSubtitle": { + "description": "Logs settings description" + }, + "loadingSharedLink": "Loading shared link...", + "@loadingSharedLink": { + "description": "Status when opening shared URL" + }, + "pressBackAgainToExit": "Press back again to exit", + "@pressBackAgainToExit": { + "description": "Exit confirmation message" + }, + "tracksHeader": "Tracks", + "@tracksHeader": { + "description": "Section header for track list" + }, + "downloadAllCount": "Download All ({count})", + "@downloadAllCount": { + "description": "Download all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@tracksCount": { + "description": "Track count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "trackCopyFilePath": "Copy file path", + "@trackCopyFilePath": { + "description": "Action - copy file path" + }, + "trackRemoveFromDevice": "Remove from device", + "@trackRemoveFromDevice": { + "description": "Action - delete downloaded file" + }, + "trackLoadLyrics": "Load Lyrics", + "@trackLoadLyrics": { + "description": "Action - fetch lyrics" + }, + "trackMetadata": "Metadata", + "@trackMetadata": { + "description": "Tab title - track metadata" + }, + "trackFileInfo": "File Info", + "@trackFileInfo": { + "description": "Tab title - file information" + }, + "trackLyrics": "Lyrics", + "@trackLyrics": { + "description": "Tab title - lyrics" + }, + "trackFileNotFound": "File not found", + "@trackFileNotFound": { + "description": "Error - file doesn't exist" + }, + "trackOpenInDeezer": "Open in Deezer", + "@trackOpenInDeezer": { + "description": "Action - open track in Deezer app" + }, + "trackOpenInSpotify": "Open in Spotify", + "@trackOpenInSpotify": { + "description": "Action - open track in Spotify app" + }, + "trackTrackName": "Track name", + "@trackTrackName": { + "description": "Metadata label - track title" + }, + "trackArtist": "Artist", + "@trackArtist": { + "description": "Metadata label - artist name" + }, + "trackAlbumArtist": "Album artist", + "@trackAlbumArtist": { + "description": "Metadata label - album artist" + }, + "trackAlbum": "Album", + "@trackAlbum": { + "description": "Metadata label - album name" + }, + "trackTrackNumber": "Track number", + "@trackTrackNumber": { + "description": "Metadata label - track number" + }, + "trackDiscNumber": "Disc number", + "@trackDiscNumber": { + "description": "Metadata label - disc number" + }, + "trackDuration": "Duration", + "@trackDuration": { + "description": "Metadata label - track length" + }, + "trackAudioQuality": "Audio quality", + "@trackAudioQuality": { + "description": "Metadata label - audio quality" + }, + "trackReleaseDate": "Release date", + "@trackReleaseDate": { + "description": "Metadata label - release date" + }, + "trackGenre": "Genre", + "@trackGenre": { + "description": "Metadata label - music genre" + }, + "trackLabel": "Label", + "@trackLabel": { + "description": "Metadata label - record label" + }, + "trackCopyright": "Copyright", + "@trackCopyright": { + "description": "Metadata label - copyright information" + }, + "trackDownloaded": "Downloaded", + "@trackDownloaded": { + "description": "Metadata label - download date" + }, + "trackCopyLyrics": "Copy lyrics", + "@trackCopyLyrics": { + "description": "Action - copy lyrics to clipboard" + }, + "trackLyricsNotAvailable": "Lyrics not available for this track", + "@trackLyricsNotAvailable": { + "description": "Message when lyrics not found" + }, + "trackLyricsTimeout": "Request timed out. Try again later.", + "@trackLyricsTimeout": { + "description": "Message when lyrics request times out" + }, + "trackLyricsLoadFailed": "Failed to load lyrics", + "@trackLyricsLoadFailed": { + "description": "Message when lyrics loading fails" + }, + "trackEmbedLyrics": "Embed Lyrics", + "@trackEmbedLyrics": { + "description": "Action - embed lyrics into audio file" + }, + "trackLyricsEmbedded": "Lyrics embedded successfully", + "@trackLyricsEmbedded": { + "description": "Snackbar - lyrics saved to file" + }, + "trackInstrumental": "Instrumental track", + "@trackInstrumental": { + "description": "Message when track is instrumental (no lyrics)" + }, + "trackCopiedToClipboard": "Copied to clipboard", + "@trackCopiedToClipboard": { + "description": "Snackbar - content copied" + }, + "trackDeleteConfirmTitle": "Remove from device?", + "@trackDeleteConfirmTitle": { + "description": "Delete confirmation title" + }, + "trackDeleteConfirmMessage": "This will permanently delete the downloaded file and remove it from your history.", + "@trackDeleteConfirmMessage": { + "description": "Delete confirmation message" + }, + "trackCannotOpen": "Cannot open: {message}", + "@trackCannotOpen": { + "description": "Error opening file", + "placeholders": { + "message": { + "type": "String" + } + } + }, + "dateToday": "Today", + "@dateToday": { + "description": "Relative date - today" + }, + "dateYesterday": "Yesterday", + "@dateYesterday": { + "description": "Relative date - yesterday" + }, + "dateDaysAgo": "{count} days ago", + "@dateDaysAgo": { + "description": "Relative date - days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dateWeeksAgo": "{count} weeks ago", + "@dateWeeksAgo": { + "description": "Relative date - weeks ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dateMonthsAgo": "{count} months ago", + "@dateMonthsAgo": { + "description": "Relative date - months ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "concurrentSequential": "Sequential", + "@concurrentSequential": { + "description": "Download mode - one at a time" + }, + "concurrentParallel2": "2 Parallel", + "@concurrentParallel2": { + "description": "Download mode - 2 simultaneous" + }, + "concurrentParallel3": "3 Parallel", + "@concurrentParallel3": { + "description": "Download mode - 3 simultaneous" + }, + "tapToSeeError": "Tap to see error details", + "@tapToSeeError": { + "description": "Tooltip for failed download" + }, + "storeFilterAll": "All", + "@storeFilterAll": { + "description": "Store filter - all extensions" + }, + "storeFilterMetadata": "Metadata", + "@storeFilterMetadata": { + "description": "Store filter - metadata providers" + }, + "storeFilterDownload": "Download", + "@storeFilterDownload": { + "description": "Store filter - download providers" + }, + "storeFilterUtility": "Utility", + "@storeFilterUtility": { + "description": "Store filter - utility extensions" + }, + "storeFilterLyrics": "Lyrics", + "@storeFilterLyrics": { + "description": "Store filter - lyrics providers" + }, + "storeFilterIntegration": "Integration", + "@storeFilterIntegration": { + "description": "Store filter - integrations" + }, + "storeClearFilters": "Clear filters", + "@storeClearFilters": { + "description": "Button to clear all filters" + }, + "storeNoResults": "No extensions found", + "@storeNoResults": { + "description": "Empty state when no extensions match filters" + }, + "extensionProviderPriority": "Provider Priority", + "@extensionProviderPriority": { + "description": "Extension capability - provider priority" + }, + "extensionInstallButton": "Install Extension", + "@extensionInstallButton": { + "description": "Button to install extension" + }, + "extensionDefaultProvider": "Default (Deezer/Spotify)", + "@extensionDefaultProvider": { + "description": "Default search provider option" + }, + "extensionDefaultProviderSubtitle": "Use built-in search", + "@extensionDefaultProviderSubtitle": { + "description": "Subtitle for default provider" + }, + "extensionAuthor": "Author", + "@extensionAuthor": { + "description": "Extension detail - author" + }, + "extensionId": "ID", + "@extensionId": { + "description": "Extension detail - unique ID" + }, + "extensionError": "Error", + "@extensionError": { + "description": "Extension detail - error message" + }, + "extensionCapabilities": "Capabilities", + "@extensionCapabilities": { + "description": "Section header - extension features" + }, + "extensionMetadataProvider": "Metadata Provider", + "@extensionMetadataProvider": { + "description": "Capability - provides metadata" + }, + "extensionDownloadProvider": "Download Provider", + "@extensionDownloadProvider": { + "description": "Capability - provides downloads" + }, + "extensionLyricsProvider": "Lyrics Provider", + "@extensionLyricsProvider": { + "description": "Capability - provides lyrics" + }, + "extensionUrlHandler": "URL Handler", + "@extensionUrlHandler": { + "description": "Capability - handles URLs" + }, + "extensionQualityOptions": "Quality Options", + "@extensionQualityOptions": { + "description": "Capability - quality selection" + }, + "extensionPostProcessingHooks": "Post-Processing Hooks", + "@extensionPostProcessingHooks": { + "description": "Capability - post-processing" + }, + "extensionPermissions": "Permissions", + "@extensionPermissions": { + "description": "Section header - required permissions" + }, + "extensionSettings": "Settings", + "@extensionSettings": { + "description": "Section header - extension settings" + }, + "extensionRemoveButton": "Remove Extension", + "@extensionRemoveButton": { + "description": "Button to uninstall extension" + }, + "extensionUpdated": "Updated", + "@extensionUpdated": { + "description": "Extension detail - last update" + }, + "extensionMinAppVersion": "Min App Version", + "@extensionMinAppVersion": { + "description": "Extension detail - minimum app version" + }, + "extensionCustomTrackMatching": "Custom Track Matching", + "@extensionCustomTrackMatching": { + "description": "Capability - custom track matching algorithm" + }, + "extensionPostProcessing": "Post-Processing", + "@extensionPostProcessing": { + "description": "Capability - post-download processing" + }, + "extensionHooksAvailable": "{count} hook(s) available", + "@extensionHooksAvailable": { + "description": "Post-processing hooks count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "extensionPatternsCount": "{count} pattern(s)", + "@extensionPatternsCount": { + "description": "URL patterns count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "extensionStrategy": "Strategy: {strategy}", + "@extensionStrategy": { + "description": "Track matching strategy name", + "placeholders": { + "strategy": { + "type": "String" + } + } + }, + "extensionsProviderPrioritySection": "Provider Priority", + "@extensionsProviderPrioritySection": { + "description": "Section header - provider priority" + }, + "extensionsInstalledSection": "Installed Extensions", + "@extensionsInstalledSection": { + "description": "Section header - installed extensions" + }, + "extensionsNoExtensions": "No extensions installed", + "@extensionsNoExtensions": { + "description": "Empty state - no extensions" + }, + "extensionsNoExtensionsSubtitle": "Install .spotiflac-ext files to add new providers", + "@extensionsNoExtensionsSubtitle": { + "description": "Empty state subtitle" + }, + "extensionsInstallButton": "Install Extension", + "@extensionsInstallButton": { + "description": "Button to install extension from file" + }, + "extensionsInfoTip": "Extensions can add new metadata and download providers. Only install extensions from trusted sources.", + "@extensionsInfoTip": { + "description": "Security warning about extensions" + }, + "extensionsInstalledSuccess": "Extension installed successfully", + "@extensionsInstalledSuccess": { + "description": "Success message after install" + }, + "extensionsDownloadPriority": "Download Priority", + "@extensionsDownloadPriority": { + "description": "Setting - download provider order" + }, + "extensionsDownloadPrioritySubtitle": "Set download service order", + "@extensionsDownloadPrioritySubtitle": { + "description": "Subtitle for download priority" + }, + "extensionsNoDownloadProvider": "No extensions with download provider", + "@extensionsNoDownloadProvider": { + "description": "Empty state - no download providers" + }, + "extensionsMetadataPriority": "Metadata Priority", + "@extensionsMetadataPriority": { + "description": "Setting - metadata provider order" + }, + "extensionsMetadataPrioritySubtitle": "Set search & metadata source order", + "@extensionsMetadataPrioritySubtitle": { + "description": "Subtitle for metadata priority" + }, + "extensionsNoMetadataProvider": "No extensions with metadata provider", + "@extensionsNoMetadataProvider": { + "description": "Empty state - no metadata providers" + }, + "extensionsSearchProvider": "Search Provider", + "@extensionsSearchProvider": { + "description": "Setting - search provider selection" + }, + "extensionsNoCustomSearch": "No extensions with custom search", + "@extensionsNoCustomSearch": { + "description": "Empty state - no search providers" + }, + "extensionsSearchProviderDescription": "Choose which service to use for searching tracks", + "@extensionsSearchProviderDescription": { + "description": "Search provider setting description" + }, + "extensionsCustomSearch": "Custom search", + "@extensionsCustomSearch": { + "description": "Label for custom search provider" + }, + "extensionsErrorLoading": "Error loading extension", + "@extensionsErrorLoading": { + "description": "Error message when extension fails to load" + }, + "qualityFlacLossless": "FLAC Lossless", + "@qualityFlacLossless": { + "description": "Quality option - CD quality FLAC" + }, + "qualityFlacLosslessSubtitle": "16-bit / 44.1kHz", + "@qualityFlacLosslessSubtitle": { + "description": "Technical spec for lossless" + }, + "qualityHiResFlac": "Hi-Res FLAC", + "@qualityHiResFlac": { + "description": "Quality option - high resolution FLAC" + }, + "qualityHiResFlacSubtitle": "24-bit / up to 96kHz", + "@qualityHiResFlacSubtitle": { + "description": "Technical spec for hi-res" + }, + "qualityHiResFlacMax": "Hi-Res FLAC Max", + "@qualityHiResFlacMax": { + "description": "Quality option - maximum resolution FLAC" + }, + "qualityHiResFlacMaxSubtitle": "24-bit / up to 192kHz", + "@qualityHiResFlacMaxSubtitle": { + "description": "Technical spec for hi-res max" + }, + "qualityLossy": "Lossy", + "@qualityLossy": { + "description": "Quality option - lossy format (MP3/Opus)" + }, + "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", + "@qualityLossyMp3Subtitle": { + "description": "Technical spec for lossy MP3" + }, + "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", + "@qualityLossyOpusSubtitle": { + "description": "Technical spec for lossy Opus" + }, + "enableLossyOption": "Enable Lossy Option", + "@enableLossyOption": { + "description": "Setting - enable lossy quality option" + }, + "enableLossyOptionSubtitleOn": "Lossy quality option is available", + "@enableLossyOptionSubtitleOn": { + "description": "Subtitle when lossy is enabled" + }, + "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", + "@enableLossyOptionSubtitleOff": { + "description": "Subtitle when lossy is disabled" + }, + "lossyFormat": "Lossy Format", + "@lossyFormat": { + "description": "Setting - choose lossy format" + }, + "lossyFormatDescription": "Choose the lossy format for conversion", + "@lossyFormatDescription": { + "description": "Description for lossy format picker" + }, + "lossyFormatMp3Subtitle": "320kbps, best compatibility", + "@lossyFormatMp3Subtitle": { + "description": "MP3 format description" + }, + "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", + "@lossyFormatOpusSubtitle": { + "description": "Opus format description" + }, + "qualityNote": "Actual quality depends on track availability from the service", + "@qualityNote": { + "description": "Note about quality availability" + }, + "youtubeQualityNote": "YouTube provides lossy audio only. Not part of lossless fallback.", + "@youtubeQualityNote": { + "description": "Note for YouTube service explaining lossy-only quality" + }, + "downloadAskBeforeDownload": "Ask Before Download", + "@downloadAskBeforeDownload": { + "description": "Setting - show quality picker" + }, + "downloadDirectory": "Download Directory", + "@downloadDirectory": { + "description": "Setting - download folder" + }, + "downloadSeparateSinglesFolder": "Separate Singles Folder", + "@downloadSeparateSinglesFolder": { + "description": "Setting - separate folder for singles" + }, + "downloadAlbumFolderStructure": "Album Folder Structure", + "@downloadAlbumFolderStructure": { + "description": "Setting - album folder organization" + }, + "downloadUseAlbumArtistForFolders": "Use Album Artist for folders", + "@downloadUseAlbumArtistForFolders": { + "description": "Setting - choose whether artist folders use Album Artist or Track Artist" + }, + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" + }, + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" + }, + "downloadUsePrimaryArtistOnly": "Primary artist only for folders", + "@downloadUsePrimaryArtistOnly": { + "description": "Setting - strip featured artists from folder name" + }, + "downloadUsePrimaryArtistOnlyEnabled": "Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)", + "@downloadUsePrimaryArtistOnlyEnabled": { + "description": "Subtitle when primary artist only is enabled" + }, + "downloadUsePrimaryArtistOnlyDisabled": "Full artist string used for folder name", + "@downloadUsePrimaryArtistOnlyDisabled": { + "description": "Subtitle when primary artist only is disabled" + }, + "downloadSaveFormat": "Save Format", + "@downloadSaveFormat": { + "description": "Setting - output file format" + }, + "downloadSelectService": "Select Service", + "@downloadSelectService": { + "description": "Dialog title - choose download service" + }, + "downloadSelectQuality": "Select Quality", + "@downloadSelectQuality": { + "description": "Dialog title - choose audio quality" + }, + "downloadFrom": "Download From", + "@downloadFrom": { + "description": "Label - download source" + }, + "downloadDefaultQualityLabel": "Default Quality", + "@downloadDefaultQualityLabel": { + "description": "Label - default quality setting" + }, + "downloadBestAvailable": "Best available", + "@downloadBestAvailable": { + "description": "Quality option - highest available" + }, + "folderNone": "None", + "@folderNone": { + "description": "Folder option - no organization" + }, + "folderNoneSubtitle": "Save all files directly to download folder", + "@folderNoneSubtitle": { + "description": "Subtitle for no folder organization" + }, + "folderArtist": "Artist", + "@folderArtist": { + "description": "Folder option - by artist" + }, + "folderArtistSubtitle": "Artist Name/filename", + "@folderArtistSubtitle": { + "description": "Folder structure example" + }, + "folderAlbum": "Album", + "@folderAlbum": { + "description": "Folder option - by album" + }, + "folderAlbumSubtitle": "Album Name/filename", + "@folderAlbumSubtitle": { + "description": "Folder structure example" + }, + "folderArtistAlbum": "Artist/Album", + "@folderArtistAlbum": { + "description": "Folder option - nested" + }, + "folderArtistAlbumSubtitle": "Artist Name/Album Name/filename", + "@folderArtistAlbumSubtitle": { + "description": "Folder structure example" + }, + "serviceTidal": "Tidal", + "@serviceTidal": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceQobuz": "Qobuz", + "@serviceQobuz": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceAmazon": "Amazon", + "@serviceAmazon": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceDeezer": "Deezer", + "@serviceDeezer": { + "description": "Service name - DO NOT TRANSLATE" + }, + "serviceSpotify": "Spotify", + "@serviceSpotify": { + "description": "Service name - DO NOT TRANSLATE" + }, + "appearanceAmoledDark": "AMOLED Dark", + "@appearanceAmoledDark": { + "description": "Theme option - pure black" + }, + "appearanceAmoledDarkSubtitle": "Pure black background", + "@appearanceAmoledDarkSubtitle": { + "description": "Subtitle for AMOLED dark" + }, + "appearanceChooseAccentColor": "Choose Accent Color", + "@appearanceChooseAccentColor": { + "description": "Color picker dialog title" + }, + "appearanceChooseTheme": "Theme Mode", + "@appearanceChooseTheme": { + "description": "Theme picker dialog title" + }, + "queueTitle": "Download Queue", + "@queueTitle": { + "description": "Queue screen title" + }, + "queueClearAll": "Clear All", + "@queueClearAll": { + "description": "Button - clear all queue items" + }, + "queueClearAllMessage": "Are you sure you want to clear all downloads?", + "@queueClearAllMessage": { + "description": "Clear queue confirmation" + }, + "queueExportFailed": "Export", + "@queueExportFailed": { + "description": "Button - export failed downloads to TXT" + }, + "queueExportFailedSuccess": "Failed downloads exported to TXT file", + "@queueExportFailedSuccess": { + "description": "Success message after exporting failed downloads" + }, + "queueExportFailedClear": "Clear Failed", + "@queueExportFailedClear": { + "description": "Action to clear failed downloads after export" + }, + "queueExportFailedError": "Failed to export downloads", + "@queueExportFailedError": { + "description": "Error message when export fails" + }, + "settingsAutoExportFailed": "Auto-export failed downloads", + "@settingsAutoExportFailed": { + "description": "Setting toggle for auto-export" + }, + "settingsAutoExportFailedSubtitle": "Save failed downloads to TXT file automatically", + "@settingsAutoExportFailedSubtitle": { + "description": "Subtitle for auto-export setting" + }, + "settingsDownloadNetwork": "Download Network", + "@settingsDownloadNetwork": { + "description": "Setting for network type preference" + }, + "settingsDownloadNetworkAny": "WiFi + Mobile Data", + "@settingsDownloadNetworkAny": { + "description": "Network option - use any connection" + }, + "settingsDownloadNetworkWifiOnly": "WiFi Only", + "@settingsDownloadNetworkWifiOnly": { + "description": "Network option - only use WiFi" + }, + "settingsDownloadNetworkSubtitle": "Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.", + "@settingsDownloadNetworkSubtitle": { + "description": "Subtitle explaining network preference" + }, + "queueEmpty": "No downloads in queue", + "@queueEmpty": { + "description": "Empty queue state title" + }, + "queueEmptySubtitle": "Add tracks from the home screen", + "@queueEmptySubtitle": { + "description": "Empty queue state subtitle" + }, + "queueClearCompleted": "Clear completed", + "@queueClearCompleted": { + "description": "Button - clear finished downloads" + }, + "queueDownloadFailed": "Download Failed", + "@queueDownloadFailed": { + "description": "Error dialog title" + }, + "queueTrackLabel": "Track:", + "@queueTrackLabel": { + "description": "Label in error dialog" + }, + "queueArtistLabel": "Artist:", + "@queueArtistLabel": { + "description": "Label in error dialog" + }, + "queueErrorLabel": "Error:", + "@queueErrorLabel": { + "description": "Label in error dialog" + }, + "queueUnknownError": "Unknown error", + "@queueUnknownError": { + "description": "Fallback error message" + }, + "albumFolderArtistAlbum": "Artist / Album", + "@albumFolderArtistAlbum": { + "description": "Album folder option" + }, + "albumFolderArtistAlbumSubtitle": "Albums/Artist Name/Album Name/", + "@albumFolderArtistAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistYearAlbum": "Artist / [Year] Album", + "@albumFolderArtistYearAlbum": { + "description": "Album folder option with year" + }, + "albumFolderArtistYearAlbumSubtitle": "Albums/Artist Name/[2005] Album Name/", + "@albumFolderArtistYearAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderAlbumOnly": "Album Only", + "@albumFolderAlbumOnly": { + "description": "Album folder option" + }, + "albumFolderAlbumOnlySubtitle": "Albums/Album Name/", + "@albumFolderAlbumOnlySubtitle": { + "description": "Folder structure example" + }, + "albumFolderYearAlbum": "[Year] Album", + "@albumFolderYearAlbum": { + "description": "Album folder option with year" + }, + "albumFolderYearAlbumSubtitle": "Albums/[2005] Album Name/", + "@albumFolderYearAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistAlbumSingles": "Artist / Album + Singles", + "@albumFolderArtistAlbumSingles": { + "description": "Album folder option with singles inside artist" + }, + "albumFolderArtistAlbumSinglesSubtitle": "Artist/Album/ and Artist/Singles/", + "@albumFolderArtistAlbumSinglesSubtitle": { + "description": "Folder structure example" + }, + "downloadedAlbumDeleteSelected": "Delete Selected", + "@downloadedAlbumDeleteSelected": { + "description": "Button - delete selected tracks" + }, + "downloadedAlbumDeleteMessage": "Delete {count} {count, plural, =1{track} other{tracks}} from this album?\n\nThis will also delete the files from storage.", + "@downloadedAlbumDeleteMessage": { + "description": "Delete confirmation with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumTracksHeader": "Tracks", + "@downloadedAlbumTracksHeader": { + "description": "Section header for tracks" + }, + "downloadedAlbumDownloadedCount": "{count} downloaded", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumSelectedCount": "{count} selected", + "@downloadedAlbumSelectedCount": { + "description": "Selection count indicator", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumAllSelected": "All tracks selected", + "@downloadedAlbumAllSelected": { + "description": "Status - all items selected" + }, + "downloadedAlbumTapToSelect": "Tap tracks to select", + "@downloadedAlbumTapToSelect": { + "description": "Selection hint" + }, + "downloadedAlbumDeleteCount": "Delete {count} {count, plural, =1{track} other{tracks}}", + "@downloadedAlbumDeleteCount": { + "description": "Delete button text with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumSelectToDelete": "Select tracks to delete", + "@downloadedAlbumSelectToDelete": { + "description": "Placeholder when nothing selected" + }, + "downloadedAlbumDiscHeader": "Disc {discNumber}", + "@downloadedAlbumDiscHeader": { + "description": "Header for disc separator in multi-disc albums", + "placeholders": { + "discNumber": { + "type": "int", + "example": "1" + } + } + }, + "utilityFunctions": "Utility Functions", + "@utilityFunctions": { + "description": "Extension capability - utility functions" + }, + "recentTypeArtist": "Artist", + "@recentTypeArtist": { + "description": "Recent access item type - artist" + }, + "recentTypeAlbum": "Album", + "@recentTypeAlbum": { + "description": "Recent access item type - album" + }, + "recentTypeSong": "Song", + "@recentTypeSong": { + "description": "Recent access item type - song/track" + }, + "recentTypePlaylist": "Playlist", + "@recentTypePlaylist": { + "description": "Recent access item type - playlist" + }, + "recentEmpty": "No recent items yet", + "@recentEmpty": { + "description": "Empty state text for recent access list" + }, + "recentShowAllDownloads": "Show All Downloads", + "@recentShowAllDownloads": { + "description": "Button label to unhide hidden downloads in recent access" + }, + "recentPlaylistInfo": "Playlist: {name}", + "@recentPlaylistInfo": { + "description": "Snackbar message when tapping playlist in recent access", + "placeholders": { + "name": { + "type": "String", + "description": "Playlist name" + } + } + }, + "errorGeneric": "Error: {message}", + "@errorGeneric": { + "description": "Generic error message format", + "placeholders": { + "message": { + "type": "String", + "description": "Error message" + } + } + }, + "discographyDownload": "Download Discography", + "@discographyDownload": { + "description": "Button - download artist discography" + }, + "discographyDownloadAll": "Download All", + "@discographyDownloadAll": { + "description": "Option - download entire discography" + }, + "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", + "@discographyDownloadAllSubtitle": { + "description": "Subtitle showing total tracks and albums", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographyAlbumsOnly": "Albums Only", + "@discographyAlbumsOnly": { + "description": "Option - download only albums" + }, + "discographyAlbumsOnlySubtitle": "{count} tracks from {albumCount} albums", + "@discographyAlbumsOnlySubtitle": { + "description": "Subtitle showing album tracks count", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographySinglesOnly": "Singles & EPs Only", + "@discographySinglesOnly": { + "description": "Option - download only singles" + }, + "discographySinglesOnlySubtitle": "{count} tracks from {albumCount} singles", + "@discographySinglesOnlySubtitle": { + "description": "Subtitle showing singles tracks count", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographySelectAlbums": "Select Albums...", + "@discographySelectAlbums": { + "description": "Option - manually select albums to download" + }, + "discographySelectAlbumsSubtitle": "Choose specific albums or singles", + "@discographySelectAlbumsSubtitle": { + "description": "Subtitle for select albums option" + }, + "discographyFetchingTracks": "Fetching tracks...", + "@discographyFetchingTracks": { + "description": "Progress - fetching album tracks" + }, + "discographyFetchingAlbum": "Fetching {current} of {total}...", + "@discographyFetchingAlbum": { + "description": "Progress - fetching specific album", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "discographySelectedCount": "{count} selected", + "@discographySelectedCount": { + "description": "Selection count badge", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discographyDownloadSelected": "Download Selected", + "@discographyDownloadSelected": { + "description": "Button - download selected albums" + }, + "discographyAddedToQueue": "Added {count} tracks to queue", + "@discographyAddedToQueue": { + "description": "Snackbar - tracks added from discography", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discographySkippedDownloaded": "{added} added, {skipped} already downloaded", + "@discographySkippedDownloaded": { + "description": "Snackbar - with skipped tracks count", + "placeholders": { + "added": { + "type": "int" + }, + "skipped": { + "type": "int" + } + } + }, + "discographyNoAlbums": "No albums available", + "@discographyNoAlbums": { + "description": "Error - no albums found for artist" + }, + "discographyFailedToFetch": "Failed to fetch some albums", + "@discographyFailedToFetch": { + "description": "Error - some albums failed to load" + }, + "sectionStorageAccess": "Storage Access", + "@sectionStorageAccess": { + "description": "Section header for storage access settings" + }, + "allFilesAccess": "All Files Access", + "@allFilesAccess": { + "description": "Toggle for MANAGE_EXTERNAL_STORAGE permission" + }, + "allFilesAccessEnabledSubtitle": "Can write to any folder", + "@allFilesAccessEnabledSubtitle": { + "description": "Subtitle when all files access is enabled" + }, + "allFilesAccessDisabledSubtitle": "Limited to media folders only", + "@allFilesAccessDisabledSubtitle": { + "description": "Subtitle when all files access is disabled" + }, + "allFilesAccessDescription": "Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.", + "@allFilesAccessDescription": { + "description": "Description explaining when to enable all files access" + }, + "allFilesAccessDeniedMessage": "Permission was denied. Please enable 'All files access' manually in system settings.", + "@allFilesAccessDeniedMessage": { + "description": "Message when permission is permanently denied" + }, + "allFilesAccessDisabledMessage": "All Files Access disabled. The app will use limited storage access.", + "@allFilesAccessDisabledMessage": { + "description": "Snackbar message when user disables all files access" + }, + "settingsLocalLibrary": "Local Library", + "@settingsLocalLibrary": { + "description": "Settings menu item - local library" + }, + "settingsLocalLibrarySubtitle": "Scan music & detect duplicates", + "@settingsLocalLibrarySubtitle": { + "description": "Subtitle for local library settings" + }, + "settingsCache": "Storage & Cache", + "@settingsCache": { + "description": "Settings menu item - cache management" + }, + "settingsCacheSubtitle": "View size and clear cached data", + "@settingsCacheSubtitle": { + "description": "Subtitle for cache management menu" + }, + "libraryTitle": "Local Library", + "@libraryTitle": { + "description": "Library settings page title" + }, + "libraryStatus": "Library Status", + "@libraryStatus": { + "description": "Section header for library status" + }, + "libraryScanSettings": "Scan Settings", + "@libraryScanSettings": { + "description": "Section header for scan settings" + }, + "libraryEnableLocalLibrary": "Enable Local Library", + "@libraryEnableLocalLibrary": { + "description": "Toggle to enable library scanning" + }, + "libraryEnableLocalLibrarySubtitle": "Scan and track your existing music", + "@libraryEnableLocalLibrarySubtitle": { + "description": "Subtitle for enable toggle" + }, + "libraryFolder": "Library Folder", + "@libraryFolder": { + "description": "Folder selection setting" + }, + "libraryFolderHint": "Tap to select folder", + "@libraryFolderHint": { + "description": "Placeholder when no folder selected" + }, + "libraryShowDuplicateIndicator": "Show Duplicate Indicator", + "@libraryShowDuplicateIndicator": { + "description": "Toggle for duplicate indicator in search" + }, + "libraryShowDuplicateIndicatorSubtitle": "Show when searching for existing tracks", + "@libraryShowDuplicateIndicatorSubtitle": { + "description": "Subtitle for duplicate indicator toggle" + }, + "libraryActions": "Actions", + "@libraryActions": { + "description": "Section header for library actions" + }, + "libraryScan": "Scan Library", + "@libraryScan": { + "description": "Button to start library scan" + }, + "libraryScanSubtitle": "Scan for audio files", + "@libraryScanSubtitle": { + "description": "Subtitle for scan button" + }, + "libraryScanSelectFolderFirst": "Select a folder first", + "@libraryScanSelectFolderFirst": { + "description": "Message when trying to scan without folder" + }, + "libraryCleanupMissingFiles": "Cleanup Missing Files", + "@libraryCleanupMissingFiles": { + "description": "Button to remove entries for missing files" + }, + "libraryCleanupMissingFilesSubtitle": "Remove entries for files that no longer exist", + "@libraryCleanupMissingFilesSubtitle": { + "description": "Subtitle for cleanup button" + }, + "libraryClear": "Clear Library", + "@libraryClear": { + "description": "Button to clear all library entries" + }, + "libraryClearSubtitle": "Remove all scanned tracks", + "@libraryClearSubtitle": { + "description": "Subtitle for clear button" + }, + "libraryClearConfirmTitle": "Clear Library", + "@libraryClearConfirmTitle": { + "description": "Dialog title for clear confirmation" + }, + "libraryClearConfirmMessage": "This will remove all scanned tracks from your library. Your actual music files will not be deleted.", + "@libraryClearConfirmMessage": { + "description": "Dialog message for clear confirmation" + }, + "libraryAbout": "About Local Library", + "@libraryAbout": { + "description": "Section header for about info" + }, + "libraryAboutDescription": "Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.", + "@libraryAboutDescription": { + "description": "Description of local library feature" + }, + "libraryTracksCount": "{count} tracks", + "@libraryTracksCount": { + "description": "Track count in library", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "libraryLastScanned": "Last scanned: {time}", + "@libraryLastScanned": { + "description": "Last scan time display", + "placeholders": { + "time": { + "type": "String" + } + } + }, + "libraryLastScannedNever": "Never", + "@libraryLastScannedNever": { + "description": "Shown when library has never been scanned" + }, + "libraryScanning": "Scanning...", + "@libraryScanning": { + "description": "Status during scan" + }, + "libraryScanProgress": "{progress}% of {total} files", + "@libraryScanProgress": { + "description": "Scan progress display", + "placeholders": { + "progress": { + "type": "String" + }, + "total": { + "type": "int" + } + } + }, + "libraryInLibrary": "In Library", + "@libraryInLibrary": { + "description": "Badge shown on tracks that exist in local library" + }, + "libraryRemovedMissingFiles": "Removed {count} missing files from library", + "@libraryRemovedMissingFiles": { + "description": "Snackbar after cleanup", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "libraryCleared": "Library cleared", + "@libraryCleared": { + "description": "Snackbar after clearing library" + }, + "libraryStorageAccessRequired": "Storage Access Required", + "@libraryStorageAccessRequired": { + "description": "Dialog title for storage permission" + }, + "libraryStorageAccessMessage": "SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.", + "@libraryStorageAccessMessage": { + "description": "Dialog message for storage permission" + }, + "libraryFolderNotExist": "Selected folder does not exist", + "@libraryFolderNotExist": { + "description": "Error when folder doesn't exist" + }, + "librarySourceDownloaded": "Downloaded", + "@librarySourceDownloaded": { + "description": "Badge for tracks downloaded via SpotiFLAC" + }, + "librarySourceLocal": "Local", + "@librarySourceLocal": { + "description": "Badge for tracks from local library scan" + }, + "libraryFilterAll": "All", + "@libraryFilterAll": { + "description": "Filter chip - show all library items" + }, + "libraryFilterDownloaded": "Downloaded", + "@libraryFilterDownloaded": { + "description": "Filter chip - show only downloaded items" + }, + "libraryFilterLocal": "Local", + "@libraryFilterLocal": { + "description": "Filter chip - show only local library items" + }, + "libraryFilterTitle": "Filters", + "@libraryFilterTitle": { + "description": "Filter bottom sheet title" + }, + "libraryFilterReset": "Reset", + "@libraryFilterReset": { + "description": "Reset all filters button" + }, + "libraryFilterApply": "Apply", + "@libraryFilterApply": { + "description": "Apply filters button" + }, + "libraryFilterSource": "Source", + "@libraryFilterSource": { + "description": "Filter section - source type" + }, + "libraryFilterQuality": "Quality", + "@libraryFilterQuality": { + "description": "Filter section - audio quality" + }, + "libraryFilterQualityHiRes": "Hi-Res (24bit)", + "@libraryFilterQualityHiRes": { + "description": "Filter option - high resolution audio" + }, + "libraryFilterQualityCD": "CD (16bit)", + "@libraryFilterQualityCD": { + "description": "Filter option - CD quality audio" + }, + "libraryFilterQualityLossy": "Lossy", + "@libraryFilterQualityLossy": { + "description": "Filter option - lossy compressed audio" + }, + "libraryFilterFormat": "Format", + "@libraryFilterFormat": { + "description": "Filter section - file format" + }, + "libraryFilterDate": "Date Added", + "@libraryFilterDate": { + "description": "Filter section - date range" + }, + "libraryFilterDateToday": "Today", + "@libraryFilterDateToday": { + "description": "Filter option - today only" + }, + "libraryFilterDateWeek": "This Week", + "@libraryFilterDateWeek": { + "description": "Filter option - this week" + }, + "libraryFilterDateMonth": "This Month", + "@libraryFilterDateMonth": { + "description": "Filter option - this month" + }, + "libraryFilterDateYear": "This Year", + "@libraryFilterDateYear": { + "description": "Filter option - this year" + }, + "libraryFilterSort": "Sort", + "@libraryFilterSort": { + "description": "Filter section - sort order" + }, + "libraryFilterSortLatest": "Latest", + "@libraryFilterSortLatest": { + "description": "Sort option - newest first" + }, + "libraryFilterSortOldest": "Oldest", + "@libraryFilterSortOldest": { + "description": "Sort option - oldest first" + }, + "libraryFilterActive": "{count} filter(s) active", + "@libraryFilterActive": { + "description": "Badge showing number of active filters", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "timeJustNow": "Just now", + "@timeJustNow": { + "description": "Relative time - less than a minute ago" + }, + "timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}", + "@timeMinutesAgo": { + "description": "Relative time - minutes ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}", + "@timeHoursAgo": { + "description": "Relative time - hours ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "storageSwitchTitle": "Switch Storage Mode", + "@storageSwitchTitle": { + "description": "Dialog title when switching storage mode" + }, + "storageSwitchToSafTitle": "Switch to SAF Storage?", + "@storageSwitchToSafTitle": { + "description": "Dialog title when switching to SAF" + }, + "storageSwitchToAppTitle": "Switch to App Storage?", + "@storageSwitchToAppTitle": { + "description": "Dialog title when switching to app storage" + }, + "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", + "@storageSwitchToSafMessage": { + "description": "Explanation when switching to SAF" + }, + "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", + "@storageSwitchToAppMessage": { + "description": "Explanation when switching to app storage" + }, + "storageSwitchExistingDownloads": "Existing Downloads", + "@storageSwitchExistingDownloads": { + "description": "Section header for existing downloads info" + }, + "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", + "@storageSwitchExistingDownloadsInfo": { + "description": "Info about existing downloads count", + "placeholders": { + "count": { + "type": "int" + }, + "mode": { + "type": "String" + } + } + }, + "storageSwitchNewDownloads": "New Downloads", + "@storageSwitchNewDownloads": { + "description": "Section header for new downloads info" + }, + "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", + "@storageSwitchNewDownloadsLocation": { + "description": "Shows where new downloads will go", + "placeholders": { + "location": { + "type": "String" + } + } + }, + "storageSwitchContinue": "Continue", + "@storageSwitchContinue": { + "description": "Button to proceed with storage switch" + }, + "storageSwitchSelectFolder": "Select SAF Folder", + "@storageSwitchSelectFolder": { + "description": "Button to select SAF folder" + }, + "storageAppStorage": "App Storage", + "@storageAppStorage": { + "description": "Label for app storage mode" + }, + "storageSafStorage": "SAF Storage", + "@storageSafStorage": { + "description": "Label for SAF storage mode" + }, + "storageModeBadge": "Storage: {mode}", + "@storageModeBadge": { + "description": "Badge showing storage mode for a track", + "placeholders": { + "mode": { + "type": "String" + } + } + }, + "storageStatsTitle": "Storage Statistics", + "@storageStatsTitle": { + "description": "Section title for storage stats" + }, + "storageStatsAppCount": "{count} tracks in App Storage", + "@storageStatsAppCount": { + "description": "Count of tracks in app storage", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "storageStatsSafCount": "{count} tracks in SAF Storage", + "@storageStatsSafCount": { + "description": "Count of tracks in SAF storage", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "storageModeInfo": "Your files are stored in multiple locations", + "@storageModeInfo": { + "description": "Info when user has files in both storage modes" + }, + "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", + "@tutorialWelcomeTitle": { + "description": "Tutorial welcome page title" + }, + "tutorialWelcomeDesc": "Let's learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.", + "@tutorialWelcomeDesc": { + "description": "Tutorial welcome page description" + }, + "tutorialWelcomeTip1": "Download music from Spotify, Deezer, or paste any supported URL", + "@tutorialWelcomeTip1": { + "description": "Tutorial welcome tip 1" + }, + "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "@tutorialWelcomeTip2": { + "description": "Tutorial welcome tip 2" + }, + "tutorialWelcomeTip3": "Automatic metadata, cover art, and lyrics embedding", + "@tutorialWelcomeTip3": { + "description": "Tutorial welcome tip 3" + }, + "tutorialSearchTitle": "Finding Music", + "@tutorialSearchTitle": { + "description": "Tutorial search page title" + }, + "tutorialSearchDesc": "There are two easy ways to find music you want to download.", + "@tutorialSearchDesc": { + "description": "Tutorial search page description" + }, + "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", + "@tutorialSearchTip1": { + "description": "Tutorial search tip 1" + }, + "tutorialSearchTip2": "Or type the song name, artist, or album to search", + "@tutorialSearchTip2": { + "description": "Tutorial search tip 2" + }, + "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", + "@tutorialSearchTip3": { + "description": "Tutorial search tip 3" + }, + "tutorialDownloadTitle": "Downloading Music", + "@tutorialDownloadTitle": { + "description": "Tutorial download page title" + }, + "tutorialDownloadDesc": "Downloading music is simple and fast. Here's how it works.", + "@tutorialDownloadDesc": { + "description": "Tutorial download page description" + }, + "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", + "@tutorialDownloadTip1": { + "description": "Tutorial download tip 1" + }, + "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", + "@tutorialDownloadTip2": { + "description": "Tutorial download tip 2" + }, + "tutorialDownloadTip3": "Download entire albums or playlists with one tap", + "@tutorialDownloadTip3": { + "description": "Tutorial download tip 3" + }, + "tutorialLibraryTitle": "Your Library", + "@tutorialLibraryTitle": { + "description": "Tutorial library page title" + }, + "tutorialLibraryDesc": "All your downloaded music is organized in the Library tab.", + "@tutorialLibraryDesc": { + "description": "Tutorial library page description" + }, + "tutorialLibraryTip1": "View download progress and queue in the Library tab", + "@tutorialLibraryTip1": { + "description": "Tutorial library tip 1" + }, + "tutorialLibraryTip2": "Tap any track to play it with your music player", + "@tutorialLibraryTip2": { + "description": "Tutorial library tip 2" + }, + "tutorialLibraryTip3": "Switch between list and grid view for better browsing", + "@tutorialLibraryTip3": { + "description": "Tutorial library tip 3" + }, + "tutorialExtensionsTitle": "Extensions", + "@tutorialExtensionsTitle": { + "description": "Tutorial extensions page title" + }, + "tutorialExtensionsDesc": "Extend the app's capabilities with community extensions.", + "@tutorialExtensionsDesc": { + "description": "Tutorial extensions page description" + }, + "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "@tutorialExtensionsTip1": { + "description": "Tutorial extensions tip 1" + }, + "tutorialExtensionsTip2": "Add new download providers or search sources", + "@tutorialExtensionsTip2": { + "description": "Tutorial extensions tip 2" + }, + "tutorialExtensionsTip3": "Get lyrics, enhanced metadata, and more features", + "@tutorialExtensionsTip3": { + "description": "Tutorial extensions tip 3" + }, + "tutorialSettingsTitle": "Customize Your Experience", + "@tutorialSettingsTitle": { + "description": "Tutorial settings page title" + }, + "tutorialSettingsDesc": "Personalize the app in Settings to match your preferences.", + "@tutorialSettingsDesc": { + "description": "Tutorial settings page description" + }, + "tutorialSettingsTip1": "Change download location and folder organization", + "@tutorialSettingsTip1": { + "description": "Tutorial settings tip 1" + }, + "tutorialSettingsTip2": "Set default audio quality and format preferences", + "@tutorialSettingsTip2": { + "description": "Tutorial settings tip 2" + }, + "tutorialSettingsTip3": "Customize app theme and appearance", + "@tutorialSettingsTip3": { + "description": "Tutorial settings tip 3" + }, + "tutorialReadyMessage": "You're all set! Start downloading your favorite music now.", + "@tutorialReadyMessage": { + "description": "Tutorial completion message" + }, + "tutorialExample": "EXAMPLE", + "@tutorialExample": { + "description": "Example label in tutorial" + }, + "libraryForceFullScan": "Force Full Scan", + "@libraryForceFullScan": { + "description": "Button to force a complete rescan of library" + }, + "libraryForceFullScanSubtitle": "Rescan all files, ignoring cache", + "@libraryForceFullScanSubtitle": { + "description": "Subtitle for force full scan button" + }, + "cleanupOrphanedDownloads": "Cleanup Orphaned Downloads", + "@cleanupOrphanedDownloads": { + "description": "Button to remove history entries for deleted files" + }, + "cleanupOrphanedDownloadsSubtitle": "Remove history entries for files that no longer exist", + "@cleanupOrphanedDownloadsSubtitle": { + "description": "Subtitle for orphaned cleanup button" + }, + "cleanupOrphanedDownloadsResult": "Removed {count} orphaned entries from history", + "@cleanupOrphanedDownloadsResult": { + "description": "Snackbar after orphan cleanup", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cleanupOrphanedDownloadsNone": "No orphaned entries found", + "@cleanupOrphanedDownloadsNone": { + "description": "Snackbar when no orphans found" + }, + "cacheTitle": "Storage & Cache", + "@cacheTitle": { + "description": "Cache management page title" + }, + "cacheSummaryTitle": "Cache overview", + "@cacheSummaryTitle": { + "description": "Heading for cache summary card" + }, + "cacheSummarySubtitle": "Clearing cache will not remove downloaded music files.", + "@cacheSummarySubtitle": { + "description": "Helper text for cache summary card" + }, + "cacheEstimatedTotal": "Estimated cache usage: {size}", + "@cacheEstimatedTotal": { + "description": "Total cache size shown in summary", + "placeholders": { + "size": { + "type": "String" + } + } + }, + "cacheSectionStorage": "Cached Data", + "@cacheSectionStorage": { + "description": "Section header for cache entries" + }, + "cacheSectionMaintenance": "Maintenance", + "@cacheSectionMaintenance": { + "description": "Section header for cleanup actions" + }, + "cacheAppDirectory": "App cache directory", + "@cacheAppDirectory": { + "description": "Cache item title for app cache directory" + }, + "cacheAppDirectoryDesc": "HTTP responses, WebView data, and other temporary app data.", + "@cacheAppDirectoryDesc": { + "description": "Description of what app cache directory contains" + }, + "cacheTempDirectory": "Temporary directory", + "@cacheTempDirectory": { + "description": "Cache item title for temporary files directory" + }, + "cacheTempDirectoryDesc": "Temporary files from downloads and audio conversion.", + "@cacheTempDirectoryDesc": { + "description": "Description of what temporary directory contains" + }, + "cacheCoverImage": "Cover image cache", + "@cacheCoverImage": { + "description": "Cache item title for persistent cover images" + }, + "cacheCoverImageDesc": "Downloaded album and track cover art. Will re-download when viewed.", + "@cacheCoverImageDesc": { + "description": "Description of what cover image cache contains" + }, + "cacheLibraryCover": "Library cover cache", + "@cacheLibraryCover": { + "description": "Cache item title for local library cover art images" + }, + "cacheLibraryCoverDesc": "Cover art extracted from local music files. Will re-extract on next scan.", + "@cacheLibraryCoverDesc": { + "description": "Description of what library cover cache contains" + }, + "cacheExploreFeed": "Explore feed cache", + "@cacheExploreFeed": { + "description": "Cache item title for explore home feed cache" + }, + "cacheExploreFeedDesc": "Explore tab content (new releases, trending). Will refresh on next visit.", + "@cacheExploreFeedDesc": { + "description": "Description of what explore feed cache contains" + }, + "cacheTrackLookup": "Track lookup cache", + "@cacheTrackLookup": { + "description": "Cache item title for track ID lookup cache" + }, + "cacheTrackLookupDesc": "Spotify/Deezer track ID lookups. Clearing may slow next few searches.", + "@cacheTrackLookupDesc": { + "description": "Description of what track lookup cache contains" + }, + "cacheCleanupUnusedDesc": "Remove orphaned download history and library entries for missing files.", + "@cacheCleanupUnusedDesc": { + "description": "Description of what cleanup unused data does" + }, + "cacheNoData": "No cached data", + "@cacheNoData": { + "description": "Label when cache category has no data" + }, + "cacheSizeWithFiles": "{size} in {count} files", + "@cacheSizeWithFiles": { + "description": "Cache size and file count", + "placeholders": { + "size": { + "type": "String" + }, + "count": { + "type": "int" + } + } + }, + "cacheSizeOnly": "{size}", + "@cacheSizeOnly": { + "description": "Cache size only", + "placeholders": { + "size": { + "type": "String" + } + } + }, + "cacheEntries": "{count} entries", + "@cacheEntries": { + "description": "Track cache entry count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cacheClearSuccess": "Cleared: {target}", + "@cacheClearSuccess": { + "description": "Snackbar after clearing selected cache", + "placeholders": { + "target": { + "type": "String" + } + } + }, + "cacheClearConfirmTitle": "Clear cache?", + "@cacheClearConfirmTitle": { + "description": "Dialog title before clearing one cache category" + }, + "cacheClearConfirmMessage": "This will clear cached data for {target}. Downloaded music files will not be deleted.", + "@cacheClearConfirmMessage": { + "description": "Dialog message before clearing selected cache", + "placeholders": { + "target": { + "type": "String" + } + } + }, + "cacheClearAllConfirmTitle": "Clear all cache?", + "@cacheClearAllConfirmTitle": { + "description": "Dialog title before clearing all caches" + }, + "cacheClearAllConfirmMessage": "This will clear all cache categories on this page. Downloaded music files will not be deleted.", + "@cacheClearAllConfirmMessage": { + "description": "Dialog message before clearing all caches" + }, + "cacheClearAll": "Clear all cache", + "@cacheClearAll": { + "description": "Button label to clear all caches" + }, + "cacheCleanupUnused": "Cleanup unused data", + "@cacheCleanupUnused": { + "description": "Action title for cleaning unused entries" + }, + "cacheCleanupUnusedSubtitle": "Remove orphaned download history and missing library entries", + "@cacheCleanupUnusedSubtitle": { + "description": "Subtitle for cleanup unused data action" + }, + "cacheCleanupResult": "Cleanup completed: {downloadCount} orphaned downloads, {libraryCount} missing library entries", + "@cacheCleanupResult": { + "description": "Snackbar after unused data cleanup", + "placeholders": { + "downloadCount": { + "type": "int" + }, + "libraryCount": { + "type": "int" + } + } + }, + "cacheRefreshStats": "Refresh stats", + "@cacheRefreshStats": { + "description": "Button label to refresh cache statistics" + }, + "trackSaveCoverArt": "Save Cover Art", + "@trackSaveCoverArt": { + "description": "Menu action - save album cover art as file" + }, + "trackSaveCoverArtSubtitle": "Save album art as .jpg file", + "@trackSaveCoverArtSubtitle": { + "description": "Subtitle for save cover art action" + }, + "trackSaveLyrics": "Save Lyrics (.lrc)", + "@trackSaveLyrics": { + "description": "Menu action - save lyrics as .lrc file" + }, + "trackSaveLyricsSubtitle": "Fetch and save lyrics as .lrc file", + "@trackSaveLyricsSubtitle": { + "description": "Subtitle for save lyrics action" + }, + "trackSaveLyricsProgress": "Saving lyrics...", + "@trackSaveLyricsProgress": { + "description": "Snackbar while saving lyrics to file" + }, + "trackReEnrich": "Re-enrich Metadata", + "@trackReEnrich": { + "description": "Menu action - re-embed metadata into audio file" + }, + "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", + "@trackReEnrichSubtitle": { + "description": "Subtitle for re-enrich metadata action" + }, + "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", + "@trackReEnrichOnlineSubtitle": { + "description": "Subtitle for re-enrich metadata action for local items" + }, + "trackEditMetadata": "Edit Metadata", + "@trackEditMetadata": { + "description": "Menu action - edit embedded metadata" + }, + "trackCoverSaved": "Cover art saved to {fileName}", + "@trackCoverSaved": { + "description": "Snackbar after cover art saved", + "placeholders": { + "fileName": { + "type": "String" + } + } + }, + "trackCoverNoSource": "No cover art source available", + "@trackCoverNoSource": { + "description": "Snackbar when no cover art URL or embedded cover" + }, + "trackLyricsSaved": "Lyrics saved to {fileName}", + "@trackLyricsSaved": { + "description": "Snackbar after lyrics saved", + "placeholders": { + "fileName": { + "type": "String" + } + } + }, + "trackReEnrichProgress": "Re-enriching metadata...", + "@trackReEnrichProgress": { + "description": "Snackbar while re-enriching metadata" + }, + "trackReEnrichSearching": "Searching metadata online...", + "@trackReEnrichSearching": { + "description": "Snackbar while searching metadata from internet for local items" + }, + "trackReEnrichSuccess": "Metadata re-enriched successfully", + "@trackReEnrichSuccess": { + "description": "Snackbar after successful re-enrichment" + }, + "trackReEnrichFfmpegFailed": "FFmpeg metadata embed failed", + "@trackReEnrichFfmpegFailed": { + "description": "Snackbar when FFmpeg embed fails for MP3/Opus" + }, + "trackSaveFailed": "Failed: {error}", + "@trackSaveFailed": { + "description": "Snackbar when save operation fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "trackConvertFormat": "Convert Format", + "@trackConvertFormat": { + "description": "Menu item - convert audio format" + }, + "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "@trackConvertFormatSubtitle": { + "description": "Subtitle for convert format menu item" + }, + "trackConvertTitle": "Convert Audio", + "@trackConvertTitle": { + "description": "Title of convert bottom sheet" + }, + "trackConvertTargetFormat": "Target Format", + "@trackConvertTargetFormat": { + "description": "Label for format selection" + }, + "trackConvertBitrate": "Bitrate", + "@trackConvertBitrate": { + "description": "Label for bitrate selection" + }, + "trackConvertConfirmTitle": "Confirm Conversion", + "@trackConvertConfirmTitle": { + "description": "Confirmation dialog title" + }, + "trackConvertConfirmMessage": "Convert from {sourceFormat} to {targetFormat} at {bitrate}?\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessage": { + "description": "Confirmation dialog message", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "trackConvertConverting": "Converting audio...", + "@trackConvertConverting": { + "description": "Snackbar while converting" + }, + "trackConvertSuccess": "Converted to {format} successfully", + "@trackConvertSuccess": { + "description": "Snackbar after successful conversion", + "placeholders": { + "format": { + "type": "String" + } + } + }, + "trackConvertFailed": "Conversion failed", + "@trackConvertFailed": { + "description": "Snackbar when conversion fails" + } +} \ No newline at end of file From 898e28c40c05671b6431e965232e9e1802e6d13f Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:40:51 +0700 Subject: [PATCH 018/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 178 ++++++++++++++++++++-------------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 6917c56c..e18e02ea 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -13,7 +13,7 @@ "@navHome": { "description": "Bottom navigation - Home tab" }, - "navLibrary": "Archiv", + "navLibrary": "Bibliothek", "@navLibrary": { "description": "Bottom navigation - Library tab" }, @@ -1161,7 +1161,7 @@ } } }, - "dialogImportPlaylistTitle": "Wiedergabeliste importieren", + "dialogImportPlaylistTitle": "Playlist importieren", "@dialogImportPlaylistTitle": { "description": "Dialog title - import CSV playlist" }, @@ -1536,7 +1536,7 @@ "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" }, - "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "filenameShowAdvancedTagsDescription": "Formatierte Tags für Track-Padding und Datumsmuster aktivieren", "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, @@ -2172,7 +2172,7 @@ "@dateToday": { "description": "Relative date - today" }, - "dateYesterday": "Yesterday", + "dateYesterday": "Gestern", "@dateYesterday": { "description": "Relative date - yesterday" }, @@ -2203,7 +2203,7 @@ } } }, - "concurrentSequential": "Sequential", + "concurrentSequential": "Sequenziell", "@concurrentSequential": { "description": "Download mode - one at a time" }, @@ -2223,11 +2223,11 @@ "@storeFilterAll": { "description": "Store filter - all extensions" }, - "storeFilterMetadata": "Metadata", + "storeFilterMetadata": "Metadaten", "@storeFilterMetadata": { "description": "Store filter - metadata providers" }, - "storeFilterDownload": "Download", + "storeFilterDownload": "Herunterladen", "@storeFilterDownload": { "description": "Store filter - download providers" }, @@ -2243,7 +2243,7 @@ "@storeFilterIntegration": { "description": "Store filter - integrations" }, - "storeClearFilters": "Clear filters", + "storeClearFilters": "Filter entfernen", "@storeClearFilters": { "description": "Button to clear all filters" }, @@ -2279,7 +2279,7 @@ "@extensionError": { "description": "Extension detail - error message" }, - "extensionCapabilities": "Capabilities", + "extensionCapabilities": "Eigenschaften", "@extensionCapabilities": { "description": "Section header - extension features" }, @@ -2307,11 +2307,11 @@ "@extensionPostProcessingHooks": { "description": "Capability - post-processing" }, - "extensionPermissions": "Permissions", + "extensionPermissions": "Berechtigungen", "@extensionPermissions": { "description": "Section header - required permissions" }, - "extensionSettings": "Settings", + "extensionSettings": "Einstellungen", "@extensionSettings": { "description": "Section header - extension settings" }, @@ -2319,7 +2319,7 @@ "@extensionRemoveButton": { "description": "Button to uninstall extension" }, - "extensionUpdated": "Updated", + "extensionUpdated": "Aktualisiert", "@extensionUpdated": { "description": "Extension detail - last update" }, @@ -2374,7 +2374,7 @@ "@extensionsNoExtensions": { "description": "Empty state - no extensions" }, - "extensionsNoExtensionsSubtitle": "Install .spotiflac-ext files to add new providers", + "extensionsNoExtensionsSubtitle": "Installiere .spotiflac-ext Dateien um neue Anbieter hinzuzufügen", "@extensionsNoExtensionsSubtitle": { "description": "Empty state subtitle" }, @@ -2422,11 +2422,11 @@ "@extensionsNoCustomSearch": { "description": "Empty state - no search providers" }, - "extensionsSearchProviderDescription": "Choose which service to use for searching tracks", + "extensionsSearchProviderDescription": "Wähle den Dienst für die Suche von Titel", "@extensionsSearchProviderDescription": { "description": "Search provider setting description" }, - "extensionsCustomSearch": "Custom search", + "extensionsCustomSearch": "Benutzerdefinierte Suche", "@extensionsCustomSearch": { "description": "Label for custom search provider" }, @@ -2434,7 +2434,7 @@ "@extensionsErrorLoading": { "description": "Error message when extension fails to load" }, - "qualityFlacLossless": "FLAC Lossless", + "qualityFlacLossless": "FLAC Verlustfrei", "@qualityFlacLossless": { "description": "Quality option - CD quality FLAC" }, @@ -2482,7 +2482,7 @@ "@enableLossyOptionSubtitleOff": { "description": "Subtitle when lossy is disabled" }, - "lossyFormat": "Lossy Format", + "lossyFormat": "Verlustbehaftetes Format", "@lossyFormat": { "description": "Setting - choose lossy format" }, @@ -2546,19 +2546,19 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "Save Format", + "downloadSaveFormat": "Speicherformat", "@downloadSaveFormat": { "description": "Setting - output file format" }, - "downloadSelectService": "Select Service", + "downloadSelectService": "Provider auswählen", "@downloadSelectService": { "description": "Dialog title - choose download service" }, - "downloadSelectQuality": "Select Quality", + "downloadSelectQuality": "Qualität wählen", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" }, - "downloadFrom": "Download From", + "downloadFrom": "Herunterladen von", "@downloadFrom": { "description": "Label - download source" }, @@ -2566,7 +2566,7 @@ "@downloadDefaultQualityLabel": { "description": "Label - default quality setting" }, - "downloadBestAvailable": "Best available", + "downloadBestAvailable": "Beste Qualität", "@downloadBestAvailable": { "description": "Quality option - highest available" }, @@ -2594,7 +2594,7 @@ "@folderAlbumSubtitle": { "description": "Folder structure example" }, - "folderArtistAlbum": "Artist/Album", + "folderArtistAlbum": "Künstler/Album", "@folderArtistAlbum": { "description": "Folder option - nested" }, @@ -2622,7 +2622,7 @@ "@serviceSpotify": { "description": "Service name - DO NOT TRANSLATE" }, - "appearanceAmoledDark": "AMOLED Dark", + "appearanceAmoledDark": "AMOLED Schwarz", "@appearanceAmoledDark": { "description": "Theme option - pure black" }, @@ -2634,15 +2634,15 @@ "@appearanceChooseAccentColor": { "description": "Color picker dialog title" }, - "appearanceChooseTheme": "Theme Mode", + "appearanceChooseTheme": "Designmodus", "@appearanceChooseTheme": { "description": "Theme picker dialog title" }, - "queueTitle": "Download Queue", + "queueTitle": "Download-Warteliste", "@queueTitle": { "description": "Queue screen title" }, - "queueClearAll": "Clear All", + "queueClearAll": "Alles löschen", "@queueClearAll": { "description": "Button - clear all queue items" }, @@ -2658,7 +2658,7 @@ "@queueExportFailedSuccess": { "description": "Success message after exporting failed downloads" }, - "queueExportFailedClear": "Clear Failed", + "queueExportFailedClear": "Löschung fehlgeschlagen", "@queueExportFailedClear": { "description": "Action to clear failed downloads after export" }, @@ -2670,7 +2670,7 @@ "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" }, - "settingsAutoExportFailedSubtitle": "Save failed downloads to TXT file automatically", + "settingsAutoExportFailedSubtitle": "Fehlgeschlagene Downloads automatisch in eine TXT-Datei speichern", "@settingsAutoExportFailedSubtitle": { "description": "Subtitle for auto-export setting" }, @@ -2710,7 +2710,7 @@ "@queueTrackLabel": { "description": "Label in error dialog" }, - "queueArtistLabel": "Artist:", + "queueArtistLabel": "Künstler:", "@queueArtistLabel": { "description": "Label in error dialog" }, @@ -2718,11 +2718,11 @@ "@queueErrorLabel": { "description": "Label in error dialog" }, - "queueUnknownError": "Unknown error", + "queueUnknownError": "Unbekannter Fehler", "@queueUnknownError": { "description": "Fallback error message" }, - "albumFolderArtistAlbum": "Artist / Album", + "albumFolderArtistAlbum": "Künstler/Album", "@albumFolderArtistAlbum": { "description": "Album folder option" }, @@ -2738,7 +2738,7 @@ "@albumFolderArtistYearAlbumSubtitle": { "description": "Folder structure example" }, - "albumFolderAlbumOnly": "Album Only", + "albumFolderAlbumOnly": "Nur Alben", "@albumFolderAlbumOnly": { "description": "Album folder option" }, @@ -2805,7 +2805,7 @@ "@downloadedAlbumTapToSelect": { "description": "Selection hint" }, - "downloadedAlbumDeleteCount": "Delete {count} {count, plural, =1{track} other{tracks}}", + "downloadedAlbumDeleteCount": "Lösche {count} {count, plural, one {}=1{Titel}other{Titel}}", "@downloadedAlbumDeleteCount": { "description": "Delete button text with count", "placeholders": { @@ -2880,7 +2880,7 @@ "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyDownloadAll": "Download All", + "discographyDownloadAll": "Alle Herunterladen", "@discographyDownloadAll": { "description": "Option - download entire discography" }, @@ -2896,7 +2896,7 @@ } } }, - "discographyAlbumsOnly": "Albums Only", + "discographyAlbumsOnly": "Nur Alben", "@discographyAlbumsOnly": { "description": "Option - download only albums" }, @@ -2994,7 +2994,7 @@ "@discographyFailedToFetch": { "description": "Error - some albums failed to load" }, - "sectionStorageAccess": "Storage Access", + "sectionStorageAccess": "Speicherzugriff", "@sectionStorageAccess": { "description": "Section header for storage access settings" }, @@ -3022,7 +3022,7 @@ "@allFilesAccessDisabledMessage": { "description": "Snackbar message when user disables all files access" }, - "settingsLocalLibrary": "Local Library", + "settingsLocalLibrary": "Lokale Bibliothek", "@settingsLocalLibrary": { "description": "Settings menu item - local library" }, @@ -3038,7 +3038,7 @@ "@settingsCacheSubtitle": { "description": "Subtitle for cache management menu" }, - "libraryTitle": "Local Library", + "libraryTitle": "Lokale Bibliothek", "@libraryTitle": { "description": "Library settings page title" }, @@ -3046,7 +3046,7 @@ "@libraryStatus": { "description": "Section header for library status" }, - "libraryScanSettings": "Scan Settings", + "libraryScanSettings": "Scan Einstellungen", "@libraryScanSettings": { "description": "Section header for scan settings" }, @@ -3074,11 +3074,11 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, - "libraryActions": "Actions", + "libraryActions": "Aktionen", "@libraryActions": { "description": "Section header for library actions" }, - "libraryScan": "Scan Library", + "libraryScan": "Bibliothek scannen", "@libraryScan": { "description": "Button to start library scan" }, @@ -3098,7 +3098,7 @@ "@libraryCleanupMissingFilesSubtitle": { "description": "Subtitle for cleanup button" }, - "libraryClear": "Clear Library", + "libraryClear": "Bibliothek löschen", "@libraryClear": { "description": "Button to clear all library entries" }, @@ -3106,7 +3106,7 @@ "@libraryClearSubtitle": { "description": "Subtitle for clear button" }, - "libraryClearConfirmTitle": "Clear Library", + "libraryClearConfirmTitle": "Bibliothek löschen", "@libraryClearConfirmTitle": { "description": "Dialog title for clear confirmation" }, @@ -3144,7 +3144,7 @@ "@libraryLastScannedNever": { "description": "Shown when library has never been scanned" }, - "libraryScanning": "Scanning...", + "libraryScanning": "Scannen...", "@libraryScanning": { "description": "Status during scan" }, @@ -3160,7 +3160,7 @@ } } }, - "libraryInLibrary": "In Library", + "libraryInLibrary": "In Bibliothek", "@libraryInLibrary": { "description": "Badge shown on tracks that exist in local library" }, @@ -3189,7 +3189,7 @@ "@libraryFolderNotExist": { "description": "Error when folder doesn't exist" }, - "librarySourceDownloaded": "Downloaded", + "librarySourceDownloaded": "Heruntergeladen", "@librarySourceDownloaded": { "description": "Badge for tracks downloaded via SpotiFLAC" }, @@ -3201,7 +3201,7 @@ "@libraryFilterAll": { "description": "Filter chip - show all library items" }, - "libraryFilterDownloaded": "Downloaded", + "libraryFilterDownloaded": "Heruntergeladen", "@libraryFilterDownloaded": { "description": "Filter chip - show only downloaded items" }, @@ -3209,7 +3209,7 @@ "@libraryFilterLocal": { "description": "Filter chip - show only local library items" }, - "libraryFilterTitle": "Filters", + "libraryFilterTitle": "Filter", "@libraryFilterTitle": { "description": "Filter bottom sheet title" }, @@ -3225,7 +3225,7 @@ "@libraryFilterSource": { "description": "Filter section - source type" }, - "libraryFilterQuality": "Quality", + "libraryFilterQuality": "Qualität", "@libraryFilterQuality": { "description": "Filter section - audio quality" }, @@ -3245,7 +3245,7 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Date Added", + "libraryFilterDate": "Datum Hinzugefügt", "@libraryFilterDate": { "description": "Filter section - date range" }, @@ -3253,15 +3253,15 @@ "@libraryFilterDateToday": { "description": "Filter option - today only" }, - "libraryFilterDateWeek": "This Week", + "libraryFilterDateWeek": "Diese Woche", "@libraryFilterDateWeek": { "description": "Filter option - this week" }, - "libraryFilterDateMonth": "This Month", + "libraryFilterDateMonth": "In diesem Monat", "@libraryFilterDateMonth": { "description": "Filter option - this month" }, - "libraryFilterDateYear": "This Year", + "libraryFilterDateYear": "Dieses Jahr", "@libraryFilterDateYear": { "description": "Filter option - this year" }, @@ -3286,7 +3286,7 @@ } } }, - "timeJustNow": "Just now", + "timeJustNow": "Gerade eben", "@timeJustNow": { "description": "Relative time - less than a minute ago" }, @@ -3299,7 +3299,7 @@ } } }, - "timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}", + "timeHoursAgo": "{count, plural, one {}=1{vor 1 Stunde} other{vor {count} Stunden}}", "@timeHoursAgo": { "description": "Relative time - hours ago", "placeholders": { @@ -3344,7 +3344,7 @@ } } }, - "storageSwitchNewDownloads": "New Downloads", + "storageSwitchNewDownloads": "Neue Downloads", "@storageSwitchNewDownloads": { "description": "Section header for new downloads info" }, @@ -3357,7 +3357,7 @@ } } }, - "storageSwitchContinue": "Continue", + "storageSwitchContinue": "Fortfahren", "@storageSwitchContinue": { "description": "Button to proceed with storage switch" }, @@ -3365,11 +3365,11 @@ "@storageSwitchSelectFolder": { "description": "Button to select SAF folder" }, - "storageAppStorage": "App Storage", + "storageAppStorage": "App-Speicher", "@storageAppStorage": { "description": "Label for app storage mode" }, - "storageSafStorage": "SAF Storage", + "storageSafStorage": "App-Speicher", "@storageSafStorage": { "description": "Label for SAF storage mode" }, @@ -3420,15 +3420,15 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "Hole dir FLAC Audio von Tidal, Qobuz oder Amazon Musik", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, - "tutorialWelcomeTip3": "Automatic metadata, cover art, and lyrics embedding", + "tutorialWelcomeTip3": "Automatische Metadaten, Cover und Lyrics einbetten", "@tutorialWelcomeTip3": { "description": "Tutorial welcome tip 3" }, - "tutorialSearchTitle": "Finding Music", + "tutorialSearchTitle": "Suche Musik", "@tutorialSearchTitle": { "description": "Tutorial search page title" }, @@ -3436,15 +3436,15 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", + "tutorialSearchTip1": "Füge eine Spotify-oder Deezer-URL direkt in das Suchfeld ein", "@tutorialSearchTip1": { "description": "Tutorial search tip 1" }, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", + "tutorialSearchTip2": "Oder gebe den Song-Namen, Interpret oder Album ein um nach ihm zu suchen", "@tutorialSearchTip2": { "description": "Tutorial search tip 2" }, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", + "tutorialSearchTip3": "Unterstützt Tracks, Alben, Wiedergabelisten und Künstlerseiten", "@tutorialSearchTip3": { "description": "Tutorial search tip 3" }, @@ -3452,7 +3452,7 @@ "@tutorialDownloadTitle": { "description": "Tutorial download page title" }, - "tutorialDownloadDesc": "Downloading music is simple and fast. Here's how it works.", + "tutorialDownloadDesc": "Das Herunterladen von Musik ist einfach und schnell. So funktioniert es.", "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, @@ -3460,43 +3460,43 @@ "@tutorialDownloadTip1": { "description": "Tutorial download tip 1" }, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", + "tutorialDownloadTip2": "Wählen Sie Ihre bevorzugte Qualität (FLAC, Hi-Res oder MP3)", "@tutorialDownloadTip2": { "description": "Tutorial download tip 2" }, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", + "tutorialDownloadTip3": "Lade ganze Alben oder Playlists mit einem Klick herunter", "@tutorialDownloadTip3": { "description": "Tutorial download tip 3" }, - "tutorialLibraryTitle": "Your Library", + "tutorialLibraryTitle": "Deine Bibliothek", "@tutorialLibraryTitle": { "description": "Tutorial library page title" }, - "tutorialLibraryDesc": "All your downloaded music is organized in the Library tab.", + "tutorialLibraryDesc": "Die gesamte heruntergeladene Musik ist in der Bibliothek organisiert.", "@tutorialLibraryDesc": { "description": "Tutorial library page description" }, - "tutorialLibraryTip1": "View download progress and queue in the Library tab", + "tutorialLibraryTip1": "Downloadfortschritt und Warteschlange im Bibliotheks-Tab anzeigen", "@tutorialLibraryTip1": { "description": "Tutorial library tip 1" }, - "tutorialLibraryTip2": "Tap any track to play it with your music player", + "tutorialLibraryTip2": "Tippe auf einen Titel, um ihn mit deinem Musikplayer abzuspielen", "@tutorialLibraryTip2": { "description": "Tutorial library tip 2" }, - "tutorialLibraryTip3": "Switch between list and grid view for better browsing", + "tutorialLibraryTip3": "Wechsle zwischen Listen- und Gitteransicht für ein besseres Surfen", "@tutorialLibraryTip3": { "description": "Tutorial library tip 3" }, - "tutorialExtensionsTitle": "Extensions", + "tutorialExtensionsTitle": "Erweiterungen", "@tutorialExtensionsTitle": { "description": "Tutorial extensions page title" }, - "tutorialExtensionsDesc": "Extend the app's capabilities with community extensions.", + "tutorialExtensionsDesc": "Erweitere die Fähigkeiten der App mit Community-Erweiterungen.", "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "tutorialExtensionsTip1": "Im Store Tab findest du nützliche Erweiterungen", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -3520,7 +3520,7 @@ "@tutorialSettingsTip1": { "description": "Tutorial settings tip 1" }, - "tutorialSettingsTip2": "Set default audio quality and format preferences", + "tutorialSettingsTip2": "Standard Audioqualität und Formateinstellungen festlegen", "@tutorialSettingsTip2": { "description": "Tutorial settings tip 2" }, @@ -3532,7 +3532,7 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "EXAMPLE", + "tutorialExample": "BEISPIEL", "@tutorialExample": { "description": "Example label in tutorial" }, @@ -3548,7 +3548,7 @@ "@cleanupOrphanedDownloads": { "description": "Button to remove history entries for deleted files" }, - "cleanupOrphanedDownloadsSubtitle": "Remove history entries for files that no longer exist", + "cleanupOrphanedDownloadsSubtitle": "Verlaufseinträge für Dateien löschen, die nicht mehr existieren", "@cleanupOrphanedDownloadsSubtitle": { "description": "Subtitle for orphaned cleanup button" }, @@ -3573,7 +3573,7 @@ "@cacheSummaryTitle": { "description": "Heading for cache summary card" }, - "cacheSummarySubtitle": "Clearing cache will not remove downloaded music files.", + "cacheSummarySubtitle": "Das Leeren des Caches entfernt nicht heruntergeladene Musikdateien.", "@cacheSummarySubtitle": { "description": "Helper text for cache summary card" }, @@ -3586,11 +3586,11 @@ } } }, - "cacheSectionStorage": "Cached Data", + "cacheSectionStorage": "Zwischengespeicherte Daten", "@cacheSectionStorage": { "description": "Section header for cache entries" }, - "cacheSectionMaintenance": "Maintenance", + "cacheSectionMaintenance": "Wartung", "@cacheSectionMaintenance": { "description": "Section header for cleanup actions" }, @@ -3606,7 +3606,7 @@ "@cacheTempDirectory": { "description": "Cache item title for temporary files directory" }, - "cacheTempDirectoryDesc": "Temporary files from downloads and audio conversion.", + "cacheTempDirectoryDesc": "Temporäre Dateien von Downloads und Audio-Konvertierung.", "@cacheTempDirectoryDesc": { "description": "Description of what temporary directory contains" }, @@ -3689,7 +3689,7 @@ } } }, - "cacheClearConfirmTitle": "Clear cache?", + "cacheClearConfirmTitle": "Cache leeren?", "@cacheClearConfirmTitle": { "description": "Dialog title before clearing one cache category" }, @@ -3734,7 +3734,7 @@ } } }, - "cacheRefreshStats": "Refresh stats", + "cacheRefreshStats": "Statistik aktualisieren", "@cacheRefreshStats": { "description": "Button label to refresh cache statistics" }, @@ -3770,7 +3770,7 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, - "trackEditMetadata": "Edit Metadata", + "trackEditMetadata": "Metadaten bearbeiten", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" }, @@ -3829,11 +3829,11 @@ "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, - "trackConvertTitle": "Convert Audio", + "trackConvertTitle": "Audio konvertieren", "@trackConvertTitle": { "description": "Title of convert bottom sheet" }, - "trackConvertTargetFormat": "Target Format", + "trackConvertTargetFormat": "Zielformat", "@trackConvertTargetFormat": { "description": "Label for format selection" }, From a6bef63aa7bf4914b724b07b684ccfdf00e27503 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Mon, 16 Feb 2026 17:38:32 +0700 Subject: [PATCH 019/104] New translations app_en.arb (Indonesian) --- lib/l10n/arb/app_id.arb | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index c87ceedb..4c64039c 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -13,7 +13,7 @@ "@navHome": { "description": "Bottom navigation - Home tab" }, - "navLibrary": "Library", + "navLibrary": "Pustaka", "@navLibrary": { "description": "Bottom navigation - Library tab" }, @@ -131,7 +131,7 @@ "@historyNoSinglesSubtitle": { "description": "Empty state subtitle for singles filter" }, - "historySearchHint": "Search history...", + "historySearchHint": "Cari riwayat...", "@historySearchHint": { "description": "Search bar placeholder in history" }, @@ -267,7 +267,7 @@ "@appearanceHistoryViewList": { "description": "List layout option" }, - "appearanceHistoryViewGrid": "Grid", + "appearanceHistoryViewGrid": "Kisi", "@appearanceHistoryViewGrid": { "description": "Grid layout option" }, @@ -430,7 +430,7 @@ "@optionsSpotifyWarning": { "description": "Info about Spotify API requirement" }, - "optionsSpotifyDeprecationWarning": "Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.", + "optionsSpotifyDeprecationWarning": "Pencarian Spotify akan dihentikan pada 3 Maret 2026 karena perubahan API Spotify. Silakan beralih ke Deezer.", "@optionsSpotifyDeprecationWarning": { "description": "Warning about Spotify API deprecation" }, @@ -524,7 +524,7 @@ "@aboutLogoArtist": { "description": "Role description for logo artist" }, - "aboutTranslators": "Translators", + "aboutTranslators": "Penerjemah", "@aboutTranslators": { "description": "Section for translators" }, @@ -560,23 +560,23 @@ "@aboutFeatureRequestSubtitle": { "description": "Subtitle for feature request" }, - "aboutTelegramChannel": "Telegram Channel", + "aboutTelegramChannel": "Saluran Telegram", "@aboutTelegramChannel": { "description": "Link to Telegram channel" }, - "aboutTelegramChannelSubtitle": "Announcements and updates", + "aboutTelegramChannelSubtitle": "Pengumuman dan pembaruan", "@aboutTelegramChannelSubtitle": { "description": "Subtitle for Telegram channel" }, - "aboutTelegramChat": "Telegram Community", + "aboutTelegramChat": "Komunitas Telegram", "@aboutTelegramChat": { "description": "Link to Telegram chat group" }, - "aboutTelegramChatSubtitle": "Chat with other users", + "aboutTelegramChatSubtitle": "Berbincang dengan pengguna lain", "@aboutTelegramChatSubtitle": { "description": "Subtitle for Telegram chat" }, - "aboutSocial": "Social", + "aboutSocial": "Sosial", "@aboutSocial": { "description": "Section for social links" }, @@ -624,7 +624,7 @@ "@aboutSpotiSaver": { "description": "Name of SpotiSaver API service - DO NOT TRANSLATE" }, - "aboutSpotiSaverDesc": "Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!", + "aboutSpotiSaverDesc": "Tidal perangkat streaming FLAC resolusi tinggi. Bagian penting dari teka-teki tanpa kehilangan kualitas!", "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, @@ -653,7 +653,7 @@ "@albumDownloadRemaining": { "description": "Button to download remaining tracks" }, - "playlistTitle": "Playlist", + "playlistTitle": "Daftar putar", "@playlistTitle": { "description": "Playlist screen title" }, @@ -875,7 +875,7 @@ "@setupIosEmptyFolderWarning": { "description": "iOS folder selection warning" }, - "setupIcloudNotSupported": "iCloud Drive is not supported. Please use the app Documents folder.", + "setupIcloudNotSupported": "iCloud Drive tidak didukung. Silakan gunakan folder Dokumen di aplikasi.", "@setupIcloudNotSupported": { "description": "Error when user selects iCloud Drive on iOS" }, @@ -1027,7 +1027,7 @@ "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "OK", + "dialogOk": "OKE", "@dialogOk": { "description": "Dialog button - confirm/acknowledge" }, From d49bab403d6a4c94e586863db78607286e371f2e Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Tue, 17 Feb 2026 17:43:01 +0700 Subject: [PATCH 020/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index e18e02ea..a3cc99ea 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -1165,7 +1165,7 @@ "@dialogImportPlaylistTitle": { "description": "Dialog title - import CSV playlist" }, - "dialogImportPlaylistMessage": "{count} Titel in CSV gefunden. Zur Warteschlange hinzufügen?", + "dialogImportPlaylistMessage": "{count} Titel gefunden hinzufügen?", "csvImportTracks": "{count} Titel aus CSV", "@csvImportTracks": { "description": "Label shown in quality picker for CSV import", @@ -1183,7 +1183,7 @@ } } }, - "snackbarAddedToQueue": "\"{trackName}\" zur Warteschlange hinzugefügt", + "snackbarAddedToQueue": "\"{trackName}\" hinzugefügt", "@snackbarAddedToQueue": { "description": "Snackbar - track added to download queue", "placeholders": { @@ -1192,7 +1192,7 @@ } } }, - "snackbarAddedTracksToQueue": "{count} Titel zur Warteschlange hinzugefügt", + "snackbarAddedTracksToQueue": "{count} Titel hinzugefügt", "@snackbarAddedTracksToQueue": { "description": "Snackbar - multiple tracks added to queue", "placeholders": { @@ -1349,7 +1349,7 @@ } } }, - "statusQueued": "In der Warteschlange", + "statusQueued": "In Warteschlange", "@statusQueued": { "description": "Download status - waiting in queue" }, @@ -2734,7 +2734,7 @@ "@albumFolderArtistYearAlbum": { "description": "Album folder option with year" }, - "albumFolderArtistYearAlbumSubtitle": "Albums/Artist Name/[2005] Album Name/", + "albumFolderArtistYearAlbumSubtitle": "Albums/Künster Name/[2005] Album Name/", "@albumFolderArtistYearAlbumSubtitle": { "description": "Folder structure example" }, @@ -2884,7 +2884,7 @@ "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", + "discographyDownloadAllSubtitle": "{count} Titel von {albumCount} Releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", "placeholders": { @@ -2900,7 +2900,7 @@ "@discographyAlbumsOnly": { "description": "Option - download only albums" }, - "discographyAlbumsOnlySubtitle": "{count} tracks from {albumCount} albums", + "discographyAlbumsOnlySubtitle": "{count} Titel von {albumCount} Albums", "@discographyAlbumsOnlySubtitle": { "description": "Subtitle showing album tracks count", "placeholders": { @@ -2916,7 +2916,7 @@ "@discographySinglesOnly": { "description": "Option - download only singles" }, - "discographySinglesOnlySubtitle": "{count} tracks from {albumCount} singles", + "discographySinglesOnlySubtitle": "{count} Titel von {albumCount} Singles", "@discographySinglesOnlySubtitle": { "description": "Subtitle showing singles tracks count", "placeholders": { @@ -3070,7 +3070,7 @@ "@libraryShowDuplicateIndicator": { "description": "Toggle for duplicate indicator in search" }, - "libraryShowDuplicateIndicatorSubtitle": "Show when searching for existing tracks", + "libraryShowDuplicateIndicatorSubtitle": "Bei der Suche nach vorhandenen Titeln anzeigen", "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, @@ -3476,7 +3476,7 @@ "@tutorialLibraryDesc": { "description": "Tutorial library page description" }, - "tutorialLibraryTip1": "Downloadfortschritt und Warteschlange im Bibliotheks-Tab anzeigen", + "tutorialLibraryTip1": "Fortschritt und Warteschlange im Bibliothek‑Tab anzeigen", "@tutorialLibraryTip1": { "description": "Tutorial library tip 1" }, @@ -3762,7 +3762,7 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", + "trackReEnrichSubtitle": "Metadaten neu einbetten ohne erneuten herunterladen", "@trackReEnrichSubtitle": { "description": "Subtitle for re-enrich metadata action" }, From e83fd6602381d4868284a8eab32fed660651d266 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Tue, 17 Feb 2026 17:43:02 +0700 Subject: [PATCH 021/104] New translations app_en.arb (Russian) --- lib/l10n/arb/app_ru.arb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 9db59bc7..e10cf8c6 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -1532,7 +1532,7 @@ "@filenameHint": { "description": "Default filename format hint" }, - "filenameShowAdvancedTags": "Show advanced tags", + "filenameShowAdvancedTags": "Показать расширенные теги", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" }, @@ -2534,7 +2534,7 @@ "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" }, - "downloadUsePrimaryArtistOnly": "Primary artist only for folders", + "downloadUsePrimaryArtistOnly": "Основной исполнитель только для папок", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" }, @@ -3693,7 +3693,7 @@ "@cacheClearConfirmTitle": { "description": "Dialog title before clearing one cache category" }, - "cacheClearConfirmMessage": "This will clear cached data for {target}. Downloaded music files will not be deleted.", + "cacheClearConfirmMessage": "Это очистит кэш для {target}. Загруженные музыкальные файлы не будут удалены.", "@cacheClearConfirmMessage": { "description": "Dialog message before clearing selected cache", "placeholders": { From cc08596adf3d9a083ad07cb85b6ec63ad81ca11f Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:19 +0700 Subject: [PATCH 022/104] Update source file app_en.arb --- lib/l10n/arb/app_en.arb | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 2ff691cb..148fbd13 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1420,6 +1420,37 @@ "@qualityNote": {"description": "Note about quality availability"}, "youtubeQualityNote": "YouTube provides lossy audio only. Not part of lossless fallback.", "@youtubeQualityNote": {"description": "Note for YouTube service explaining lossy-only quality"}, + "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "@youtubeOpusBitrateTitle": {"description": "Title for YouTube Opus bitrate setting"}, + "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "@youtubeMp3BitrateTitle": {"description": "Title for YouTube MP3 bitrate setting"}, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": {"type": "int"}, + "min": {"type": "int"}, + "max": {"type": "int"} + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": {"type": "int"}, + "max": {"type": "int"} + } + }, + "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "@youtubeBitrateFieldLabel": {"description": "Label for YouTube bitrate input field"}, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": {"type": "int"}, + "max": {"type": "int"} + } + }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": {"description": "Setting - show quality picker"}, From 5a8ff7db37ea3d2911deefb413656eacede5cb6d Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:22 +0700 Subject: [PATCH 023/104] New translations app_en.arb (French) --- lib/l10n/arb/app_fr.arb | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index 90199b08..aaa57b9d 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -2506,6 +2506,57 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, + "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, + "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" + }, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": { + "type": "int" + }, + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "@youtubeBitrateFieldLabel": { + "description": "Label for YouTube bitrate input field" + }, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" From 016564eda7a41c6bc704bbcc42987a39c49b1a3a Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:23 +0700 Subject: [PATCH 024/104] New translations app_en.arb (Spanish) --- lib/l10n/arb/app_es-ES.arb | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/l10n/arb/app_es-ES.arb b/lib/l10n/arb/app_es-ES.arb index 3b35c79c..40831d26 100644 --- a/lib/l10n/arb/app_es-ES.arb +++ b/lib/l10n/arb/app_es-ES.arb @@ -2506,6 +2506,57 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, + "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, + "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" + }, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": { + "type": "int" + }, + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "@youtubeBitrateFieldLabel": { + "description": "Label for YouTube bitrate input field" + }, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "downloadAskBeforeDownload": "Preguntar antes de descargar", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" From 8a21902fa12a35e4b0e59b9e96f3a6356bdf7075 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:24 +0700 Subject: [PATCH 025/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 115 +++++++++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 32 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index a3cc99ea..e06b10b9 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -1532,7 +1532,7 @@ "@filenameHint": { "description": "Default filename format hint" }, - "filenameShowAdvancedTags": "Show advanced tags", + "filenameShowAdvancedTags": "Erweiterte Tags anzeigen", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" }, @@ -2139,19 +2139,19 @@ "@trackEmbedLyrics": { "description": "Action - embed lyrics into audio file" }, - "trackLyricsEmbedded": "Lyrics embedded successfully", + "trackLyricsEmbedded": "Lyrics erfolgreich eingebettet", "@trackLyricsEmbedded": { "description": "Snackbar - lyrics saved to file" }, - "trackInstrumental": "Instrumental track", + "trackInstrumental": "Instrumentalspur", "@trackInstrumental": { "description": "Message when track is instrumental (no lyrics)" }, - "trackCopiedToClipboard": "Copied to clipboard", + "trackCopiedToClipboard": "In Zwischenablage kopiert", "@trackCopiedToClipboard": { "description": "Snackbar - content copied" }, - "trackDeleteConfirmTitle": "Remove from device?", + "trackDeleteConfirmTitle": "Vom Gerät entfernen?", "@trackDeleteConfirmTitle": { "description": "Delete confirmation title" }, @@ -2159,7 +2159,7 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "Cannot open: {message}", + "trackCannotOpen": "Fehler beim Öffnen von: {message}", "@trackCannotOpen": { "description": "Error opening file", "placeholders": { @@ -2176,7 +2176,7 @@ "@dateYesterday": { "description": "Relative date - yesterday" }, - "dateDaysAgo": "{count} days ago", + "dateDaysAgo": "Vor {count} Tagen", "@dateDaysAgo": { "description": "Relative date - days ago", "placeholders": { @@ -2185,7 +2185,7 @@ } } }, - "dateWeeksAgo": "{count} weeks ago", + "dateWeeksAgo": "Vor {count} Wochen", "@dateWeeksAgo": { "description": "Relative date - weeks ago", "placeholders": { @@ -2194,7 +2194,7 @@ } } }, - "dateMonthsAgo": "{count} months ago", + "dateMonthsAgo": "Vor {count} Monaten", "@dateMonthsAgo": { "description": "Relative date - months ago", "placeholders": { @@ -2215,7 +2215,7 @@ "@concurrentParallel3": { "description": "Download mode - 3 simultaneous" }, - "tapToSeeError": "Tap to see error details", + "tapToSeeError": "Tippe, um Fehlerdetails zu sehen", "@tapToSeeError": { "description": "Tooltip for failed download" }, @@ -2247,23 +2247,23 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "No extensions found", + "storeNoResults": "Keine Erweiterungen gefunden", "@storeNoResults": { "description": "Empty state when no extensions match filters" }, - "extensionProviderPriority": "Provider Priority", + "extensionProviderPriority": "Angebotspriorität", "@extensionProviderPriority": { "description": "Extension capability - provider priority" }, - "extensionInstallButton": "Install Extension", + "extensionInstallButton": "Erweiterung installieren", "@extensionInstallButton": { "description": "Button to install extension" }, - "extensionDefaultProvider": "Default (Deezer/Spotify)", + "extensionDefaultProvider": "Standard (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" }, - "extensionDefaultProviderSubtitle": "Use built-in search", + "extensionDefaultProviderSubtitle": "Eingebaute Suche verwenden", "@extensionDefaultProviderSubtitle": { "description": "Subtitle for default provider" }, @@ -2283,15 +2283,15 @@ "@extensionCapabilities": { "description": "Section header - extension features" }, - "extensionMetadataProvider": "Metadata Provider", + "extensionMetadataProvider": "Metadaten-Anbieter", "@extensionMetadataProvider": { "description": "Capability - provides metadata" }, - "extensionDownloadProvider": "Download Provider", + "extensionDownloadProvider": "Download-Anbieter", "@extensionDownloadProvider": { "description": "Capability - provides downloads" }, - "extensionLyricsProvider": "Lyrics Provider", + "extensionLyricsProvider": "Lyrics-Anbieter", "@extensionLyricsProvider": { "description": "Capability - provides lyrics" }, @@ -2299,7 +2299,7 @@ "@extensionUrlHandler": { "description": "Capability - handles URLs" }, - "extensionQualityOptions": "Quality Options", + "extensionQualityOptions": "Qualitätsoptionen", "@extensionQualityOptions": { "description": "Capability - quality selection" }, @@ -2315,7 +2315,7 @@ "@extensionSettings": { "description": "Section header - extension settings" }, - "extensionRemoveButton": "Remove Extension", + "extensionRemoveButton": "Erweiterung entfernen", "@extensionRemoveButton": { "description": "Button to uninstall extension" }, @@ -2323,19 +2323,19 @@ "@extensionUpdated": { "description": "Extension detail - last update" }, - "extensionMinAppVersion": "Min App Version", + "extensionMinAppVersion": "Min App-Version", "@extensionMinAppVersion": { "description": "Extension detail - minimum app version" }, - "extensionCustomTrackMatching": "Custom Track Matching", + "extensionCustomTrackMatching": "Benutzerdefiniertes Track-Matching", "@extensionCustomTrackMatching": { "description": "Capability - custom track matching algorithm" }, - "extensionPostProcessing": "Post-Processing", + "extensionPostProcessing": "Post-processing", "@extensionPostProcessing": { "description": "Capability - post-download processing" }, - "extensionHooksAvailable": "{count} hook(s) available", + "extensionHooksAvailable": "{count} Hook(s) verfügbar", "@extensionHooksAvailable": { "description": "Post-processing hooks count", "placeholders": { @@ -2344,7 +2344,7 @@ } } }, - "extensionPatternsCount": "{count} pattern(s)", + "extensionPatternsCount": "{count} Muster", "@extensionPatternsCount": { "description": "URL patterns count", "placeholders": { @@ -2353,7 +2353,7 @@ } } }, - "extensionStrategy": "Strategy: {strategy}", + "extensionStrategy": "Strategie: {strategy}", "@extensionStrategy": { "description": "Track matching strategy name", "placeholders": { @@ -2362,15 +2362,15 @@ } } }, - "extensionsProviderPrioritySection": "Provider Priority", + "extensionsProviderPrioritySection": "Provider-Priorität", "@extensionsProviderPrioritySection": { "description": "Section header - provider priority" }, - "extensionsInstalledSection": "Installed Extensions", + "extensionsInstalledSection": "Installierte Erweiterungen", "@extensionsInstalledSection": { "description": "Section header - installed extensions" }, - "extensionsNoExtensions": "No extensions installed", + "extensionsNoExtensions": "Keine Erweiterungen installiert", "@extensionsNoExtensions": { "description": "Empty state - no extensions" }, @@ -2378,7 +2378,7 @@ "@extensionsNoExtensionsSubtitle": { "description": "Empty state subtitle" }, - "extensionsInstallButton": "Install Extension", + "extensionsInstallButton": "Erweiterung installieren", "@extensionsInstallButton": { "description": "Button to install extension from file" }, @@ -2386,11 +2386,11 @@ "@extensionsInfoTip": { "description": "Security warning about extensions" }, - "extensionsInstalledSuccess": "Extension installed successfully", + "extensionsInstalledSuccess": "Erweiterung erfolgreich installiert", "@extensionsInstalledSuccess": { "description": "Success message after install" }, - "extensionsDownloadPriority": "Download Priority", + "extensionsDownloadPriority": "Download-Priorität", "@extensionsDownloadPriority": { "description": "Setting - download provider order" }, @@ -2506,6 +2506,57 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, + "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, + "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" + }, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": { + "type": "int" + }, + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "@youtubeBitrateFieldLabel": { + "description": "Label for YouTube bitrate input field" + }, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" From f9ad6046e8d009504c32ae35f07be05f336c3bca Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:25 +0700 Subject: [PATCH 026/104] New translations app_en.arb (Japanese) --- lib/l10n/arb/app_ja.arb | 247 ++++++++++++++++++++++++---------------- 1 file changed, 149 insertions(+), 98 deletions(-) diff --git a/lib/l10n/arb/app_ja.arb b/lib/l10n/arb/app_ja.arb index 4746d216..59bbb37e 100644 --- a/lib/l10n/arb/app_ja.arb +++ b/lib/l10n/arb/app_ja.arb @@ -13,7 +13,7 @@ "@navHome": { "description": "Bottom navigation - Home tab" }, - "navLibrary": "Library", + "navLibrary": "ライブラリ", "@navLibrary": { "description": "Bottom navigation - Library tab" }, @@ -344,7 +344,7 @@ "@optionsConcurrentSequential": { "description": "Download one at a time" }, - "optionsConcurrentParallel": "{count} parallel downloads", + "optionsConcurrentParallel": "{count} 件の分割ダウンロード", "@optionsConcurrentParallel": { "description": "Multiple parallel downloads", "placeholders": { @@ -2135,7 +2135,7 @@ "@trackLyricsLoadFailed": { "description": "Message when lyrics loading fails" }, - "trackEmbedLyrics": "Embed Lyrics", + "trackEmbedLyrics": "歌詞を埋め込む", "@trackEmbedLyrics": { "description": "Action - embed lyrics into audio file" }, @@ -2462,11 +2462,11 @@ "@qualityLossy": { "description": "Quality option - lossy format (MP3/Opus)" }, - "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", + "qualityLossyMp3Subtitle": "MP3 320kbps (FLAC から変換)", "@qualityLossyMp3Subtitle": { "description": "Technical spec for lossy MP3" }, - "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", + "qualityLossyOpusSubtitle": "Opus 128kbps (FLAC から変換)", "@qualityLossyOpusSubtitle": { "description": "Technical spec for lossy Opus" }, @@ -2506,6 +2506,57 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, + "youtubeOpusBitrateTitle": "YouTube Opus のビットレート", + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, + "youtubeMp3BitrateTitle": "YouTube MP3 のビットレート", + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" + }, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": { + "type": "int" + }, + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateFieldLabel": "ビットレート (kbps)", + "@youtubeBitrateFieldLabel": { + "description": "Label for YouTube bitrate input field" + }, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "downloadAskBeforeDownload": "ダウンロード前に確認する", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2650,7 +2701,7 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "Export", + "queueExportFailed": "エクスポート", "@queueExportFailed": { "description": "Button - export failed downloads to TXT" }, @@ -2658,15 +2709,15 @@ "@queueExportFailedSuccess": { "description": "Success message after exporting failed downloads" }, - "queueExportFailedClear": "Clear Failed", + "queueExportFailedClear": "消去に失敗しました", "@queueExportFailedClear": { "description": "Action to clear failed downloads after export" }, - "queueExportFailedError": "Failed to export downloads", + "queueExportFailedError": "ダウンロードのエクスポートに失敗しました", "@queueExportFailedError": { "description": "Error message when export fails" }, - "settingsAutoExportFailed": "Auto-export failed downloads", + "settingsAutoExportFailed": "ダウンロードの自動エクスポートに失敗しました", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" }, @@ -2674,15 +2725,15 @@ "@settingsAutoExportFailedSubtitle": { "description": "Subtitle for auto-export setting" }, - "settingsDownloadNetwork": "Download Network", + "settingsDownloadNetwork": "ダウンロードネットワーク", "@settingsDownloadNetwork": { "description": "Setting for network type preference" }, - "settingsDownloadNetworkAny": "WiFi + Mobile Data", + "settingsDownloadNetworkAny": "Wi-Fi + モバイルデータ", "@settingsDownloadNetworkAny": { "description": "Network option - use any connection" }, - "settingsDownloadNetworkWifiOnly": "WiFi Only", + "settingsDownloadNetworkWifiOnly": "Wi-Fi のみ", "@settingsDownloadNetworkWifiOnly": { "description": "Network option - only use WiFi" }, @@ -2754,7 +2805,7 @@ "@albumFolderYearAlbumSubtitle": { "description": "Folder structure example" }, - "albumFolderArtistAlbumSingles": "Artist / Album + Singles", + "albumFolderArtistAlbumSingles": "アーティスト / アルバム + シングル", "@albumFolderArtistAlbumSingles": { "description": "Album folder option with singles inside artist" }, @@ -2852,7 +2903,7 @@ "@recentEmpty": { "description": "Empty state text for recent access list" }, - "recentShowAllDownloads": "Show All Downloads", + "recentShowAllDownloads": "すべてのダウンロードを表示", "@recentShowAllDownloads": { "description": "Button label to unhide hidden downloads in recent access" }, @@ -2994,11 +3045,11 @@ "@discographyFailedToFetch": { "description": "Error - some albums failed to load" }, - "sectionStorageAccess": "Storage Access", + "sectionStorageAccess": "ストレージアクセス", "@sectionStorageAccess": { "description": "Section header for storage access settings" }, - "allFilesAccess": "All Files Access", + "allFilesAccess": "すべてのファイルへのアクセス", "@allFilesAccess": { "description": "Toggle for MANAGE_EXTERNAL_STORAGE permission" }, @@ -3022,7 +3073,7 @@ "@allFilesAccessDisabledMessage": { "description": "Snackbar message when user disables all files access" }, - "settingsLocalLibrary": "Local Library", + "settingsLocalLibrary": "ローカルライブラリ", "@settingsLocalLibrary": { "description": "Settings menu item - local library" }, @@ -3030,7 +3081,7 @@ "@settingsLocalLibrarySubtitle": { "description": "Subtitle for local library settings" }, - "settingsCache": "Storage & Cache", + "settingsCache": "ストレージとキャッシュ", "@settingsCache": { "description": "Settings menu item - cache management" }, @@ -3038,19 +3089,19 @@ "@settingsCacheSubtitle": { "description": "Subtitle for cache management menu" }, - "libraryTitle": "Local Library", + "libraryTitle": "ローカルライブラリ", "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Library Status", + "libraryStatus": "ライブラリの状態", "@libraryStatus": { "description": "Section header for library status" }, - "libraryScanSettings": "Scan Settings", + "libraryScanSettings": "スキャン設定", "@libraryScanSettings": { "description": "Section header for scan settings" }, - "libraryEnableLocalLibrary": "Enable Local Library", + "libraryEnableLocalLibrary": "ローカルライブラリを有効", "@libraryEnableLocalLibrary": { "description": "Toggle to enable library scanning" }, @@ -3058,11 +3109,11 @@ "@libraryEnableLocalLibrarySubtitle": { "description": "Subtitle for enable toggle" }, - "libraryFolder": "Library Folder", + "libraryFolder": "ライブラリのフォルダ", "@libraryFolder": { "description": "Folder selection setting" }, - "libraryFolderHint": "Tap to select folder", + "libraryFolderHint": "タップでフォルダを選択", "@libraryFolderHint": { "description": "Placeholder when no folder selected" }, @@ -3074,15 +3125,15 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, - "libraryActions": "Actions", + "libraryActions": "アクション", "@libraryActions": { "description": "Section header for library actions" }, - "libraryScan": "Scan Library", + "libraryScan": "ライブラリをスキャン", "@libraryScan": { "description": "Button to start library scan" }, - "libraryScanSubtitle": "Scan for audio files", + "libraryScanSubtitle": "オーディオファイルをスキャン", "@libraryScanSubtitle": { "description": "Subtitle for scan button" }, @@ -3098,7 +3149,7 @@ "@libraryCleanupMissingFilesSubtitle": { "description": "Subtitle for cleanup button" }, - "libraryClear": "Clear Library", + "libraryClear": "ライブラリを消去", "@libraryClear": { "description": "Button to clear all library entries" }, @@ -3106,7 +3157,7 @@ "@libraryClearSubtitle": { "description": "Subtitle for clear button" }, - "libraryClearConfirmTitle": "Clear Library", + "libraryClearConfirmTitle": "ライブラリを消去", "@libraryClearConfirmTitle": { "description": "Dialog title for clear confirmation" }, @@ -3114,7 +3165,7 @@ "@libraryClearConfirmMessage": { "description": "Dialog message for clear confirmation" }, - "libraryAbout": "About Local Library", + "libraryAbout": "ローカルライブラリについて", "@libraryAbout": { "description": "Section header for about info" }, @@ -3122,7 +3173,7 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} tracks", + "libraryTracksCount": "{count} 個のトラック", "@libraryTracksCount": { "description": "Track count in library", "placeholders": { @@ -3131,7 +3182,7 @@ } } }, - "libraryLastScanned": "Last scanned: {time}", + "libraryLastScanned": "最終スキャン: {time}", "@libraryLastScanned": { "description": "Last scan time display", "placeholders": { @@ -3144,7 +3195,7 @@ "@libraryLastScannedNever": { "description": "Shown when library has never been scanned" }, - "libraryScanning": "Scanning...", + "libraryScanning": "スキャン中...", "@libraryScanning": { "description": "Status during scan" }, @@ -3160,7 +3211,7 @@ } } }, - "libraryInLibrary": "In Library", + "libraryInLibrary": "ライブラリ内", "@libraryInLibrary": { "description": "Badge shown on tracks that exist in local library" }, @@ -3177,7 +3228,7 @@ "@libraryCleared": { "description": "Snackbar after clearing library" }, - "libraryStorageAccessRequired": "Storage Access Required", + "libraryStorageAccessRequired": "ストレージアクセスが必要です", "@libraryStorageAccessRequired": { "description": "Dialog title for storage permission" }, @@ -3189,47 +3240,47 @@ "@libraryFolderNotExist": { "description": "Error when folder doesn't exist" }, - "librarySourceDownloaded": "Downloaded", + "librarySourceDownloaded": "ダウンロード済み", "@librarySourceDownloaded": { "description": "Badge for tracks downloaded via SpotiFLAC" }, - "librarySourceLocal": "Local", + "librarySourceLocal": "ローカル", "@librarySourceLocal": { "description": "Badge for tracks from local library scan" }, - "libraryFilterAll": "All", + "libraryFilterAll": "すべて", "@libraryFilterAll": { "description": "Filter chip - show all library items" }, - "libraryFilterDownloaded": "Downloaded", + "libraryFilterDownloaded": "ダウンロード済み", "@libraryFilterDownloaded": { "description": "Filter chip - show only downloaded items" }, - "libraryFilterLocal": "Local", + "libraryFilterLocal": "ローカル", "@libraryFilterLocal": { "description": "Filter chip - show only local library items" }, - "libraryFilterTitle": "Filters", + "libraryFilterTitle": "フィルター", "@libraryFilterTitle": { "description": "Filter bottom sheet title" }, - "libraryFilterReset": "Reset", + "libraryFilterReset": "リセット", "@libraryFilterReset": { "description": "Reset all filters button" }, - "libraryFilterApply": "Apply", + "libraryFilterApply": "適用", "@libraryFilterApply": { "description": "Apply filters button" }, - "libraryFilterSource": "Source", + "libraryFilterSource": "ソース", "@libraryFilterSource": { "description": "Filter section - source type" }, - "libraryFilterQuality": "Quality", + "libraryFilterQuality": "品質", "@libraryFilterQuality": { "description": "Filter section - audio quality" }, - "libraryFilterQualityHiRes": "Hi-Res (24bit)", + "libraryFilterQualityHiRes": "ハイレゾ (24bit)", "@libraryFilterQualityHiRes": { "description": "Filter option - high resolution audio" }, @@ -3241,15 +3292,15 @@ "@libraryFilterQualityLossy": { "description": "Filter option - lossy compressed audio" }, - "libraryFilterFormat": "Format", + "libraryFilterFormat": "形式", "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Date Added", + "libraryFilterDate": "日付を追加しました", "@libraryFilterDate": { "description": "Filter section - date range" }, - "libraryFilterDateToday": "Today", + "libraryFilterDateToday": "今日", "@libraryFilterDateToday": { "description": "Filter option - today only" }, @@ -3277,7 +3328,7 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} filter(s) active", + "libraryFilterActive": "{count} 個のフィルターが有効", "@libraryFilterActive": { "description": "Badge showing number of active filters", "placeholders": { @@ -3290,7 +3341,7 @@ "@timeJustNow": { "description": "Relative time - less than a minute ago" }, - "timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}", + "timeMinutesAgo": "{count, plural, =1{1 分前} other{{count} 分前}}", "@timeMinutesAgo": { "description": "Relative time - minutes ago", "placeholders": { @@ -3299,7 +3350,7 @@ } } }, - "timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}", + "timeHoursAgo": "{count, plural, =1{1 時間前} other{{count} 時間前}}", "@timeHoursAgo": { "description": "Relative time - hours ago", "placeholders": { @@ -3308,15 +3359,15 @@ } } }, - "storageSwitchTitle": "Switch Storage Mode", + "storageSwitchTitle": "ストレージモードを切り替え", "@storageSwitchTitle": { "description": "Dialog title when switching storage mode" }, - "storageSwitchToSafTitle": "Switch to SAF Storage?", + "storageSwitchToSafTitle": "SAF ストレージに切り替えますか?", "@storageSwitchToSafTitle": { "description": "Dialog title when switching to SAF" }, - "storageSwitchToAppTitle": "Switch to App Storage?", + "storageSwitchToAppTitle": "アプリストレージに切り替えますか?", "@storageSwitchToAppTitle": { "description": "Dialog title when switching to app storage" }, @@ -3344,7 +3395,7 @@ } } }, - "storageSwitchNewDownloads": "New Downloads", + "storageSwitchNewDownloads": "新規ダウンロード", "@storageSwitchNewDownloads": { "description": "Section header for new downloads info" }, @@ -3357,23 +3408,23 @@ } } }, - "storageSwitchContinue": "Continue", + "storageSwitchContinue": "続行", "@storageSwitchContinue": { "description": "Button to proceed with storage switch" }, - "storageSwitchSelectFolder": "Select SAF Folder", + "storageSwitchSelectFolder": "SAF フォルダを選択", "@storageSwitchSelectFolder": { "description": "Button to select SAF folder" }, - "storageAppStorage": "App Storage", + "storageAppStorage": "アプリストレージ", "@storageAppStorage": { "description": "Label for app storage mode" }, - "storageSafStorage": "SAF Storage", + "storageSafStorage": "SAF ストレージ", "@storageSafStorage": { "description": "Label for SAF storage mode" }, - "storageModeBadge": "Storage: {mode}", + "storageModeBadge": "ストレージ: {mode}", "@storageModeBadge": { "description": "Badge showing storage mode for a track", "placeholders": { @@ -3382,11 +3433,11 @@ } } }, - "storageStatsTitle": "Storage Statistics", + "storageStatsTitle": "ストレージの統計", "@storageStatsTitle": { "description": "Section title for storage stats" }, - "storageStatsAppCount": "{count} tracks in App Storage", + "storageStatsAppCount": "アプリストレージ内の {count} 個のトラック", "@storageStatsAppCount": { "description": "Count of tracks in app storage", "placeholders": { @@ -3395,7 +3446,7 @@ } } }, - "storageStatsSafCount": "{count} tracks in SAF Storage", + "storageStatsSafCount": "SAF ストレージ内の {count} 個のトラック", "@storageStatsSafCount": { "description": "Count of tracks in SAF storage", "placeholders": { @@ -3408,7 +3459,7 @@ "@storageModeInfo": { "description": "Info when user has files in both storage modes" }, - "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", + "tutorialWelcomeTitle": "SpotiFLAC へようこそ!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" }, @@ -3448,7 +3499,7 @@ "@tutorialSearchTip3": { "description": "Tutorial search tip 3" }, - "tutorialDownloadTitle": "Downloading Music", + "tutorialDownloadTitle": "音楽をダウンロード中", "@tutorialDownloadTitle": { "description": "Tutorial download page title" }, @@ -3468,7 +3519,7 @@ "@tutorialDownloadTip3": { "description": "Tutorial download tip 3" }, - "tutorialLibraryTitle": "Your Library", + "tutorialLibraryTitle": "あなたのライブラリ", "@tutorialLibraryTitle": { "description": "Tutorial library page title" }, @@ -3488,7 +3539,7 @@ "@tutorialLibraryTip3": { "description": "Tutorial library tip 3" }, - "tutorialExtensionsTitle": "Extensions", + "tutorialExtensionsTitle": "拡張", "@tutorialExtensionsTitle": { "description": "Tutorial extensions page title" }, @@ -3532,11 +3583,11 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "EXAMPLE", + "tutorialExample": "例", "@tutorialExample": { "description": "Example label in tutorial" }, - "libraryForceFullScan": "Force Full Scan", + "libraryForceFullScan": "強制フルスキャン", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" }, @@ -3565,11 +3616,11 @@ "@cleanupOrphanedDownloadsNone": { "description": "Snackbar when no orphans found" }, - "cacheTitle": "Storage & Cache", + "cacheTitle": "ストレージとキャッシュ", "@cacheTitle": { "description": "Cache management page title" }, - "cacheSummaryTitle": "Cache overview", + "cacheSummaryTitle": "キャッシュの概要", "@cacheSummaryTitle": { "description": "Heading for cache summary card" }, @@ -3586,15 +3637,15 @@ } } }, - "cacheSectionStorage": "Cached Data", + "cacheSectionStorage": "キャッシュ済みデータ", "@cacheSectionStorage": { "description": "Section header for cache entries" }, - "cacheSectionMaintenance": "Maintenance", + "cacheSectionMaintenance": "メンテナンス", "@cacheSectionMaintenance": { "description": "Section header for cleanup actions" }, - "cacheAppDirectory": "App cache directory", + "cacheAppDirectory": "アプリキャッシュのディレクトリ", "@cacheAppDirectory": { "description": "Cache item title for app cache directory" }, @@ -3602,7 +3653,7 @@ "@cacheAppDirectoryDesc": { "description": "Description of what app cache directory contains" }, - "cacheTempDirectory": "Temporary directory", + "cacheTempDirectory": "一時ディレクトリ", "@cacheTempDirectory": { "description": "Cache item title for temporary files directory" }, @@ -3610,7 +3661,7 @@ "@cacheTempDirectoryDesc": { "description": "Description of what temporary directory contains" }, - "cacheCoverImage": "Cover image cache", + "cacheCoverImage": "カバー画像のキャッシュ", "@cacheCoverImage": { "description": "Cache item title for persistent cover images" }, @@ -3618,7 +3669,7 @@ "@cacheCoverImageDesc": { "description": "Description of what cover image cache contains" }, - "cacheLibraryCover": "Library cover cache", + "cacheLibraryCover": "ライブラリのカバーキャッシュ", "@cacheLibraryCover": { "description": "Cache item title for local library cover art images" }, @@ -3646,7 +3697,7 @@ "@cacheCleanupUnusedDesc": { "description": "Description of what cleanup unused data does" }, - "cacheNoData": "No cached data", + "cacheNoData": "キャッシュデータはありません", "@cacheNoData": { "description": "Label when cache category has no data" }, @@ -3671,7 +3722,7 @@ } } }, - "cacheEntries": "{count} entries", + "cacheEntries": "{count} 個のエントリ", "@cacheEntries": { "description": "Track cache entry count", "placeholders": { @@ -3680,7 +3731,7 @@ } } }, - "cacheClearSuccess": "Cleared: {target}", + "cacheClearSuccess": "消去済み: {target}", "@cacheClearSuccess": { "description": "Snackbar after clearing selected cache", "placeholders": { @@ -3689,7 +3740,7 @@ } } }, - "cacheClearConfirmTitle": "Clear cache?", + "cacheClearConfirmTitle": "キャッシュを消去しますか?", "@cacheClearConfirmTitle": { "description": "Dialog title before clearing one cache category" }, @@ -3702,7 +3753,7 @@ } } }, - "cacheClearAllConfirmTitle": "Clear all cache?", + "cacheClearAllConfirmTitle": "すべてのキャッシュを消去しますか?", "@cacheClearAllConfirmTitle": { "description": "Dialog title before clearing all caches" }, @@ -3710,11 +3761,11 @@ "@cacheClearAllConfirmMessage": { "description": "Dialog message before clearing all caches" }, - "cacheClearAll": "Clear all cache", + "cacheClearAll": "すべてのキャッシュを消去", "@cacheClearAll": { "description": "Button label to clear all caches" }, - "cacheCleanupUnused": "Cleanup unused data", + "cacheCleanupUnused": "未使用のデータを削除", "@cacheCleanupUnused": { "description": "Action title for cleaning unused entries" }, @@ -3734,11 +3785,11 @@ } } }, - "cacheRefreshStats": "Refresh stats", + "cacheRefreshStats": "状態を更新", "@cacheRefreshStats": { "description": "Button label to refresh cache statistics" }, - "trackSaveCoverArt": "Save Cover Art", + "trackSaveCoverArt": "カバー画像を保存", "@trackSaveCoverArt": { "description": "Menu action - save album cover art as file" }, @@ -3746,7 +3797,7 @@ "@trackSaveCoverArtSubtitle": { "description": "Subtitle for save cover art action" }, - "trackSaveLyrics": "Save Lyrics (.lrc)", + "trackSaveLyrics": "歌詞を保存 (.lrc)", "@trackSaveLyrics": { "description": "Menu action - save lyrics as .lrc file" }, @@ -3770,7 +3821,7 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, - "trackEditMetadata": "Edit Metadata", + "trackEditMetadata": "メタデータを編集", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" }, @@ -3812,7 +3863,7 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, - "trackSaveFailed": "Failed: {error}", + "trackSaveFailed": "失敗: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", "placeholders": { @@ -3821,27 +3872,27 @@ } } }, - "trackConvertFormat": "Convert Format", + "trackConvertFormat": "変換の形式", "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "trackConvertFormatSubtitle": "MP3 または Opus に変換", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, - "trackConvertTitle": "Convert Audio", + "trackConvertTitle": "オーディオを変換", "@trackConvertTitle": { "description": "Title of convert bottom sheet" }, - "trackConvertTargetFormat": "Target Format", + "trackConvertTargetFormat": "ターゲットの形式", "@trackConvertTargetFormat": { "description": "Label for format selection" }, - "trackConvertBitrate": "Bitrate", + "trackConvertBitrate": "ビットレート", "@trackConvertBitrate": { "description": "Label for bitrate selection" }, - "trackConvertConfirmTitle": "Confirm Conversion", + "trackConvertConfirmTitle": "変換を確認", "@trackConvertConfirmTitle": { "description": "Confirmation dialog title" }, @@ -3860,7 +3911,7 @@ } } }, - "trackConvertConverting": "Converting audio...", + "trackConvertConverting": "オーディオを変換中...", "@trackConvertConverting": { "description": "Snackbar while converting" }, @@ -3873,7 +3924,7 @@ } } }, - "trackConvertFailed": "Conversion failed", + "trackConvertFailed": "変換に失敗しました", "@trackConvertFailed": { "description": "Snackbar when conversion fails" } From 22dd0ee0f6e633d64b5a38ca8f911149a57fa84d Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:26 +0700 Subject: [PATCH 027/104] New translations app_en.arb (Korean) --- lib/l10n/arb/app_ko.arb | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/l10n/arb/app_ko.arb b/lib/l10n/arb/app_ko.arb index 173ef898..72697d30 100644 --- a/lib/l10n/arb/app_ko.arb +++ b/lib/l10n/arb/app_ko.arb @@ -2506,6 +2506,57 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, + "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, + "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" + }, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": { + "type": "int" + }, + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "@youtubeBitrateFieldLabel": { + "description": "Label for YouTube bitrate input field" + }, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" From 0a6590fafd8b9c60c40a5eb5ba38bba0e30428f5 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:27 +0700 Subject: [PATCH 028/104] New translations app_en.arb (Dutch) --- lib/l10n/arb/app_nl.arb | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/l10n/arb/app_nl.arb b/lib/l10n/arb/app_nl.arb index 3ef8bfff..6185ecfe 100644 --- a/lib/l10n/arb/app_nl.arb +++ b/lib/l10n/arb/app_nl.arb @@ -2506,6 +2506,57 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, + "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, + "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" + }, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": { + "type": "int" + }, + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "@youtubeBitrateFieldLabel": { + "description": "Label for YouTube bitrate input field" + }, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" From baf95ec328b61a853499288823668f11eda4ae7b Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:28 +0700 Subject: [PATCH 029/104] New translations app_en.arb (Portuguese) --- lib/l10n/arb/app_pt-PT.arb | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/l10n/arb/app_pt-PT.arb b/lib/l10n/arb/app_pt-PT.arb index 359e8ed3..05cf5136 100644 --- a/lib/l10n/arb/app_pt-PT.arb +++ b/lib/l10n/arb/app_pt-PT.arb @@ -2506,6 +2506,57 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, + "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, + "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" + }, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": { + "type": "int" + }, + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "@youtubeBitrateFieldLabel": { + "description": "Label for YouTube bitrate input field" + }, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "downloadAskBeforeDownload": "Perguntar qualidade antes de baixar", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" From 5971ffc47085534124dddfeeb7f253bb06a89eff Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:30 +0700 Subject: [PATCH 030/104] New translations app_en.arb (Russian) --- lib/l10n/arb/app_ru.arb | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index e10cf8c6..4fd04114 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -2506,6 +2506,57 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, + "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, + "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" + }, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": { + "type": "int" + }, + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "@youtubeBitrateFieldLabel": { + "description": "Label for YouTube bitrate input field" + }, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "downloadAskBeforeDownload": "Спрашивать перед скачиванием", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" From 6628682f9735be9083bf2fdc7caf0cb27f084707 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:31 +0700 Subject: [PATCH 031/104] New translations app_en.arb (Chinese Simplified) --- lib/l10n/arb/app_zh_CN.arb | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/l10n/arb/app_zh_CN.arb b/lib/l10n/arb/app_zh_CN.arb index 4457abbf..24b28679 100644 --- a/lib/l10n/arb/app_zh_CN.arb +++ b/lib/l10n/arb/app_zh_CN.arb @@ -2506,6 +2506,57 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, + "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, + "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" + }, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": { + "type": "int" + }, + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "@youtubeBitrateFieldLabel": { + "description": "Label for YouTube bitrate input field" + }, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" From 8b16157047a7302e1ea73fa42a07bb987363b5a1 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:32 +0700 Subject: [PATCH 032/104] New translations app_en.arb (Chinese Traditional) --- lib/l10n/arb/app_zh_TW.arb | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/l10n/arb/app_zh_TW.arb b/lib/l10n/arb/app_zh_TW.arb index e103b61f..3896bf11 100644 --- a/lib/l10n/arb/app_zh_TW.arb +++ b/lib/l10n/arb/app_zh_TW.arb @@ -2506,6 +2506,57 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, + "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, + "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" + }, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": { + "type": "int" + }, + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "@youtubeBitrateFieldLabel": { + "description": "Label for YouTube bitrate input field" + }, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" From b63dbbbfd574a887013abd7adfd331d4a96b4f26 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:33 +0700 Subject: [PATCH 033/104] New translations app_en.arb (Indonesian) --- lib/l10n/arb/app_id.arb | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index 4c64039c..0ac9b108 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -2506,6 +2506,57 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, + "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, + "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" + }, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": { + "type": "int" + }, + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "@youtubeBitrateFieldLabel": { + "description": "Label for YouTube bitrate input field" + }, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "downloadAskBeforeDownload": "Tanya Sebelum Unduh", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" From 8eec73d88c7b3e6a61e56c4d0780ca9938007050 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:34 +0700 Subject: [PATCH 034/104] New translations app_en.arb (Hindi) --- lib/l10n/arb/app_hi.arb | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/l10n/arb/app_hi.arb b/lib/l10n/arb/app_hi.arb index e69d363e..249a490d 100644 --- a/lib/l10n/arb/app_hi.arb +++ b/lib/l10n/arb/app_hi.arb @@ -2506,6 +2506,57 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, + "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, + "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" + }, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": { + "type": "int" + }, + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "@youtubeBitrateFieldLabel": { + "description": "Label for YouTube bitrate input field" + }, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" From 85bf3cfa84ccc064b7527c134a057573c31d1a45 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:44:35 +0700 Subject: [PATCH 035/104] New translations app_en.arb (Turkish) --- lib/l10n/arb/app_tr-TR.arb | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/l10n/arb/app_tr-TR.arb b/lib/l10n/arb/app_tr-TR.arb index 0ab6a6c3..c06e485d 100644 --- a/lib/l10n/arb/app_tr-TR.arb +++ b/lib/l10n/arb/app_tr-TR.arb @@ -2506,6 +2506,57 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, + "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, + "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" + }, + "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "@youtubeBitrateSubtitle": { + "description": "Subtitle showing current bitrate and valid range", + "placeholders": { + "bitrate": { + "type": "int" + }, + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "@youtubeBitrateInputHelp": { + "description": "Helper text for manual YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "@youtubeBitrateFieldLabel": { + "description": "Label for YouTube bitrate input field" + }, + "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "@youtubeBitrateValidationError": { + "description": "Validation error for invalid YouTube bitrate input", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" From d76d020cfef61955dd6ff7afa46bcb30b1a456c1 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Thu, 19 Feb 2026 18:17:08 +0700 Subject: [PATCH 036/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index e06b10b9..87eb23e7 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -2446,7 +2446,7 @@ "@qualityHiResFlac": { "description": "Quality option - high resolution FLAC" }, - "qualityHiResFlacSubtitle": "24-bit / up to 96kHz", + "qualityHiResFlacSubtitle": "24-Bit / bis 96kHz", "@qualityHiResFlacSubtitle": { "description": "Technical spec for hi-res" }, @@ -2454,7 +2454,7 @@ "@qualityHiResFlacMax": { "description": "Quality option - maximum resolution FLAC" }, - "qualityHiResFlacMaxSubtitle": "24-bit / up to 192kHz", + "qualityHiResFlacMaxSubtitle": "24-Bit / bis 192kHz", "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, @@ -2462,15 +2462,15 @@ "@qualityLossy": { "description": "Quality option - lossy format (MP3/Opus)" }, - "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", + "qualityLossyMp3Subtitle": "MP3 320kbps (konvertiert von FLAC)", "@qualityLossyMp3Subtitle": { "description": "Technical spec for lossy MP3" }, - "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", + "qualityLossyOpusSubtitle": "Opus 128kbps (von FLAC konvertiert)", "@qualityLossyOpusSubtitle": { "description": "Technical spec for lossy Opus" }, - "enableLossyOption": "Enable Lossy Option", + "enableLossyOption": "Lossy-Option aktivieren", "@enableLossyOption": { "description": "Setting - enable lossy quality option" }, From a4e22de455f8bfa4dc60091e1754f303d09483e2 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:25:11 +0700 Subject: [PATCH 037/104] New translations app_en.arb (Korean) --- lib/l10n/arb/app_ko.arb | 682 ++++++++++++++++++++-------------------- 1 file changed, 341 insertions(+), 341 deletions(-) diff --git a/lib/l10n/arb/app_ko.arb b/lib/l10n/arb/app_ko.arb index 72697d30..5c509893 100644 --- a/lib/l10n/arb/app_ko.arb +++ b/lib/l10n/arb/app_ko.arb @@ -107,31 +107,31 @@ } } }, - "historyNoDownloads": "No download history", + "historyNoDownloads": "다운로드 기록 없음", "@historyNoDownloads": { "description": "Empty state title" }, - "historyNoDownloadsSubtitle": "Downloaded tracks will appear here", + "historyNoDownloadsSubtitle": "다운로드한 음반은 여기에 표시됩니다", "@historyNoDownloadsSubtitle": { "description": "Empty state subtitle" }, - "historyNoAlbums": "No album downloads", + "historyNoAlbums": "다운로드한 앨범 없음", "@historyNoAlbums": { "description": "Empty state when filtering albums" }, - "historyNoAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "historyNoAlbumsSubtitle": "다운로드한 앨범의 수록곡은 여기에 표시됩니다", "@historyNoAlbumsSubtitle": { "description": "Empty state subtitle for albums filter" }, - "historyNoSingles": "No single downloads", + "historyNoSingles": "다운로드한 싱글 음반 없음", "@historyNoSingles": { "description": "Empty state when filtering singles" }, - "historyNoSinglesSubtitle": "Single track downloads will appear here", + "historyNoSinglesSubtitle": "다운로드한 싱글 음반은 여기에 표시됩니다", "@historyNoSinglesSubtitle": { "description": "Empty state subtitle for singles filter" }, - "historySearchHint": "Search history...", + "historySearchHint": "검색 기록...", "@historySearchHint": { "description": "Search bar placeholder in history" }, @@ -139,79 +139,79 @@ "@settingsTitle": { "description": "Settings screen title" }, - "settingsDownload": "Download", + "settingsDownload": "다운로드", "@settingsDownload": { "description": "Settings section - download options" }, - "settingsAppearance": "Appearance", + "settingsAppearance": "외관", "@settingsAppearance": { "description": "Settings section - visual customization" }, - "settingsOptions": "Options", + "settingsOptions": "옵션", "@settingsOptions": { "description": "Settings section - app options" }, - "settingsExtensions": "Extensions", + "settingsExtensions": "확장 기능", "@settingsExtensions": { "description": "Settings section - extension management" }, - "settingsAbout": "About", + "settingsAbout": "정보", "@settingsAbout": { "description": "Settings section - app info" }, - "downloadTitle": "Download", + "downloadTitle": "다운로드", "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "Download Location", + "downloadLocation": "저장 경로", "@downloadLocation": { "description": "Setting for download folder" }, - "downloadLocationSubtitle": "Choose where to save files", + "downloadLocationSubtitle": "파일을 어떤 경로에 저장할까요?", "@downloadLocationSubtitle": { "description": "Subtitle for download location" }, - "downloadLocationDefault": "Default location", + "downloadLocationDefault": "기본 경로", "@downloadLocationDefault": { "description": "Shown when using default folder" }, - "downloadDefaultService": "Default Service", + "downloadDefaultService": "기본 서비스", "@downloadDefaultService": { "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" }, - "downloadDefaultServiceSubtitle": "Service used for downloads", + "downloadDefaultServiceSubtitle": "다운로드에 사용할 서비스", "@downloadDefaultServiceSubtitle": { "description": "Subtitle for default service" }, - "downloadDefaultQuality": "Default Quality", + "downloadDefaultQuality": "기본 품질", "@downloadDefaultQuality": { "description": "Setting for audio quality" }, - "downloadAskQuality": "Ask Quality Before Download", + "downloadAskQuality": "다운로드할 때 품질 선택", "@downloadAskQuality": { "description": "Toggle to show quality picker" }, - "downloadAskQualitySubtitle": "Show quality picker for each download", + "downloadAskQualitySubtitle": "다운로드를 할 때마다 품질을 선택하도록 합니다", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" }, - "downloadFilenameFormat": "Filename Format", + "downloadFilenameFormat": "파일 이름 형식", "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, - "downloadFolderOrganization": "Folder Organization", + "downloadFolderOrganization": "폴더 분류 형식", "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "Separate Singles", + "downloadSeparateSingles": "싱글 음반으로 분리", "@downloadSeparateSingles": { "description": "Toggle to separate single tracks" }, - "downloadSeparateSinglesSubtitle": "Put single tracks in a separate folder", + "downloadSeparateSinglesSubtitle": "싱글 음반을 별도의 폴더로 분리합니다.", "@downloadSeparateSinglesSubtitle": { "description": "Subtitle for separate singles toggle" }, - "qualityBest": "Best Available", + "qualityBest": "최상의 품질", "@qualityBest": { "description": "Audio quality option - highest available" }, @@ -227,11 +227,11 @@ "@quality128": { "description": "Audio quality option - 128kbps MP3" }, - "appearanceTitle": "Appearance", + "appearanceTitle": "외관", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "Theme", + "appearanceTheme": "테마", "@appearanceTheme": { "description": "Theme mode setting" }, @@ -251,15 +251,15 @@ "@appearanceDynamicColor": { "description": "Material You dynamic colors" }, - "appearanceDynamicColorSubtitle": "Use colors from your wallpaper", + "appearanceDynamicColorSubtitle": "배경 화면을 참고하여 강조 색상이 지정됩니다", "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "Accent Color", + "appearanceAccentColor": "강조 색상", "@appearanceAccentColor": { "description": "Custom accent color picker" }, - "appearanceHistoryView": "History View", + "appearanceHistoryView": "기록 정렬 방식", "@appearanceHistoryView": { "description": "Layout style for history" }, @@ -271,23 +271,23 @@ "@appearanceHistoryViewGrid": { "description": "Grid layout option" }, - "optionsTitle": "Options", + "optionsTitle": "옵션", "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "Search Source", + "optionsSearchSource": "검색 출처", "@optionsSearchSource": { "description": "Section for search provider settings" }, - "optionsPrimaryProvider": "Primary Provider", + "optionsPrimaryProvider": "기본 제공자", "@optionsPrimaryProvider": { "description": "Main search provider setting" }, - "optionsPrimaryProviderSubtitle": "Service used when searching by track name.", + "optionsPrimaryProviderSubtitle": "음반 이름으로 검색할 때 사용되는 서비스", "@optionsPrimaryProviderSubtitle": { "description": "Subtitle for primary provider" }, - "optionsUsingExtension": "Using extension: {extensionName}", + "optionsUsingExtension": "확장 기능을 사용: {extensionName}", "@optionsUsingExtension": { "description": "Shows active extension name", "placeholders": { @@ -296,11 +296,11 @@ } } }, - "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", + "optionsSwitchBack": "Deezer 또는 Spotify를 탭하여 확장 기능에서 다시 전환하세요.", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" }, - "optionsAutoFallback": "Auto Fallback", + "optionsAutoFallback": "자동 재시도", "@optionsAutoFallback": { "description": "Auto-retry with other services" }, @@ -308,7 +308,7 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsUseExtensionProviders": "Use Extension Providers", + "optionsUseExtensionProviders": "확장 기능 사용", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" }, @@ -316,35 +316,35 @@ "@optionsUseExtensionProvidersOn": { "description": "Status when extension providers enabled" }, - "optionsUseExtensionProvidersOff": "Using built-in providers only", + "optionsUseExtensionProvidersOff": "기본으로 제공되는 기능만 사용", "@optionsUseExtensionProvidersOff": { "description": "Status when extension providers disabled" }, - "optionsEmbedLyrics": "Embed Lyrics", + "optionsEmbedLyrics": "가사 삽입", "@optionsEmbedLyrics": { "description": "Embed lyrics in audio files" }, - "optionsEmbedLyricsSubtitle": "Embed synced lyrics into FLAC files", + "optionsEmbedLyricsSubtitle": "FLAC 파일에 동기화된 가사를 삽입합니다", "@optionsEmbedLyricsSubtitle": { "description": "Subtitle for embed lyrics" }, - "optionsMaxQualityCover": "Max Quality Cover", + "optionsMaxQualityCover": "고품질 커버 이미지", "@optionsMaxQualityCover": { "description": "Download highest quality album art" }, - "optionsMaxQualityCoverSubtitle": "Download highest resolution cover art", + "optionsMaxQualityCoverSubtitle": "최고 품질의 커버 이미지를 다운로드", "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, - "optionsConcurrentDownloads": "Concurrent Downloads", + "optionsConcurrentDownloads": "동시 다운로드", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" }, - "optionsConcurrentSequential": "Sequential (1 at a time)", + "optionsConcurrentSequential": "순차 다운로드 (한 번에 하나)", "@optionsConcurrentSequential": { "description": "Download one at a time" }, - "optionsConcurrentParallel": "{count} parallel downloads", + "optionsConcurrentParallel": "{count}개 동시 다운로드", "@optionsConcurrentParallel": { "description": "Multiple parallel downloads", "placeholders": { @@ -353,63 +353,63 @@ } } }, - "optionsConcurrentWarning": "Parallel downloads may trigger rate limiting", + "optionsConcurrentWarning": "동시에 다수의 음반을 다운로드하면 속도 제한이 발생할 수 있습니다", "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "Extension Store", + "optionsExtensionStore": "확장 기능 스토어", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "Show Store tab in navigation", + "optionsExtensionStoreSubtitle": "탐색 메뉴에 스토어 탭 표시", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, - "optionsCheckUpdates": "Check for Updates", + "optionsCheckUpdates": "업데이트 확인", "@optionsCheckUpdates": { "description": "Auto update check toggle" }, - "optionsCheckUpdatesSubtitle": "Notify when new version is available", + "optionsCheckUpdatesSubtitle": "새로운 버전이 출시되면 알림", "@optionsCheckUpdatesSubtitle": { "description": "Subtitle for update check" }, - "optionsUpdateChannel": "Update Channel", + "optionsUpdateChannel": "업데이트 채널", "@optionsUpdateChannel": { "description": "Stable vs preview releases" }, - "optionsUpdateChannelStable": "Stable releases only", + "optionsUpdateChannelStable": "안정적인 버전만 수령", "@optionsUpdateChannelStable": { "description": "Only stable updates" }, - "optionsUpdateChannelPreview": "Get preview releases", + "optionsUpdateChannelPreview": "미리보기 버전을 수령", "@optionsUpdateChannelPreview": { "description": "Include beta/preview updates" }, - "optionsUpdateChannelWarning": "Preview may contain bugs or incomplete features", + "optionsUpdateChannelWarning": "미리보기 버전은 불안정할 수 있습니다", "@optionsUpdateChannelWarning": { "description": "Warning about preview channel" }, - "optionsClearHistory": "Clear Download History", + "optionsClearHistory": "다운로드 기록 삭제", "@optionsClearHistory": { "description": "Delete all download history" }, - "optionsClearHistorySubtitle": "Remove all downloaded tracks from history", + "optionsClearHistorySubtitle": "기록에서 모든 다운로드 음반을 제거합니다", "@optionsClearHistorySubtitle": { "description": "Subtitle for clear history" }, - "optionsDetailedLogging": "Detailed Logging", + "optionsDetailedLogging": "상세 로깅", "@optionsDetailedLogging": { "description": "Enable verbose logs for debugging" }, - "optionsDetailedLoggingOn": "Detailed logs are being recorded", + "optionsDetailedLoggingOn": "상세한 로그가 기록되고 있습니다", "@optionsDetailedLoggingOn": { "description": "Status when logging enabled" }, - "optionsDetailedLoggingOff": "Enable for bug reports", + "optionsDetailedLoggingOff": "버그 신고를 위한 기능입니다", "@optionsDetailedLoggingOff": { "description": "Status when logging disabled" }, - "optionsSpotifyCredentials": "Spotify Credentials", + "optionsSpotifyCredentials": "Spotify 자격 증명", "@optionsSpotifyCredentials": { "description": "Spotify API credentials setting" }, @@ -422,39 +422,39 @@ } } }, - "optionsSpotifyCredentialsRequired": "Required - tap to configure", + "optionsSpotifyCredentialsRequired": "탭하여 설정", "@optionsSpotifyCredentialsRequired": { "description": "Prompt to set up credentials" }, - "optionsSpotifyWarning": "Spotify requires your own API credentials. Get them free from developer.spotify.com", + "optionsSpotifyWarning": "Spotify는 사용자 고유의 API 자격 증명을 요구합니다. developer.spotify.com에서 무료로 발급받으세요", "@optionsSpotifyWarning": { "description": "Info about Spotify API requirement" }, - "optionsSpotifyDeprecationWarning": "Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.", + "optionsSpotifyDeprecationWarning": "Spotify API 변경으로 인해 Spotify 검색 기능은 2026년 3월 3일부터 더 이상 지원되지 않습니다. Deezer로 전환해 주세요", "@optionsSpotifyDeprecationWarning": { "description": "Warning about Spotify API deprecation" }, - "extensionsTitle": "Extensions", + "extensionsTitle": "확장 기능", "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "Installed Extensions", + "extensionsInstalled": "확장 기능 설치", "@extensionsInstalled": { "description": "Section header for installed extensions" }, - "extensionsNone": "No extensions installed", + "extensionsNone": "설치된 확장 기능 없음", "@extensionsNone": { "description": "Empty state title" }, - "extensionsNoneSubtitle": "Install extensions from the Store tab", + "extensionsNoneSubtitle": "스토어 탭에서 확장 프로그램을 설치하세요", "@extensionsNoneSubtitle": { "description": "Empty state subtitle" }, - "extensionsEnabled": "Enabled", + "extensionsEnabled": "활성화", "@extensionsEnabled": { "description": "Extension status - active" }, - "extensionsDisabled": "Disabled", + "extensionsDisabled": "비활성화", "@extensionsDisabled": { "description": "Extension status - inactive" }, @@ -476,63 +476,63 @@ } } }, - "extensionsUninstall": "Uninstall", + "extensionsUninstall": "삭제", "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "Set as Search Provider", + "extensionsSetAsSearch": "검색 제공자", "@extensionsSetAsSearch": { "description": "Use extension for search" }, - "storeTitle": "Extension Store", + "storeTitle": "확장 기능 스토어", "@storeTitle": { "description": "Store screen title" }, - "storeSearch": "Search extensions...", + "storeSearch": "확장 기능 검색", "@storeSearch": { "description": "Store search placeholder" }, - "storeInstall": "Install", + "storeInstall": "설치", "@storeInstall": { "description": "Install extension button" }, - "storeInstalled": "Installed", + "storeInstalled": "설치됨", "@storeInstalled": { "description": "Already installed badge" }, - "storeUpdate": "Update", + "storeUpdate": "업데이트", "@storeUpdate": { "description": "Update available button" }, - "aboutTitle": "About", + "aboutTitle": "정보", "@aboutTitle": { "description": "About page title" }, - "aboutContributors": "Contributors", + "aboutContributors": "기여자", "@aboutContributors": { "description": "Section for contributors" }, - "aboutMobileDeveloper": "Mobile version developer", + "aboutMobileDeveloper": "모바일 버전 개발자", "@aboutMobileDeveloper": { "description": "Role description for mobile dev" }, - "aboutOriginalCreator": "Creator of the original SpotiFLAC", + "aboutOriginalCreator": "오리지널 SpotiFLAC 제작자", "@aboutOriginalCreator": { "description": "Role description for original creator" }, - "aboutLogoArtist": "The talented artist who created our beautiful app logo!", + "aboutLogoArtist": "아름다운 로고를 만들어주신 재능 있는 아티스트!", "@aboutLogoArtist": { "description": "Role description for logo artist" }, - "aboutTranslators": "Translators", + "aboutTranslators": "번역가들", "@aboutTranslators": { "description": "Section for translators" }, - "aboutSpecialThanks": "Special Thanks", + "aboutSpecialThanks": "특별 감사", "@aboutSpecialThanks": { "description": "Section for special thanks" }, - "aboutLinks": "Links", + "aboutLinks": "바로가기", "@aboutLinks": { "description": "Section for external links" }, @@ -540,23 +540,23 @@ "@aboutMobileSource": { "description": "Link to mobile GitHub repo" }, - "aboutPCSource": "PC source code", + "aboutPCSource": "PC 소스 코드", "@aboutPCSource": { "description": "Link to PC GitHub repo" }, - "aboutReportIssue": "Report an issue", + "aboutReportIssue": "문제 신고", "@aboutReportIssue": { "description": "Link to report bugs" }, - "aboutReportIssueSubtitle": "Report any problems you encounter", + "aboutReportIssueSubtitle": "발생하는 모든 문제를 신고하여 주세요.", "@aboutReportIssueSubtitle": { "description": "Subtitle for report issue" }, - "aboutFeatureRequest": "Feature request", + "aboutFeatureRequest": "기능 요청", "@aboutFeatureRequest": { "description": "Link to suggest features" }, - "aboutFeatureRequestSubtitle": "Suggest new features for the app", + "aboutFeatureRequestSubtitle": "앱의 새로운 기능을 제안하여 주세요.", "@aboutFeatureRequestSubtitle": { "description": "Subtitle for feature request" }, @@ -564,7 +564,7 @@ "@aboutTelegramChannel": { "description": "Link to Telegram channel" }, - "aboutTelegramChannelSubtitle": "Announcements and updates", + "aboutTelegramChannelSubtitle": "공지 및 업데이트 안내", "@aboutTelegramChannelSubtitle": { "description": "Subtitle for Telegram channel" }, @@ -572,15 +572,15 @@ "@aboutTelegramChat": { "description": "Link to Telegram chat group" }, - "aboutTelegramChatSubtitle": "Chat with other users", + "aboutTelegramChatSubtitle": "다른 이용자와 소통", "@aboutTelegramChatSubtitle": { "description": "Subtitle for Telegram chat" }, - "aboutSocial": "Social", + "aboutSocial": "소셜", "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "Support", + "aboutSupport": "지원", "@aboutSupport": { "description": "Section for support/donation links" }, @@ -592,15 +592,15 @@ "@aboutVersion": { "description": "Version info label" }, - "aboutBinimumDesc": "The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn't exist!", + "aboutBinimumDesc": "QQDL 및 HiFi API 개발자입니다. 이 API가 없었다면 Tidal 다운로드는 불가능했을 것입니다!", "@aboutBinimumDesc": { "description": "Credit description for binimum" }, - "aboutSachinsenalDesc": "The original HiFi project creator. The foundation of Tidal integration!", + "aboutSachinsenalDesc": "최초의 하이파이 프로젝트 창시자. 타이달 연동의 기반을 마련한 사람!", "@aboutSachinsenalDesc": { "description": "Credit description for sachinsenal0x64" }, - "aboutSjdonadoDesc": "Creator of I Don't Have Spotify (IDHS). The fallback link resolver that saves the day!", + "aboutSjdonadoDesc": "I Don't Have Spotify(IDHS) 개발자입니다. 위급 상황 발생 시 해결해 주는 대체 링크 해결 도구를 만들었습니다!", "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, @@ -608,7 +608,7 @@ "@aboutDoubleDouble": { "description": "Name of Amazon API service - DO NOT TRANSLATE" }, - "aboutDoubleDoubleDesc": "Amazing API for Amazon Music downloads. Thank you for making it free!", + "aboutDoubleDoubleDesc": "아마존 뮤직 다운로드를 위한 훌륭한 API네요. 무료로 제공해주셔서 감사합니다!", "@aboutDoubleDoubleDesc": { "description": "Credit for DoubleDouble API" }, @@ -616,7 +616,7 @@ "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" }, - "aboutDabMusicDesc": "The best Qobuz streaming API. Hi-Res downloads wouldn't be possible without this!", + "aboutDabMusicDesc": "최고의 Qobuz 스트리밍 API입니다. 이 API가 없었다면 고해상도 다운로드는 불가능했을 겁니다!", "@aboutDabMusicDesc": { "description": "Credit for DAB Music API" }, @@ -624,19 +624,19 @@ "@aboutSpotiSaver": { "description": "Name of SpotiSaver API service - DO NOT TRANSLATE" }, - "aboutSpotiSaverDesc": "Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!", + "aboutSpotiSaverDesc": "Tidal Hi-Res FLAC 스트리밍 엔드포인트. 무손실 음원 재생의 핵심 요소!", "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", + "aboutAppDescription": "Tidal, Qobuz, Amazon Music에서 Spotify 트랙을 무손실 음질로 다운로드하세요.", "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "Album", + "albumTitle": "앨범", "@albumTitle": { "description": "Album screen title" }, - "albumTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "albumTracks": "{count, plural,=1{1 track}other{{count} tracks}}", "@albumTracks": { "description": "Album track count", "placeholders": { @@ -645,35 +645,35 @@ } } }, - "albumDownloadAll": "Download All", + "albumDownloadAll": "모두 다운로드", "@albumDownloadAll": { "description": "Button to download all tracks" }, - "albumDownloadRemaining": "Download Remaining", + "albumDownloadRemaining": "이어서 다운로드", "@albumDownloadRemaining": { "description": "Button to download remaining tracks" }, - "playlistTitle": "Playlist", + "playlistTitle": "재생 목록", "@playlistTitle": { "description": "Playlist screen title" }, - "artistTitle": "Artist", + "artistTitle": "아티스트", "@artistTitle": { "description": "Artist screen title" }, - "artistAlbums": "Albums", + "artistAlbums": "앨범", "@artistAlbums": { "description": "Section header for artist albums" }, - "artistSingles": "Singles & EPs", + "artistSingles": "싱글 및 EP", "@artistSingles": { "description": "Section header for singles/EPs" }, - "artistCompilations": "Compilations", + "artistCompilations": "편집", "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, =1{1 release} other{{count} releases}}", + "artistReleases": "{count, plural,=1{1 release}other{{count} releases}}", "@artistReleases": { "description": "Artist release count", "placeholders": { @@ -682,11 +682,11 @@ } } }, - "artistPopular": "Popular", + "artistPopular": "인기순", "@artistPopular": { "description": "Section header for popular/top tracks" }, - "artistMonthlyListeners": "{count} monthly listeners", + "artistMonthlyListeners": "월간 청취자: {count}", "@artistMonthlyListeners": { "description": "Monthly listener count display", "placeholders": { @@ -696,55 +696,55 @@ } } }, - "trackMetadataTitle": "Track Info", + "trackMetadataTitle": "트랙 정보", "@trackMetadataTitle": { "description": "Track metadata screen title" }, - "trackMetadataArtist": "Artist", + "trackMetadataArtist": "아티스트", "@trackMetadataArtist": { "description": "Metadata field - artist name" }, - "trackMetadataAlbum": "Album", + "trackMetadataAlbum": "앨범", "@trackMetadataAlbum": { "description": "Metadata field - album name" }, - "trackMetadataDuration": "Duration", + "trackMetadataDuration": "길이", "@trackMetadataDuration": { "description": "Metadata field - track length" }, - "trackMetadataQuality": "Quality", + "trackMetadataQuality": "품질", "@trackMetadataQuality": { "description": "Metadata field - audio quality" }, - "trackMetadataPath": "File Path", + "trackMetadataPath": "파일 경로", "@trackMetadataPath": { "description": "Metadata field - file location" }, - "trackMetadataDownloadedAt": "Downloaded", + "trackMetadataDownloadedAt": "다운로드 일자", "@trackMetadataDownloadedAt": { "description": "Metadata field - download date" }, - "trackMetadataService": "Service", + "trackMetadataService": "제공업체", "@trackMetadataService": { "description": "Metadata field - download service used" }, - "trackMetadataPlay": "Play", + "trackMetadataPlay": "재생", "@trackMetadataPlay": { "description": "Action button - play track" }, - "trackMetadataShare": "Share", + "trackMetadataShare": "공유", "@trackMetadataShare": { "description": "Action button - share track" }, - "trackMetadataDelete": "Delete", + "trackMetadataDelete": "삭제", "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "Re-download", + "trackMetadataRedownload": "다시 다운로드", "@trackMetadataRedownload": { "description": "Action button - download again" }, - "trackMetadataOpenFolder": "Open Folder", + "trackMetadataOpenFolder": "경로 열기", "@trackMetadataOpenFolder": { "description": "Action button - open containing folder" }, @@ -752,67 +752,67 @@ "@setupTitle": { "description": "Setup wizard title" }, - "setupSubtitle": "Let's get you started", + "setupSubtitle": "시작해 볼까요?", "@setupSubtitle": { "description": "Setup wizard subtitle" }, - "setupStoragePermission": "Storage Permission", + "setupStoragePermission": "스토리지 접근 권한", "@setupStoragePermission": { "description": "Storage permission step title" }, - "setupStoragePermissionSubtitle": "Required to save downloaded files", + "setupStoragePermissionSubtitle": "다운로드한 파일을 저장하려면 필요합니다.", "@setupStoragePermissionSubtitle": { "description": "Explanation for storage permission" }, - "setupStoragePermissionGranted": "Permission granted", + "setupStoragePermissionGranted": "권한이 부여됨", "@setupStoragePermissionGranted": { "description": "Status when permission granted" }, - "setupStoragePermissionDenied": "Permission denied", + "setupStoragePermissionDenied": "권한이 거부됨", "@setupStoragePermissionDenied": { "description": "Status when permission denied" }, - "setupGrantPermission": "Grant Permission", + "setupGrantPermission": "권한을 제공해 주세요.", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "Download Location", + "setupDownloadLocation": "다운로드 경로", "@setupDownloadLocation": { "description": "Download folder step title" }, - "setupChooseFolder": "Choose Folder", + "setupChooseFolder": "폴더 선택", "@setupChooseFolder": { "description": "Button to pick folder" }, - "setupContinue": "Continue", + "setupContinue": "계속", "@setupContinue": { "description": "Continue to next step button" }, - "setupSkip": "Skip for now", + "setupSkip": "다음에 할래요", "@setupSkip": { "description": "Skip current step button" }, - "setupStorageAccessRequired": "Storage Access Required", + "setupStorageAccessRequired": "스토리지 접근 권한 필요", "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.", + "setupStorageAccessMessage": "SpotiFLAC이 선택한 폴더에 음악 파일을 저장하려면 \"모든 파일 접근\" 권한이 필요합니다.", "@setupStorageAccessMessage": { "description": "Explanation for storage access" }, - "setupStorageAccessMessageAndroid11": "Android 11+ requires \"All files access\" permission to save files to your chosen download folder.", + "setupStorageAccessMessageAndroid11": "Android 11 이상 버전에서는 선택한 다운로드 폴더에 파일을 저장하려면 \"모든 파일 접근\" 권한이 필요합니다.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" }, - "setupOpenSettings": "Open Settings", + "setupOpenSettings": "설정으로 이동", "@setupOpenSettings": { "description": "Button to open system settings" }, - "setupPermissionDeniedMessage": "Permission denied. Please grant all permissions to continue.", + "setupPermissionDeniedMessage": "권한이 거부되었습니다. 계속하려면 모든 권한을 허용해 주세요.", "@setupPermissionDeniedMessage": { "description": "Error when permission denied" }, - "setupPermissionRequired": "{permissionType} Permission Required", + "setupPermissionRequired": "{permissionType} 권한 필요", "@setupPermissionRequired": { "description": "Generic permission required title", "placeholders": { @@ -822,7 +822,7 @@ } } }, - "setupPermissionRequiredMessage": "{permissionType} permission is required for the best experience. You can change this later in Settings.", + "setupPermissionRequiredMessage": "최상의 사용 경험을 위해 {permissionType} 권한이 필요합니다. 설정에서 나중에 변경할 수 있습니다.", "@setupPermissionRequiredMessage": { "description": "Generic permission required message", "placeholders": { @@ -831,67 +831,67 @@ } } }, - "setupSelectDownloadFolder": "Select Download Folder", + "setupSelectDownloadFolder": "다운로드 경로 선택", "@setupSelectDownloadFolder": { "description": "Folder selection step title" }, - "setupUseDefaultFolder": "Use Default Folder?", + "setupUseDefaultFolder": "기본 폴더를 사용하시겠습니까?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" }, - "setupNoFolderSelected": "No folder selected. Would you like to use the default Music folder?", + "setupNoFolderSelected": "선택된 폴더가 없습니다. 기본 음악 폴더를 사용하시겠습니까?", "@setupNoFolderSelected": { "description": "Prompt when no folder selected" }, - "setupUseDefault": "Use Default", + "setupUseDefault": "기본값 사용", "@setupUseDefault": { "description": "Button to use default folder" }, - "setupDownloadLocationTitle": "Download Location", + "setupDownloadLocationTitle": "다운로드 경로", "@setupDownloadLocationTitle": { "description": "Download location dialog title" }, - "setupDownloadLocationIosMessage": "On iOS, downloads are saved to the app's Documents folder. You can access them via the Files app.", + "setupDownloadLocationIosMessage": "iOS에서는 다운로드한 파일이 앱의 문서 폴더에 저장됩니다. 파일 앱을 통해 해당 파일에 접근할 수 있습니다.", "@setupDownloadLocationIosMessage": { "description": "iOS-specific folder info" }, - "setupAppDocumentsFolder": "App Documents Folder", + "setupAppDocumentsFolder": "앱 문서 폴더", "@setupAppDocumentsFolder": { "description": "iOS documents folder option" }, - "setupAppDocumentsFolderSubtitle": "Recommended - accessible via Files app", + "setupAppDocumentsFolderSubtitle": "권장 사항 - 파일 앱을 통해 접근 가능", "@setupAppDocumentsFolderSubtitle": { "description": "Subtitle for documents folder" }, - "setupChooseFromFiles": "Choose from Files", + "setupChooseFromFiles": "파일 탐색기에서 선택", "@setupChooseFromFiles": { "description": "iOS file picker option" }, - "setupChooseFromFilesSubtitle": "Select iCloud or other location", + "setupChooseFromFilesSubtitle": "iCloud 또는 다른 위치를 선택하세요", "@setupChooseFromFilesSubtitle": { "description": "Subtitle for file picker" }, - "setupIosEmptyFolderWarning": "iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.", + "setupIosEmptyFolderWarning": "iOS 제한 사항: 빈 폴더는 선택할 수 없습니다. 파일이 하나 이상 있는 폴더를 선택하세요.", "@setupIosEmptyFolderWarning": { "description": "iOS folder selection warning" }, - "setupIcloudNotSupported": "iCloud Drive is not supported. Please use the app Documents folder.", + "setupIcloudNotSupported": "iCloud Drive는 지원되지 않습니다. 앱의 문서 폴더를 사용해 주세요.", "@setupIcloudNotSupported": { "description": "Error when user selects iCloud Drive on iOS" }, - "setupDownloadInFlac": "Download Spotify tracks in FLAC", + "setupDownloadInFlac": "Spotify 음악을 FLAC 형식으로 다운로드하세요.", "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "Storage", + "setupStepStorage": "저장소", "@setupStepStorage": { "description": "Setup step indicator - storage" }, - "setupStepNotification": "Notification", + "setupStepNotification": "알림", "@setupStepNotification": { "description": "Setup step indicator - notification" }, - "setupStepFolder": "Folder", + "setupStepFolder": "폴더", "@setupStepFolder": { "description": "Setup step indicator - folder" }, @@ -899,239 +899,239 @@ "@setupStepSpotify": { "description": "Setup step indicator - Spotify API" }, - "setupStepPermission": "Permission", + "setupStepPermission": "권한", "@setupStepPermission": { "description": "Setup step indicator - permission" }, - "setupStorageGranted": "Storage Permission Granted!", + "setupStorageGranted": "저장소 접근 권한이 부여되었습니다!", "@setupStorageGranted": { "description": "Success message for storage permission" }, - "setupStorageRequired": "Storage Permission Required", + "setupStorageRequired": "저장소 접근 권한이 필요합니다.", "@setupStorageRequired": { "description": "Title when storage permission needed" }, - "setupStorageDescription": "SpotiFLAC needs storage permission to save your downloaded music files.", + "setupStorageDescription": "SpotiFLAC은 다운로드한 음악 파일을 저장하기 위해 저장소 접근 권한이 필요합니다.", "@setupStorageDescription": { "description": "Explanation for storage permission" }, - "setupNotificationGranted": "Notification Permission Granted!", + "setupNotificationGranted": "알림 권한이 부여되었습니다!", "@setupNotificationGranted": { "description": "Success message for notification permission" }, - "setupNotificationEnable": "Enable Notifications", + "setupNotificationEnable": "알림 활성화", "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "Get notified when downloads complete or require attention.", + "setupNotificationDescription": "다운로드가 완료되거나 주의가 필요할 때 알림을 받으세요.", "@setupNotificationDescription": { "description": "Explanation for notifications" }, - "setupFolderSelected": "Download Folder Selected!", + "setupFolderSelected": "다운로드 폴더가 선택되었습니다!", "@setupFolderSelected": { "description": "Success message for folder selection" }, - "setupFolderChoose": "Choose Download Folder", + "setupFolderChoose": "다운로드 폴더를 선택하세요", "@setupFolderChoose": { "description": "Button to choose folder" }, - "setupFolderDescription": "Select a folder where your downloaded music will be saved.", + "setupFolderDescription": "다운로드한 음악 파일이 저장될 폴더를 선택하세요.", "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "Change Folder", + "setupChangeFolder": "폴더 변경", "@setupChangeFolder": { "description": "Button to change selected folder" }, - "setupSelectFolder": "Select Folder", + "setupSelectFolder": "폴더 선택", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "Spotify API (Optional)", + "setupSpotifyApiOptional": "Spotify API (선택 사항)", "@setupSpotifyApiOptional": { "description": "Spotify API step title" }, - "setupSpotifyApiDescription": "Add your Spotify API credentials for better search results and access to Spotify-exclusive content.", + "setupSpotifyApiDescription": "Spotify API 자격 증명을 추가하면 검색 결과가 향상되고 Spotify 독점 콘텐츠에 액세스할 수 있습니다.", "@setupSpotifyApiDescription": { "description": "Explanation for Spotify API" }, - "setupUseSpotifyApi": "Use Spotify API", + "setupUseSpotifyApi": "Spotify API 사용", "@setupUseSpotifyApi": { "description": "Toggle to enable Spotify API" }, - "setupEnterCredentialsBelow": "Enter your credentials below", + "setupEnterCredentialsBelow": "아래에 자격 증명을 입력하세요", "@setupEnterCredentialsBelow": { "description": "Prompt to enter credentials" }, - "setupUsingDeezer": "Using Deezer (no account needed)", + "setupUsingDeezer": "Deezer 사용 (계정 필요 없음)", "@setupUsingDeezer": { "description": "Status when using Deezer" }, - "setupEnterClientId": "Enter Spotify Client ID", + "setupEnterClientId": "Spotify 클라이언트 ID를 입력하세요", "@setupEnterClientId": { "description": "Placeholder for client ID field" }, - "setupEnterClientSecret": "Enter Spotify Client Secret", + "setupEnterClientSecret": "Spotify 클라이언트 비밀번호를 입력하세요", "@setupEnterClientSecret": { "description": "Placeholder for client secret field" }, - "setupGetFreeCredentials": "Get your free API credentials from the Spotify Developer Dashboard.", + "setupGetFreeCredentials": "Spotify 개발자 대시보드에서 무료 API 자격 증명을 받으세요.", "@setupGetFreeCredentials": { "description": "Info about getting Spotify credentials" }, - "setupEnableNotifications": "Enable Notifications", + "setupEnableNotifications": "알림 활성화", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "You can now proceed to the next step.", + "setupProceedToNextStep": "이제 다음 단계로 진행하실 수 있습니다.", "@setupProceedToNextStep": { "description": "Message after completing a step" }, - "setupNotificationProgressDescription": "You will receive download progress notifications.", + "setupNotificationProgressDescription": "다운로드 진행 상황 알림을 받게 됩니다.", "@setupNotificationProgressDescription": { "description": "Info about notification usage" }, - "setupNotificationBackgroundDescription": "Get notified about download progress and completion. This helps you track downloads when the app is in background.", + "setupNotificationBackgroundDescription": "알림으로 다운로드 진행 상황을 확인하세요. 앱이 백그라운드에서 실행 중일 때 다운로드 상태와 완료 여부를 확인할 수 있습니다.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" }, - "setupSkipForNow": "Skip for now", + "setupSkipForNow": "다음에 할래요.", "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "Back", + "setupBack": "이전", "@setupBack": { "description": "Back button text" }, - "setupNext": "Next", + "setupNext": "다음", "@setupNext": { "description": "Next button text" }, - "setupGetStarted": "Get Started", + "setupGetStarted": "시작하기", "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "Skip & Start", + "setupSkipAndStart": "건너뛰고 시작", "@setupSkipAndStart": { "description": "Skip setup and start app" }, - "setupAllowAccessToManageFiles": "Please enable \"Allow access to manage all files\" in the next screen.", + "setupAllowAccessToManageFiles": "다음 화면에서 \"모든 파일 관리 권한 허용\"을 활성화해 주세요.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "Get credentials from developer.spotify.com", + "setupGetCredentialsFromSpotify": "developer.spotify.com에서 자격 증명을 받으세요.", "@setupGetCredentialsFromSpotify": { "description": "Link text for Spotify developer portal" }, - "dialogCancel": "Cancel", + "dialogCancel": "취소", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "OK", + "dialogOk": "확인", "@dialogOk": { "description": "Dialog button - confirm/acknowledge" }, - "dialogSave": "Save", + "dialogSave": "저장", "@dialogSave": { "description": "Dialog button - save changes" }, - "dialogDelete": "Delete", + "dialogDelete": "삭제", "@dialogDelete": { "description": "Dialog button - delete item" }, - "dialogRetry": "Retry", + "dialogRetry": "재시도", "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "Close", + "dialogClose": "닫기", "@dialogClose": { "description": "Dialog button - close dialog" }, - "dialogYes": "Yes", + "dialogYes": "확인", "@dialogYes": { "description": "Dialog button - confirm yes" }, - "dialogNo": "No", + "dialogNo": "거절", "@dialogNo": { "description": "Dialog button - confirm no" }, - "dialogClear": "Clear", + "dialogClear": "지우기", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "Confirm", + "dialogConfirm": "확정", "@dialogConfirm": { "description": "Dialog button - confirm action" }, - "dialogDone": "Done", + "dialogDone": "완료", "@dialogDone": { "description": "Dialog button - action completed" }, - "dialogImport": "Import", + "dialogImport": "불러오기", "@dialogImport": { "description": "Dialog button - import data" }, - "dialogDiscard": "Discard", + "dialogDiscard": "취소", "@dialogDiscard": { "description": "Dialog button - discard changes" }, - "dialogRemove": "Remove", + "dialogRemove": "제거", "@dialogRemove": { "description": "Dialog button - remove item" }, - "dialogUninstall": "Uninstall", + "dialogUninstall": "삭제", "@dialogUninstall": { "description": "Dialog button - uninstall extension" }, - "dialogDiscardChanges": "Discard Changes?", + "dialogDiscardChanges": "변경사항 취소", "@dialogDiscardChanges": { "description": "Dialog title - unsaved changes warning" }, - "dialogUnsavedChanges": "You have unsaved changes. Do you want to discard them?", + "dialogUnsavedChanges": "저장되지 않은 변경 사항이 있습니다. 삭제하시겠습니까?", "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "Download Failed", + "dialogDownloadFailed": "다운로드 실패", "@dialogDownloadFailed": { "description": "Dialog title - download error" }, - "dialogTrackLabel": "Track:", + "dialogTrackLabel": "트랙:", "@dialogTrackLabel": { "description": "Label for track name in error dialog" }, - "dialogArtistLabel": "Artist:", + "dialogArtistLabel": "아티스트:", "@dialogArtistLabel": { "description": "Label for artist name in error dialog" }, - "dialogErrorLabel": "Error:", + "dialogErrorLabel": "오류:", "@dialogErrorLabel": { "description": "Label for error message" }, - "dialogClearAll": "Clear All", + "dialogClearAll": "모두 제거:", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "Are you sure you want to clear all downloads?", + "dialogClearAllDownloads": "다운로드한 파일을 모두 삭제하시겠습니까?", "@dialogClearAllDownloads": { "description": "Dialog message - clear downloads confirmation" }, - "dialogRemoveFromDevice": "Remove from device?", + "dialogRemoveFromDevice": "기기에서 삭제하시겠습니까?", "@dialogRemoveFromDevice": { "description": "Dialog title - delete file confirmation" }, - "dialogRemoveExtension": "Remove Extension", + "dialogRemoveExtension": "확장 프로그램 제거", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" }, - "dialogRemoveExtensionMessage": "Are you sure you want to remove this extension? This cannot be undone.", + "dialogRemoveExtensionMessage": "이 확장 프로그램을 정말로 제거하시겠습니까? 이 작업은 되돌릴 수 없습니다.", "@dialogRemoveExtensionMessage": { "description": "Dialog message - uninstall confirmation" }, - "dialogUninstallExtension": "Uninstall Extension?", + "dialogUninstallExtension": "확장 프로그램을 제거하시겠습니까?", "@dialogUninstallExtension": { "description": "Dialog title - uninstall extension" }, - "dialogUninstallExtensionMessage": "Are you sure you want to remove {extensionName}?", + "dialogUninstallExtensionMessage": "{extensionName}을 정말로 삭제하시겠습니까?", "@dialogUninstallExtensionMessage": { "description": "Dialog message - uninstall specific extension", "placeholders": { @@ -1140,19 +1140,19 @@ } } }, - "dialogClearHistoryTitle": "Clear History", + "dialogClearHistoryTitle": "기록 삭제", "@dialogClearHistoryTitle": { "description": "Dialog title - clear download history" }, - "dialogClearHistoryMessage": "Are you sure you want to clear all download history? This cannot be undone.", + "dialogClearHistoryMessage": "다운로드 기록을 모두 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", "@dialogClearHistoryMessage": { "description": "Dialog message - clear history confirmation" }, - "dialogDeleteSelectedTitle": "Delete Selected", + "dialogDeleteSelectedTitle": "선택한 항목 삭제", "@dialogDeleteSelectedTitle": { "description": "Dialog title - delete selected items" }, - "dialogDeleteSelectedMessage": "Delete {count} {count, plural, =1{track} other{tracks}} from history?\n\nThis will also delete the files from storage.", + "dialogDeleteSelectedMessage": "기록에서 {count} {count, plural, =1{track} other{tracks}}를 삭제하시겠습니까?", "@dialogDeleteSelectedMessage": { "description": "Dialog message - delete selected tracks", "placeholders": { @@ -1161,12 +1161,12 @@ } } }, - "dialogImportPlaylistTitle": "Import Playlist", + "dialogImportPlaylistTitle": "재생 목록 가져오기", "@dialogImportPlaylistTitle": { "description": "Dialog title - import CSV playlist" }, - "dialogImportPlaylistMessage": "Found {count} tracks in CSV. Add them to download queue?", - "csvImportTracks": "{count} tracks from CSV", + "dialogImportPlaylistMessage": "CSV 파일에서 {count}개의 트랙을 찾았습니다. 다운로드 대기열에 추가하시겠습니까?", + "csvImportTracks": "CSV 파일의 트랙: {count}", "@csvImportTracks": { "description": "Label shown in quality picker for CSV import", "placeholders": { @@ -1183,7 +1183,7 @@ } } }, - "snackbarAddedToQueue": "Added \"{trackName}\" to queue", + "snackbarAddedToQueue": "\"{trackName}\"(을)를 대기열에 추가했습니다.", "@snackbarAddedToQueue": { "description": "Snackbar - track added to download queue", "placeholders": { @@ -1192,7 +1192,7 @@ } } }, - "snackbarAddedTracksToQueue": "Added {count} tracks to queue", + "snackbarAddedTracksToQueue": "대기열에 {count}개의 트랙을 추가했습니다.", "@snackbarAddedTracksToQueue": { "description": "Snackbar - multiple tracks added to queue", "placeholders": { @@ -1201,7 +1201,7 @@ } } }, - "snackbarAlreadyDownloaded": "\"{trackName}\" already downloaded", + "snackbarAlreadyDownloaded": "\"{trackName}\"(은)는 이미 다운로드되었습니다.", "@snackbarAlreadyDownloaded": { "description": "Snackbar - track already exists", "placeholders": { @@ -1210,7 +1210,7 @@ } } }, - "snackbarAlreadyInLibrary": "\"{trackName}\" already exists in your library", + "snackbarAlreadyInLibrary": "라이브러리에 \"{trackName}\"(은)는 이미 존재합니다.", "@snackbarAlreadyInLibrary": { "description": "Snackbar - track already exists in local library", "placeholders": { @@ -1219,19 +1219,19 @@ } } }, - "snackbarHistoryCleared": "History cleared", + "snackbarHistoryCleared": "기록 삭제됨", "@snackbarHistoryCleared": { "description": "Snackbar - history deleted" }, - "snackbarCredentialsSaved": "Credentials saved", + "snackbarCredentialsSaved": "자격 증명이 저장되었습니다.", "@snackbarCredentialsSaved": { "description": "Snackbar - Spotify credentials saved" }, - "snackbarCredentialsCleared": "Credentials cleared", + "snackbarCredentialsCleared": "자격 증명이 제거되었습니다.", "@snackbarCredentialsCleared": { "description": "Snackbar - Spotify credentials removed" }, - "snackbarDeletedTracks": "Deleted {count} {count, plural, =1{track} other{tracks}}", + "snackbarDeletedTracks": "{count}{count, plural,=1{track}other{tracks}} 제거됨", "@snackbarDeletedTracks": { "description": "Snackbar - tracks deleted", "placeholders": { @@ -1240,7 +1240,7 @@ } } }, - "snackbarCannotOpenFile": "Cannot open file: {error}", + "snackbarCannotOpenFile": "파일을 열 수 없습니다: {error}", "@snackbarCannotOpenFile": { "description": "Snackbar - file open error", "placeholders": { @@ -1249,7 +1249,7 @@ } } }, - "snackbarFillAllFields": "Please fill all fields", + "snackbarFillAllFields": "모든 항목을 입력해 주세요.", "@snackbarFillAllFields": { "description": "Snackbar - validation error" }, @@ -1257,7 +1257,7 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "Failed to load: {error}", + "snackbarFailedToLoad": "불러오기 실패: {error}", "@snackbarFailedToLoad": { "description": "Snackbar - loading error", "placeholders": { @@ -1266,7 +1266,7 @@ } } }, - "snackbarUrlCopied": "{platform} URL copied to clipboard", + "snackbarUrlCopied": "{platform} 링크가 클립보드에 저장됨", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", "placeholders": { @@ -1276,23 +1276,23 @@ } } }, - "snackbarFileNotFound": "File not found", + "snackbarFileNotFound": "파일을 찾을 수 없음", "@snackbarFileNotFound": { "description": "Snackbar - file doesn't exist" }, - "snackbarSelectExtFile": "Please select a .spotiflac-ext file", + "snackbarSelectExtFile": ".spotiflac-ext 확장자 파일을 선택", "@snackbarSelectExtFile": { "description": "Snackbar - wrong file type selected" }, - "snackbarProviderPrioritySaved": "Provider priority saved", + "snackbarProviderPrioritySaved": "제공자 우선순위 저장됨", "@snackbarProviderPrioritySaved": { "description": "Snackbar - provider order saved" }, - "snackbarMetadataProviderSaved": "Metadata provider priority saved", + "snackbarMetadataProviderSaved": "메타데이터 제공자 우선순위 저장됨", "@snackbarMetadataProviderSaved": { "description": "Snackbar - metadata provider order saved" }, - "snackbarExtensionInstalled": "{extensionName} installed.", + "snackbarExtensionInstalled": "{extensionName}(이)가 설치됨", "@snackbarExtensionInstalled": { "description": "Snackbar - extension installed successfully", "placeholders": { @@ -1301,7 +1301,7 @@ } } }, - "snackbarExtensionUpdated": "{extensionName} updated.", + "snackbarExtensionUpdated": "{extensionName}(이)가 설치됨.", "@snackbarExtensionUpdated": { "description": "Snackbar - extension updated successfully", "placeholders": { @@ -1310,11 +1310,11 @@ } } }, - "snackbarFailedToInstall": "Failed to install extension", + "snackbarFailedToInstall": "확장 프로그램 설치 실패", "@snackbarFailedToInstall": { "description": "Snackbar - extension install error" }, - "snackbarFailedToUpdate": "Failed to update extension", + "snackbarFailedToUpdate": "확장 프로그램 업데이트 실패", "@snackbarFailedToUpdate": { "description": "Snackbar - extension update error" }, @@ -1322,11 +1322,11 @@ "@errorRateLimited": { "description": "Error title - too many requests" }, - "errorRateLimitedMessage": "Too many requests. Please wait a moment before searching again.", + "errorRateLimitedMessage": "요청이 너무 많습니다. 잠시 후 다시 검색해 주세요.", "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "Failed to load {item}", + "errorFailedToLoad": "{item} 로드 실패", "@errorFailedToLoad": { "description": "Error message - loading failed", "placeholders": { @@ -1336,11 +1336,11 @@ } } }, - "errorNoTracksFound": "No tracks found", + "errorNoTracksFound": "트랙을 찾을 수 없습니다", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorMissingExtensionSource": "Cannot load {item}: missing extension source", + "errorMissingExtensionSource": "확장 소스가 누락되어, {item}(을)를 로드할 수 없습니다", "@errorMissingExtensionSource": { "description": "Error - extension source not available", "placeholders": { @@ -1349,79 +1349,79 @@ } } }, - "statusQueued": "Queued", + "statusQueued": "대기열에 추가됨", "@statusQueued": { "description": "Download status - waiting in queue" }, - "statusDownloading": "Downloading", + "statusDownloading": "다운로드 중", "@statusDownloading": { "description": "Download status - in progress" }, - "statusFinalizing": "Finalizing", + "statusFinalizing": "마무리 중", "@statusFinalizing": { "description": "Download status - writing metadata" }, - "statusCompleted": "Completed", + "statusCompleted": "완료됨", "@statusCompleted": { "description": "Download status - finished" }, - "statusFailed": "Failed", + "statusFailed": "실패", "@statusFailed": { "description": "Download status - error occurred" }, - "statusSkipped": "Skipped", + "statusSkipped": "생략됨", "@statusSkipped": { "description": "Download status - already exists" }, - "statusPaused": "Paused", + "statusPaused": "중단됨", "@statusPaused": { "description": "Download status - paused" }, - "actionPause": "Pause", + "actionPause": "멈추기", "@actionPause": { "description": "Action button - pause download" }, - "actionResume": "Resume", + "actionResume": "재시작", "@actionResume": { "description": "Action button - resume download" }, - "actionCancel": "Cancel", + "actionCancel": "취소", "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "Stop", + "actionStop": "정지", "@actionStop": { "description": "Action button - stop operation" }, - "actionSelect": "Select", + "actionSelect": "선택", "@actionSelect": { "description": "Action button - enter selection mode" }, - "actionSelectAll": "Select All", + "actionSelectAll": "모두 선택", "@actionSelectAll": { "description": "Action button - select all items" }, - "actionDeselect": "Deselect", + "actionDeselect": "선택 해제", "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "Paste", + "actionPaste": "붙여넣기", "@actionPaste": { "description": "Action button - paste from clipboard" }, - "actionImportCsv": "Import CSV", + "actionImportCsv": "CSV 불러오기", "@actionImportCsv": { "description": "Action button - import CSV file" }, - "actionRemoveCredentials": "Remove Credentials", + "actionRemoveCredentials": "자격 증명 제거", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" }, - "actionSaveCredentials": "Save Credentials", + "actionSaveCredentials": "자격 증명 저장", "@actionSaveCredentials": { "description": "Action button - save Spotify credentials" }, - "selectionSelected": "{count} selected", + "selectionSelected": "{count}개 선택됨", "@selectionSelected": { "description": "Selection count indicator", "placeholders": { @@ -1430,15 +1430,15 @@ } } }, - "selectionAllSelected": "All tracks selected", + "selectionAllSelected": "모든 트랙 선택됨", "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "Tap tracks to select", + "selectionTapToSelect": "트랙을 탭하여 선택하세요", "@selectionTapToSelect": { "description": "Hint - how to select items" }, - "selectionDeleteTracks": "Delete {count} {count, plural, =1{track} other{tracks}}", + "selectionDeleteTracks": "{count} {count, plural, =1{track} other{tracks}}개 제거됨", "@selectionDeleteTracks": { "description": "Delete button with count", "placeholders": { @@ -1447,11 +1447,11 @@ } } }, - "selectionSelectToDelete": "Select tracks to delete", + "selectionSelectToDelete": "삭제할 트랙을 선택", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" }, - "progressFetchingMetadata": "Fetching metadata... {current}/{total}", + "progressFetchingMetadata": "메타데이터 가져오는 중... {current}/{total}", "@progressFetchingMetadata": { "description": "Progress indicator - loading track info", "placeholders": { @@ -1463,59 +1463,59 @@ } } }, - "progressReadingCsv": "Reading CSV...", + "progressReadingCsv": "CSV 파일을 읽는 중...", "@progressReadingCsv": { "description": "Progress indicator - parsing CSV file" }, - "searchSongs": "Songs", + "searchSongs": "곡들", "@searchSongs": { "description": "Search result category - songs" }, - "searchArtists": "Artists", + "searchArtists": "아티스트들", "@searchArtists": { "description": "Search result category - artists" }, - "searchAlbums": "Albums", + "searchAlbums": "앨범들", "@searchAlbums": { "description": "Search result category - albums" }, - "searchPlaylists": "Playlists", + "searchPlaylists": "재생목록들", "@searchPlaylists": { "description": "Search result category - playlists" }, - "tooltipPlay": "Play", + "tooltipPlay": "재생", "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "Cancel", + "tooltipCancel": "취소", "@tooltipCancel": { "description": "Tooltip - cancel button" }, - "tooltipStop": "Stop", + "tooltipStop": "정지", "@tooltipStop": { "description": "Tooltip - stop button" }, - "tooltipRetry": "Retry", + "tooltipRetry": "재시도", "@tooltipRetry": { "description": "Tooltip - retry button" }, - "tooltipRemove": "Remove", + "tooltipRemove": "제거", "@tooltipRemove": { "description": "Tooltip - remove button" }, - "tooltipClear": "Clear", + "tooltipClear": "지우기", "@tooltipClear": { "description": "Tooltip - clear button" }, - "tooltipPaste": "Paste", + "tooltipPaste": "", "@tooltipPaste": { "description": "Tooltip - paste button" }, - "filenameFormat": "Filename Format", + "filenameFormat": "", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "Preview: {preview}", + "filenameFormatPreview": "", "@filenameFormatPreview": { "description": "Preview of filename pattern", "placeholders": { @@ -1524,7 +1524,7 @@ } } }, - "filenameAvailablePlaceholders": "Available placeholders:", + "filenameAvailablePlaceholders": "사용 가능한 표현: ", "@filenameAvailablePlaceholders": { "description": "Label for placeholder list" }, @@ -1532,19 +1532,19 @@ "@filenameHint": { "description": "Default filename format hint" }, - "filenameShowAdvancedTags": "Show advanced tags", + "filenameShowAdvancedTags": "고급 태그 표시", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" }, - "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "filenameShowAdvancedTagsDescription": "트랙 패딩 및 날짜 패턴에 대한 서식 있는 태그를 활성화합니다.", "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "Folder Organization", + "folderOrganization": "폴더 정리", "@folderOrganization": { "description": "Setting title - folder structure" }, - "folderOrganizationNone": "No organization", + "folderOrganizationNone": "정리하지 않음", "@folderOrganizationNone": { "description": "Folder option - flat structure" }, @@ -1741,83 +1741,83 @@ "@logShareLogs": { "description": "Share button tooltip" }, - "logClearLogs": "Clear logs", + "logClearLogs": "로그 제거", "@logClearLogs": { "description": "Clear button tooltip" }, - "logClearLogsTitle": "Clear Logs", + "logClearLogsTitle": "로그 제거", "@logClearLogsTitle": { "description": "Clear logs dialog title" }, - "logClearLogsMessage": "Are you sure you want to clear all logs?", + "logClearLogsMessage": "모든 로그를 삭제하시겠습니까?", "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "ISP BLOCKING DETECTED", + "logIspBlocking": "ISP 차단이 감지되었습니다", "@logIspBlocking": { "description": "Error category - ISP blocking" }, - "logRateLimited": "RATE LIMITED", + "logRateLimited": "요금 제한", "@logRateLimited": { "description": "Error category - rate limiting" }, - "logNetworkError": "NETWORK ERROR", + "logNetworkError": "네트워크 오류", "@logNetworkError": { "description": "Error category - network issues" }, - "logTrackNotFound": "TRACK NOT FOUND", + "logTrackNotFound": "트랙을 찾을 수 없습니다", "@logTrackNotFound": { "description": "Error category - missing tracks" }, - "logFilterBySeverity": "Filter logs by severity", + "logFilterBySeverity": "심각성에 따라 로그 분류", "@logFilterBySeverity": { "description": "Filter dialog title" }, - "logNoLogsYet": "No logs yet", + "logNoLogsYet": "어떠한 로그도 없음", "@logNoLogsYet": { "description": "Empty state title" }, - "logNoLogsYetSubtitle": "Logs will appear here as you use the app", + "logNoLogsYetSubtitle": "앱을 사용하는 동안 로그가 여기에 표시됩니다.", "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "Issue Summary", + "logIssueSummary": "문제 요약", "@logIssueSummary": { "description": "Section header for error summary" }, - "logIspBlockingDescription": "Your ISP may be blocking access to download services", + "logIspBlockingDescription": "인터넷 서비스 제공업체(ISP)에서 다운로드 서비스 접속을 차단했을 수 있습니다.", "@logIspBlockingDescription": { "description": "ISP blocking explanation" }, - "logIspBlockingSuggestion": "Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8", + "logIspBlockingSuggestion": "VPN을 사용하거나 DNS를 1.1.1.1 또는 8.8.8.8로 변경해 보세요.", "@logIspBlockingSuggestion": { "description": "ISP blocking fix suggestion" }, - "logRateLimitedDescription": "Too many requests to the service", + "logRateLimitedDescription": "서비스에 대한 요청이 너무 많습니다", "@logRateLimitedDescription": { "description": "Rate limit explanation" }, - "logRateLimitedSuggestion": "Wait a few minutes before trying again", + "logRateLimitedSuggestion": "몇 분 정도 기다렸다가 다시 시도해 보세요.", "@logRateLimitedSuggestion": { "description": "Rate limit fix suggestion" }, - "logNetworkErrorDescription": "Connection issues detected", + "logNetworkErrorDescription": "연결 문제가 감지되었습니다.", "@logNetworkErrorDescription": { "description": "Network error explanation" }, - "logNetworkErrorSuggestion": "Check your internet connection", + "logNetworkErrorSuggestion": "인터넷 연결 상태를 확인하세요.", "@logNetworkErrorSuggestion": { "description": "Network error fix suggestion" }, - "logTrackNotFoundDescription": "Some tracks could not be found on download services", + "logTrackNotFoundDescription": "일부 트랙은 다운로드 서비스 제공자에서 찾을 수 없었습니다.", "@logTrackNotFoundDescription": { "description": "Track not found explanation" }, - "logTrackNotFoundSuggestion": "The track may not be available in lossless quality", + "logTrackNotFoundSuggestion": "해당 트랙은 무손실 음질로 제공되지 않을 수 있습니다.", "@logTrackNotFoundSuggestion": { "description": "Track not found explanation" }, - "logTotalErrors": "Total errors: {count}", + "logTotalErrors": "총 오류 수: {count}", "@logTotalErrors": { "description": "Error count display", "placeholders": { @@ -1826,7 +1826,7 @@ } } }, - "logAffected": "Affected: {domains}", + "logAffected": "영향을 받는 도메인: {domains}", "@logAffected": { "description": "Affected domains display", "placeholders": { @@ -1835,7 +1835,7 @@ } } }, - "logEntriesFiltered": "Entries ({count} filtered)", + "logEntriesFiltered": "({count} filtered)개 항목 필터링", "@logEntriesFiltered": { "description": "Log count with filter active", "placeholders": { @@ -1844,7 +1844,7 @@ } } }, - "logEntries": "Entries ({count})", + "logEntries": "항목 수: ({count})", "@logEntries": { "description": "Total log count", "placeholders": { @@ -1853,11 +1853,11 @@ } } }, - "credentialsTitle": "Spotify Credentials", + "credentialsTitle": "Spotify 자격 증명", "@credentialsTitle": { "description": "Credentials dialog title" }, - "credentialsDescription": "Enter your Client ID and Secret to use your own Spotify application quota.", + "credentialsDescription": "Spotify 애플리케이션 할당량을 사용하려면 클라이언트 ID와 비밀키를 입력하세요.", "@credentialsDescription": { "description": "Credentials dialog explanation" }, @@ -1865,43 +1865,43 @@ "@credentialsClientId": { "description": "Client ID field label - DO NOT TRANSLATE" }, - "credentialsClientIdHint": "Paste Client ID", + "credentialsClientIdHint": "Client ID를 붙여넣으세요", "@credentialsClientIdHint": { "description": "Client ID placeholder" }, - "credentialsClientSecret": "Client Secret", + "credentialsClientSecret": "비밀키", "@credentialsClientSecret": { "description": "Client Secret field label - DO NOT TRANSLATE" }, - "credentialsClientSecretHint": "Paste Client Secret", + "credentialsClientSecretHint": "비밀키를 붙여넣으세요", "@credentialsClientSecretHint": { "description": "Client Secret placeholder" }, - "channelStable": "Stable", + "channelStable": "안정", "@channelStable": { "description": "Update channel - stable releases" }, - "channelPreview": "Preview", + "channelPreview": "베타", "@channelPreview": { "description": "Update channel - beta/preview releases" }, - "sectionSearchSource": "Search Source", + "sectionSearchSource": "검색 소스", "@sectionSearchSource": { "description": "Settings section header" }, - "sectionDownload": "Download", + "sectionDownload": "다운로드", "@sectionDownload": { "description": "Settings section header" }, - "sectionPerformance": "Performance", + "sectionPerformance": "성능", "@sectionPerformance": { "description": "Settings section header" }, - "sectionApp": "App", + "sectionApp": "앱", "@sectionApp": { "description": "Settings section header" }, - "sectionData": "Data", + "sectionData": "데이터", "@sectionData": { "description": "Settings section header" }, @@ -1909,55 +1909,55 @@ "@sectionDebug": { "description": "Settings section header" }, - "sectionService": "Service", + "sectionService": "서비스", "@sectionService": { "description": "Settings section header" }, - "sectionAudioQuality": "Audio Quality", + "sectionAudioQuality": "오디오 품질", "@sectionAudioQuality": { "description": "Settings section header" }, - "sectionFileSettings": "File Settings", + "sectionFileSettings": "파일 설정", "@sectionFileSettings": { "description": "Settings section header" }, - "sectionLyrics": "Lyrics", + "sectionLyrics": "가사", "@sectionLyrics": { "description": "Settings section header" }, - "lyricsMode": "Lyrics Mode", + "lyricsMode": "가사 설정", "@lyricsMode": { "description": "Setting - how to save lyrics" }, - "lyricsModeDescription": "Choose how lyrics are saved with your downloads", + "lyricsModeDescription": "다운로드한 파일에 가사를 저장하는 방법을 선택하세요.", "@lyricsModeDescription": { "description": "Lyrics mode picker description" }, - "lyricsModeEmbed": "Embed in file", + "lyricsModeEmbed": "파일에 포함", "@lyricsModeEmbed": { "description": "Lyrics mode option - embed in audio file" }, - "lyricsModeEmbedSubtitle": "Lyrics stored inside FLAC metadata", + "lyricsModeEmbedSubtitle": "FLAC 메타데이터 내에 저장됩니다.", "@lyricsModeEmbedSubtitle": { "description": "Subtitle for embed option" }, - "lyricsModeExternal": "External .lrc file", + "lyricsModeExternal": "외부 .lrc 파일", "@lyricsModeExternal": { "description": "Lyrics mode option - separate LRC file" }, - "lyricsModeExternalSubtitle": "Separate .lrc file for players like Samsung Music", + "lyricsModeExternalSubtitle": "삼성 뮤직과 같은 플레이어용 별도 .lrc 파일", "@lyricsModeExternalSubtitle": { "description": "Subtitle for external option" }, - "lyricsModeBoth": "Both", + "lyricsModeBoth": "둘 다", "@lyricsModeBoth": { "description": "Lyrics mode option - embed and external" }, - "lyricsModeBothSubtitle": "Embed and save .lrc file", + "lyricsModeBothSubtitle": ".lrc 파일을 삽입하고 저장합니다.", "@lyricsModeBothSubtitle": { "description": "Subtitle for both option" }, - "sectionColor": "Color", + "sectionColor": "색상", "@sectionColor": { "description": "Settings section header" }, From 05e100a492a3bddfaad87b3f8ad17f392c7b2626 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 21 Feb 2026 18:43:19 +0700 Subject: [PATCH 038/104] New translations app_en.arb (Russian) --- lib/l10n/arb/app_ru.arb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 4fd04114..9cb864de 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -2506,15 +2506,15 @@ "@youtubeQualityNote": { "description": "Note for YouTube service explaining lossy-only quality" }, - "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", + "youtubeOpusBitrateTitle": "Битрейт YouTube Opus", "@youtubeOpusBitrateTitle": { "description": "Title for YouTube Opus bitrate setting" }, - "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", + "youtubeMp3BitrateTitle": "Битрейт YouTube MP3", "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", + "youtubeBitrateSubtitle": "{bitrate}кбит/с ({min}-{max})", "@youtubeBitrateSubtitle": { "description": "Subtitle showing current bitrate and valid range", "placeholders": { @@ -2529,7 +2529,7 @@ } } }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", + "youtubeBitrateInputHelp": "Введите битрейт ({min}-{max} кбит/с)", "@youtubeBitrateInputHelp": { "description": "Helper text for manual YouTube bitrate input", "placeholders": { @@ -2541,11 +2541,11 @@ } } }, - "youtubeBitrateFieldLabel": "Bitrate (kbps)", + "youtubeBitrateFieldLabel": "Битрейт (кбит/с)", "@youtubeBitrateFieldLabel": { "description": "Label for YouTube bitrate input field" }, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "youtubeBitrateValidationError": "Битрейт должен быть между {min} и {max} кбит/с", "@youtubeBitrateValidationError": { "description": "Validation error for invalid YouTube bitrate input", "placeholders": { @@ -3669,7 +3669,7 @@ "@cacheCoverImageDesc": { "description": "Description of what cover image cache contains" }, - "cacheLibraryCover": "Library cover cache", + "cacheLibraryCover": "Кэш обложек библиотеки", "@cacheLibraryCover": { "description": "Cache item title for local library cover art images" }, From b3d93880b54d04c04850531a73e23ac62711349c Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:40 +0700 Subject: [PATCH 039/104] Update source file app_en.arb --- lib/l10n/arb/app_en.arb | 353 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 351 insertions(+), 2 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 148fbd13..562073ff 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -175,6 +175,22 @@ "@optionsAutoFallback": {"description": "Auto-retry with other services"}, "optionsAutoFallbackSubtitle": "Try other services if download fails", "@optionsAutoFallbackSubtitle": {"description": "Subtitle for auto fallback"}, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": {"description": "Toggle to skip to the next queue track when current track stream resolution fails"}, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": {"description": "Subtitle when auto skip on resolve failure is enabled"}, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": {"description": "Subtitle when auto skip on resolve failure is disabled"}, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": {"description": "Tap behavior mode for track lists"}, + "modeDownloader": "Downloader Mode", + "@modeDownloader": {"description": "Interaction mode where taps queue downloads"}, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": {"description": "Subtitle for downloader interaction mode"}, + "modeStreaming": "Streaming Mode", + "@modeStreaming": {"description": "Interaction mode where taps start playback"}, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": {"description": "Subtitle for streaming interaction mode"}, "optionsUseExtensionProviders": "Use Extension Providers", "@optionsUseExtensionProviders": {"description": "Enable extension download providers"}, "optionsUseExtensionProvidersOn": "Extensions will be tried first", @@ -759,6 +775,8 @@ }, "errorNoTracksFound": "No tracks found", "@errorNoTracksFound": {"description": "Error - search returned no results"}, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": {"description": "Error - seek disabled for live decrypted stream"}, "errorMissingExtensionSource": "Cannot load {item}: missing extension source", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1151,6 +1169,13 @@ "count": {"type": "int"} } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": {"type": "int"} + } + }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -1669,8 +1694,12 @@ "discographyDownload": "Download Discography", "@discographyDownload": {"description": "Button - download artist discography"}, + "discographyPlay": "Play Discography", + "@discographyPlay": {"description": "Button - play artist discography"}, "discographyDownloadAll": "Download All", "@discographyDownloadAll": {"description": "Option - download entire discography"}, + "discographyPlayAll": "Play All", + "@discographyPlayAll": {"description": "Option - play entire discography"}, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -1722,6 +1751,8 @@ }, "discographyDownloadSelected": "Download Selected", "@discographyDownloadSelected": {"description": "Button - download selected albums"}, + "discographyPlaySelected": "Play Selected", + "@discographyPlaySelected": {"description": "Button - play selected albums"}, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -1814,6 +1845,13 @@ "count": {"type": "int"} } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": {"type": "int"} + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -2187,7 +2225,7 @@ "@trackSaveLyricsSubtitle": {"description": "Subtitle for save lyrics action"}, "trackSaveLyricsProgress": "Saving lyrics...", "@trackSaveLyricsProgress": {"description": "Snackbar while saving lyrics to file"}, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Re-enrich", "@trackReEnrich": {"description": "Menu action - re-embed metadata into audio file"}, "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", "@trackReEnrichSubtitle": {"description": "Subtitle for re-enrich metadata action"}, @@ -2258,5 +2296,316 @@ } }, "trackConvertFailed": "Conversion failed", - "@trackConvertFailed": {"description": "Snackbar when conversion fails"} + "@trackConvertFailed": {"description": "Snackbar when conversion fails"}, + + "actionCreate": "Create", + "@actionCreate": {"description": "Generic action button - create"}, + + "collectionFoldersTitle": "My folders", + "@collectionFoldersTitle": {"description": "Library section title for custom folders"}, + "collectionWishlist": "Wishlist", + "@collectionWishlist": {"description": "Custom folder for saved tracks to download later"}, + "collectionLoved": "Loved", + "@collectionLoved": {"description": "Custom folder for favorite tracks"}, + "collectionPlaylists": "Playlists", + "@collectionPlaylists": {"description": "Custom user playlists folder"}, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": {"description": "Single playlist label"}, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": {"description": "Action to add a track to user playlist"}, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": {"description": "Action to create a new playlist"}, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": {"description": "Empty state title when user has no playlists"}, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": {"description": "Empty state subtitle when user has no playlists"}, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": {"type": "int"} + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": {"type": "String"} + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": {"type": "String"} + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": {"description": "Snackbar after creating playlist"}, + "collectionPlaylistNameHint": "Playlist name", + "@collectionPlaylistNameHint": {"description": "Hint text for playlist name input"}, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": {"description": "Validation error for empty playlist name"}, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": {"description": "Action to rename playlist"}, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": {"description": "Action to delete playlist"}, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": {"type": "String"} + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": {"description": "Snackbar after deleting playlist"}, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": {"description": "Snackbar after renaming playlist"}, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": {"description": "Wishlist empty state title"}, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": {"description": "Wishlist empty state subtitle"}, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": {"description": "Loved empty state title"}, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": {"description": "Loved empty state subtitle"}, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": {"description": "Playlist empty state title"}, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": {"description": "Playlist empty state subtitle"}, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": {"description": "Tooltip for removing track from playlist"}, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": {"description": "Tooltip for removing track from wishlist/loved folder"}, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": {"type": "String"} + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": {"type": "String"} + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": {"type": "String"} + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": {"type": "String"} + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": {"type": "String"} + } + }, + + "trackOptionAddToLoved": "Add to Loved", + "@trackOptionAddToLoved": {"description": "Bottom sheet action label - add track to loved folder"}, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": {"description": "Bottom sheet action label - remove track from loved folder"}, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": {"description": "Bottom sheet action label - add track to wishlist"}, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": {"description": "Bottom sheet action label - remove track from wishlist"}, + + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": {"description": "Bottom sheet action to pick a custom cover image for a playlist"}, + "collectionPlaylistRemoveCover": "Remove cover image", + "@collectionPlaylistRemoveCover": {"description": "Bottom sheet action to remove custom cover image from a playlist"}, + + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": {"type": "int"} + } + }, + "selectionShareNoFiles": "No shareable files found", + "@selectionShareNoFiles": {"description": "Snackbar when no selected files exist on disk"}, + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": {"type": "int"} + } + }, + "selectionConvertNoConvertible": "No convertible tracks selected", + "@selectionConvertNoConvertible": {"description": "Snackbar when no selected tracks support conversion"}, + "selectionBatchConvertConfirmTitle": "Batch Convert", + "@selectionBatchConvertConfirmTitle": {"description": "Confirmation dialog title for batch conversion"}, + "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": {"type": "int"}, + "format": {"type": "String"}, + "bitrate": {"type": "String"} + } + }, + "selectionBatchConvertProgress": "Converting {current} of {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": {"type": "int"}, + "total": {"type": "int"} + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": {"type": "int"}, + "total": {"type": "int"}, + "format": {"type": "String"} + } + }, + + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": {"description": "Title for mode selection step in setup wizard"}, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": {"description": "Description for mode selection step"}, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": {"description": "Title for downloader mode option"}, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": {"description": "Downloader mode feature 1"}, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": {"description": "Downloader mode feature 2"}, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": {"description": "Downloader mode feature 3"}, + "setupModeStreamingTitle": "Streaming", + "@setupModeStreamingTitle": {"description": "Title for streaming mode option"}, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": {"description": "Streaming mode feature 1"}, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": {"description": "Streaming mode feature 2"}, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": {"description": "Streaming mode feature 3"}, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": {"description": "Hint that mode can be changed later"}, + + "settingsSmartQueueTitle": "Smart Queue", + "@settingsSmartQueueTitle": {"description": "Title for Smart Queue toggle in settings"}, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": {"description": "Subtitle for Smart Queue toggle in settings"}, + + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": {"description": "Title for the What's New screen"}, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": {"description": "Subtitle for the What's New screen"}, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": {"description": "Welcome page title in What's New screen"}, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": {"description": "Welcome page description in What's New screen"}, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": {"description": "Welcome page tip 1"}, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": {"description": "Welcome page tip 2"}, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": {"description": "Welcome page tip 3"}, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": {"description": "What's New feature: Streaming Mode title"}, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": {"description": "What's New feature: Streaming Mode description"}, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": {"description": "What's New feature: Smart Queue title"}, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": {"description": "What's New feature: Smart Queue description"}, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": {"description": "What's New feature: Dual Mode title"}, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": {"description": "What's New feature: Dual Mode description"}, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": {"description": "What's New feature: Library redesign title"}, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": {"description": "What's New feature: Library redesign description"}, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": {"description": "What's New feature: Full-Screen Player title"}, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": {"description": "What's New feature: Full-Screen Player description"}, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": {"description": "What's New feature: Context Menus title"}, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": {"description": "What's New feature: Context Menus description"}, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": {"description": "What's New feature: Performance title"}, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": {"description": "What's New feature: Performance description"}, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": {"description": "What's New feature: Batch Tools title"}, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": {"description": "What's New feature: Batch Tools description"}, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": {"description": "What's New tip: streaming instant play"}, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": {"description": "What's New tip: streaming synced lyrics"}, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": {"description": "What's New tip: streaming download from player"}, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": {"description": "What's New tip: smart queue auto-fill"}, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": {"description": "What's New tip: smart queue artist discovery"}, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": {"description": "What's New tip: smart queue endless"}, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": {"description": "What's New tip: dual mode switch"}, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": {"description": "What's New tip: dual mode adaptive UI"}, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": {"description": "What's New tip: dual mode use cases"}, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": {"description": "What's New tip: library drag and drop"}, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": {"description": "What's New tip: library custom covers"}, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": {"description": "What's New tip: library multi-select"}, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": {"description": "What's New tip: player parallax"}, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": {"description": "What's New tip: player persistence"}, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": {"description": "What's New tip: player lyrics"}, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": {"description": "What's New tip: context menu add to playlist"}, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": {"description": "What's New tip: context menu share/convert"}, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": {"description": "What's New tip: context menu re-enrich"}, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": {"description": "What's New tip: batch share"}, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": {"description": "What's New tip: batch convert"}, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": {"description": "What's New tip: batch re-enrich"}, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": {"description": "What's New tip: performance startup"}, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": {"description": "What's New tip: performance memory"}, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": {"description": "What's New tip: performance SQLite"}, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": {"description": "Ready card message on last What's New page"}, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": {"description": "Button text to dismiss What's New screen"}, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": {"type": "int"}, + "total": {"type": "int"} + } + } } From 08e9c8d4631b8bd7fdf62c98645ebd12bfe87147 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:42 +0700 Subject: [PATCH 040/104] New translations app_en.arb (French) --- lib/l10n/arb/app_fr.arb | 605 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 604 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index aaa57b9d..841895b4 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -308,6 +308,38 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": { + "description": "Toggle to skip to the next queue track when current track stream resolution fails" + }, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": { + "description": "Subtitle when auto skip on resolve failure is enabled" + }, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": { + "description": "Subtitle when auto skip on resolve failure is disabled" + }, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": { + "description": "Tap behavior mode for track lists" + }, + "modeDownloader": "Downloader Mode", + "@modeDownloader": { + "description": "Interaction mode where taps queue downloads" + }, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": { + "description": "Subtitle for downloader interaction mode" + }, + "modeStreaming": "Streaming Mode", + "@modeStreaming": { + "description": "Interaction mode where taps start playback" + }, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": { + "description": "Subtitle for streaming interaction mode" + }, "optionsUseExtensionProviders": "Utiliser des fournisseurs d'extension", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -1340,6 +1372,10 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": { + "description": "Error - seek disabled for live decrypted stream" + }, "errorMissingExtensionSource": "Cannot load {item}: missing extension source", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -2022,6 +2058,15 @@ } } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2931,10 +2976,18 @@ "@discographyDownload": { "description": "Button - download artist discography" }, + "discographyPlay": "Play Discography", + "@discographyPlay": { + "description": "Button - play artist discography" + }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, + "discographyPlayAll": "Play All", + "@discographyPlayAll": { + "description": "Option - play entire discography" + }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3016,6 +3069,10 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, + "discographyPlaySelected": "Play Selected", + "@discographyPlaySelected": { + "description": "Button - play selected albums" + }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3182,6 +3239,15 @@ } } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -3809,7 +3875,7 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Re-enrich", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, @@ -3927,5 +3993,542 @@ "trackConvertFailed": "Conversion failed", "@trackConvertFailed": { "description": "Snackbar when conversion fails" + }, + "actionCreate": "Create", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "My folders", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Wishlist", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Loved", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Playlists", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Playlist name", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Add to Loved", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Remove cover image", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "No shareable files found", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "No convertible tracks selected", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Batch Convert", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Converting {current} of {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": { + "description": "Title for mode selection step in setup wizard" + }, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": { + "description": "Description for mode selection step" + }, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": { + "description": "Title for downloader mode option" + }, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": { + "description": "Downloader mode feature 1" + }, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": { + "description": "Downloader mode feature 2" + }, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": { + "description": "Downloader mode feature 3" + }, + "setupModeStreamingTitle": "Streaming", + "@setupModeStreamingTitle": { + "description": "Title for streaming mode option" + }, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": { + "description": "Streaming mode feature 1" + }, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": { + "description": "Streaming mode feature 2" + }, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": { + "description": "Streaming mode feature 3" + }, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": { + "description": "Hint that mode can be changed later" + }, + "settingsSmartQueueTitle": "Smart Queue", + "@settingsSmartQueueTitle": { + "description": "Title for Smart Queue toggle in settings" + }, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": { + "description": "Subtitle for Smart Queue toggle in settings" + }, + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": { + "description": "Title for the What's New screen" + }, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": { + "description": "Subtitle for the What's New screen" + }, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": { + "description": "Welcome page title in What's New screen" + }, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": { + "description": "Welcome page description in What's New screen" + }, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": { + "description": "Welcome page tip 1" + }, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": { + "description": "Welcome page tip 2" + }, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": { + "description": "Welcome page tip 3" + }, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": { + "description": "What's New feature: Streaming Mode title" + }, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": { + "description": "What's New feature: Streaming Mode description" + }, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": { + "description": "What's New feature: Smart Queue title" + }, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": { + "description": "What's New feature: Smart Queue description" + }, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": { + "description": "What's New feature: Dual Mode title" + }, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": { + "description": "What's New feature: Dual Mode description" + }, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": { + "description": "What's New feature: Library redesign title" + }, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": { + "description": "What's New feature: Library redesign description" + }, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": { + "description": "What's New feature: Full-Screen Player title" + }, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": { + "description": "What's New feature: Full-Screen Player description" + }, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": { + "description": "What's New feature: Context Menus title" + }, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": { + "description": "What's New feature: Context Menus description" + }, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": { + "description": "What's New feature: Performance title" + }, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": { + "description": "What's New feature: Performance description" + }, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": { + "description": "What's New feature: Batch Tools title" + }, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": { + "description": "What's New feature: Batch Tools description" + }, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": { + "description": "What's New tip: streaming instant play" + }, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": { + "description": "What's New tip: streaming synced lyrics" + }, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": { + "description": "What's New tip: streaming download from player" + }, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": { + "description": "What's New tip: smart queue auto-fill" + }, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": { + "description": "What's New tip: smart queue artist discovery" + }, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": { + "description": "What's New tip: smart queue endless" + }, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": { + "description": "What's New tip: dual mode switch" + }, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": { + "description": "What's New tip: dual mode adaptive UI" + }, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": { + "description": "What's New tip: dual mode use cases" + }, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": { + "description": "What's New tip: library drag and drop" + }, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": { + "description": "What's New tip: library custom covers" + }, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": { + "description": "What's New tip: library multi-select" + }, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": { + "description": "What's New tip: player parallax" + }, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": { + "description": "What's New tip: player persistence" + }, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": { + "description": "What's New tip: player lyrics" + }, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": { + "description": "What's New tip: context menu add to playlist" + }, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": { + "description": "What's New tip: context menu share/convert" + }, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": { + "description": "What's New tip: context menu re-enrich" + }, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": { + "description": "What's New tip: batch share" + }, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": { + "description": "What's New tip: batch convert" + }, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": { + "description": "What's New tip: batch re-enrich" + }, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": { + "description": "What's New tip: performance startup" + }, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": { + "description": "What's New tip: performance memory" + }, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": { + "description": "What's New tip: performance SQLite" + }, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": { + "description": "Ready card message on last What's New page" + }, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": { + "description": "Button text to dismiss What's New screen" + }, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } } } \ No newline at end of file From 5e13651ed9dd4c1787dbf0d23035e3c96ef7faf0 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:43 +0700 Subject: [PATCH 041/104] New translations app_en.arb (Spanish) --- lib/l10n/arb/app_es-ES.arb | 605 ++++++++++++++++++++++++++++++++++++- 1 file changed, 604 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_es-ES.arb b/lib/l10n/arb/app_es-ES.arb index 40831d26..85cb5bc1 100644 --- a/lib/l10n/arb/app_es-ES.arb +++ b/lib/l10n/arb/app_es-ES.arb @@ -308,6 +308,38 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": { + "description": "Toggle to skip to the next queue track when current track stream resolution fails" + }, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": { + "description": "Subtitle when auto skip on resolve failure is enabled" + }, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": { + "description": "Subtitle when auto skip on resolve failure is disabled" + }, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": { + "description": "Tap behavior mode for track lists" + }, + "modeDownloader": "Downloader Mode", + "@modeDownloader": { + "description": "Interaction mode where taps queue downloads" + }, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": { + "description": "Subtitle for downloader interaction mode" + }, + "modeStreaming": "Streaming Mode", + "@modeStreaming": { + "description": "Interaction mode where taps start playback" + }, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": { + "description": "Subtitle for streaming interaction mode" + }, "optionsUseExtensionProviders": "Usar proveedores de extensiones", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -1340,6 +1372,10 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": { + "description": "Error - seek disabled for live decrypted stream" + }, "errorMissingExtensionSource": "No se puede cargar {item}: falta una fuente de extensión", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -2022,6 +2058,15 @@ } } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, "tracksCount": "{count, plural, one {}=1{1 pista} other{{count} pistas}}", "@tracksCount": { "description": "Track count display", @@ -2931,10 +2976,18 @@ "@discographyDownload": { "description": "Button - download artist discography" }, + "discographyPlay": "Play Discography", + "@discographyPlay": { + "description": "Button - play artist discography" + }, "discographyDownloadAll": "Descargar Todo", "@discographyDownloadAll": { "description": "Option - download entire discography" }, + "discographyPlayAll": "Play All", + "@discographyPlayAll": { + "description": "Option - play entire discography" + }, "discographyDownloadAllSubtitle": "{count} pistas de {albumCount} lanzamientos", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3016,6 +3069,10 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, + "discographyPlaySelected": "Play Selected", + "@discographyPlaySelected": { + "description": "Button - play selected albums" + }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3182,6 +3239,15 @@ } } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -3809,7 +3875,7 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Re-enrich", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, @@ -3927,5 +3993,542 @@ "trackConvertFailed": "Conversion failed", "@trackConvertFailed": { "description": "Snackbar when conversion fails" + }, + "actionCreate": "Create", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "My folders", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Wishlist", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Loved", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Playlists", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Playlist name", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Add to Loved", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Remove cover image", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "No shareable files found", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "No convertible tracks selected", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Batch Convert", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Converting {current} of {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": { + "description": "Title for mode selection step in setup wizard" + }, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": { + "description": "Description for mode selection step" + }, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": { + "description": "Title for downloader mode option" + }, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": { + "description": "Downloader mode feature 1" + }, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": { + "description": "Downloader mode feature 2" + }, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": { + "description": "Downloader mode feature 3" + }, + "setupModeStreamingTitle": "Streaming", + "@setupModeStreamingTitle": { + "description": "Title for streaming mode option" + }, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": { + "description": "Streaming mode feature 1" + }, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": { + "description": "Streaming mode feature 2" + }, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": { + "description": "Streaming mode feature 3" + }, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": { + "description": "Hint that mode can be changed later" + }, + "settingsSmartQueueTitle": "Smart Queue", + "@settingsSmartQueueTitle": { + "description": "Title for Smart Queue toggle in settings" + }, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": { + "description": "Subtitle for Smart Queue toggle in settings" + }, + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": { + "description": "Title for the What's New screen" + }, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": { + "description": "Subtitle for the What's New screen" + }, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": { + "description": "Welcome page title in What's New screen" + }, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": { + "description": "Welcome page description in What's New screen" + }, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": { + "description": "Welcome page tip 1" + }, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": { + "description": "Welcome page tip 2" + }, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": { + "description": "Welcome page tip 3" + }, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": { + "description": "What's New feature: Streaming Mode title" + }, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": { + "description": "What's New feature: Streaming Mode description" + }, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": { + "description": "What's New feature: Smart Queue title" + }, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": { + "description": "What's New feature: Smart Queue description" + }, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": { + "description": "What's New feature: Dual Mode title" + }, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": { + "description": "What's New feature: Dual Mode description" + }, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": { + "description": "What's New feature: Library redesign title" + }, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": { + "description": "What's New feature: Library redesign description" + }, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": { + "description": "What's New feature: Full-Screen Player title" + }, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": { + "description": "What's New feature: Full-Screen Player description" + }, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": { + "description": "What's New feature: Context Menus title" + }, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": { + "description": "What's New feature: Context Menus description" + }, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": { + "description": "What's New feature: Performance title" + }, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": { + "description": "What's New feature: Performance description" + }, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": { + "description": "What's New feature: Batch Tools title" + }, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": { + "description": "What's New feature: Batch Tools description" + }, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": { + "description": "What's New tip: streaming instant play" + }, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": { + "description": "What's New tip: streaming synced lyrics" + }, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": { + "description": "What's New tip: streaming download from player" + }, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": { + "description": "What's New tip: smart queue auto-fill" + }, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": { + "description": "What's New tip: smart queue artist discovery" + }, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": { + "description": "What's New tip: smart queue endless" + }, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": { + "description": "What's New tip: dual mode switch" + }, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": { + "description": "What's New tip: dual mode adaptive UI" + }, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": { + "description": "What's New tip: dual mode use cases" + }, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": { + "description": "What's New tip: library drag and drop" + }, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": { + "description": "What's New tip: library custom covers" + }, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": { + "description": "What's New tip: library multi-select" + }, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": { + "description": "What's New tip: player parallax" + }, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": { + "description": "What's New tip: player persistence" + }, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": { + "description": "What's New tip: player lyrics" + }, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": { + "description": "What's New tip: context menu add to playlist" + }, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": { + "description": "What's New tip: context menu share/convert" + }, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": { + "description": "What's New tip: context menu re-enrich" + }, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": { + "description": "What's New tip: batch share" + }, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": { + "description": "What's New tip: batch convert" + }, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": { + "description": "What's New tip: batch re-enrich" + }, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": { + "description": "What's New tip: performance startup" + }, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": { + "description": "What's New tip: performance memory" + }, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": { + "description": "What's New tip: performance SQLite" + }, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": { + "description": "Ready card message on last What's New page" + }, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": { + "description": "Button text to dismiss What's New screen" + }, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } } } \ No newline at end of file From 7af2212d11c5e64416694908470069d9e2d4b1ab Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:44 +0700 Subject: [PATCH 042/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 607 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 605 insertions(+), 2 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 87eb23e7..a3b71f1d 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -308,6 +308,38 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": { + "description": "Toggle to skip to the next queue track when current track stream resolution fails" + }, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": { + "description": "Subtitle when auto skip on resolve failure is enabled" + }, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": { + "description": "Subtitle when auto skip on resolve failure is disabled" + }, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": { + "description": "Tap behavior mode for track lists" + }, + "modeDownloader": "Downloader Mode", + "@modeDownloader": { + "description": "Interaction mode where taps queue downloads" + }, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": { + "description": "Subtitle for downloader interaction mode" + }, + "modeStreaming": "Streaming-Modus", + "@modeStreaming": { + "description": "Interaction mode where taps start playback" + }, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": { + "description": "Subtitle for streaming interaction mode" + }, "optionsUseExtensionProviders": "Erweiterungs-Anbieter verwenden", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -1340,6 +1372,10 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": { + "description": "Error - seek disabled for live decrypted stream" + }, "errorMissingExtensionSource": "Kann {item} nicht lade wegen fehlender Erweiterungsquelle", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -2022,6 +2058,15 @@ } } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, "tracksCount": "{count, plural, =1{1 Titel} other{{count} Titel}}", "@tracksCount": { "description": "Track count display", @@ -2931,10 +2976,18 @@ "@discographyDownload": { "description": "Button - download artist discography" }, + "discographyPlay": "Play Discography", + "@discographyPlay": { + "description": "Button - play artist discography" + }, "discographyDownloadAll": "Alle Herunterladen", "@discographyDownloadAll": { "description": "Option - download entire discography" }, + "discographyPlayAll": "Alle abspielen", + "@discographyPlayAll": { + "description": "Option - play entire discography" + }, "discographyDownloadAllSubtitle": "{count} Titel von {albumCount} Releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3016,6 +3069,10 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, + "discographyPlaySelected": "Ausgewählte abspielen", + "@discographyPlaySelected": { + "description": "Button - play selected albums" + }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3182,6 +3239,15 @@ } } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -3809,7 +3875,7 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Neu-anreichern", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, @@ -3851,7 +3917,7 @@ "@trackReEnrichProgress": { "description": "Snackbar while re-enriching metadata" }, - "trackReEnrichSearching": "Searching metadata online...", + "trackReEnrichSearching": "Suche Metadaten online...", "@trackReEnrichSearching": { "description": "Snackbar while searching metadata from internet for local items" }, @@ -3927,5 +3993,542 @@ "trackConvertFailed": "Conversion failed", "@trackConvertFailed": { "description": "Snackbar when conversion fails" + }, + "actionCreate": "Erstellen", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "Meine Ordner", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Wunschliste", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Lieblingssongs", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Playlisten", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Playlist-Name", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Zu Lieblingssongs hinzufügen", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Cover entfernen", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Teile {count} {count, plural, one {}=1{Titel}other{Titel}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "Keine teilbare Dateien gefunden", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Konvertiere {count} {count, plural, one {}=1{Titel}other{Titel}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "Keine konvertierbare Titel ausgewählt", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Batch-Konvertierung", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Konvertiere {count} {format} {count, plural, one {}=1{Titel} other{Titel}} zu {bitrate}?\n\nOriginaldateien werden nach der Konvertierung gelöscht.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Konvertiere {current} von {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": { + "description": "Title for mode selection step in setup wizard" + }, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": { + "description": "Description for mode selection step" + }, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": { + "description": "Title for downloader mode option" + }, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": { + "description": "Downloader mode feature 1" + }, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": { + "description": "Downloader mode feature 2" + }, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": { + "description": "Downloader mode feature 3" + }, + "setupModeStreamingTitle": "Streamen", + "@setupModeStreamingTitle": { + "description": "Title for streaming mode option" + }, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": { + "description": "Streaming mode feature 1" + }, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": { + "description": "Streaming mode feature 2" + }, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": { + "description": "Streaming mode feature 3" + }, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": { + "description": "Hint that mode can be changed later" + }, + "settingsSmartQueueTitle": "Smarte Warteschlange", + "@settingsSmartQueueTitle": { + "description": "Title for Smart Queue toggle in settings" + }, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": { + "description": "Subtitle for Smart Queue toggle in settings" + }, + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": { + "description": "Title for the What's New screen" + }, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": { + "description": "Subtitle for the What's New screen" + }, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": { + "description": "Welcome page title in What's New screen" + }, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": { + "description": "Welcome page description in What's New screen" + }, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": { + "description": "Welcome page tip 1" + }, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": { + "description": "Welcome page tip 2" + }, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": { + "description": "Welcome page tip 3" + }, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": { + "description": "What's New feature: Streaming Mode title" + }, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": { + "description": "What's New feature: Streaming Mode description" + }, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": { + "description": "What's New feature: Smart Queue title" + }, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": { + "description": "What's New feature: Smart Queue description" + }, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": { + "description": "What's New feature: Dual Mode title" + }, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": { + "description": "What's New feature: Dual Mode description" + }, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": { + "description": "What's New feature: Library redesign title" + }, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": { + "description": "What's New feature: Library redesign description" + }, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": { + "description": "What's New feature: Full-Screen Player title" + }, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": { + "description": "What's New feature: Full-Screen Player description" + }, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": { + "description": "What's New feature: Context Menus title" + }, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": { + "description": "What's New feature: Context Menus description" + }, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": { + "description": "What's New feature: Performance title" + }, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": { + "description": "What's New feature: Performance description" + }, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": { + "description": "What's New feature: Batch Tools title" + }, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": { + "description": "What's New feature: Batch Tools description" + }, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": { + "description": "What's New tip: streaming instant play" + }, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": { + "description": "What's New tip: streaming synced lyrics" + }, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": { + "description": "What's New tip: streaming download from player" + }, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": { + "description": "What's New tip: smart queue auto-fill" + }, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": { + "description": "What's New tip: smart queue artist discovery" + }, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": { + "description": "What's New tip: smart queue endless" + }, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": { + "description": "What's New tip: dual mode switch" + }, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": { + "description": "What's New tip: dual mode adaptive UI" + }, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": { + "description": "What's New tip: dual mode use cases" + }, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": { + "description": "What's New tip: library drag and drop" + }, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": { + "description": "What's New tip: library custom covers" + }, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": { + "description": "What's New tip: library multi-select" + }, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": { + "description": "What's New tip: player parallax" + }, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": { + "description": "What's New tip: player persistence" + }, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": { + "description": "What's New tip: player lyrics" + }, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": { + "description": "What's New tip: context menu add to playlist" + }, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": { + "description": "What's New tip: context menu share/convert" + }, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": { + "description": "What's New tip: context menu re-enrich" + }, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": { + "description": "What's New tip: batch share" + }, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": { + "description": "What's New tip: batch convert" + }, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": { + "description": "What's New tip: batch re-enrich" + }, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": { + "description": "What's New tip: performance startup" + }, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": { + "description": "What's New tip: performance memory" + }, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": { + "description": "What's New tip: performance SQLite" + }, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": { + "description": "Ready card message on last What's New page" + }, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": { + "description": "Button text to dismiss What's New screen" + }, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } } } \ No newline at end of file From 7ddf67a977e1508d1e4f6c5c00f0b4df01615560 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:45 +0700 Subject: [PATCH 043/104] New translations app_en.arb (Japanese) --- lib/l10n/arb/app_ja.arb | 605 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 604 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_ja.arb b/lib/l10n/arb/app_ja.arb index 59bbb37e..2cf76c9d 100644 --- a/lib/l10n/arb/app_ja.arb +++ b/lib/l10n/arb/app_ja.arb @@ -308,6 +308,38 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": { + "description": "Toggle to skip to the next queue track when current track stream resolution fails" + }, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": { + "description": "Subtitle when auto skip on resolve failure is enabled" + }, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": { + "description": "Subtitle when auto skip on resolve failure is disabled" + }, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": { + "description": "Tap behavior mode for track lists" + }, + "modeDownloader": "Downloader Mode", + "@modeDownloader": { + "description": "Interaction mode where taps queue downloads" + }, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": { + "description": "Subtitle for downloader interaction mode" + }, + "modeStreaming": "Streaming Mode", + "@modeStreaming": { + "description": "Interaction mode where taps start playback" + }, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": { + "description": "Subtitle for streaming interaction mode" + }, "optionsUseExtensionProviders": "拡張のプロバイダーを使用する", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -1340,6 +1372,10 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": { + "description": "Error - seek disabled for live decrypted stream" + }, "errorMissingExtensionSource": "{item} を読み込めません: 拡張ソースがありません", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -2022,6 +2058,15 @@ } } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, "tracksCount": "{count, plural, =1{1 個のトラック} other{{count} 個のトラック}}", "@tracksCount": { "description": "Track count display", @@ -2931,10 +2976,18 @@ "@discographyDownload": { "description": "Button - download artist discography" }, + "discographyPlay": "Play Discography", + "@discographyPlay": { + "description": "Button - play artist discography" + }, "discographyDownloadAll": "すべてダウンロード", "@discographyDownloadAll": { "description": "Option - download entire discography" }, + "discographyPlayAll": "Play All", + "@discographyPlayAll": { + "description": "Option - play entire discography" + }, "discographyDownloadAllSubtitle": "{albumCount} 個のリリースから {count} 個のトラック", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3016,6 +3069,10 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, + "discographyPlaySelected": "Play Selected", + "@discographyPlaySelected": { + "description": "Button - play selected albums" + }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3182,6 +3239,15 @@ } } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "最終スキャン: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -3809,7 +3875,7 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Re-enrich", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, @@ -3927,5 +3993,542 @@ "trackConvertFailed": "変換に失敗しました", "@trackConvertFailed": { "description": "Snackbar when conversion fails" + }, + "actionCreate": "Create", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "My folders", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Wishlist", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Loved", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Playlists", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Playlist name", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Add to Loved", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Remove cover image", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "No shareable files found", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "No convertible tracks selected", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Batch Convert", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Converting {current} of {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": { + "description": "Title for mode selection step in setup wizard" + }, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": { + "description": "Description for mode selection step" + }, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": { + "description": "Title for downloader mode option" + }, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": { + "description": "Downloader mode feature 1" + }, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": { + "description": "Downloader mode feature 2" + }, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": { + "description": "Downloader mode feature 3" + }, + "setupModeStreamingTitle": "Streaming", + "@setupModeStreamingTitle": { + "description": "Title for streaming mode option" + }, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": { + "description": "Streaming mode feature 1" + }, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": { + "description": "Streaming mode feature 2" + }, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": { + "description": "Streaming mode feature 3" + }, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": { + "description": "Hint that mode can be changed later" + }, + "settingsSmartQueueTitle": "Smart Queue", + "@settingsSmartQueueTitle": { + "description": "Title for Smart Queue toggle in settings" + }, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": { + "description": "Subtitle for Smart Queue toggle in settings" + }, + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": { + "description": "Title for the What's New screen" + }, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": { + "description": "Subtitle for the What's New screen" + }, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": { + "description": "Welcome page title in What's New screen" + }, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": { + "description": "Welcome page description in What's New screen" + }, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": { + "description": "Welcome page tip 1" + }, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": { + "description": "Welcome page tip 2" + }, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": { + "description": "Welcome page tip 3" + }, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": { + "description": "What's New feature: Streaming Mode title" + }, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": { + "description": "What's New feature: Streaming Mode description" + }, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": { + "description": "What's New feature: Smart Queue title" + }, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": { + "description": "What's New feature: Smart Queue description" + }, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": { + "description": "What's New feature: Dual Mode title" + }, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": { + "description": "What's New feature: Dual Mode description" + }, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": { + "description": "What's New feature: Library redesign title" + }, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": { + "description": "What's New feature: Library redesign description" + }, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": { + "description": "What's New feature: Full-Screen Player title" + }, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": { + "description": "What's New feature: Full-Screen Player description" + }, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": { + "description": "What's New feature: Context Menus title" + }, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": { + "description": "What's New feature: Context Menus description" + }, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": { + "description": "What's New feature: Performance title" + }, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": { + "description": "What's New feature: Performance description" + }, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": { + "description": "What's New feature: Batch Tools title" + }, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": { + "description": "What's New feature: Batch Tools description" + }, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": { + "description": "What's New tip: streaming instant play" + }, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": { + "description": "What's New tip: streaming synced lyrics" + }, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": { + "description": "What's New tip: streaming download from player" + }, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": { + "description": "What's New tip: smart queue auto-fill" + }, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": { + "description": "What's New tip: smart queue artist discovery" + }, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": { + "description": "What's New tip: smart queue endless" + }, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": { + "description": "What's New tip: dual mode switch" + }, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": { + "description": "What's New tip: dual mode adaptive UI" + }, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": { + "description": "What's New tip: dual mode use cases" + }, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": { + "description": "What's New tip: library drag and drop" + }, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": { + "description": "What's New tip: library custom covers" + }, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": { + "description": "What's New tip: library multi-select" + }, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": { + "description": "What's New tip: player parallax" + }, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": { + "description": "What's New tip: player persistence" + }, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": { + "description": "What's New tip: player lyrics" + }, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": { + "description": "What's New tip: context menu add to playlist" + }, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": { + "description": "What's New tip: context menu share/convert" + }, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": { + "description": "What's New tip: context menu re-enrich" + }, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": { + "description": "What's New tip: batch share" + }, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": { + "description": "What's New tip: batch convert" + }, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": { + "description": "What's New tip: batch re-enrich" + }, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": { + "description": "What's New tip: performance startup" + }, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": { + "description": "What's New tip: performance memory" + }, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": { + "description": "What's New tip: performance SQLite" + }, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": { + "description": "Ready card message on last What's New page" + }, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": { + "description": "Button text to dismiss What's New screen" + }, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } } } \ No newline at end of file From 57b5877fdccf076c9d5364ce4aa2f9b8edf54fac Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:46 +0700 Subject: [PATCH 044/104] New translations app_en.arb (Korean) --- lib/l10n/arb/app_ko.arb | 605 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 604 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_ko.arb b/lib/l10n/arb/app_ko.arb index 5c509893..7739b0dd 100644 --- a/lib/l10n/arb/app_ko.arb +++ b/lib/l10n/arb/app_ko.arb @@ -308,6 +308,38 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": { + "description": "Toggle to skip to the next queue track when current track stream resolution fails" + }, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": { + "description": "Subtitle when auto skip on resolve failure is enabled" + }, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": { + "description": "Subtitle when auto skip on resolve failure is disabled" + }, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": { + "description": "Tap behavior mode for track lists" + }, + "modeDownloader": "Downloader Mode", + "@modeDownloader": { + "description": "Interaction mode where taps queue downloads" + }, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": { + "description": "Subtitle for downloader interaction mode" + }, + "modeStreaming": "Streaming Mode", + "@modeStreaming": { + "description": "Interaction mode where taps start playback" + }, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": { + "description": "Subtitle for streaming interaction mode" + }, "optionsUseExtensionProviders": "확장 기능 사용", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -1340,6 +1372,10 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": { + "description": "Error - seek disabled for live decrypted stream" + }, "errorMissingExtensionSource": "확장 소스가 누락되어, {item}(을)를 로드할 수 없습니다", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -2022,6 +2058,15 @@ } } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2931,10 +2976,18 @@ "@discographyDownload": { "description": "Button - download artist discography" }, + "discographyPlay": "Play Discography", + "@discographyPlay": { + "description": "Button - play artist discography" + }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, + "discographyPlayAll": "Play All", + "@discographyPlayAll": { + "description": "Option - play entire discography" + }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3016,6 +3069,10 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, + "discographyPlaySelected": "Play Selected", + "@discographyPlaySelected": { + "description": "Button - play selected albums" + }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3182,6 +3239,15 @@ } } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -3809,7 +3875,7 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Re-enrich", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, @@ -3927,5 +3993,542 @@ "trackConvertFailed": "Conversion failed", "@trackConvertFailed": { "description": "Snackbar when conversion fails" + }, + "actionCreate": "Create", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "My folders", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Wishlist", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Loved", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Playlists", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Playlist name", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Add to Loved", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Remove cover image", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "No shareable files found", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "No convertible tracks selected", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Batch Convert", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Converting {current} of {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": { + "description": "Title for mode selection step in setup wizard" + }, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": { + "description": "Description for mode selection step" + }, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": { + "description": "Title for downloader mode option" + }, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": { + "description": "Downloader mode feature 1" + }, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": { + "description": "Downloader mode feature 2" + }, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": { + "description": "Downloader mode feature 3" + }, + "setupModeStreamingTitle": "Streaming", + "@setupModeStreamingTitle": { + "description": "Title for streaming mode option" + }, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": { + "description": "Streaming mode feature 1" + }, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": { + "description": "Streaming mode feature 2" + }, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": { + "description": "Streaming mode feature 3" + }, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": { + "description": "Hint that mode can be changed later" + }, + "settingsSmartQueueTitle": "Smart Queue", + "@settingsSmartQueueTitle": { + "description": "Title for Smart Queue toggle in settings" + }, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": { + "description": "Subtitle for Smart Queue toggle in settings" + }, + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": { + "description": "Title for the What's New screen" + }, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": { + "description": "Subtitle for the What's New screen" + }, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": { + "description": "Welcome page title in What's New screen" + }, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": { + "description": "Welcome page description in What's New screen" + }, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": { + "description": "Welcome page tip 1" + }, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": { + "description": "Welcome page tip 2" + }, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": { + "description": "Welcome page tip 3" + }, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": { + "description": "What's New feature: Streaming Mode title" + }, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": { + "description": "What's New feature: Streaming Mode description" + }, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": { + "description": "What's New feature: Smart Queue title" + }, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": { + "description": "What's New feature: Smart Queue description" + }, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": { + "description": "What's New feature: Dual Mode title" + }, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": { + "description": "What's New feature: Dual Mode description" + }, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": { + "description": "What's New feature: Library redesign title" + }, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": { + "description": "What's New feature: Library redesign description" + }, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": { + "description": "What's New feature: Full-Screen Player title" + }, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": { + "description": "What's New feature: Full-Screen Player description" + }, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": { + "description": "What's New feature: Context Menus title" + }, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": { + "description": "What's New feature: Context Menus description" + }, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": { + "description": "What's New feature: Performance title" + }, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": { + "description": "What's New feature: Performance description" + }, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": { + "description": "What's New feature: Batch Tools title" + }, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": { + "description": "What's New feature: Batch Tools description" + }, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": { + "description": "What's New tip: streaming instant play" + }, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": { + "description": "What's New tip: streaming synced lyrics" + }, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": { + "description": "What's New tip: streaming download from player" + }, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": { + "description": "What's New tip: smart queue auto-fill" + }, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": { + "description": "What's New tip: smart queue artist discovery" + }, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": { + "description": "What's New tip: smart queue endless" + }, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": { + "description": "What's New tip: dual mode switch" + }, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": { + "description": "What's New tip: dual mode adaptive UI" + }, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": { + "description": "What's New tip: dual mode use cases" + }, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": { + "description": "What's New tip: library drag and drop" + }, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": { + "description": "What's New tip: library custom covers" + }, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": { + "description": "What's New tip: library multi-select" + }, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": { + "description": "What's New tip: player parallax" + }, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": { + "description": "What's New tip: player persistence" + }, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": { + "description": "What's New tip: player lyrics" + }, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": { + "description": "What's New tip: context menu add to playlist" + }, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": { + "description": "What's New tip: context menu share/convert" + }, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": { + "description": "What's New tip: context menu re-enrich" + }, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": { + "description": "What's New tip: batch share" + }, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": { + "description": "What's New tip: batch convert" + }, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": { + "description": "What's New tip: batch re-enrich" + }, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": { + "description": "What's New tip: performance startup" + }, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": { + "description": "What's New tip: performance memory" + }, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": { + "description": "What's New tip: performance SQLite" + }, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": { + "description": "Ready card message on last What's New page" + }, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": { + "description": "Button text to dismiss What's New screen" + }, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } } } \ No newline at end of file From 8c047600a04f97d2fea963765feca21841e7fb19 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:48 +0700 Subject: [PATCH 045/104] New translations app_en.arb (Dutch) --- lib/l10n/arb/app_nl.arb | 605 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 604 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_nl.arb b/lib/l10n/arb/app_nl.arb index 6185ecfe..b96df20f 100644 --- a/lib/l10n/arb/app_nl.arb +++ b/lib/l10n/arb/app_nl.arb @@ -308,6 +308,38 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": { + "description": "Toggle to skip to the next queue track when current track stream resolution fails" + }, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": { + "description": "Subtitle when auto skip on resolve failure is enabled" + }, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": { + "description": "Subtitle when auto skip on resolve failure is disabled" + }, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": { + "description": "Tap behavior mode for track lists" + }, + "modeDownloader": "Downloader Mode", + "@modeDownloader": { + "description": "Interaction mode where taps queue downloads" + }, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": { + "description": "Subtitle for downloader interaction mode" + }, + "modeStreaming": "Streaming Mode", + "@modeStreaming": { + "description": "Interaction mode where taps start playback" + }, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": { + "description": "Subtitle for streaming interaction mode" + }, "optionsUseExtensionProviders": "Use Extension Providers", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -1340,6 +1372,10 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": { + "description": "Error - seek disabled for live decrypted stream" + }, "errorMissingExtensionSource": "Cannot load {item}: missing extension source", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -2022,6 +2058,15 @@ } } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2931,10 +2976,18 @@ "@discographyDownload": { "description": "Button - download artist discography" }, + "discographyPlay": "Play Discography", + "@discographyPlay": { + "description": "Button - play artist discography" + }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, + "discographyPlayAll": "Play All", + "@discographyPlayAll": { + "description": "Option - play entire discography" + }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3016,6 +3069,10 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, + "discographyPlaySelected": "Play Selected", + "@discographyPlaySelected": { + "description": "Button - play selected albums" + }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3182,6 +3239,15 @@ } } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -3809,7 +3875,7 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Re-enrich", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, @@ -3927,5 +3993,542 @@ "trackConvertFailed": "Conversion failed", "@trackConvertFailed": { "description": "Snackbar when conversion fails" + }, + "actionCreate": "Create", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "My folders", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Wishlist", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Loved", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Playlists", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Playlist name", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Add to Loved", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Remove cover image", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "No shareable files found", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "No convertible tracks selected", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Batch Convert", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Converting {current} of {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": { + "description": "Title for mode selection step in setup wizard" + }, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": { + "description": "Description for mode selection step" + }, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": { + "description": "Title for downloader mode option" + }, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": { + "description": "Downloader mode feature 1" + }, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": { + "description": "Downloader mode feature 2" + }, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": { + "description": "Downloader mode feature 3" + }, + "setupModeStreamingTitle": "Streaming", + "@setupModeStreamingTitle": { + "description": "Title for streaming mode option" + }, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": { + "description": "Streaming mode feature 1" + }, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": { + "description": "Streaming mode feature 2" + }, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": { + "description": "Streaming mode feature 3" + }, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": { + "description": "Hint that mode can be changed later" + }, + "settingsSmartQueueTitle": "Smart Queue", + "@settingsSmartQueueTitle": { + "description": "Title for Smart Queue toggle in settings" + }, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": { + "description": "Subtitle for Smart Queue toggle in settings" + }, + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": { + "description": "Title for the What's New screen" + }, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": { + "description": "Subtitle for the What's New screen" + }, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": { + "description": "Welcome page title in What's New screen" + }, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": { + "description": "Welcome page description in What's New screen" + }, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": { + "description": "Welcome page tip 1" + }, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": { + "description": "Welcome page tip 2" + }, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": { + "description": "Welcome page tip 3" + }, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": { + "description": "What's New feature: Streaming Mode title" + }, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": { + "description": "What's New feature: Streaming Mode description" + }, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": { + "description": "What's New feature: Smart Queue title" + }, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": { + "description": "What's New feature: Smart Queue description" + }, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": { + "description": "What's New feature: Dual Mode title" + }, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": { + "description": "What's New feature: Dual Mode description" + }, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": { + "description": "What's New feature: Library redesign title" + }, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": { + "description": "What's New feature: Library redesign description" + }, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": { + "description": "What's New feature: Full-Screen Player title" + }, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": { + "description": "What's New feature: Full-Screen Player description" + }, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": { + "description": "What's New feature: Context Menus title" + }, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": { + "description": "What's New feature: Context Menus description" + }, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": { + "description": "What's New feature: Performance title" + }, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": { + "description": "What's New feature: Performance description" + }, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": { + "description": "What's New feature: Batch Tools title" + }, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": { + "description": "What's New feature: Batch Tools description" + }, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": { + "description": "What's New tip: streaming instant play" + }, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": { + "description": "What's New tip: streaming synced lyrics" + }, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": { + "description": "What's New tip: streaming download from player" + }, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": { + "description": "What's New tip: smart queue auto-fill" + }, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": { + "description": "What's New tip: smart queue artist discovery" + }, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": { + "description": "What's New tip: smart queue endless" + }, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": { + "description": "What's New tip: dual mode switch" + }, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": { + "description": "What's New tip: dual mode adaptive UI" + }, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": { + "description": "What's New tip: dual mode use cases" + }, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": { + "description": "What's New tip: library drag and drop" + }, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": { + "description": "What's New tip: library custom covers" + }, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": { + "description": "What's New tip: library multi-select" + }, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": { + "description": "What's New tip: player parallax" + }, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": { + "description": "What's New tip: player persistence" + }, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": { + "description": "What's New tip: player lyrics" + }, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": { + "description": "What's New tip: context menu add to playlist" + }, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": { + "description": "What's New tip: context menu share/convert" + }, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": { + "description": "What's New tip: context menu re-enrich" + }, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": { + "description": "What's New tip: batch share" + }, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": { + "description": "What's New tip: batch convert" + }, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": { + "description": "What's New tip: batch re-enrich" + }, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": { + "description": "What's New tip: performance startup" + }, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": { + "description": "What's New tip: performance memory" + }, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": { + "description": "What's New tip: performance SQLite" + }, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": { + "description": "Ready card message on last What's New page" + }, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": { + "description": "Button text to dismiss What's New screen" + }, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } } } \ No newline at end of file From 0222ea6ccbe08414fbdff8b755f30eefea6ad61f Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:49 +0700 Subject: [PATCH 046/104] New translations app_en.arb (Portuguese) --- lib/l10n/arb/app_pt-PT.arb | 605 ++++++++++++++++++++++++++++++++++++- 1 file changed, 604 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_pt-PT.arb b/lib/l10n/arb/app_pt-PT.arb index 05cf5136..2cb659a1 100644 --- a/lib/l10n/arb/app_pt-PT.arb +++ b/lib/l10n/arb/app_pt-PT.arb @@ -308,6 +308,38 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": { + "description": "Toggle to skip to the next queue track when current track stream resolution fails" + }, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": { + "description": "Subtitle when auto skip on resolve failure is enabled" + }, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": { + "description": "Subtitle when auto skip on resolve failure is disabled" + }, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": { + "description": "Tap behavior mode for track lists" + }, + "modeDownloader": "Downloader Mode", + "@modeDownloader": { + "description": "Interaction mode where taps queue downloads" + }, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": { + "description": "Subtitle for downloader interaction mode" + }, + "modeStreaming": "Streaming Mode", + "@modeStreaming": { + "description": "Interaction mode where taps start playback" + }, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": { + "description": "Subtitle for streaming interaction mode" + }, "optionsUseExtensionProviders": "Usar Provedores de Extensão", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -1340,6 +1372,10 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": { + "description": "Error - seek disabled for live decrypted stream" + }, "errorMissingExtensionSource": "Não é possível carregar {item}: faltando a fonte da extensão", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -2022,6 +2058,15 @@ } } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, "tracksCount": "{count, plural, one {}=1{1 faixa} other{{count} faixas}}", "@tracksCount": { "description": "Track count display", @@ -2931,10 +2976,18 @@ "@discographyDownload": { "description": "Button - download artist discography" }, + "discographyPlay": "Play Discography", + "@discographyPlay": { + "description": "Button - play artist discography" + }, "discographyDownloadAll": "Baixar Tudo", "@discographyDownloadAll": { "description": "Option - download entire discography" }, + "discographyPlayAll": "Play All", + "@discographyPlayAll": { + "description": "Option - play entire discography" + }, "discographyDownloadAllSubtitle": "{count} faixas de {albumCount} lançamentos", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3016,6 +3069,10 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, + "discographyPlaySelected": "Play Selected", + "@discographyPlaySelected": { + "description": "Button - play selected albums" + }, "discographyAddedToQueue": "{count} faixas adicionadas à fila", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3182,6 +3239,15 @@ } } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -3809,7 +3875,7 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Re-enrich", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, @@ -3927,5 +3993,542 @@ "trackConvertFailed": "Conversion failed", "@trackConvertFailed": { "description": "Snackbar when conversion fails" + }, + "actionCreate": "Create", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "My folders", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Wishlist", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Loved", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Playlists", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Playlist name", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Add to Loved", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Remove cover image", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "No shareable files found", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "No convertible tracks selected", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Batch Convert", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Converting {current} of {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": { + "description": "Title for mode selection step in setup wizard" + }, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": { + "description": "Description for mode selection step" + }, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": { + "description": "Title for downloader mode option" + }, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": { + "description": "Downloader mode feature 1" + }, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": { + "description": "Downloader mode feature 2" + }, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": { + "description": "Downloader mode feature 3" + }, + "setupModeStreamingTitle": "Streaming", + "@setupModeStreamingTitle": { + "description": "Title for streaming mode option" + }, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": { + "description": "Streaming mode feature 1" + }, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": { + "description": "Streaming mode feature 2" + }, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": { + "description": "Streaming mode feature 3" + }, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": { + "description": "Hint that mode can be changed later" + }, + "settingsSmartQueueTitle": "Smart Queue", + "@settingsSmartQueueTitle": { + "description": "Title for Smart Queue toggle in settings" + }, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": { + "description": "Subtitle for Smart Queue toggle in settings" + }, + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": { + "description": "Title for the What's New screen" + }, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": { + "description": "Subtitle for the What's New screen" + }, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": { + "description": "Welcome page title in What's New screen" + }, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": { + "description": "Welcome page description in What's New screen" + }, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": { + "description": "Welcome page tip 1" + }, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": { + "description": "Welcome page tip 2" + }, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": { + "description": "Welcome page tip 3" + }, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": { + "description": "What's New feature: Streaming Mode title" + }, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": { + "description": "What's New feature: Streaming Mode description" + }, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": { + "description": "What's New feature: Smart Queue title" + }, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": { + "description": "What's New feature: Smart Queue description" + }, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": { + "description": "What's New feature: Dual Mode title" + }, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": { + "description": "What's New feature: Dual Mode description" + }, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": { + "description": "What's New feature: Library redesign title" + }, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": { + "description": "What's New feature: Library redesign description" + }, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": { + "description": "What's New feature: Full-Screen Player title" + }, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": { + "description": "What's New feature: Full-Screen Player description" + }, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": { + "description": "What's New feature: Context Menus title" + }, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": { + "description": "What's New feature: Context Menus description" + }, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": { + "description": "What's New feature: Performance title" + }, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": { + "description": "What's New feature: Performance description" + }, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": { + "description": "What's New feature: Batch Tools title" + }, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": { + "description": "What's New feature: Batch Tools description" + }, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": { + "description": "What's New tip: streaming instant play" + }, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": { + "description": "What's New tip: streaming synced lyrics" + }, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": { + "description": "What's New tip: streaming download from player" + }, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": { + "description": "What's New tip: smart queue auto-fill" + }, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": { + "description": "What's New tip: smart queue artist discovery" + }, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": { + "description": "What's New tip: smart queue endless" + }, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": { + "description": "What's New tip: dual mode switch" + }, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": { + "description": "What's New tip: dual mode adaptive UI" + }, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": { + "description": "What's New tip: dual mode use cases" + }, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": { + "description": "What's New tip: library drag and drop" + }, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": { + "description": "What's New tip: library custom covers" + }, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": { + "description": "What's New tip: library multi-select" + }, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": { + "description": "What's New tip: player parallax" + }, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": { + "description": "What's New tip: player persistence" + }, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": { + "description": "What's New tip: player lyrics" + }, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": { + "description": "What's New tip: context menu add to playlist" + }, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": { + "description": "What's New tip: context menu share/convert" + }, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": { + "description": "What's New tip: context menu re-enrich" + }, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": { + "description": "What's New tip: batch share" + }, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": { + "description": "What's New tip: batch convert" + }, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": { + "description": "What's New tip: batch re-enrich" + }, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": { + "description": "What's New tip: performance startup" + }, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": { + "description": "What's New tip: performance memory" + }, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": { + "description": "What's New tip: performance SQLite" + }, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": { + "description": "Ready card message on last What's New page" + }, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": { + "description": "Button text to dismiss What's New screen" + }, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } } } \ No newline at end of file From deb6d92b55ed4631f8d0c1c296616af9f0bea651 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:50 +0700 Subject: [PATCH 047/104] New translations app_en.arb (Russian) --- lib/l10n/arb/app_ru.arb | 605 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 604 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 9cb864de..55c9d945 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -308,6 +308,38 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": { + "description": "Toggle to skip to the next queue track when current track stream resolution fails" + }, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": { + "description": "Subtitle when auto skip on resolve failure is enabled" + }, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": { + "description": "Subtitle when auto skip on resolve failure is disabled" + }, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": { + "description": "Tap behavior mode for track lists" + }, + "modeDownloader": "Downloader Mode", + "@modeDownloader": { + "description": "Interaction mode where taps queue downloads" + }, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": { + "description": "Subtitle for downloader interaction mode" + }, + "modeStreaming": "Streaming Mode", + "@modeStreaming": { + "description": "Interaction mode where taps start playback" + }, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": { + "description": "Subtitle for streaming interaction mode" + }, "optionsUseExtensionProviders": "Использовать провайдера расширений", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -1340,6 +1372,10 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": { + "description": "Error - seek disabled for live decrypted stream" + }, "errorMissingExtensionSource": "Невозможно загрузить {item}: отсутствует источник расширения", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -2022,6 +2058,15 @@ } } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, "tracksCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}", "@tracksCount": { "description": "Track count display", @@ -2931,10 +2976,18 @@ "@discographyDownload": { "description": "Button - download artist discography" }, + "discographyPlay": "Play Discography", + "@discographyPlay": { + "description": "Button - play artist discography" + }, "discographyDownloadAll": "Скачать всё", "@discographyDownloadAll": { "description": "Option - download entire discography" }, + "discographyPlayAll": "Play All", + "@discographyPlayAll": { + "description": "Option - play entire discography" + }, "discographyDownloadAllSubtitle": "{count} треков из {albumCount} релизов", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3016,6 +3069,10 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, + "discographyPlaySelected": "Play Selected", + "@discographyPlaySelected": { + "description": "Button - play selected albums" + }, "discographyAddedToQueue": "Добавлено {count} треков в очередь", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3182,6 +3239,15 @@ } } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Последнее сканирование: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -3809,7 +3875,7 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Re-enrich", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, @@ -3927,5 +3993,542 @@ "trackConvertFailed": "Ошибка конвертации", "@trackConvertFailed": { "description": "Snackbar when conversion fails" + }, + "actionCreate": "Create", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "My folders", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Wishlist", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Loved", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Playlists", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Playlist name", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Add to Loved", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Remove cover image", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "No shareable files found", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "No convertible tracks selected", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Batch Convert", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Converting {current} of {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": { + "description": "Title for mode selection step in setup wizard" + }, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": { + "description": "Description for mode selection step" + }, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": { + "description": "Title for downloader mode option" + }, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": { + "description": "Downloader mode feature 1" + }, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": { + "description": "Downloader mode feature 2" + }, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": { + "description": "Downloader mode feature 3" + }, + "setupModeStreamingTitle": "Streaming", + "@setupModeStreamingTitle": { + "description": "Title for streaming mode option" + }, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": { + "description": "Streaming mode feature 1" + }, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": { + "description": "Streaming mode feature 2" + }, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": { + "description": "Streaming mode feature 3" + }, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": { + "description": "Hint that mode can be changed later" + }, + "settingsSmartQueueTitle": "Smart Queue", + "@settingsSmartQueueTitle": { + "description": "Title for Smart Queue toggle in settings" + }, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": { + "description": "Subtitle for Smart Queue toggle in settings" + }, + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": { + "description": "Title for the What's New screen" + }, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": { + "description": "Subtitle for the What's New screen" + }, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": { + "description": "Welcome page title in What's New screen" + }, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": { + "description": "Welcome page description in What's New screen" + }, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": { + "description": "Welcome page tip 1" + }, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": { + "description": "Welcome page tip 2" + }, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": { + "description": "Welcome page tip 3" + }, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": { + "description": "What's New feature: Streaming Mode title" + }, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": { + "description": "What's New feature: Streaming Mode description" + }, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": { + "description": "What's New feature: Smart Queue title" + }, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": { + "description": "What's New feature: Smart Queue description" + }, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": { + "description": "What's New feature: Dual Mode title" + }, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": { + "description": "What's New feature: Dual Mode description" + }, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": { + "description": "What's New feature: Library redesign title" + }, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": { + "description": "What's New feature: Library redesign description" + }, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": { + "description": "What's New feature: Full-Screen Player title" + }, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": { + "description": "What's New feature: Full-Screen Player description" + }, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": { + "description": "What's New feature: Context Menus title" + }, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": { + "description": "What's New feature: Context Menus description" + }, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": { + "description": "What's New feature: Performance title" + }, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": { + "description": "What's New feature: Performance description" + }, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": { + "description": "What's New feature: Batch Tools title" + }, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": { + "description": "What's New feature: Batch Tools description" + }, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": { + "description": "What's New tip: streaming instant play" + }, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": { + "description": "What's New tip: streaming synced lyrics" + }, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": { + "description": "What's New tip: streaming download from player" + }, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": { + "description": "What's New tip: smart queue auto-fill" + }, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": { + "description": "What's New tip: smart queue artist discovery" + }, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": { + "description": "What's New tip: smart queue endless" + }, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": { + "description": "What's New tip: dual mode switch" + }, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": { + "description": "What's New tip: dual mode adaptive UI" + }, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": { + "description": "What's New tip: dual mode use cases" + }, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": { + "description": "What's New tip: library drag and drop" + }, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": { + "description": "What's New tip: library custom covers" + }, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": { + "description": "What's New tip: library multi-select" + }, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": { + "description": "What's New tip: player parallax" + }, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": { + "description": "What's New tip: player persistence" + }, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": { + "description": "What's New tip: player lyrics" + }, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": { + "description": "What's New tip: context menu add to playlist" + }, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": { + "description": "What's New tip: context menu share/convert" + }, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": { + "description": "What's New tip: context menu re-enrich" + }, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": { + "description": "What's New tip: batch share" + }, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": { + "description": "What's New tip: batch convert" + }, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": { + "description": "What's New tip: batch re-enrich" + }, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": { + "description": "What's New tip: performance startup" + }, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": { + "description": "What's New tip: performance memory" + }, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": { + "description": "What's New tip: performance SQLite" + }, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": { + "description": "Ready card message on last What's New page" + }, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": { + "description": "Button text to dismiss What's New screen" + }, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } } } \ No newline at end of file From 72f4663dd5773296be212ddcc11ff56347af9801 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:51 +0700 Subject: [PATCH 048/104] New translations app_en.arb (Chinese Simplified) --- lib/l10n/arb/app_zh_CN.arb | 605 ++++++++++++++++++++++++++++++++++++- 1 file changed, 604 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_zh_CN.arb b/lib/l10n/arb/app_zh_CN.arb index 24b28679..9dbae850 100644 --- a/lib/l10n/arb/app_zh_CN.arb +++ b/lib/l10n/arb/app_zh_CN.arb @@ -308,6 +308,38 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": { + "description": "Toggle to skip to the next queue track when current track stream resolution fails" + }, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": { + "description": "Subtitle when auto skip on resolve failure is enabled" + }, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": { + "description": "Subtitle when auto skip on resolve failure is disabled" + }, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": { + "description": "Tap behavior mode for track lists" + }, + "modeDownloader": "Downloader Mode", + "@modeDownloader": { + "description": "Interaction mode where taps queue downloads" + }, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": { + "description": "Subtitle for downloader interaction mode" + }, + "modeStreaming": "Streaming Mode", + "@modeStreaming": { + "description": "Interaction mode where taps start playback" + }, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": { + "description": "Subtitle for streaming interaction mode" + }, "optionsUseExtensionProviders": "Use Extension Providers", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -1340,6 +1372,10 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": { + "description": "Error - seek disabled for live decrypted stream" + }, "errorMissingExtensionSource": "Cannot load {item}: missing extension source", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -2022,6 +2058,15 @@ } } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2931,10 +2976,18 @@ "@discographyDownload": { "description": "Button - download artist discography" }, + "discographyPlay": "Play Discography", + "@discographyPlay": { + "description": "Button - play artist discography" + }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, + "discographyPlayAll": "Play All", + "@discographyPlayAll": { + "description": "Option - play entire discography" + }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3016,6 +3069,10 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, + "discographyPlaySelected": "Play Selected", + "@discographyPlaySelected": { + "description": "Button - play selected albums" + }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3182,6 +3239,15 @@ } } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -3809,7 +3875,7 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Re-enrich", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, @@ -3927,5 +3993,542 @@ "trackConvertFailed": "Conversion failed", "@trackConvertFailed": { "description": "Snackbar when conversion fails" + }, + "actionCreate": "Create", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "My folders", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Wishlist", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Loved", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Playlists", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Playlist name", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Add to Loved", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Remove cover image", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "No shareable files found", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "No convertible tracks selected", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Batch Convert", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Converting {current} of {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": { + "description": "Title for mode selection step in setup wizard" + }, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": { + "description": "Description for mode selection step" + }, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": { + "description": "Title for downloader mode option" + }, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": { + "description": "Downloader mode feature 1" + }, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": { + "description": "Downloader mode feature 2" + }, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": { + "description": "Downloader mode feature 3" + }, + "setupModeStreamingTitle": "Streaming", + "@setupModeStreamingTitle": { + "description": "Title for streaming mode option" + }, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": { + "description": "Streaming mode feature 1" + }, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": { + "description": "Streaming mode feature 2" + }, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": { + "description": "Streaming mode feature 3" + }, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": { + "description": "Hint that mode can be changed later" + }, + "settingsSmartQueueTitle": "Smart Queue", + "@settingsSmartQueueTitle": { + "description": "Title for Smart Queue toggle in settings" + }, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": { + "description": "Subtitle for Smart Queue toggle in settings" + }, + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": { + "description": "Title for the What's New screen" + }, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": { + "description": "Subtitle for the What's New screen" + }, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": { + "description": "Welcome page title in What's New screen" + }, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": { + "description": "Welcome page description in What's New screen" + }, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": { + "description": "Welcome page tip 1" + }, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": { + "description": "Welcome page tip 2" + }, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": { + "description": "Welcome page tip 3" + }, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": { + "description": "What's New feature: Streaming Mode title" + }, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": { + "description": "What's New feature: Streaming Mode description" + }, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": { + "description": "What's New feature: Smart Queue title" + }, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": { + "description": "What's New feature: Smart Queue description" + }, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": { + "description": "What's New feature: Dual Mode title" + }, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": { + "description": "What's New feature: Dual Mode description" + }, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": { + "description": "What's New feature: Library redesign title" + }, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": { + "description": "What's New feature: Library redesign description" + }, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": { + "description": "What's New feature: Full-Screen Player title" + }, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": { + "description": "What's New feature: Full-Screen Player description" + }, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": { + "description": "What's New feature: Context Menus title" + }, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": { + "description": "What's New feature: Context Menus description" + }, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": { + "description": "What's New feature: Performance title" + }, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": { + "description": "What's New feature: Performance description" + }, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": { + "description": "What's New feature: Batch Tools title" + }, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": { + "description": "What's New feature: Batch Tools description" + }, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": { + "description": "What's New tip: streaming instant play" + }, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": { + "description": "What's New tip: streaming synced lyrics" + }, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": { + "description": "What's New tip: streaming download from player" + }, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": { + "description": "What's New tip: smart queue auto-fill" + }, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": { + "description": "What's New tip: smart queue artist discovery" + }, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": { + "description": "What's New tip: smart queue endless" + }, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": { + "description": "What's New tip: dual mode switch" + }, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": { + "description": "What's New tip: dual mode adaptive UI" + }, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": { + "description": "What's New tip: dual mode use cases" + }, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": { + "description": "What's New tip: library drag and drop" + }, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": { + "description": "What's New tip: library custom covers" + }, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": { + "description": "What's New tip: library multi-select" + }, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": { + "description": "What's New tip: player parallax" + }, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": { + "description": "What's New tip: player persistence" + }, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": { + "description": "What's New tip: player lyrics" + }, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": { + "description": "What's New tip: context menu add to playlist" + }, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": { + "description": "What's New tip: context menu share/convert" + }, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": { + "description": "What's New tip: context menu re-enrich" + }, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": { + "description": "What's New tip: batch share" + }, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": { + "description": "What's New tip: batch convert" + }, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": { + "description": "What's New tip: batch re-enrich" + }, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": { + "description": "What's New tip: performance startup" + }, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": { + "description": "What's New tip: performance memory" + }, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": { + "description": "What's New tip: performance SQLite" + }, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": { + "description": "Ready card message on last What's New page" + }, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": { + "description": "Button text to dismiss What's New screen" + }, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } } } \ No newline at end of file From 765caab6dfc94463895ae0dc64f022d10add4343 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:52 +0700 Subject: [PATCH 049/104] New translations app_en.arb (Chinese Traditional) --- lib/l10n/arb/app_zh_TW.arb | 605 ++++++++++++++++++++++++++++++++++++- 1 file changed, 604 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_zh_TW.arb b/lib/l10n/arb/app_zh_TW.arb index 3896bf11..6b2af6ab 100644 --- a/lib/l10n/arb/app_zh_TW.arb +++ b/lib/l10n/arb/app_zh_TW.arb @@ -308,6 +308,38 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": { + "description": "Toggle to skip to the next queue track when current track stream resolution fails" + }, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": { + "description": "Subtitle when auto skip on resolve failure is enabled" + }, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": { + "description": "Subtitle when auto skip on resolve failure is disabled" + }, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": { + "description": "Tap behavior mode for track lists" + }, + "modeDownloader": "Downloader Mode", + "@modeDownloader": { + "description": "Interaction mode where taps queue downloads" + }, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": { + "description": "Subtitle for downloader interaction mode" + }, + "modeStreaming": "Streaming Mode", + "@modeStreaming": { + "description": "Interaction mode where taps start playback" + }, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": { + "description": "Subtitle for streaming interaction mode" + }, "optionsUseExtensionProviders": "Use Extension Providers", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -1340,6 +1372,10 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": { + "description": "Error - seek disabled for live decrypted stream" + }, "errorMissingExtensionSource": "Cannot load {item}: missing extension source", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -2022,6 +2058,15 @@ } } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2931,10 +2976,18 @@ "@discographyDownload": { "description": "Button - download artist discography" }, + "discographyPlay": "Play Discography", + "@discographyPlay": { + "description": "Button - play artist discography" + }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, + "discographyPlayAll": "Play All", + "@discographyPlayAll": { + "description": "Option - play entire discography" + }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3016,6 +3069,10 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, + "discographyPlaySelected": "Play Selected", + "@discographyPlaySelected": { + "description": "Button - play selected albums" + }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3182,6 +3239,15 @@ } } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -3809,7 +3875,7 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Re-enrich", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, @@ -3927,5 +3993,542 @@ "trackConvertFailed": "Conversion failed", "@trackConvertFailed": { "description": "Snackbar when conversion fails" + }, + "actionCreate": "Create", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "My folders", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Wishlist", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Loved", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Playlists", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Playlist name", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Add to Loved", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Remove cover image", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "No shareable files found", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "No convertible tracks selected", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Batch Convert", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Converting {current} of {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": { + "description": "Title for mode selection step in setup wizard" + }, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": { + "description": "Description for mode selection step" + }, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": { + "description": "Title for downloader mode option" + }, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": { + "description": "Downloader mode feature 1" + }, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": { + "description": "Downloader mode feature 2" + }, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": { + "description": "Downloader mode feature 3" + }, + "setupModeStreamingTitle": "Streaming", + "@setupModeStreamingTitle": { + "description": "Title for streaming mode option" + }, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": { + "description": "Streaming mode feature 1" + }, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": { + "description": "Streaming mode feature 2" + }, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": { + "description": "Streaming mode feature 3" + }, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": { + "description": "Hint that mode can be changed later" + }, + "settingsSmartQueueTitle": "Smart Queue", + "@settingsSmartQueueTitle": { + "description": "Title for Smart Queue toggle in settings" + }, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": { + "description": "Subtitle for Smart Queue toggle in settings" + }, + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": { + "description": "Title for the What's New screen" + }, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": { + "description": "Subtitle for the What's New screen" + }, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": { + "description": "Welcome page title in What's New screen" + }, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": { + "description": "Welcome page description in What's New screen" + }, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": { + "description": "Welcome page tip 1" + }, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": { + "description": "Welcome page tip 2" + }, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": { + "description": "Welcome page tip 3" + }, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": { + "description": "What's New feature: Streaming Mode title" + }, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": { + "description": "What's New feature: Streaming Mode description" + }, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": { + "description": "What's New feature: Smart Queue title" + }, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": { + "description": "What's New feature: Smart Queue description" + }, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": { + "description": "What's New feature: Dual Mode title" + }, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": { + "description": "What's New feature: Dual Mode description" + }, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": { + "description": "What's New feature: Library redesign title" + }, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": { + "description": "What's New feature: Library redesign description" + }, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": { + "description": "What's New feature: Full-Screen Player title" + }, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": { + "description": "What's New feature: Full-Screen Player description" + }, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": { + "description": "What's New feature: Context Menus title" + }, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": { + "description": "What's New feature: Context Menus description" + }, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": { + "description": "What's New feature: Performance title" + }, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": { + "description": "What's New feature: Performance description" + }, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": { + "description": "What's New feature: Batch Tools title" + }, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": { + "description": "What's New feature: Batch Tools description" + }, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": { + "description": "What's New tip: streaming instant play" + }, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": { + "description": "What's New tip: streaming synced lyrics" + }, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": { + "description": "What's New tip: streaming download from player" + }, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": { + "description": "What's New tip: smart queue auto-fill" + }, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": { + "description": "What's New tip: smart queue artist discovery" + }, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": { + "description": "What's New tip: smart queue endless" + }, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": { + "description": "What's New tip: dual mode switch" + }, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": { + "description": "What's New tip: dual mode adaptive UI" + }, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": { + "description": "What's New tip: dual mode use cases" + }, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": { + "description": "What's New tip: library drag and drop" + }, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": { + "description": "What's New tip: library custom covers" + }, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": { + "description": "What's New tip: library multi-select" + }, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": { + "description": "What's New tip: player parallax" + }, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": { + "description": "What's New tip: player persistence" + }, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": { + "description": "What's New tip: player lyrics" + }, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": { + "description": "What's New tip: context menu add to playlist" + }, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": { + "description": "What's New tip: context menu share/convert" + }, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": { + "description": "What's New tip: context menu re-enrich" + }, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": { + "description": "What's New tip: batch share" + }, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": { + "description": "What's New tip: batch convert" + }, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": { + "description": "What's New tip: batch re-enrich" + }, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": { + "description": "What's New tip: performance startup" + }, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": { + "description": "What's New tip: performance memory" + }, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": { + "description": "What's New tip: performance SQLite" + }, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": { + "description": "Ready card message on last What's New page" + }, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": { + "description": "Button text to dismiss What's New screen" + }, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } } } \ No newline at end of file From 103c55c0725f20371c8b3b5c012e9f3297488e3f Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:53 +0700 Subject: [PATCH 050/104] New translations app_en.arb (Indonesian) --- lib/l10n/arb/app_id.arb | 605 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 604 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index 0ac9b108..9bf75109 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -308,6 +308,38 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": { + "description": "Toggle to skip to the next queue track when current track stream resolution fails" + }, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": { + "description": "Subtitle when auto skip on resolve failure is enabled" + }, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": { + "description": "Subtitle when auto skip on resolve failure is disabled" + }, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": { + "description": "Tap behavior mode for track lists" + }, + "modeDownloader": "Downloader Mode", + "@modeDownloader": { + "description": "Interaction mode where taps queue downloads" + }, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": { + "description": "Subtitle for downloader interaction mode" + }, + "modeStreaming": "Streaming Mode", + "@modeStreaming": { + "description": "Interaction mode where taps start playback" + }, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": { + "description": "Subtitle for streaming interaction mode" + }, "optionsUseExtensionProviders": "Gunakan Provider Ekstensi", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -1340,6 +1372,10 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": { + "description": "Error - seek disabled for live decrypted stream" + }, "errorMissingExtensionSource": "Tidak dapat memuat {item}: sumber ekstensi tidak ada", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -2022,6 +2058,15 @@ } } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, "tracksCount": "{count, plural, =1{1 lagu} other{{count} lagu}}", "@tracksCount": { "description": "Track count display", @@ -2931,10 +2976,18 @@ "@discographyDownload": { "description": "Button - download artist discography" }, + "discographyPlay": "Play Discography", + "@discographyPlay": { + "description": "Button - play artist discography" + }, "discographyDownloadAll": "Unduh Semua", "@discographyDownloadAll": { "description": "Option - download entire discography" }, + "discographyPlayAll": "Play All", + "@discographyPlayAll": { + "description": "Option - play entire discography" + }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3016,6 +3069,10 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, + "discographyPlaySelected": "Play Selected", + "@discographyPlaySelected": { + "description": "Button - play selected albums" + }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3182,6 +3239,15 @@ } } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -3809,7 +3875,7 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Re-enrich", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, @@ -3927,5 +3993,542 @@ "trackConvertFailed": "Conversion failed", "@trackConvertFailed": { "description": "Snackbar when conversion fails" + }, + "actionCreate": "Create", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "My folders", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Wishlist", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Loved", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Playlists", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Playlist name", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Add to Loved", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Remove cover image", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "No shareable files found", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "No convertible tracks selected", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Batch Convert", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Converting {current} of {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": { + "description": "Title for mode selection step in setup wizard" + }, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": { + "description": "Description for mode selection step" + }, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": { + "description": "Title for downloader mode option" + }, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": { + "description": "Downloader mode feature 1" + }, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": { + "description": "Downloader mode feature 2" + }, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": { + "description": "Downloader mode feature 3" + }, + "setupModeStreamingTitle": "Streaming", + "@setupModeStreamingTitle": { + "description": "Title for streaming mode option" + }, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": { + "description": "Streaming mode feature 1" + }, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": { + "description": "Streaming mode feature 2" + }, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": { + "description": "Streaming mode feature 3" + }, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": { + "description": "Hint that mode can be changed later" + }, + "settingsSmartQueueTitle": "Smart Queue", + "@settingsSmartQueueTitle": { + "description": "Title for Smart Queue toggle in settings" + }, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": { + "description": "Subtitle for Smart Queue toggle in settings" + }, + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": { + "description": "Title for the What's New screen" + }, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": { + "description": "Subtitle for the What's New screen" + }, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": { + "description": "Welcome page title in What's New screen" + }, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": { + "description": "Welcome page description in What's New screen" + }, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": { + "description": "Welcome page tip 1" + }, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": { + "description": "Welcome page tip 2" + }, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": { + "description": "Welcome page tip 3" + }, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": { + "description": "What's New feature: Streaming Mode title" + }, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": { + "description": "What's New feature: Streaming Mode description" + }, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": { + "description": "What's New feature: Smart Queue title" + }, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": { + "description": "What's New feature: Smart Queue description" + }, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": { + "description": "What's New feature: Dual Mode title" + }, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": { + "description": "What's New feature: Dual Mode description" + }, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": { + "description": "What's New feature: Library redesign title" + }, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": { + "description": "What's New feature: Library redesign description" + }, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": { + "description": "What's New feature: Full-Screen Player title" + }, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": { + "description": "What's New feature: Full-Screen Player description" + }, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": { + "description": "What's New feature: Context Menus title" + }, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": { + "description": "What's New feature: Context Menus description" + }, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": { + "description": "What's New feature: Performance title" + }, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": { + "description": "What's New feature: Performance description" + }, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": { + "description": "What's New feature: Batch Tools title" + }, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": { + "description": "What's New feature: Batch Tools description" + }, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": { + "description": "What's New tip: streaming instant play" + }, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": { + "description": "What's New tip: streaming synced lyrics" + }, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": { + "description": "What's New tip: streaming download from player" + }, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": { + "description": "What's New tip: smart queue auto-fill" + }, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": { + "description": "What's New tip: smart queue artist discovery" + }, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": { + "description": "What's New tip: smart queue endless" + }, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": { + "description": "What's New tip: dual mode switch" + }, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": { + "description": "What's New tip: dual mode adaptive UI" + }, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": { + "description": "What's New tip: dual mode use cases" + }, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": { + "description": "What's New tip: library drag and drop" + }, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": { + "description": "What's New tip: library custom covers" + }, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": { + "description": "What's New tip: library multi-select" + }, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": { + "description": "What's New tip: player parallax" + }, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": { + "description": "What's New tip: player persistence" + }, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": { + "description": "What's New tip: player lyrics" + }, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": { + "description": "What's New tip: context menu add to playlist" + }, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": { + "description": "What's New tip: context menu share/convert" + }, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": { + "description": "What's New tip: context menu re-enrich" + }, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": { + "description": "What's New tip: batch share" + }, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": { + "description": "What's New tip: batch convert" + }, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": { + "description": "What's New tip: batch re-enrich" + }, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": { + "description": "What's New tip: performance startup" + }, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": { + "description": "What's New tip: performance memory" + }, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": { + "description": "What's New tip: performance SQLite" + }, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": { + "description": "Ready card message on last What's New page" + }, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": { + "description": "Button text to dismiss What's New screen" + }, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } } } \ No newline at end of file From cd757f177f9101b4f908243f1b0040a05a09c3b5 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:54 +0700 Subject: [PATCH 051/104] New translations app_en.arb (Hindi) --- lib/l10n/arb/app_hi.arb | 605 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 604 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_hi.arb b/lib/l10n/arb/app_hi.arb index 249a490d..e236d3f6 100644 --- a/lib/l10n/arb/app_hi.arb +++ b/lib/l10n/arb/app_hi.arb @@ -308,6 +308,38 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": { + "description": "Toggle to skip to the next queue track when current track stream resolution fails" + }, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": { + "description": "Subtitle when auto skip on resolve failure is enabled" + }, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": { + "description": "Subtitle when auto skip on resolve failure is disabled" + }, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": { + "description": "Tap behavior mode for track lists" + }, + "modeDownloader": "Downloader Mode", + "@modeDownloader": { + "description": "Interaction mode where taps queue downloads" + }, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": { + "description": "Subtitle for downloader interaction mode" + }, + "modeStreaming": "Streaming Mode", + "@modeStreaming": { + "description": "Interaction mode where taps start playback" + }, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": { + "description": "Subtitle for streaming interaction mode" + }, "optionsUseExtensionProviders": "Use Extension Providers", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -1340,6 +1372,10 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": { + "description": "Error - seek disabled for live decrypted stream" + }, "errorMissingExtensionSource": "Cannot load {item}: missing extension source", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -2022,6 +2058,15 @@ } } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2931,10 +2976,18 @@ "@discographyDownload": { "description": "Button - download artist discography" }, + "discographyPlay": "Play Discography", + "@discographyPlay": { + "description": "Button - play artist discography" + }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, + "discographyPlayAll": "Play All", + "@discographyPlayAll": { + "description": "Option - play entire discography" + }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3016,6 +3069,10 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, + "discographyPlaySelected": "Play Selected", + "@discographyPlaySelected": { + "description": "Button - play selected albums" + }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3182,6 +3239,15 @@ } } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -3809,7 +3875,7 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Re-enrich", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, @@ -3927,5 +3993,542 @@ "trackConvertFailed": "Conversion failed", "@trackConvertFailed": { "description": "Snackbar when conversion fails" + }, + "actionCreate": "Create", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "My folders", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Wishlist", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Loved", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Playlists", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Playlist name", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Add to Loved", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Remove cover image", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "No shareable files found", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "No convertible tracks selected", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Batch Convert", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Converting {current} of {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": { + "description": "Title for mode selection step in setup wizard" + }, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": { + "description": "Description for mode selection step" + }, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": { + "description": "Title for downloader mode option" + }, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": { + "description": "Downloader mode feature 1" + }, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": { + "description": "Downloader mode feature 2" + }, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": { + "description": "Downloader mode feature 3" + }, + "setupModeStreamingTitle": "Streaming", + "@setupModeStreamingTitle": { + "description": "Title for streaming mode option" + }, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": { + "description": "Streaming mode feature 1" + }, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": { + "description": "Streaming mode feature 2" + }, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": { + "description": "Streaming mode feature 3" + }, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": { + "description": "Hint that mode can be changed later" + }, + "settingsSmartQueueTitle": "Smart Queue", + "@settingsSmartQueueTitle": { + "description": "Title for Smart Queue toggle in settings" + }, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": { + "description": "Subtitle for Smart Queue toggle in settings" + }, + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": { + "description": "Title for the What's New screen" + }, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": { + "description": "Subtitle for the What's New screen" + }, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": { + "description": "Welcome page title in What's New screen" + }, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": { + "description": "Welcome page description in What's New screen" + }, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": { + "description": "Welcome page tip 1" + }, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": { + "description": "Welcome page tip 2" + }, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": { + "description": "Welcome page tip 3" + }, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": { + "description": "What's New feature: Streaming Mode title" + }, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": { + "description": "What's New feature: Streaming Mode description" + }, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": { + "description": "What's New feature: Smart Queue title" + }, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": { + "description": "What's New feature: Smart Queue description" + }, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": { + "description": "What's New feature: Dual Mode title" + }, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": { + "description": "What's New feature: Dual Mode description" + }, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": { + "description": "What's New feature: Library redesign title" + }, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": { + "description": "What's New feature: Library redesign description" + }, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": { + "description": "What's New feature: Full-Screen Player title" + }, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": { + "description": "What's New feature: Full-Screen Player description" + }, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": { + "description": "What's New feature: Context Menus title" + }, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": { + "description": "What's New feature: Context Menus description" + }, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": { + "description": "What's New feature: Performance title" + }, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": { + "description": "What's New feature: Performance description" + }, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": { + "description": "What's New feature: Batch Tools title" + }, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": { + "description": "What's New feature: Batch Tools description" + }, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": { + "description": "What's New tip: streaming instant play" + }, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": { + "description": "What's New tip: streaming synced lyrics" + }, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": { + "description": "What's New tip: streaming download from player" + }, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": { + "description": "What's New tip: smart queue auto-fill" + }, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": { + "description": "What's New tip: smart queue artist discovery" + }, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": { + "description": "What's New tip: smart queue endless" + }, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": { + "description": "What's New tip: dual mode switch" + }, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": { + "description": "What's New tip: dual mode adaptive UI" + }, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": { + "description": "What's New tip: dual mode use cases" + }, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": { + "description": "What's New tip: library drag and drop" + }, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": { + "description": "What's New tip: library custom covers" + }, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": { + "description": "What's New tip: library multi-select" + }, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": { + "description": "What's New tip: player parallax" + }, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": { + "description": "What's New tip: player persistence" + }, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": { + "description": "What's New tip: player lyrics" + }, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": { + "description": "What's New tip: context menu add to playlist" + }, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": { + "description": "What's New tip: context menu share/convert" + }, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": { + "description": "What's New tip: context menu re-enrich" + }, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": { + "description": "What's New tip: batch share" + }, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": { + "description": "What's New tip: batch convert" + }, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": { + "description": "What's New tip: batch re-enrich" + }, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": { + "description": "What's New tip: performance startup" + }, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": { + "description": "What's New tip: performance memory" + }, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": { + "description": "What's New tip: performance SQLite" + }, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": { + "description": "Ready card message on last What's New page" + }, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": { + "description": "Button text to dismiss What's New screen" + }, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } } } \ No newline at end of file From e7315cbc7e5837b48f4d172e04a922791d2535b8 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:55:55 +0700 Subject: [PATCH 052/104] New translations app_en.arb (Turkish) --- lib/l10n/arb/app_tr-TR.arb | 605 ++++++++++++++++++++++++++++++++++++- 1 file changed, 604 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_tr-TR.arb b/lib/l10n/arb/app_tr-TR.arb index c06e485d..86dd938c 100644 --- a/lib/l10n/arb/app_tr-TR.arb +++ b/lib/l10n/arb/app_tr-TR.arb @@ -308,6 +308,38 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, + "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "@optionsAutoSkipUnavailableTracks": { + "description": "Toggle to skip to the next queue track when current track stream resolution fails" + }, + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "@optionsAutoSkipUnavailableTracksSubtitleOn": { + "description": "Subtitle when auto skip on resolve failure is enabled" + }, + "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "@optionsAutoSkipUnavailableTracksSubtitleOff": { + "description": "Subtitle when auto skip on resolve failure is disabled" + }, + "optionsInteractionMode": "Interaction Mode", + "@optionsInteractionMode": { + "description": "Tap behavior mode for track lists" + }, + "modeDownloader": "Downloader Mode", + "@modeDownloader": { + "description": "Interaction mode where taps queue downloads" + }, + "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "@modeDownloaderSubtitle": { + "description": "Subtitle for downloader interaction mode" + }, + "modeStreaming": "Streaming Mode", + "@modeStreaming": { + "description": "Interaction mode where taps start playback" + }, + "modeStreamingSubtitle": "Tap tracks to play instantly", + "@modeStreamingSubtitle": { + "description": "Subtitle for streaming interaction mode" + }, "optionsUseExtensionProviders": "Eklenti sağlayıcılarını kullan", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -1340,6 +1372,10 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorSeekNotSupported": "Seeking is not supported for this live stream", + "@errorSeekNotSupported": { + "description": "Error - seek disabled for live decrypted stream" + }, "errorMissingExtensionSource": "{item} yüklenemedi: Eksik eklenti kaynağı", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -2022,6 +2058,15 @@ } } }, + "playAllCount": "Play All ({count})", + "@playAllCount": { + "description": "Play all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2931,10 +2976,18 @@ "@discographyDownload": { "description": "Button - download artist discography" }, + "discographyPlay": "Play Discography", + "@discographyPlay": { + "description": "Button - play artist discography" + }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, + "discographyPlayAll": "Play All", + "@discographyPlayAll": { + "description": "Option - play entire discography" + }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3016,6 +3069,10 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, + "discographyPlaySelected": "Play Selected", + "@discographyPlaySelected": { + "description": "Button - play selected albums" + }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3182,6 +3239,15 @@ } } }, + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -3809,7 +3875,7 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich Metadata", + "trackReEnrich": "Re-enrich", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, @@ -3927,5 +3993,542 @@ "trackConvertFailed": "Conversion failed", "@trackConvertFailed": { "description": "Snackbar when conversion fails" + }, + "actionCreate": "Create", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "My folders", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Wishlist", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Loved", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Playlists", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Playlist", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Add to playlist", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Create playlist", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "No playlists yet", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Playlist created", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Playlist name", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Playlist name is required", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Rename playlist", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Delete playlist", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Playlist deleted", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Playlist renamed", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Wishlist is empty", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Loved folder is empty", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Playlist is empty", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Remove from playlist", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Remove from folder", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" removed", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Add to Loved", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Remove from Loved", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Add to Wishlist", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Change cover image", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Remove cover image", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "No shareable files found", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "No convertible tracks selected", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Batch Convert", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Converting {current} of {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "setupModeSelectionTitle": "Choose Your Mode", + "@setupModeSelectionTitle": { + "description": "Title for mode selection step in setup wizard" + }, + "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "@setupModeSelectionDescription": { + "description": "Description for mode selection step" + }, + "setupModeDownloaderTitle": "Downloader", + "@setupModeDownloaderTitle": { + "description": "Title for downloader mode option" + }, + "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "@setupModeDownloaderFeature1": { + "description": "Downloader mode feature 1" + }, + "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "@setupModeDownloaderFeature2": { + "description": "Downloader mode feature 2" + }, + "setupModeDownloaderFeature3": "Manage your local music library", + "@setupModeDownloaderFeature3": { + "description": "Downloader mode feature 3" + }, + "setupModeStreamingTitle": "Streaming", + "@setupModeStreamingTitle": { + "description": "Title for streaming mode option" + }, + "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "@setupModeStreamingFeature1": { + "description": "Streaming mode feature 1" + }, + "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "@setupModeStreamingFeature2": { + "description": "Streaming mode feature 2" + }, + "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "@setupModeStreamingFeature3": { + "description": "Streaming mode feature 3" + }, + "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "@setupModeChangeableLater": { + "description": "Hint that mode can be changed later" + }, + "settingsSmartQueueTitle": "Smart Queue", + "@settingsSmartQueueTitle": { + "description": "Title for Smart Queue toggle in settings" + }, + "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "@settingsSmartQueueSubtitle": { + "description": "Subtitle for Smart Queue toggle in settings" + }, + "whatsNewTitle": "What's New in 4.0", + "@whatsNewTitle": { + "description": "Title for the What's New screen" + }, + "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "@whatsNewSubtitle": { + "description": "Subtitle for the What's New screen" + }, + "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", + "@whatsNewWelcomeTitle": { + "description": "Welcome page title in What's New screen" + }, + "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "@whatsNewWelcomeDesc": { + "description": "Welcome page description in What's New screen" + }, + "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "@whatsNewWelcomeTip1": { + "description": "Welcome page tip 1" + }, + "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "@whatsNewWelcomeTip2": { + "description": "Welcome page tip 2" + }, + "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "@whatsNewWelcomeTip3": { + "description": "Welcome page tip 3" + }, + "whatsNewStreamingTitle": "Streaming Mode", + "@whatsNewStreamingTitle": { + "description": "What's New feature: Streaming Mode title" + }, + "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "@whatsNewStreamingDesc": { + "description": "What's New feature: Streaming Mode description" + }, + "whatsNewSmartQueueTitle": "Smart Queue", + "@whatsNewSmartQueueTitle": { + "description": "What's New feature: Smart Queue title" + }, + "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "@whatsNewSmartQueueDesc": { + "description": "What's New feature: Smart Queue description" + }, + "whatsNewDualModeTitle": "Dual Mode", + "@whatsNewDualModeTitle": { + "description": "What's New feature: Dual Mode title" + }, + "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "@whatsNewDualModeDesc": { + "description": "What's New feature: Dual Mode description" + }, + "whatsNewLibraryTitle": "Redesigned Library", + "@whatsNewLibraryTitle": { + "description": "What's New feature: Library redesign title" + }, + "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "@whatsNewLibraryDesc": { + "description": "What's New feature: Library redesign description" + }, + "whatsNewPlayerTitle": "Full-Screen Player", + "@whatsNewPlayerTitle": { + "description": "What's New feature: Full-Screen Player title" + }, + "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "@whatsNewPlayerDesc": { + "description": "What's New feature: Full-Screen Player description" + }, + "whatsNewContextMenuTitle": "Long-Press Menus", + "@whatsNewContextMenuTitle": { + "description": "What's New feature: Context Menus title" + }, + "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "@whatsNewContextMenuDesc": { + "description": "What's New feature: Context Menus description" + }, + "whatsNewPerformanceTitle": "Performance", + "@whatsNewPerformanceTitle": { + "description": "What's New feature: Performance title" + }, + "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "@whatsNewPerformanceDesc": { + "description": "What's New feature: Performance description" + }, + "whatsNewBatchToolsTitle": "Batch Tools", + "@whatsNewBatchToolsTitle": { + "description": "What's New feature: Batch Tools title" + }, + "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "@whatsNewBatchToolsDesc": { + "description": "What's New feature: Batch Tools description" + }, + "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "@whatsNewStreamingTip1": { + "description": "What's New tip: streaming instant play" + }, + "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "@whatsNewStreamingTip2": { + "description": "What's New tip: streaming synced lyrics" + }, + "whatsNewStreamingTip3": "Download tracks directly from the player", + "@whatsNewStreamingTip3": { + "description": "What's New tip: streaming download from player" + }, + "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "@whatsNewSmartQueueTip1": { + "description": "What's New tip: smart queue auto-fill" + }, + "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "@whatsNewSmartQueueTip2": { + "description": "What's New tip: smart queue artist discovery" + }, + "whatsNewSmartQueueTip3": "Never run out of music to play", + "@whatsNewSmartQueueTip3": { + "description": "What's New tip: smart queue endless" + }, + "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "@whatsNewDualModeTip1": { + "description": "What's New tip: dual mode switch" + }, + "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "@whatsNewDualModeTip2": { + "description": "What's New tip: dual mode adaptive UI" + }, + "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "@whatsNewDualModeTip3": { + "description": "What's New tip: dual mode use cases" + }, + "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "@whatsNewLibraryTip1": { + "description": "What's New tip: library drag and drop" + }, + "whatsNewLibraryTip2": "Set custom cover images for playlists", + "@whatsNewLibraryTip2": { + "description": "What's New tip: library custom covers" + }, + "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "@whatsNewLibraryTip3": { + "description": "What's New tip: library multi-select" + }, + "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "@whatsNewPlayerTip1": { + "description": "What's New tip: player parallax" + }, + "whatsNewPlayerTip2": "Playback persists across app restarts", + "@whatsNewPlayerTip2": { + "description": "What's New tip: player persistence" + }, + "whatsNewPlayerTip3": "Synced lyrics while you listen", + "@whatsNewPlayerTip3": { + "description": "What's New tip: player lyrics" + }, + "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "@whatsNewContextMenuTip1": { + "description": "What's New tip: context menu add to playlist" + }, + "whatsNewContextMenuTip2": "Share or convert with one tap", + "@whatsNewContextMenuTip2": { + "description": "What's New tip: context menu share/convert" + }, + "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "@whatsNewContextMenuTip3": { + "description": "What's New tip: context menu re-enrich" + }, + "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "@whatsNewBatchToolsTip1": { + "description": "What's New tip: batch share" + }, + "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "@whatsNewBatchToolsTip2": { + "description": "What's New tip: batch convert" + }, + "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "@whatsNewBatchToolsTip3": { + "description": "What's New tip: batch re-enrich" + }, + "whatsNewPerformanceTip1": "Faster app startup time", + "@whatsNewPerformanceTip1": { + "description": "What's New tip: performance startup" + }, + "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "@whatsNewPerformanceTip2": { + "description": "What's New tip: performance memory" + }, + "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "@whatsNewPerformanceTip3": { + "description": "What's New tip: performance SQLite" + }, + "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "@whatsNewReadyMessage": { + "description": "Ready card message on last What's New page" + }, + "whatsNewGetStarted": "Let's Go", + "@whatsNewGetStarted": { + "description": "Button text to dismiss What's New screen" + }, + "whatsNewPageIndicator": "{current} of {total}", + "@whatsNewPageIndicator": { + "description": "Page indicator text in What's New screen", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } } } \ No newline at end of file From a81fa1ead708007f823df9431ad2559e6a2f86fd Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:26:35 +0700 Subject: [PATCH 053/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index a3b71f1d..e10f8059 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -3239,7 +3239,7 @@ } } }, - "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "libraryTracksUnit": "{count, plural, =1{1 Titel} other{{count} Titel}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", "placeholders": { @@ -4479,47 +4479,47 @@ "@whatsNewContextMenuTip1": { "description": "What's New tip: context menu add to playlist" }, - "whatsNewContextMenuTip2": "Share or convert with one tap", + "whatsNewContextMenuTip2": "Mit einem Klick teilen oder konvertieren", "@whatsNewContextMenuTip2": { "description": "What's New tip: context menu share/convert" }, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "whatsNewContextMenuTip3": "Metadaten bei Bedarf neu anreichern", "@whatsNewContextMenuTip3": { "description": "What's New tip: context menu re-enrich" }, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "whatsNewBatchToolsTip1": "Mehrere Titel gleichzeitig teilen", "@whatsNewBatchToolsTip1": { "description": "What's New tip: batch share" }, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "whatsNewBatchToolsTip2": "Batch-Konvertierung in MP3 oder Opus Format", "@whatsNewBatchToolsTip2": { "description": "What's New tip: batch convert" }, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "whatsNewBatchToolsTip3": "Metadaten in Ihrer Bibliothek neu anreichern", "@whatsNewBatchToolsTip3": { "description": "What's New tip: batch re-enrich" }, - "whatsNewPerformanceTip1": "Faster app startup time", + "whatsNewPerformanceTip1": "Schnellere App Startzeit", "@whatsNewPerformanceTip1": { "description": "What's New tip: performance startup" }, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "whatsNewPerformanceTip2": "Verringerte Speicherauslastung während der Wiedergabe", "@whatsNewPerformanceTip2": { "description": "What's New tip: performance memory" }, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "whatsNewPerformanceTip3": "SQLite-gesicherter Speicher für Zuverlässigkeit", "@whatsNewPerformanceTip3": { "description": "What's New tip: performance SQLite" }, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "whatsNewReadyMessage": "Alles ist bereit, viel Spaß mit dem neuen SpotiFLAC!", "@whatsNewReadyMessage": { "description": "Ready card message on last What's New page" }, - "whatsNewGetStarted": "Let's Go", + "whatsNewGetStarted": "Los geht's", "@whatsNewGetStarted": { "description": "Button text to dismiss What's New screen" }, - "whatsNewPageIndicator": "{current} of {total}", + "whatsNewPageIndicator": "{current} von {total}", "@whatsNewPageIndicator": { "description": "Page indicator text in What's New screen", "placeholders": { From a2db5bef25abdf0b84289629f3f2d2471c4e51fe Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:26:36 +0700 Subject: [PATCH 054/104] New translations app_en.arb (Russian) --- lib/l10n/arb/app_ru.arb | 256 ++++++++++++++++++++-------------------- 1 file changed, 128 insertions(+), 128 deletions(-) diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 55c9d945..4be06559 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -308,11 +308,11 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "optionsAutoSkipUnavailableTracks": "Автопропуск недоступных треков", "@optionsAutoSkipUnavailableTracks": { "description": "Toggle to skip to the next queue track when current track stream resolution fails" }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "optionsAutoSkipUnavailableTracksSubtitleOn": "Автоматически переходить к следующему треку в очереди, когда поток не может быть решен.", "@optionsAutoSkipUnavailableTracksSubtitleOn": { "description": "Subtitle when auto skip on resolve failure is enabled" }, @@ -320,23 +320,23 @@ "@optionsAutoSkipUnavailableTracksSubtitleOff": { "description": "Subtitle when auto skip on resolve failure is disabled" }, - "optionsInteractionMode": "Interaction Mode", + "optionsInteractionMode": "Режим взаимодействия", "@optionsInteractionMode": { "description": "Tap behavior mode for track lists" }, - "modeDownloader": "Downloader Mode", + "modeDownloader": "Режим загрузчика", "@modeDownloader": { "description": "Interaction mode where taps queue downloads" }, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "modeDownloaderSubtitle": "Нажмите на треки, чтобы добавить их в очередь скачивания", "@modeDownloaderSubtitle": { "description": "Subtitle for downloader interaction mode" }, - "modeStreaming": "Streaming Mode", + "modeStreaming": "Режим трансляции", "@modeStreaming": { "description": "Interaction mode where taps start playback" }, - "modeStreamingSubtitle": "Tap tracks to play instantly", + "modeStreamingSubtitle": "Коснитесь треков для мгновенного воспроизведения", "@modeStreamingSubtitle": { "description": "Subtitle for streaming interaction mode" }, @@ -1372,7 +1372,7 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Seeking is not supported for this live stream", + "errorSeekNotSupported": "Поиск не поддерживается для этой трансляции", "@errorSeekNotSupported": { "description": "Error - seek disabled for live decrypted stream" }, @@ -1572,7 +1572,7 @@ "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" }, - "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "filenameShowAdvancedTagsDescription": "Включить форматированные теги для отслеживания заполнения и шаблонов дат", "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, @@ -2058,7 +2058,7 @@ } } }, - "playAllCount": "Play All ({count})", + "playAllCount": "Скачать все ({count})", "@playAllCount": { "description": "Play all button with count", "placeholders": { @@ -2976,7 +2976,7 @@ "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Play Discography", + "discographyPlay": "Воспроизвести дискографию", "@discographyPlay": { "description": "Button - play artist discography" }, @@ -2984,7 +2984,7 @@ "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Play All", + "discographyPlayAll": "Воспроизвести все", "@discographyPlayAll": { "description": "Option - play entire discography" }, @@ -3069,7 +3069,7 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Play Selected", + "discographyPlaySelected": "Воспроизвести выбранное", "@discographyPlaySelected": { "description": "Button - play selected albums" }, @@ -3239,7 +3239,7 @@ } } }, - "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", + "libraryTracksUnit": "{count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", "placeholders": { @@ -3613,7 +3613,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "tutorialExtensionsTip1": "Просмотрите вкладку Магазина, чтобы найти полезные расширения", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -3649,7 +3649,7 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "EXAMPLE", + "tutorialExample": "ПРИМЕР", "@tutorialExample": { "description": "Example label in tutorial" }, @@ -3715,7 +3715,7 @@ "@cacheAppDirectory": { "description": "Cache item title for app cache directory" }, - "cacheAppDirectoryDesc": "HTTP responses, WebView data, and other temporary app data.", + "cacheAppDirectoryDesc": "HTTP-ответы, данные WebView и другие временные данные приложения.", "@cacheAppDirectoryDesc": { "description": "Description of what app cache directory contains" }, @@ -3747,7 +3747,7 @@ "@cacheExploreFeed": { "description": "Cache item title for explore home feed cache" }, - "cacheExploreFeedDesc": "Explore tab content (new releases, trending). Will refresh on next visit.", + "cacheExploreFeedDesc": "Изучите содержимое вкладки (новые релизы, тренды). Они обновятся при следующем посещении.", "@cacheExploreFeedDesc": { "description": "Description of what explore feed cache contains" }, @@ -3755,7 +3755,7 @@ "@cacheTrackLookup": { "description": "Cache item title for track ID lookup cache" }, - "cacheTrackLookupDesc": "Spotify/Deezer track ID lookups. Clearing may slow next few searches.", + "cacheTrackLookupDesc": "Поиск ID трека в Spotify/Deezer. Очистка может замедлить следующие несколько поисков.", "@cacheTrackLookupDesc": { "description": "Description of what track lookup cache contains" }, @@ -3875,15 +3875,15 @@ "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Re-enrich", + "trackReEnrich": "Обновить", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", + "trackReEnrichSubtitle": "Повторно встраивать метаданные без повторного скачивания", "@trackReEnrichSubtitle": { "description": "Subtitle for re-enrich metadata action" }, - "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", + "trackReEnrichOnlineSubtitle": "Поиск в сети метаданных и встраивание в файл", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, @@ -3913,7 +3913,7 @@ } } }, - "trackReEnrichProgress": "Re-enriching metadata...", + "trackReEnrichProgress": "Обновление метаданных...", "@trackReEnrichProgress": { "description": "Snackbar while re-enriching metadata" }, @@ -3921,7 +3921,7 @@ "@trackReEnrichSearching": { "description": "Snackbar while searching metadata from internet for local items" }, - "trackReEnrichSuccess": "Metadata re-enriched successfully", + "trackReEnrichSuccess": "Метаданные успешно обновлены", "@trackReEnrichSuccess": { "description": "Snackbar after successful re-enrichment" }, @@ -3994,47 +3994,47 @@ "@trackConvertFailed": { "description": "Snackbar when conversion fails" }, - "actionCreate": "Create", + "actionCreate": "Создать", "@actionCreate": { "description": "Generic action button - create" }, - "collectionFoldersTitle": "My folders", + "collectionFoldersTitle": "Мои папки", "@collectionFoldersTitle": { "description": "Library section title for custom folders" }, - "collectionWishlist": "Wishlist", + "collectionWishlist": "Список желаемого", "@collectionWishlist": { "description": "Custom folder for saved tracks to download later" }, - "collectionLoved": "Loved", + "collectionLoved": "Любимые", "@collectionLoved": { "description": "Custom folder for favorite tracks" }, - "collectionPlaylists": "Playlists", + "collectionPlaylists": "Плейлисты", "@collectionPlaylists": { "description": "Custom user playlists folder" }, - "collectionPlaylist": "Playlist", + "collectionPlaylist": "Плейлист", "@collectionPlaylist": { "description": "Single playlist label" }, - "collectionAddToPlaylist": "Add to playlist", + "collectionAddToPlaylist": "Добавить в плейлист", "@collectionAddToPlaylist": { "description": "Action to add a track to user playlist" }, - "collectionCreatePlaylist": "Create playlist", + "collectionCreatePlaylist": "Создать плейлист", "@collectionCreatePlaylist": { "description": "Action to create a new playlist" }, - "collectionNoPlaylistsYet": "No playlists yet", + "collectionNoPlaylistsYet": "Плейлисты отсутствуют", "@collectionNoPlaylistsYet": { "description": "Empty state title when user has no playlists" }, - "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "collectionNoPlaylistsSubtitle": "Создайте плейлист, чтобы начать классифицировать треки", "@collectionNoPlaylistsSubtitle": { "description": "Empty state subtitle when user has no playlists" }, - "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "collectionPlaylistTracks": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}", "@collectionPlaylistTracks": { "description": "Track count label for custom playlists", "placeholders": { @@ -4043,7 +4043,7 @@ } } }, - "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "collectionAddedToPlaylist": "Добавлено в \"{playlistName}\"", "@collectionAddedToPlaylist": { "description": "Snackbar after adding track to playlist", "placeholders": { @@ -4052,7 +4052,7 @@ } } }, - "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "collectionAlreadyInPlaylist": "Уже в \"{playlistName}\"", "@collectionAlreadyInPlaylist": { "description": "Snackbar when track already exists in playlist", "placeholders": { @@ -4061,27 +4061,27 @@ } } }, - "collectionPlaylistCreated": "Playlist created", + "collectionPlaylistCreated": "Плейлист создан", "@collectionPlaylistCreated": { "description": "Snackbar after creating playlist" }, - "collectionPlaylistNameHint": "Playlist name", + "collectionPlaylistNameHint": "Название плейлиста", "@collectionPlaylistNameHint": { "description": "Hint text for playlist name input" }, - "collectionPlaylistNameRequired": "Playlist name is required", + "collectionPlaylistNameRequired": "Имя плейлиста обязательно", "@collectionPlaylistNameRequired": { "description": "Validation error for empty playlist name" }, - "collectionRenamePlaylist": "Rename playlist", + "collectionRenamePlaylist": "Переименовать плейлист", "@collectionRenamePlaylist": { "description": "Action to rename playlist" }, - "collectionDeletePlaylist": "Delete playlist", + "collectionDeletePlaylist": "Удалить плейлист", "@collectionDeletePlaylist": { "description": "Action to delete playlist" }, - "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "collectionDeletePlaylistMessage": "Удалить \"{playlistName}\" и все треки внутри него?", "@collectionDeletePlaylistMessage": { "description": "Confirmation message for deleting playlist", "placeholders": { @@ -4090,47 +4090,47 @@ } } }, - "collectionPlaylistDeleted": "Playlist deleted", + "collectionPlaylistDeleted": "Плейлист удалён", "@collectionPlaylistDeleted": { "description": "Snackbar after deleting playlist" }, - "collectionPlaylistRenamed": "Playlist renamed", + "collectionPlaylistRenamed": "Плейлист переименован", "@collectionPlaylistRenamed": { "description": "Snackbar after renaming playlist" }, - "collectionWishlistEmptyTitle": "Wishlist is empty", + "collectionWishlistEmptyTitle": "Список желаний пуст", "@collectionWishlistEmptyTitle": { "description": "Wishlist empty state title" }, - "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "collectionWishlistEmptySubtitle": "Нажмите + на треках, чтобы сохранить то, что вы хотите скачать позже", "@collectionWishlistEmptySubtitle": { "description": "Wishlist empty state subtitle" }, - "collectionLovedEmptyTitle": "Loved folder is empty", + "collectionLovedEmptyTitle": "Папка Любимые пуста", "@collectionLovedEmptyTitle": { "description": "Loved empty state title" }, - "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "collectionLovedEmptySubtitle": "Нажмите \"любовь\" на треках, чтобы сохранить ваши избранные", "@collectionLovedEmptySubtitle": { "description": "Loved empty state subtitle" }, - "collectionPlaylistEmptyTitle": "Playlist is empty", + "collectionPlaylistEmptyTitle": "Плейлист пуст", "@collectionPlaylistEmptyTitle": { "description": "Playlist empty state title" }, - "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "collectionPlaylistEmptySubtitle": "Удерживайте + на любом треке, чтобы добавить его сюда", "@collectionPlaylistEmptySubtitle": { "description": "Playlist empty state subtitle" }, - "collectionRemoveFromPlaylist": "Remove from playlist", + "collectionRemoveFromPlaylist": "Удалить из плейлиста", "@collectionRemoveFromPlaylist": { "description": "Tooltip for removing track from playlist" }, - "collectionRemoveFromFolder": "Remove from folder", + "collectionRemoveFromFolder": "Убрать из папки", "@collectionRemoveFromFolder": { "description": "Tooltip for removing track from wishlist/loved folder" }, - "collectionRemoved": "\"{trackName}\" removed", + "collectionRemoved": "\"{trackName}\" удалён", "@collectionRemoved": { "description": "Snackbar after removing a track from a collection", "placeholders": { @@ -4139,7 +4139,7 @@ } } }, - "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "collectionAddedToLoved": "\"{trackName}\" добавлен в Любимые", "@collectionAddedToLoved": { "description": "Snackbar after adding track to loved folder", "placeholders": { @@ -4148,7 +4148,7 @@ } } }, - "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "collectionRemovedFromLoved": "\"{trackName}\" удалено из Любимых", "@collectionRemovedFromLoved": { "description": "Snackbar after removing track from loved folder", "placeholders": { @@ -4157,7 +4157,7 @@ } } }, - "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "collectionAddedToWishlist": "\"{trackName}\" добавлен в список желаний", "@collectionAddedToWishlist": { "description": "Snackbar after adding track to wishlist", "placeholders": { @@ -4166,7 +4166,7 @@ } } }, - "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "collectionRemovedFromWishlist": "\"{trackName}\" удалён из списка желаний", "@collectionRemovedFromWishlist": { "description": "Snackbar after removing track from wishlist", "placeholders": { @@ -4175,31 +4175,31 @@ } } }, - "trackOptionAddToLoved": "Add to Loved", + "trackOptionAddToLoved": "Добавить в Любимые", "@trackOptionAddToLoved": { "description": "Bottom sheet action label - add track to loved folder" }, - "trackOptionRemoveFromLoved": "Remove from Loved", + "trackOptionRemoveFromLoved": "Исключить из Любимых", "@trackOptionRemoveFromLoved": { "description": "Bottom sheet action label - remove track from loved folder" }, - "trackOptionAddToWishlist": "Add to Wishlist", + "trackOptionAddToWishlist": "Добавить в список желаний", "@trackOptionAddToWishlist": { "description": "Bottom sheet action label - add track to wishlist" }, - "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "trackOptionRemoveFromWishlist": "Удалить из списка желаний", "@trackOptionRemoveFromWishlist": { "description": "Bottom sheet action label - remove track from wishlist" }, - "collectionPlaylistChangeCover": "Change cover image", + "collectionPlaylistChangeCover": "Изменить обложку", "@collectionPlaylistChangeCover": { "description": "Bottom sheet action to pick a custom cover image for a playlist" }, - "collectionPlaylistRemoveCover": "Remove cover image", + "collectionPlaylistRemoveCover": "Удалить обложку", "@collectionPlaylistRemoveCover": { "description": "Bottom sheet action to remove custom cover image from a playlist" }, - "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", + "selectionShareCount": "Отправить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", "@selectionShareCount": { "description": "Share button text with count in selection mode", "placeholders": { @@ -4212,7 +4212,7 @@ "@selectionShareNoFiles": { "description": "Snackbar when no selected files exist on disk" }, - "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", + "selectionConvertCount": "Конвертировать {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", "@selectionConvertCount": { "description": "Convert button text with count in selection mode", "placeholders": { @@ -4221,11 +4221,11 @@ } } }, - "selectionConvertNoConvertible": "No convertible tracks selected", + "selectionConvertNoConvertible": "Не выбраны конвертируемые треки", "@selectionConvertNoConvertible": { "description": "Snackbar when no selected tracks support conversion" }, - "selectionBatchConvertConfirmTitle": "Batch Convert", + "selectionBatchConvertConfirmTitle": "Пакетная конвертация", "@selectionBatchConvertConfirmTitle": { "description": "Confirmation dialog title for batch conversion" }, @@ -4244,7 +4244,7 @@ } } }, - "selectionBatchConvertProgress": "Converting {current} of {total}...", + "selectionBatchConvertProgress": "Конвертация {current} из {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", "placeholders": { @@ -4256,7 +4256,7 @@ } } }, - "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "selectionBatchConvertSuccess": "Конвертировано {success} треков {total} в {format}", "@selectionBatchConvertSuccess": { "description": "Snackbar after batch conversion completes", "placeholders": { @@ -4271,39 +4271,39 @@ } } }, - "setupModeSelectionTitle": "Choose Your Mode", + "setupModeSelectionTitle": "Выберите свой режим", "@setupModeSelectionTitle": { "description": "Title for mode selection step in setup wizard" }, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "setupModeSelectionDescription": "Как вы хотите использовать SpotiFLAC? Вы всегда можете изменить это позже в настройках.", "@setupModeSelectionDescription": { "description": "Description for mode selection step" }, - "setupModeDownloaderTitle": "Downloader", + "setupModeDownloaderTitle": "Загрузчик", "@setupModeDownloaderTitle": { "description": "Title for downloader mode option" }, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "setupModeDownloaderFeature1": "Скачать треки в FLAC формате без потерь", "@setupModeDownloaderFeature1": { "description": "Downloader mode feature 1" }, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "setupModeDownloaderFeature2": "Скачать музыку на ваше устройство для оффлайн прослушивания", "@setupModeDownloaderFeature2": { "description": "Downloader mode feature 2" }, - "setupModeDownloaderFeature3": "Manage your local music library", + "setupModeDownloaderFeature3": "Управление вашей музыкальной библиотекой", "@setupModeDownloaderFeature3": { "description": "Downloader mode feature 3" }, - "setupModeStreamingTitle": "Streaming", + "setupModeStreamingTitle": "Трансляция", "@setupModeStreamingTitle": { "description": "Title for streaming mode option" }, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "setupModeStreamingFeature1": "Трансляция треков мгновенно без скачивания", "@setupModeStreamingFeature1": { "description": "Streaming mode feature 1" }, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "setupModeStreamingFeature2": "Умная Очередь автоматически открывает новую музыку для вас", "@setupModeStreamingFeature2": { "description": "Streaming mode feature 2" }, @@ -4311,23 +4311,23 @@ "@setupModeStreamingFeature3": { "description": "Streaming mode feature 3" }, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "setupModeChangeableLater": "Вы можете переключаться между режимами в любое время в Настройках.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" }, - "settingsSmartQueueTitle": "Smart Queue", + "settingsSmartQueueTitle": "Умная очередь", "@settingsSmartQueueTitle": { "description": "Title for Smart Queue toggle in settings" }, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "settingsSmartQueueSubtitle": "Автоматически искать и добавлять похожие треки в очередь", "@settingsSmartQueueSubtitle": { "description": "Subtitle for Smart Queue toggle in settings" }, - "whatsNewTitle": "What's New in 4.0", + "whatsNewTitle": "Что нового в 4.0", "@whatsNewTitle": { "description": "Title for the What's New screen" }, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "whatsNewSubtitle": "В SpotiFLAC развивается — вот что изменилось с 3.x", "@whatsNewSubtitle": { "description": "Subtitle for the What's New screen" }, @@ -4335,31 +4335,31 @@ "@whatsNewWelcomeTitle": { "description": "Welcome page title in What's New screen" }, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "whatsNewWelcomeDesc": "С возвращением! Это крупное обновление с новыми возможностями. Смахните влево для того, чтобы увидеть, что изменилось.", "@whatsNewWelcomeDesc": { "description": "Welcome page description in What's New screen" }, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "whatsNewWelcomeTip1": "Новый режим трансляции с мгновенным воспроизведением", "@whatsNewWelcomeTip1": { "description": "Welcome page tip 1" }, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "whatsNewWelcomeTip2": "Переработанный дизайн библиотеки и полноэкранный плеер", "@whatsNewWelcomeTip2": { "description": "Welcome page tip 2" }, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "whatsNewWelcomeTip3": "Пакетные инструменты, повышение производительности и многое другое", "@whatsNewWelcomeTip3": { "description": "Welcome page tip 3" }, - "whatsNewStreamingTitle": "Streaming Mode", + "whatsNewStreamingTitle": "Режим трансляции", "@whatsNewStreamingTitle": { "description": "What's New feature: Streaming Mode title" }, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "whatsNewStreamingDesc": "Коснитесь трека для мгновенного воспроизведения — загрузка не требуется. Полноэкранный проигрыватель с синхронизированными текстами и мультимедиа элементами.", "@whatsNewStreamingDesc": { "description": "What's New feature: Streaming Mode description" }, - "whatsNewSmartQueueTitle": "Smart Queue", + "whatsNewSmartQueueTitle": "Умная очередь", "@whatsNewSmartQueueTitle": { "description": "What's New feature: Smart Queue title" }, @@ -4367,15 +4367,15 @@ "@whatsNewSmartQueueDesc": { "description": "What's New feature: Smart Queue description" }, - "whatsNewDualModeTitle": "Dual Mode", + "whatsNewDualModeTitle": "Двойной режим", "@whatsNewDualModeTitle": { "description": "What's New feature: Dual Mode title" }, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "whatsNewDualModeDesc": "Переключение между режимами загрузчика и стриминга в любое время. Все кнопки адаптируются автоматически.", "@whatsNewDualModeDesc": { "description": "What's New feature: Dual Mode description" }, - "whatsNewLibraryTitle": "Redesigned Library", + "whatsNewLibraryTitle": "Переработанная библиотека", "@whatsNewLibraryTitle": { "description": "What's New feature: Library redesign title" }, @@ -4383,31 +4383,31 @@ "@whatsNewLibraryDesc": { "description": "What's New feature: Library redesign description" }, - "whatsNewPlayerTitle": "Full-Screen Player", + "whatsNewPlayerTitle": "Полноэкранный плеер", "@whatsNewPlayerTitle": { "description": "What's New feature: Full-Screen Player title" }, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "whatsNewPlayerDesc": "Параллакс обложки, синхронизированные тексты, сохранение воспроизведения при перезапуске и кнопка скачать в плеере.", "@whatsNewPlayerDesc": { "description": "What's New feature: Full-Screen Player description" }, - "whatsNewContextMenuTitle": "Long-Press Menus", + "whatsNewContextMenuTitle": "Меню длинного нажатия", "@whatsNewContextMenuTitle": { "description": "What's New feature: Context Menus title" }, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "whatsNewContextMenuDesc": "Долгое нажатие на трек для быстрых действий — добавить в плейлист, поделиться, конвертировать или обновить метаданные.", "@whatsNewContextMenuDesc": { "description": "What's New feature: Context Menus description" }, - "whatsNewPerformanceTitle": "Performance", + "whatsNewPerformanceTitle": "Производительность", "@whatsNewPerformanceTitle": { "description": "What's New feature: Performance title" }, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "whatsNewPerformanceDesc": "Более быстрый запуск, меньшее использование памяти, поддержка SQLite и детальное обновление пользовательского интерфейса.", "@whatsNewPerformanceDesc": { "description": "What's New feature: Performance description" }, - "whatsNewBatchToolsTitle": "Batch Tools", + "whatsNewBatchToolsTitle": "Пакетные инструменты", "@whatsNewBatchToolsTitle": { "description": "What's New feature: Batch Tools title" }, @@ -4415,111 +4415,111 @@ "@whatsNewBatchToolsDesc": { "description": "What's New feature: Batch Tools description" }, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "whatsNewStreamingTip1": "Коснитесь трека, чтобы мгновенно его воспроизвести", "@whatsNewStreamingTip1": { "description": "What's New tip: streaming instant play" }, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", + "whatsNewStreamingTip2": "Синхронизированные тексты песен в полноэкранном плеере", "@whatsNewStreamingTip2": { "description": "What's New tip: streaming synced lyrics" }, - "whatsNewStreamingTip3": "Download tracks directly from the player", + "whatsNewStreamingTip3": "Скачивайте треки прямо из плеера", "@whatsNewStreamingTip3": { "description": "What's New tip: streaming download from player" }, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", + "whatsNewSmartQueueTip1": "Автозаполнение очереди соответствующими треками", "@whatsNewSmartQueueTip1": { "description": "What's New tip: smart queue auto-fill" }, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", + "whatsNewSmartQueueTip2": "Откройте для себя новых исполнителей слушая музыку", "@whatsNewSmartQueueTip2": { "description": "What's New tip: smart queue artist discovery" }, - "whatsNewSmartQueueTip3": "Never run out of music to play", + "whatsNewSmartQueueTip3": "Музыка для прослушивания никогда не закончится", "@whatsNewSmartQueueTip3": { "description": "What's New tip: smart queue endless" }, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", + "whatsNewDualModeTip1": "Переключайте режимы в любое время в настройках", "@whatsNewDualModeTip1": { "description": "What's New tip: dual mode switch" }, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", + "whatsNewDualModeTip2": "Кнопки интерфейса адаптируются к текущему режиму", "@whatsNewDualModeTip2": { "description": "What's New tip: dual mode adaptive UI" }, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "whatsNewDualModeTip3": "Скачать для автономной трансляции и мгновенного воспроизведения", "@whatsNewDualModeTip3": { "description": "What's New tip: dual mode use cases" }, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", + "whatsNewLibraryTip1": "Перетащите для организации плейлистов", "@whatsNewLibraryTip1": { "description": "What's New tip: library drag and drop" }, - "whatsNewLibraryTip2": "Set custom cover images for playlists", + "whatsNewLibraryTip2": "Установить пользовательские обложки для плейлистов", "@whatsNewLibraryTip2": { "description": "What's New tip: library custom covers" }, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", + "whatsNewLibraryTip3": "Мульти-выбор треков для пакетных действий", "@whatsNewLibraryTip3": { "description": "What's New tip: library multi-select" }, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "whatsNewPlayerTip1": "Обложки с эффектом параллакса", "@whatsNewPlayerTip1": { "description": "What's New tip: player parallax" }, - "whatsNewPlayerTip2": "Playback persists across app restarts", + "whatsNewPlayerTip2": "Воспроизведение продолжится после перезапуска приложения", "@whatsNewPlayerTip2": { "description": "What's New tip: player persistence" }, - "whatsNewPlayerTip3": "Synced lyrics while you listen", + "whatsNewPlayerTip3": "Синхронизированные тексты песен во время прослушивания", "@whatsNewPlayerTip3": { "description": "What's New tip: player lyrics" }, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", + "whatsNewContextMenuTip1": "Мгновенно добавляйте треки в любой плейлист", "@whatsNewContextMenuTip1": { "description": "What's New tip: context menu add to playlist" }, - "whatsNewContextMenuTip2": "Share or convert with one tap", + "whatsNewContextMenuTip2": "Поделиться или конвертировать одним нажатием", "@whatsNewContextMenuTip2": { "description": "What's New tip: context menu share/convert" }, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", + "whatsNewContextMenuTip3": "При необходимости восстановить метаданные", "@whatsNewContextMenuTip3": { "description": "What's New tip: context menu re-enrich" }, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", + "whatsNewBatchToolsTip1": "Поделиться сразу несколькими треками", "@whatsNewBatchToolsTip1": { "description": "What's New tip: batch share" }, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", + "whatsNewBatchToolsTip2": "Пакетное конвертирование в формат MP3 или Opus", "@whatsNewBatchToolsTip2": { "description": "What's New tip: batch convert" }, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", + "whatsNewBatchToolsTip3": "Повторное обновление метаданных в вашей библиотеке", "@whatsNewBatchToolsTip3": { "description": "What's New tip: batch re-enrich" }, - "whatsNewPerformanceTip1": "Faster app startup time", + "whatsNewPerformanceTip1": "Быстрый запуск приложения", "@whatsNewPerformanceTip1": { "description": "What's New tip: performance startup" }, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", + "whatsNewPerformanceTip2": "Уменьшение использования памяти во время воспроизведения", "@whatsNewPerformanceTip2": { "description": "What's New tip: performance memory" }, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", + "whatsNewPerformanceTip3": "Надежное хранилище с поддержкой SQLite", "@whatsNewPerformanceTip3": { "description": "What's New tip: performance SQLite" }, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", + "whatsNewReadyMessage": "Всё готово — наслаждайтесь новым SpotiFLAC!", "@whatsNewReadyMessage": { "description": "Ready card message on last What's New page" }, - "whatsNewGetStarted": "Let's Go", + "whatsNewGetStarted": "Поехали", "@whatsNewGetStarted": { "description": "Button text to dismiss What's New screen" }, - "whatsNewPageIndicator": "{current} of {total}", + "whatsNewPageIndicator": "{current} из {total}", "@whatsNewPageIndicator": { "description": "Page indicator text in What's New screen", "placeholders": { From a202ca4865a3e452899332cf65673dbeedc68703 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:31:40 +0700 Subject: [PATCH 055/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 70 ++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index e10f8059..27369eeb 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -316,7 +316,7 @@ "@optionsAutoSkipUnavailableTracksSubtitleOn": { "description": "Subtitle when auto skip on resolve failure is enabled" }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "optionsAutoSkipUnavailableTracksSubtitleOff": "Beim Fehlschlagen der Titelerkennung anhalten und einen Fehler anzeigen.", "@optionsAutoSkipUnavailableTracksSubtitleOff": { "description": "Subtitle when auto skip on resolve failure is disabled" }, @@ -324,7 +324,7 @@ "@optionsInteractionMode": { "description": "Tap behavior mode for track lists" }, - "modeDownloader": "Downloader Mode", + "modeDownloader": "Downloader-Modus", "@modeDownloader": { "description": "Interaction mode where taps queue downloads" }, @@ -1372,7 +1372,7 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Seeking is not supported for this live stream", + "errorSeekNotSupported": "Suche wird für diesen Live-Stream nicht unterstützt", "@errorSeekNotSupported": { "description": "Error - seek disabled for live decrypted stream" }, @@ -2459,7 +2459,7 @@ "@extensionsNoMetadataProvider": { "description": "Empty state - no metadata providers" }, - "extensionsSearchProvider": "Search Provider", + "extensionsSearchProvider": "Such-Provider", "@extensionsSearchProvider": { "description": "Setting - search provider selection" }, @@ -2590,7 +2590,7 @@ "@youtubeBitrateFieldLabel": { "description": "Label for YouTube bitrate input field" }, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", + "youtubeBitrateValidationError": "Bitrate muss zwischen {min} und {max} kbps liegen", "@youtubeBitrateValidationError": { "description": "Validation error for invalid YouTube bitrate input", "placeholders": { @@ -2622,7 +2622,7 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Künstlerordner verwenden den Album-Interpreten, wenn verfügbar", "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { "description": "Subtitle when Album Artist is used for folder naming" }, @@ -2658,7 +2658,7 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "Default Quality", + "downloadDefaultQualityLabel": "Standard-Qualität", "@downloadDefaultQualityLabel": { "description": "Label - default quality setting" }, @@ -2742,7 +2742,7 @@ "@queueClearAll": { "description": "Button - clear all queue items" }, - "queueClearAllMessage": "Are you sure you want to clear all downloads?", + "queueClearAllMessage": "Bist du dir sicher, dass du alle Downloads löschen möchten?", "@queueClearAllMessage": { "description": "Clear queue confirmation" }, @@ -3150,7 +3150,7 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Library Status", + "libraryStatus": "Bibliotheksstatus", "@libraryStatus": { "description": "Section header for library status" }, @@ -3166,7 +3166,7 @@ "@libraryEnableLocalLibrarySubtitle": { "description": "Subtitle for enable toggle" }, - "libraryFolder": "Library Folder", + "libraryFolder": "Bibliotheksordner", "@libraryFolder": { "description": "Folder selection setting" }, @@ -3202,7 +3202,7 @@ "@libraryCleanupMissingFiles": { "description": "Button to remove entries for missing files" }, - "libraryCleanupMissingFilesSubtitle": "Remove entries for files that no longer exist", + "libraryCleanupMissingFilesSubtitle": "Verlaufseinträge für Dateien löschen, die nicht mehr existieren", "@libraryCleanupMissingFilesSubtitle": { "description": "Subtitle for cleanup button" }, @@ -3230,7 +3230,7 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} tracks", + "libraryTracksCount": "{count} Titel", "@libraryTracksCount": { "description": "Track count in library", "placeholders": { @@ -3617,11 +3617,11 @@ "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, - "tutorialExtensionsTip2": "Add new download providers or search sources", + "tutorialExtensionsTip2": "Neue Download- oder Suchanbieter hinzufügen", "@tutorialExtensionsTip2": { "description": "Tutorial extensions tip 2" }, - "tutorialExtensionsTip3": "Get lyrics, enhanced metadata, and more features", + "tutorialExtensionsTip3": "Lyrics, erweiterte Metadaten und mehr Funktionen erhalten", "@tutorialExtensionsTip3": { "description": "Tutorial extensions tip 3" }, @@ -3633,7 +3633,7 @@ "@tutorialSettingsDesc": { "description": "Tutorial settings page description" }, - "tutorialSettingsTip1": "Change download location and folder organization", + "tutorialSettingsTip1": "Downloadverzeichnis und Ordnerorganisation ändern", "@tutorialSettingsTip1": { "description": "Tutorial settings tip 1" }, @@ -3669,7 +3669,7 @@ "@cleanupOrphanedDownloadsSubtitle": { "description": "Subtitle for orphaned cleanup button" }, - "cleanupOrphanedDownloadsResult": "Removed {count} orphaned entries from history", + "cleanupOrphanedDownloadsResult": "Entfernte {count} verwaiste Einträge aus dem Verlauf", "@cleanupOrphanedDownloadsResult": { "description": "Snackbar after orphan cleanup", "placeholders": { @@ -3686,7 +3686,7 @@ "@cacheTitle": { "description": "Cache management page title" }, - "cacheSummaryTitle": "Cache overview", + "cacheSummaryTitle": "Cache-Übersicht", "@cacheSummaryTitle": { "description": "Heading for cache summary card" }, @@ -3763,7 +3763,7 @@ "@cacheCleanupUnusedDesc": { "description": "Description of what cleanup unused data does" }, - "cacheNoData": "No cached data", + "cacheNoData": "Keine gecachten Daten", "@cacheNoData": { "description": "Label when cache category has no data" }, @@ -3855,7 +3855,7 @@ "@cacheRefreshStats": { "description": "Button label to refresh cache statistics" }, - "trackSaveCoverArt": "Save Cover Art", + "trackSaveCoverArt": "Cover speichern", "@trackSaveCoverArt": { "description": "Menu action - save album cover art as file" }, @@ -3938,7 +3938,7 @@ } } }, - "trackConvertFormat": "Convert Format", + "trackConvertFormat": "Format konvertieren", "@trackConvertFormat": { "description": "Menu item - convert audio format" }, @@ -4030,11 +4030,11 @@ "@collectionNoPlaylistsYet": { "description": "Empty state title when user has no playlists" }, - "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", + "collectionNoPlaylistsSubtitle": "Playlist erstellen, um Titel zu kategorisieren", "@collectionNoPlaylistsSubtitle": { "description": "Empty state subtitle when user has no playlists" }, - "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", + "collectionPlaylistTracks": "{count, plural, =1{1 Titel} other{{count} Titel}}", "@collectionPlaylistTracks": { "description": "Track count label for custom playlists", "placeholders": { @@ -4061,7 +4061,7 @@ } } }, - "collectionPlaylistCreated": "Playlist created", + "collectionPlaylistCreated": "Playlist erstellt", "@collectionPlaylistCreated": { "description": "Snackbar after creating playlist" }, @@ -4081,7 +4081,7 @@ "@collectionDeletePlaylist": { "description": "Action to delete playlist" }, - "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", + "collectionDeletePlaylistMessage": "Willst du \"{playlistName}\" und alle darin enthaltenen Titel löschen?", "@collectionDeletePlaylistMessage": { "description": "Confirmation message for deleting playlist", "placeholders": { @@ -4256,7 +4256,7 @@ } } }, - "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", + "selectionBatchConvertSuccess": "{success} von {total} Titeln in {format} konvertiert", "@selectionBatchConvertSuccess": { "description": "Snackbar after batch conversion completes", "placeholders": { @@ -4287,7 +4287,7 @@ "@setupModeDownloaderFeature1": { "description": "Downloader mode feature 1" }, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", + "setupModeDownloaderFeature2": "Speichere Musik auf deinem Gerät, um sie offline anzuhören", "@setupModeDownloaderFeature2": { "description": "Downloader mode feature 2" }, @@ -4303,15 +4303,15 @@ "@setupModeStreamingFeature1": { "description": "Streaming mode feature 1" }, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", + "setupModeStreamingFeature2": "Die smarte Warteschlange entdeckt automatisch neue Musik für dich", "@setupModeStreamingFeature2": { "description": "Streaming mode feature 2" }, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", + "setupModeStreamingFeature3": "Bei Bedarf jeden Track mit Wiedergabesteuerungen abspielen", "@setupModeStreamingFeature3": { "description": "Streaming mode feature 3" }, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", + "setupModeChangeableLater": "Du kannst jederzeit in den Einstellungen zwischen den Modi wechseln.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" }, @@ -4327,7 +4327,7 @@ "@whatsNewTitle": { "description": "Title for the What's New screen" }, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", + "whatsNewSubtitle": "SpotiFLAC hat sich weiterentwickelt hier ist was sich seit 3.x geändert hat", "@whatsNewSubtitle": { "description": "Subtitle for the What's New screen" }, @@ -4351,7 +4351,7 @@ "@whatsNewWelcomeTip3": { "description": "Welcome page tip 3" }, - "whatsNewStreamingTitle": "Streaming Mode", + "whatsNewStreamingTitle": "Streaming-Modus", "@whatsNewStreamingTitle": { "description": "What's New feature: Streaming Mode title" }, @@ -4359,7 +4359,7 @@ "@whatsNewStreamingDesc": { "description": "What's New feature: Streaming Mode description" }, - "whatsNewSmartQueueTitle": "Smart Queue", + "whatsNewSmartQueueTitle": "Smarte Warteschlange", "@whatsNewSmartQueueTitle": { "description": "What's New feature: Smart Queue title" }, @@ -4367,7 +4367,7 @@ "@whatsNewSmartQueueDesc": { "description": "What's New feature: Smart Queue description" }, - "whatsNewDualModeTitle": "Dual Mode", + "whatsNewDualModeTitle": "Dual-Modus", "@whatsNewDualModeTitle": { "description": "What's New feature: Dual Mode title" }, @@ -4407,7 +4407,7 @@ "@whatsNewPerformanceDesc": { "description": "What's New feature: Performance description" }, - "whatsNewBatchToolsTitle": "Batch Tools", + "whatsNewBatchToolsTitle": "Batch-Tools", "@whatsNewBatchToolsTitle": { "description": "What's New feature: Batch Tools title" }, @@ -4447,7 +4447,7 @@ "@whatsNewDualModeTip2": { "description": "What's New tip: dual mode adaptive UI" }, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", + "whatsNewDualModeTip3": "Download für Offline, Stream für Sofortwiedergabe", "@whatsNewDualModeTip3": { "description": "What's New tip: dual mode use cases" }, From 652b1b0821760b849b9503e09f353a339b18bc86 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 27 Feb 2026 21:04:13 +0700 Subject: [PATCH 056/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 102 ++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 27369eeb..4c64a50f 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -312,7 +312,7 @@ "@optionsAutoSkipUnavailableTracks": { "description": "Toggle to skip to the next queue track when current track stream resolution fails" }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatisch zum nächsten Queue-Track springen, wenn ein Stream nicht aufgebaut werden kann.", "@optionsAutoSkipUnavailableTracksSubtitleOn": { "description": "Subtitle when auto skip on resolve failure is enabled" }, @@ -320,7 +320,7 @@ "@optionsAutoSkipUnavailableTracksSubtitleOff": { "description": "Subtitle when auto skip on resolve failure is disabled" }, - "optionsInteractionMode": "Interaction Mode", + "optionsInteractionMode": "Interaktionsmodus", "@optionsInteractionMode": { "description": "Tap behavior mode for track lists" }, @@ -2172,7 +2172,7 @@ "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, - "trackLyricsTimeout": "Anfrage Timeout. Versuchen Sie es später erneut.", + "trackLyricsTimeout": "Anfrage Timeout. Versuche es später erneut.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" }, @@ -2447,7 +2447,7 @@ "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" }, - "extensionsMetadataPriority": "Metadata Priority", + "extensionsMetadataPriority": "Metadaten Priorität", "@extensionsMetadataPriority": { "description": "Setting - metadata provider order" }, @@ -2794,11 +2794,11 @@ "@queueEmptySubtitle": { "description": "Empty queue state subtitle" }, - "queueClearCompleted": "Clear completed", + "queueClearCompleted": "Löschung Erledigt", "@queueClearCompleted": { "description": "Button - clear finished downloads" }, - "queueDownloadFailed": "Download Failed", + "queueDownloadFailed": "Download fehlgeschlagen", "@queueDownloadFailed": { "description": "Error dialog title" }, @@ -2858,7 +2858,7 @@ "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, - "downloadedAlbumDeleteSelected": "Delete Selected", + "downloadedAlbumDeleteSelected": "Ausgewählte löschen", "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" }, @@ -2884,7 +2884,7 @@ } } }, - "downloadedAlbumSelectedCount": "{count} selected", + "downloadedAlbumSelectedCount": "{count} ausgewählt", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", "placeholders": { @@ -2924,7 +2924,7 @@ } } }, - "utilityFunctions": "Utility Functions", + "utilityFunctions": "Hilfsfunktionen", "@utilityFunctions": { "description": "Extension capability - utility functions" }, @@ -2962,7 +2962,7 @@ } } }, - "errorGeneric": "Error: {message}", + "errorGeneric": "Fehler: {message}", "@errorGeneric": { "description": "Generic error message format", "placeholders": { @@ -2976,7 +2976,7 @@ "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Play Discography", + "discographyPlay": "Diskographie abspielen", "@discographyPlay": { "description": "Button - play artist discography" }, @@ -3032,7 +3032,7 @@ } } }, - "discographySelectAlbums": "Select Albums...", + "discographySelectAlbums": "Alben auswählen...", "@discographySelectAlbums": { "description": "Option - manually select albums to download" }, @@ -3056,7 +3056,7 @@ } } }, - "discographySelectedCount": "{count} selected", + "discographySelectedCount": "{count} ausgewählt", "@discographySelectedCount": { "description": "Selection count badge", "placeholders": { @@ -3065,7 +3065,7 @@ } } }, - "discographyDownloadSelected": "Download Selected", + "discographyDownloadSelected": "Auswahl herunterladen", "@discographyDownloadSelected": { "description": "Button - download selected albums" }, @@ -3106,7 +3106,7 @@ "@sectionStorageAccess": { "description": "Section header for storage access settings" }, - "allFilesAccess": "All Files Access", + "allFilesAccess": "Zugriff auf alle Dateien", "@allFilesAccess": { "description": "Toggle for MANAGE_EXTERNAL_STORAGE permission" }, @@ -3138,7 +3138,7 @@ "@settingsLocalLibrarySubtitle": { "description": "Subtitle for local library settings" }, - "settingsCache": "Storage & Cache", + "settingsCache": "Speicher & Cache", "@settingsCache": { "description": "Settings menu item - cache management" }, @@ -3290,7 +3290,7 @@ } } }, - "libraryCleared": "Library cleared", + "libraryCleared": "Bibliothek geleert", "@libraryCleared": { "description": "Snackbar after clearing library" }, @@ -3490,7 +3490,7 @@ "@storageSafStorage": { "description": "Label for SAF storage mode" }, - "storageModeBadge": "Storage: {mode}", + "storageModeBadge": "Speicher: {mode}", "@storageModeBadge": { "description": "Badge showing storage mode for a track", "placeholders": { @@ -3577,7 +3577,7 @@ "@tutorialDownloadTip1": { "description": "Tutorial download tip 1" }, - "tutorialDownloadTip2": "Wählen Sie Ihre bevorzugte Qualität (FLAC, Hi-Res oder MP3)", + "tutorialDownloadTip2": "Wähle deine bevorzugte Qualität (FLAC, Hi-Res oder MP3)", "@tutorialDownloadTip2": { "description": "Tutorial download tip 2" }, @@ -3653,7 +3653,7 @@ "@tutorialExample": { "description": "Example label in tutorial" }, - "libraryForceFullScan": "Force Full Scan", + "libraryForceFullScan": "Vollen Neu-Scan erzwingen", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" }, @@ -3682,7 +3682,7 @@ "@cleanupOrphanedDownloadsNone": { "description": "Snackbar when no orphans found" }, - "cacheTitle": "Storage & Cache", + "cacheTitle": "Speicher & Cache", "@cacheTitle": { "description": "Cache management page title" }, @@ -3788,7 +3788,7 @@ } } }, - "cacheEntries": "{count} entries", + "cacheEntries": "{count} Einträge", "@cacheEntries": { "description": "Track cache entry count", "placeholders": { @@ -3819,7 +3819,7 @@ } } }, - "cacheClearAllConfirmTitle": "Clear all cache?", + "cacheClearAllConfirmTitle": "Gesamten Cache leeren?", "@cacheClearAllConfirmTitle": { "description": "Dialog title before clearing all caches" }, @@ -3827,7 +3827,7 @@ "@cacheClearAllConfirmMessage": { "description": "Dialog message before clearing all caches" }, - "cacheClearAll": "Clear all cache", + "cacheClearAll": "Gesamten Cache leeren", "@cacheClearAll": { "description": "Button label to clear all caches" }, @@ -3871,7 +3871,7 @@ "@trackSaveLyricsSubtitle": { "description": "Subtitle for save lyrics action" }, - "trackSaveLyricsProgress": "Saving lyrics...", + "trackSaveLyricsProgress": "Speichere Lyrics...", "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, @@ -3929,7 +3929,7 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, - "trackSaveFailed": "Failed: {error}", + "trackSaveFailed": "Fehler: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", "placeholders": { @@ -4018,15 +4018,15 @@ "@collectionPlaylist": { "description": "Single playlist label" }, - "collectionAddToPlaylist": "Add to playlist", + "collectionAddToPlaylist": "Zur Playlist hinzufügen", "@collectionAddToPlaylist": { "description": "Action to add a track to user playlist" }, - "collectionCreatePlaylist": "Create playlist", + "collectionCreatePlaylist": "Playlist erstellen", "@collectionCreatePlaylist": { "description": "Action to create a new playlist" }, - "collectionNoPlaylistsYet": "No playlists yet", + "collectionNoPlaylistsYet": "Noch keine Playlists", "@collectionNoPlaylistsYet": { "description": "Empty state title when user has no playlists" }, @@ -4073,11 +4073,11 @@ "@collectionPlaylistNameRequired": { "description": "Validation error for empty playlist name" }, - "collectionRenamePlaylist": "Rename playlist", + "collectionRenamePlaylist": "Playlist umbenennen", "@collectionRenamePlaylist": { "description": "Action to rename playlist" }, - "collectionDeletePlaylist": "Delete playlist", + "collectionDeletePlaylist": "Playlist löschen", "@collectionDeletePlaylist": { "description": "Action to delete playlist" }, @@ -4090,11 +4090,11 @@ } } }, - "collectionPlaylistDeleted": "Playlist deleted", + "collectionPlaylistDeleted": "Playlist gelöscht", "@collectionPlaylistDeleted": { "description": "Snackbar after deleting playlist" }, - "collectionPlaylistRenamed": "Playlist renamed", + "collectionPlaylistRenamed": "Playlist umbenannt", "@collectionPlaylistRenamed": { "description": "Snackbar after renaming playlist" }, @@ -4183,7 +4183,7 @@ "@trackOptionRemoveFromLoved": { "description": "Bottom sheet action label - remove track from loved folder" }, - "trackOptionAddToWishlist": "Add to Wishlist", + "trackOptionAddToWishlist": "Zur Wunschliste hinzufügen", "@trackOptionAddToWishlist": { "description": "Bottom sheet action label - add track to wishlist" }, @@ -4271,11 +4271,11 @@ } } }, - "setupModeSelectionTitle": "Choose Your Mode", + "setupModeSelectionTitle": "Modus wählen", "@setupModeSelectionTitle": { "description": "Title for mode selection step in setup wizard" }, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", + "setupModeSelectionDescription": "Wie möchtest du SpotiFLAC verwenden? Sie können dies jederzeit in den Einstellungen ändern.", "@setupModeSelectionDescription": { "description": "Description for mode selection step" }, @@ -4283,7 +4283,7 @@ "@setupModeDownloaderTitle": { "description": "Title for downloader mode option" }, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", + "setupModeDownloaderFeature1": "Titel in verlustfreier FLAC-Qualität herunterladen", "@setupModeDownloaderFeature1": { "description": "Downloader mode feature 1" }, @@ -4335,11 +4335,11 @@ "@whatsNewWelcomeTitle": { "description": "Welcome page title in What's New screen" }, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", + "whatsNewWelcomeDesc": "Willkommen zurück! Dies ist ein großes Update mit neuen Features. Wische durch um zu sehen, was sich geändert hat.", "@whatsNewWelcomeDesc": { "description": "Welcome page description in What's New screen" }, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", + "whatsNewWelcomeTip1": "Neuer Streaming-Modus mit Sofortwiedergabe", "@whatsNewWelcomeTip1": { "description": "Welcome page tip 1" }, @@ -4355,7 +4355,7 @@ "@whatsNewStreamingTitle": { "description": "What's New feature: Streaming Mode title" }, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", + "whatsNewStreamingDesc": "Tippe auf einen Titel, um sofort abzuspielen kein Download nötig. Vollbild-Player mit synchronisierten Songtexten und Mediensteuerungen.", "@whatsNewStreamingDesc": { "description": "What's New feature: Streaming Mode description" }, @@ -4363,7 +4363,7 @@ "@whatsNewSmartQueueTitle": { "description": "What's New feature: Smart Queue title" }, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", + "whatsNewSmartQueueDesc": "Deine Warteschlange automatisch kuratiert mit verwandten Tracks und Künstlern Entdeckung. Nie geht die Musik aus.", "@whatsNewSmartQueueDesc": { "description": "What's New feature: Smart Queue description" }, @@ -4371,7 +4371,7 @@ "@whatsNewDualModeTitle": { "description": "What's New feature: Dual Mode title" }, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", + "whatsNewDualModeDesc": "Wechsel jederzeit zwischen dem Downloader und dem Streaming-Modus. Alle Tasten passen sich automatisch an.", "@whatsNewDualModeDesc": { "description": "What's New feature: Dual Mode description" }, @@ -4379,7 +4379,7 @@ "@whatsNewLibraryTitle": { "description": "What's New feature: Library redesign title" }, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", + "whatsNewLibraryDesc": "Playlist-erstes Layout mit Drag-and-Drop Kategorisierung, benutzerdefinierten Covern und Mehrfachauswahl-Aktionen.", "@whatsNewLibraryDesc": { "description": "What's New feature: Library redesign description" }, @@ -4387,15 +4387,15 @@ "@whatsNewPlayerTitle": { "description": "What's New feature: Full-Screen Player title" }, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", + "whatsNewPlayerDesc": "Cover art parallax, synchronisierte Songtexte, Wiedergabe der Persistenz bei Neustarts und Download Button im Player.", "@whatsNewPlayerDesc": { "description": "What's New feature: Full-Screen Player description" }, - "whatsNewContextMenuTitle": "Long-Press Menus", + "whatsNewContextMenuTitle": "Menü lange drücken", "@whatsNewContextMenuTitle": { "description": "What's New feature: Context Menus title" }, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", + "whatsNewContextMenuDesc": "Drücke lange auf einen Titel für schnelle Aktionen fügen Sie zu Wiedergabeliste, Teilen, Konvertieren oder erneuern Sie Metadaten hinzu.", "@whatsNewContextMenuDesc": { "description": "What's New feature: Context Menus description" }, @@ -4403,7 +4403,7 @@ "@whatsNewPerformanceTitle": { "description": "What's New feature: Performance title" }, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", + "whatsNewPerformanceDesc": "Schnelleres Starten, reduzierte Speicherauslastung, SQLite-gestützte Dauerhaftigkeit und granulare UI-Updates.", "@whatsNewPerformanceDesc": { "description": "What's New feature: Performance description" }, @@ -4411,11 +4411,11 @@ "@whatsNewBatchToolsTitle": { "description": "What's New feature: Batch Tools title" }, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", + "whatsNewBatchToolsDesc": "Mehrfachauswahl-Freigabe, Batch-Konvertierung in MP3/Opus und Batch-Bereicherung von Metadaten in Ihrer gesamten Bibliothek.", "@whatsNewBatchToolsDesc": { "description": "What's New feature: Batch Tools description" }, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", + "whatsNewStreamingTip1": "Tippe auf irgendeinen Titel, um ihn sofort abzuspielen", "@whatsNewStreamingTip1": { "description": "What's New tip: streaming instant play" }, @@ -4423,7 +4423,7 @@ "@whatsNewStreamingTip2": { "description": "What's New tip: streaming synced lyrics" }, - "whatsNewStreamingTip3": "Download tracks directly from the player", + "whatsNewStreamingTip3": "Titel direkt vom Player herunterladen", "@whatsNewStreamingTip3": { "description": "What's New tip: streaming download from player" }, @@ -4463,7 +4463,7 @@ "@whatsNewLibraryTip3": { "description": "What's New tip: library multi-select" }, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", + "whatsNewPlayerTip1": "Cover mit Parallaxen-Scrolling-Effekt", "@whatsNewPlayerTip1": { "description": "What's New tip: player parallax" }, From 3ebb8a5e792341b62763d9ddb4db37adf694e8d1 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 27 Feb 2026 21:04:14 +0700 Subject: [PATCH 057/104] New translations app_en.arb (Indonesian) --- lib/l10n/arb/app_id.arb | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index 9bf75109..eb027b8d 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -300,7 +300,7 @@ "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" }, - "optionsAutoFallback": "Auto Fallback", + "optionsAutoFallback": "Cadangan Otomatis", "@optionsAutoFallback": { "description": "Auto-retry with other services" }, @@ -308,35 +308,35 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", + "optionsAutoSkipUnavailableTracks": "Lewati Otomatis Trek yang Tidak Tersedia", "@optionsAutoSkipUnavailableTracks": { "description": "Toggle to skip to the next queue track when current track stream resolution fails" }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", + "optionsAutoSkipUnavailableTracksSubtitleOn": "Secara otomatis melompati ke trek antrean berikutnya ketika aliran data tidak dapat diselesaikan.", "@optionsAutoSkipUnavailableTracksSubtitleOn": { "description": "Subtitle when auto skip on resolve failure is enabled" }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", + "optionsAutoSkipUnavailableTracksSubtitleOff": "Berhenti jika resolusi trek gagal dan tampilkan kesalahan.", "@optionsAutoSkipUnavailableTracksSubtitleOff": { "description": "Subtitle when auto skip on resolve failure is disabled" }, - "optionsInteractionMode": "Interaction Mode", + "optionsInteractionMode": "Mode Interaksi", "@optionsInteractionMode": { "description": "Tap behavior mode for track lists" }, - "modeDownloader": "Downloader Mode", + "modeDownloader": "Mode Pengunduh", "@modeDownloader": { "description": "Interaction mode where taps queue downloads" }, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", + "modeDownloaderSubtitle": "Ketuk lagu untuk menambahkannya ke antrean unduhan", "@modeDownloaderSubtitle": { "description": "Subtitle for downloader interaction mode" }, - "modeStreaming": "Streaming Mode", + "modeStreaming": "Mode Streaming", "@modeStreaming": { "description": "Interaction mode where taps start playback" }, - "modeStreamingSubtitle": "Tap tracks to play instantly", + "modeStreamingSubtitle": "Ketuk trek untuk memutar secara instan", "@modeStreamingSubtitle": { "description": "Subtitle for streaming interaction mode" }, @@ -445,7 +445,7 @@ "@optionsSpotifyCredentials": { "description": "Spotify API credentials setting" }, - "optionsSpotifyCredentialsConfigured": "Client ID: {clientId}...", + "optionsSpotifyCredentialsConfigured": "ID Klien: {clientId}...", "@optionsSpotifyCredentialsConfigured": { "description": "Shows configured client ID preview", "placeholders": { @@ -632,7 +632,7 @@ "@aboutSachinsenalDesc": { "description": "Credit description for sachinsenal0x64" }, - "aboutSjdonadoDesc": "Creator of I Don't Have Spotify (IDHS). The fallback link resolver that saves the day!", + "aboutSjdonadoDesc": "Pencipta I Don't Have Spotify (IDHS). Penyelesai tautan cadangan yang menyelamatkan keadaan!", "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, @@ -1198,7 +1198,7 @@ "description": "Dialog title - import CSV playlist" }, "dialogImportPlaylistMessage": "Ditemukan {count} lagu di CSV. Tambahkan ke antrian unduhan?", - "csvImportTracks": "{count} tracks from CSV", + "csvImportTracks": "{count} trek dari CSV", "@csvImportTracks": { "description": "Label shown in quality picker for CSV import", "placeholders": { @@ -1242,7 +1242,7 @@ } } }, - "snackbarAlreadyInLibrary": "\"{trackName}\" already exists in your library", + "snackbarAlreadyInLibrary": "\"{trackName}\" sudah ada di perpustakaan Anda", "@snackbarAlreadyInLibrary": { "description": "Snackbar - track already exists in local library", "placeholders": { @@ -1372,7 +1372,7 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Seeking is not supported for this live stream", + "errorSeekNotSupported": "Pencarian tidak didukung untuk siaran langsung ini", "@errorSeekNotSupported": { "description": "Error - seek disabled for live decrypted stream" }, @@ -1564,7 +1564,7 @@ "@filenameAvailablePlaceholders": { "description": "Label for placeholder list" }, - "filenameHint": "{artist} - {title}", + "filenameHint": "{artis} - {judul}", "@filenameHint": { "description": "Default filename format hint" }, From ad6ef2884ad5ace044af13860fa2da5b20e75879 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Sat, 28 Feb 2026 21:18:46 +0700 Subject: [PATCH 058/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 122 ++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 4c64a50f..9e894fea 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -2058,7 +2058,7 @@ } } }, - "playAllCount": "Play All ({count})", + "playAllCount": "Alle abspielen ({count})", "@playAllCount": { "description": "Play all button with count", "placeholders": { @@ -2523,7 +2523,7 @@ "@enableLossyOptionSubtitleOn": { "description": "Subtitle when lossy is enabled" }, - "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", + "enableLossyOptionSubtitleOff": "Lädt eine FLAC herunter und konvertiert sie dann in ein verlustbehaftetes Format", "@enableLossyOptionSubtitleOff": { "description": "Subtitle when lossy is disabled" }, @@ -2602,11 +2602,11 @@ } } }, - "downloadAskBeforeDownload": "Ask Before Download", + "downloadAskBeforeDownload": "Qualität vor Download fragen", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" }, - "downloadDirectory": "Download Directory", + "downloadDirectory": "Downloadverzeichnis", "@downloadDirectory": { "description": "Setting - download folder" }, @@ -2670,7 +2670,7 @@ "@folderNone": { "description": "Folder option - no organization" }, - "folderNoneSubtitle": "Save all files directly to download folder", + "folderNoneSubtitle": "Alle Dateien direkt im Download-Ordner speichern", "@folderNoneSubtitle": { "description": "Subtitle for no folder organization" }, @@ -2678,7 +2678,7 @@ "@folderArtist": { "description": "Folder option - by artist" }, - "folderArtistSubtitle": "Artist Name/filename", + "folderArtistSubtitle": "Künstler Name/Dateiname", "@folderArtistSubtitle": { "description": "Folder structure example" }, @@ -2686,7 +2686,7 @@ "@folderAlbum": { "description": "Folder option - by album" }, - "folderAlbumSubtitle": "Album Name/filename", + "folderAlbumSubtitle": "Album Name/Dateiname", "@folderAlbumSubtitle": { "description": "Folder structure example" }, @@ -2726,7 +2726,7 @@ "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "Choose Accent Color", + "appearanceChooseAccentColor": "Akzentfarbe wählen", "@appearanceChooseAccentColor": { "description": "Color picker dialog title" }, @@ -2770,7 +2770,7 @@ "@settingsAutoExportFailedSubtitle": { "description": "Subtitle for auto-export setting" }, - "settingsDownloadNetwork": "Download Network", + "settingsDownloadNetwork": "Download Netzwerk", "@settingsDownloadNetwork": { "description": "Setting for network type preference" }, @@ -2838,7 +2838,7 @@ "@albumFolderAlbumOnly": { "description": "Album folder option" }, - "albumFolderAlbumOnlySubtitle": "Albums/Album Name/", + "albumFolderAlbumOnlySubtitle": "Alben/Album Name/", "@albumFolderAlbumOnlySubtitle": { "description": "Folder structure example" }, @@ -2875,7 +2875,7 @@ "@downloadedAlbumTracksHeader": { "description": "Section header for tracks" }, - "downloadedAlbumDownloadedCount": "{count} downloaded", + "downloadedAlbumDownloadedCount": "{count} heruntergeladen", "@downloadedAlbumDownloadedCount": { "description": "Downloaded tracks count badge", "placeholders": { @@ -2893,11 +2893,11 @@ } } }, - "downloadedAlbumAllSelected": "All tracks selected", + "downloadedAlbumAllSelected": "Alle Titel sind ausgewählt", "@downloadedAlbumAllSelected": { "description": "Status - all items selected" }, - "downloadedAlbumTapToSelect": "Tap tracks to select", + "downloadedAlbumTapToSelect": "Tippe auf Titel zum Auswählen", "@downloadedAlbumTapToSelect": { "description": "Selection hint" }, @@ -2944,11 +2944,11 @@ "@recentTypePlaylist": { "description": "Recent access item type - playlist" }, - "recentEmpty": "No recent items yet", + "recentEmpty": "Noch keine aktuellen Einträge", "@recentEmpty": { "description": "Empty state text for recent access list" }, - "recentShowAllDownloads": "Show All Downloads", + "recentShowAllDownloads": "Alle Downloads anzeigen", "@recentShowAllDownloads": { "description": "Button label to unhide hidden downloads in recent access" }, @@ -2972,7 +2972,7 @@ } } }, - "discographyDownload": "Download Discography", + "discographyDownload": "Diskographie herunterladen", "@discographyDownload": { "description": "Button - download artist discography" }, @@ -3016,7 +3016,7 @@ } } }, - "discographySinglesOnly": "Singles & EPs Only", + "discographySinglesOnly": "Nur Singles & EPs", "@discographySinglesOnly": { "description": "Option - download only singles" }, @@ -3040,7 +3040,7 @@ "@discographySelectAlbumsSubtitle": { "description": "Subtitle for select albums option" }, - "discographyFetchingTracks": "Fetching tracks...", + "discographyFetchingTracks": "Lade Titel...", "@discographyFetchingTracks": { "description": "Progress - fetching album tracks" }, @@ -3082,7 +3082,7 @@ } } }, - "discographySkippedDownloaded": "{added} added, {skipped} already downloaded", + "discographySkippedDownloaded": "{added} hinzugefügt, {skipped} bereits heruntergeladen", "@discographySkippedDownloaded": { "description": "Snackbar - with skipped tracks count", "placeholders": { @@ -3094,7 +3094,7 @@ } } }, - "discographyNoAlbums": "No albums available", + "discographyNoAlbums": "Es sind keine Alben verfügbar", "@discographyNoAlbums": { "description": "Error - no albums found for artist" }, @@ -3158,7 +3158,7 @@ "@libraryScanSettings": { "description": "Section header for scan settings" }, - "libraryEnableLocalLibrary": "Enable Local Library", + "libraryEnableLocalLibrary": "Lokale Bibliothek aktivieren", "@libraryEnableLocalLibrary": { "description": "Toggle to enable library scanning" }, @@ -3170,7 +3170,7 @@ "@libraryFolder": { "description": "Folder selection setting" }, - "libraryFolderHint": "Tap to select folder", + "libraryFolderHint": "Tippe um Ordner auszuwählen", "@libraryFolderHint": { "description": "Placeholder when no folder selected" }, @@ -3190,7 +3190,7 @@ "@libraryScan": { "description": "Button to start library scan" }, - "libraryScanSubtitle": "Scan for audio files", + "libraryScanSubtitle": "Suche nach Audiodateien", "@libraryScanSubtitle": { "description": "Subtitle for scan button" }, @@ -3222,7 +3222,7 @@ "@libraryClearConfirmMessage": { "description": "Dialog message for clear confirmation" }, - "libraryAbout": "About Local Library", + "libraryAbout": "Über die lokale Bibliothek", "@libraryAbout": { "description": "Section header for about info" }, @@ -3248,7 +3248,7 @@ } } }, - "libraryLastScanned": "Last scanned: {time}", + "libraryLastScanned": "Zuletzt gescannt: {time}", "@libraryLastScanned": { "description": "Last scan time display", "placeholders": { @@ -3281,7 +3281,7 @@ "@libraryInLibrary": { "description": "Badge shown on tracks that exist in local library" }, - "libraryRemovedMissingFiles": "Removed {count} missing files from library", + "libraryRemovedMissingFiles": "Entfernte {count} fehlende Dateien aus der Bibliothek", "@libraryRemovedMissingFiles": { "description": "Snackbar after cleanup", "placeholders": { @@ -3425,7 +3425,7 @@ } } }, - "storageSwitchTitle": "Switch Storage Mode", + "storageSwitchTitle": "Speichermodus wechseln", "@storageSwitchTitle": { "description": "Dialog title when switching storage mode" }, @@ -3445,7 +3445,7 @@ "@storageSwitchToAppMessage": { "description": "Explanation when switching to app storage" }, - "storageSwitchExistingDownloads": "Existing Downloads", + "storageSwitchExistingDownloads": "Vorhandene Downloads", "@storageSwitchExistingDownloads": { "description": "Section header for existing downloads info" }, @@ -3478,7 +3478,7 @@ "@storageSwitchContinue": { "description": "Button to proceed with storage switch" }, - "storageSwitchSelectFolder": "Select SAF Folder", + "storageSwitchSelectFolder": "SAF-Ordner wählen", "@storageSwitchSelectFolder": { "description": "Button to select SAF folder" }, @@ -3499,11 +3499,11 @@ } } }, - "storageStatsTitle": "Storage Statistics", + "storageStatsTitle": "Speicherstatistik", "@storageStatsTitle": { "description": "Section title for storage stats" }, - "storageStatsAppCount": "{count} tracks in App Storage", + "storageStatsAppCount": "{count} Titel im App-Speicher", "@storageStatsAppCount": { "description": "Count of tracks in app storage", "placeholders": { @@ -3521,7 +3521,7 @@ } } }, - "storageModeInfo": "Your files are stored in multiple locations", + "storageModeInfo": "Deine Dateien werden an mehreren Orten gespeichert", "@storageModeInfo": { "description": "Info when user has files in both storage modes" }, @@ -3565,7 +3565,7 @@ "@tutorialSearchTip3": { "description": "Tutorial search tip 3" }, - "tutorialDownloadTitle": "Downloading Music", + "tutorialDownloadTitle": "Musik wird heruntergeladen", "@tutorialDownloadTitle": { "description": "Tutorial download page title" }, @@ -3711,7 +3711,7 @@ "@cacheSectionMaintenance": { "description": "Section header for cleanup actions" }, - "cacheAppDirectory": "App cache directory", + "cacheAppDirectory": "App-Cache Verzeichnis", "@cacheAppDirectory": { "description": "Cache item title for app cache directory" }, @@ -3719,7 +3719,7 @@ "@cacheAppDirectoryDesc": { "description": "Description of what app cache directory contains" }, - "cacheTempDirectory": "Temporary directory", + "cacheTempDirectory": "Temporäres Verzeichnis", "@cacheTempDirectory": { "description": "Cache item title for temporary files directory" }, @@ -3727,7 +3727,7 @@ "@cacheTempDirectoryDesc": { "description": "Description of what temporary directory contains" }, - "cacheCoverImage": "Cover image cache", + "cacheCoverImage": "Cover-Cache", "@cacheCoverImage": { "description": "Cache item title for persistent cover images" }, @@ -3735,7 +3735,7 @@ "@cacheCoverImageDesc": { "description": "Description of what cover image cache contains" }, - "cacheLibraryCover": "Library cover cache", + "cacheLibraryCover": "Bibliotheks-Cover-Cache", "@cacheLibraryCover": { "description": "Cache item title for local library cover art images" }, @@ -3743,7 +3743,7 @@ "@cacheLibraryCoverDesc": { "description": "Description of what library cover cache contains" }, - "cacheExploreFeed": "Explore feed cache", + "cacheExploreFeed": "Feed-Cache entdecken", "@cacheExploreFeed": { "description": "Cache item title for explore home feed cache" }, @@ -3751,7 +3751,7 @@ "@cacheExploreFeedDesc": { "description": "Description of what explore feed cache contains" }, - "cacheTrackLookup": "Track lookup cache", + "cacheTrackLookup": "Titel Such-Cache", "@cacheTrackLookup": { "description": "Cache item title for track ID lookup cache" }, @@ -3797,7 +3797,7 @@ } } }, - "cacheClearSuccess": "Cleared: {target}", + "cacheClearSuccess": "Entfernt: {target}", "@cacheClearSuccess": { "description": "Snackbar after clearing selected cache", "placeholders": { @@ -3831,7 +3831,7 @@ "@cacheClearAll": { "description": "Button label to clear all caches" }, - "cacheCleanupUnused": "Cleanup unused data", + "cacheCleanupUnused": "Unbenutzte Daten bereinigen", "@cacheCleanupUnused": { "description": "Action title for cleaning unused entries" }, @@ -3863,7 +3863,7 @@ "@trackSaveCoverArtSubtitle": { "description": "Subtitle for save cover art action" }, - "trackSaveLyrics": "Save Lyrics (.lrc)", + "trackSaveLyrics": "Lyrics als .lrc speichern", "@trackSaveLyrics": { "description": "Menu action - save lyrics as .lrc file" }, @@ -3883,7 +3883,7 @@ "@trackReEnrichSubtitle": { "description": "Subtitle for re-enrich metadata action" }, - "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", + "trackReEnrichOnlineSubtitle": "Metadaten online suchen und in Datei einbinden", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, @@ -3958,7 +3958,7 @@ "@trackConvertBitrate": { "description": "Label for bitrate selection" }, - "trackConvertConfirmTitle": "Confirm Conversion", + "trackConvertConfirmTitle": "Konvertierung bestätigen", "@trackConvertConfirmTitle": { "description": "Confirmation dialog title" }, @@ -3977,7 +3977,7 @@ } } }, - "trackConvertConverting": "Converting audio...", + "trackConvertConverting": "Konvertiere Audio...", "@trackConvertConverting": { "description": "Snackbar while converting" }, @@ -3990,7 +3990,7 @@ } } }, - "trackConvertFailed": "Conversion failed", + "trackConvertFailed": "Konvertierung fehlgeschlagen", "@trackConvertFailed": { "description": "Snackbar when conversion fails" }, @@ -4098,11 +4098,11 @@ "@collectionPlaylistRenamed": { "description": "Snackbar after renaming playlist" }, - "collectionWishlistEmptyTitle": "Wishlist is empty", + "collectionWishlistEmptyTitle": "Wunschliste ist leer", "@collectionWishlistEmptyTitle": { "description": "Wishlist empty state title" }, - "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", + "collectionWishlistEmptySubtitle": "Tippe auf das + bei den Titeln, um sie zum späteren Herunterladen zu speichern", "@collectionWishlistEmptySubtitle": { "description": "Wishlist empty state subtitle" }, @@ -4110,11 +4110,11 @@ "@collectionLovedEmptyTitle": { "description": "Loved empty state title" }, - "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", + "collectionLovedEmptySubtitle": "Tippe auf das Herz, um deine Favoriten zu behalten", "@collectionLovedEmptySubtitle": { "description": "Loved empty state subtitle" }, - "collectionPlaylistEmptyTitle": "Playlist is empty", + "collectionPlaylistEmptyTitle": "Die Playlist ist leer", "@collectionPlaylistEmptyTitle": { "description": "Playlist empty state title" }, @@ -4122,11 +4122,11 @@ "@collectionPlaylistEmptySubtitle": { "description": "Playlist empty state subtitle" }, - "collectionRemoveFromPlaylist": "Remove from playlist", + "collectionRemoveFromPlaylist": "Von Playlist entfernen", "@collectionRemoveFromPlaylist": { "description": "Tooltip for removing track from playlist" }, - "collectionRemoveFromFolder": "Remove from folder", + "collectionRemoveFromFolder": "Aus Ordner entfernen", "@collectionRemoveFromFolder": { "description": "Tooltip for removing track from wishlist/loved folder" }, @@ -4179,7 +4179,7 @@ "@trackOptionAddToLoved": { "description": "Bottom sheet action label - add track to loved folder" }, - "trackOptionRemoveFromLoved": "Remove from Loved", + "trackOptionRemoveFromLoved": "Aus der Wunschliste entfernt", "@trackOptionRemoveFromLoved": { "description": "Bottom sheet action label - remove track from loved folder" }, @@ -4191,7 +4191,7 @@ "@trackOptionRemoveFromWishlist": { "description": "Bottom sheet action label - remove track from wishlist" }, - "collectionPlaylistChangeCover": "Change cover image", + "collectionPlaylistChangeCover": "Coverbild ändern", "@collectionPlaylistChangeCover": { "description": "Bottom sheet action to pick a custom cover image for a playlist" }, @@ -4299,7 +4299,7 @@ "@setupModeStreamingTitle": { "description": "Title for streaming mode option" }, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", + "setupModeStreamingFeature1": "Streame Titel sofort ohne Download", "@setupModeStreamingFeature1": { "description": "Streaming mode feature 1" }, @@ -4319,11 +4319,11 @@ "@settingsSmartQueueTitle": { "description": "Title for Smart Queue toggle in settings" }, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", + "settingsSmartQueueSubtitle": "Ähnliche Titel automatisch entdecken und zu deiner Wiedergabeliste hinzufügen", "@settingsSmartQueueSubtitle": { "description": "Subtitle for Smart Queue toggle in settings" }, - "whatsNewTitle": "What's New in 4.0", + "whatsNewTitle": "Was ist neu in 4.0", "@whatsNewTitle": { "description": "Title for the What's New screen" }, @@ -4343,11 +4343,11 @@ "@whatsNewWelcomeTip1": { "description": "Welcome page tip 1" }, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", + "whatsNewWelcomeTip2": "Neue Bibliothek und Vollbild-Player", "@whatsNewWelcomeTip2": { "description": "Welcome page tip 2" }, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", + "whatsNewWelcomeTip3": "Batch-Tools, Performance-Boosts und mehr", "@whatsNewWelcomeTip3": { "description": "Welcome page tip 3" }, @@ -4375,7 +4375,7 @@ "@whatsNewDualModeDesc": { "description": "What's New feature: Dual Mode description" }, - "whatsNewLibraryTitle": "Redesigned Library", + "whatsNewLibraryTitle": "Neu gestaltete Bibliothek", "@whatsNewLibraryTitle": { "description": "What's New feature: Library redesign title" }, @@ -4383,7 +4383,7 @@ "@whatsNewLibraryDesc": { "description": "What's New feature: Library redesign description" }, - "whatsNewPlayerTitle": "Full-Screen Player", + "whatsNewPlayerTitle": "Vollbild-Player", "@whatsNewPlayerTitle": { "description": "What's New feature: Full-Screen Player title" }, From 8675ab32156efdf81c749818b5c59ae0fffdb5d0 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Mon, 2 Mar 2026 03:16:44 +0700 Subject: [PATCH 059/104] New translations app_en.arb (Russian) --- lib/l10n/arb/app_ru.arb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 4be06559..caab1247 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -159,7 +159,7 @@ "@settingsAbout": { "description": "Settings section - app info" }, - "downloadTitle": "Скачивание", + "downloadTitle": "Скачать", "@downloadTitle": { "description": "Download settings page title" }, @@ -4175,7 +4175,7 @@ } } }, - "trackOptionAddToLoved": "Добавить в Любимые", + "trackOptionAddToLoved": "Добавить в Любимое", "@trackOptionAddToLoved": { "description": "Bottom sheet action label - add track to loved folder" }, From 062ce31cf7bf859d27c30467e4dffd4bb88435b0 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:27 +0700 Subject: [PATCH 060/104] Update source file app_en.arb --- lib/l10n/arb/app_en.arb | 114 +--------------------------------------- 1 file changed, 1 insertion(+), 113 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 562073ff..709c1213 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2495,117 +2495,5 @@ "setupModeStreamingFeature3": "Play any track on demand with playback controls", "@setupModeStreamingFeature3": {"description": "Streaming mode feature 3"}, "setupModeChangeableLater": "You can switch between modes anytime in Settings.", - "@setupModeChangeableLater": {"description": "Hint that mode can be changed later"}, - - "settingsSmartQueueTitle": "Smart Queue", - "@settingsSmartQueueTitle": {"description": "Title for Smart Queue toggle in settings"}, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", - "@settingsSmartQueueSubtitle": {"description": "Subtitle for Smart Queue toggle in settings"}, - - "whatsNewTitle": "What's New in 4.0", - "@whatsNewTitle": {"description": "Title for the What's New screen"}, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", - "@whatsNewSubtitle": {"description": "Subtitle for the What's New screen"}, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": {"description": "Welcome page title in What's New screen"}, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", - "@whatsNewWelcomeDesc": {"description": "Welcome page description in What's New screen"}, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", - "@whatsNewWelcomeTip1": {"description": "Welcome page tip 1"}, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", - "@whatsNewWelcomeTip2": {"description": "Welcome page tip 2"}, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", - "@whatsNewWelcomeTip3": {"description": "Welcome page tip 3"}, - "whatsNewStreamingTitle": "Streaming Mode", - "@whatsNewStreamingTitle": {"description": "What's New feature: Streaming Mode title"}, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", - "@whatsNewStreamingDesc": {"description": "What's New feature: Streaming Mode description"}, - "whatsNewSmartQueueTitle": "Smart Queue", - "@whatsNewSmartQueueTitle": {"description": "What's New feature: Smart Queue title"}, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", - "@whatsNewSmartQueueDesc": {"description": "What's New feature: Smart Queue description"}, - "whatsNewDualModeTitle": "Dual Mode", - "@whatsNewDualModeTitle": {"description": "What's New feature: Dual Mode title"}, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", - "@whatsNewDualModeDesc": {"description": "What's New feature: Dual Mode description"}, - "whatsNewLibraryTitle": "Redesigned Library", - "@whatsNewLibraryTitle": {"description": "What's New feature: Library redesign title"}, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", - "@whatsNewLibraryDesc": {"description": "What's New feature: Library redesign description"}, - "whatsNewPlayerTitle": "Full-Screen Player", - "@whatsNewPlayerTitle": {"description": "What's New feature: Full-Screen Player title"}, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", - "@whatsNewPlayerDesc": {"description": "What's New feature: Full-Screen Player description"}, - "whatsNewContextMenuTitle": "Long-Press Menus", - "@whatsNewContextMenuTitle": {"description": "What's New feature: Context Menus title"}, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", - "@whatsNewContextMenuDesc": {"description": "What's New feature: Context Menus description"}, - "whatsNewPerformanceTitle": "Performance", - "@whatsNewPerformanceTitle": {"description": "What's New feature: Performance title"}, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", - "@whatsNewPerformanceDesc": {"description": "What's New feature: Performance description"}, - "whatsNewBatchToolsTitle": "Batch Tools", - "@whatsNewBatchToolsTitle": {"description": "What's New feature: Batch Tools title"}, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", - "@whatsNewBatchToolsDesc": {"description": "What's New feature: Batch Tools description"}, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", - "@whatsNewStreamingTip1": {"description": "What's New tip: streaming instant play"}, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", - "@whatsNewStreamingTip2": {"description": "What's New tip: streaming synced lyrics"}, - "whatsNewStreamingTip3": "Download tracks directly from the player", - "@whatsNewStreamingTip3": {"description": "What's New tip: streaming download from player"}, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", - "@whatsNewSmartQueueTip1": {"description": "What's New tip: smart queue auto-fill"}, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", - "@whatsNewSmartQueueTip2": {"description": "What's New tip: smart queue artist discovery"}, - "whatsNewSmartQueueTip3": "Never run out of music to play", - "@whatsNewSmartQueueTip3": {"description": "What's New tip: smart queue endless"}, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", - "@whatsNewDualModeTip1": {"description": "What's New tip: dual mode switch"}, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", - "@whatsNewDualModeTip2": {"description": "What's New tip: dual mode adaptive UI"}, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", - "@whatsNewDualModeTip3": {"description": "What's New tip: dual mode use cases"}, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", - "@whatsNewLibraryTip1": {"description": "What's New tip: library drag and drop"}, - "whatsNewLibraryTip2": "Set custom cover images for playlists", - "@whatsNewLibraryTip2": {"description": "What's New tip: library custom covers"}, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", - "@whatsNewLibraryTip3": {"description": "What's New tip: library multi-select"}, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", - "@whatsNewPlayerTip1": {"description": "What's New tip: player parallax"}, - "whatsNewPlayerTip2": "Playback persists across app restarts", - "@whatsNewPlayerTip2": {"description": "What's New tip: player persistence"}, - "whatsNewPlayerTip3": "Synced lyrics while you listen", - "@whatsNewPlayerTip3": {"description": "What's New tip: player lyrics"}, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", - "@whatsNewContextMenuTip1": {"description": "What's New tip: context menu add to playlist"}, - "whatsNewContextMenuTip2": "Share or convert with one tap", - "@whatsNewContextMenuTip2": {"description": "What's New tip: context menu share/convert"}, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", - "@whatsNewContextMenuTip3": {"description": "What's New tip: context menu re-enrich"}, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", - "@whatsNewBatchToolsTip1": {"description": "What's New tip: batch share"}, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", - "@whatsNewBatchToolsTip2": {"description": "What's New tip: batch convert"}, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", - "@whatsNewBatchToolsTip3": {"description": "What's New tip: batch re-enrich"}, - "whatsNewPerformanceTip1": "Faster app startup time", - "@whatsNewPerformanceTip1": {"description": "What's New tip: performance startup"}, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", - "@whatsNewPerformanceTip2": {"description": "What's New tip: performance memory"}, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", - "@whatsNewPerformanceTip3": {"description": "What's New tip: performance SQLite"}, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", - "@whatsNewReadyMessage": {"description": "Ready card message on last What's New page"}, - "whatsNewGetStarted": "Let's Go", - "@whatsNewGetStarted": {"description": "Button text to dismiss What's New screen"}, - "whatsNewPageIndicator": "{current} of {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": {"type": "int"}, - "total": {"type": "int"} - } - } + "@setupModeChangeableLater": {"description": "Hint that mode can be changed later"} } From 76a05e717be52d77453385a62f295496707ff4b4 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:30 +0700 Subject: [PATCH 061/104] New translations app_en.arb (French) --- lib/l10n/arb/app_fr.arb | 216 ---------------------------------------- 1 file changed, 216 deletions(-) diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index 841895b4..dacc6646 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -4314,221 +4314,5 @@ "setupModeChangeableLater": "You can switch between modes anytime in Settings.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" - }, - "settingsSmartQueueTitle": "Smart Queue", - "@settingsSmartQueueTitle": { - "description": "Title for Smart Queue toggle in settings" - }, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", - "@settingsSmartQueueSubtitle": { - "description": "Subtitle for Smart Queue toggle in settings" - }, - "whatsNewTitle": "What's New in 4.0", - "@whatsNewTitle": { - "description": "Title for the What's New screen" - }, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", - "@whatsNewSubtitle": { - "description": "Subtitle for the What's New screen" - }, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": { - "description": "Welcome page title in What's New screen" - }, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", - "@whatsNewWelcomeDesc": { - "description": "Welcome page description in What's New screen" - }, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", - "@whatsNewWelcomeTip1": { - "description": "Welcome page tip 1" - }, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", - "@whatsNewWelcomeTip2": { - "description": "Welcome page tip 2" - }, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", - "@whatsNewWelcomeTip3": { - "description": "Welcome page tip 3" - }, - "whatsNewStreamingTitle": "Streaming Mode", - "@whatsNewStreamingTitle": { - "description": "What's New feature: Streaming Mode title" - }, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", - "@whatsNewStreamingDesc": { - "description": "What's New feature: Streaming Mode description" - }, - "whatsNewSmartQueueTitle": "Smart Queue", - "@whatsNewSmartQueueTitle": { - "description": "What's New feature: Smart Queue title" - }, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", - "@whatsNewSmartQueueDesc": { - "description": "What's New feature: Smart Queue description" - }, - "whatsNewDualModeTitle": "Dual Mode", - "@whatsNewDualModeTitle": { - "description": "What's New feature: Dual Mode title" - }, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", - "@whatsNewDualModeDesc": { - "description": "What's New feature: Dual Mode description" - }, - "whatsNewLibraryTitle": "Redesigned Library", - "@whatsNewLibraryTitle": { - "description": "What's New feature: Library redesign title" - }, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", - "@whatsNewLibraryDesc": { - "description": "What's New feature: Library redesign description" - }, - "whatsNewPlayerTitle": "Full-Screen Player", - "@whatsNewPlayerTitle": { - "description": "What's New feature: Full-Screen Player title" - }, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", - "@whatsNewPlayerDesc": { - "description": "What's New feature: Full-Screen Player description" - }, - "whatsNewContextMenuTitle": "Long-Press Menus", - "@whatsNewContextMenuTitle": { - "description": "What's New feature: Context Menus title" - }, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", - "@whatsNewContextMenuDesc": { - "description": "What's New feature: Context Menus description" - }, - "whatsNewPerformanceTitle": "Performance", - "@whatsNewPerformanceTitle": { - "description": "What's New feature: Performance title" - }, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", - "@whatsNewPerformanceDesc": { - "description": "What's New feature: Performance description" - }, - "whatsNewBatchToolsTitle": "Batch Tools", - "@whatsNewBatchToolsTitle": { - "description": "What's New feature: Batch Tools title" - }, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", - "@whatsNewBatchToolsDesc": { - "description": "What's New feature: Batch Tools description" - }, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", - "@whatsNewStreamingTip1": { - "description": "What's New tip: streaming instant play" - }, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", - "@whatsNewStreamingTip2": { - "description": "What's New tip: streaming synced lyrics" - }, - "whatsNewStreamingTip3": "Download tracks directly from the player", - "@whatsNewStreamingTip3": { - "description": "What's New tip: streaming download from player" - }, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", - "@whatsNewSmartQueueTip1": { - "description": "What's New tip: smart queue auto-fill" - }, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", - "@whatsNewSmartQueueTip2": { - "description": "What's New tip: smart queue artist discovery" - }, - "whatsNewSmartQueueTip3": "Never run out of music to play", - "@whatsNewSmartQueueTip3": { - "description": "What's New tip: smart queue endless" - }, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", - "@whatsNewDualModeTip1": { - "description": "What's New tip: dual mode switch" - }, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", - "@whatsNewDualModeTip2": { - "description": "What's New tip: dual mode adaptive UI" - }, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", - "@whatsNewDualModeTip3": { - "description": "What's New tip: dual mode use cases" - }, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", - "@whatsNewLibraryTip1": { - "description": "What's New tip: library drag and drop" - }, - "whatsNewLibraryTip2": "Set custom cover images for playlists", - "@whatsNewLibraryTip2": { - "description": "What's New tip: library custom covers" - }, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", - "@whatsNewLibraryTip3": { - "description": "What's New tip: library multi-select" - }, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", - "@whatsNewPlayerTip1": { - "description": "What's New tip: player parallax" - }, - "whatsNewPlayerTip2": "Playback persists across app restarts", - "@whatsNewPlayerTip2": { - "description": "What's New tip: player persistence" - }, - "whatsNewPlayerTip3": "Synced lyrics while you listen", - "@whatsNewPlayerTip3": { - "description": "What's New tip: player lyrics" - }, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", - "@whatsNewContextMenuTip1": { - "description": "What's New tip: context menu add to playlist" - }, - "whatsNewContextMenuTip2": "Share or convert with one tap", - "@whatsNewContextMenuTip2": { - "description": "What's New tip: context menu share/convert" - }, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", - "@whatsNewContextMenuTip3": { - "description": "What's New tip: context menu re-enrich" - }, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", - "@whatsNewBatchToolsTip1": { - "description": "What's New tip: batch share" - }, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", - "@whatsNewBatchToolsTip2": { - "description": "What's New tip: batch convert" - }, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", - "@whatsNewBatchToolsTip3": { - "description": "What's New tip: batch re-enrich" - }, - "whatsNewPerformanceTip1": "Faster app startup time", - "@whatsNewPerformanceTip1": { - "description": "What's New tip: performance startup" - }, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", - "@whatsNewPerformanceTip2": { - "description": "What's New tip: performance memory" - }, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", - "@whatsNewPerformanceTip3": { - "description": "What's New tip: performance SQLite" - }, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", - "@whatsNewReadyMessage": { - "description": "Ready card message on last What's New page" - }, - "whatsNewGetStarted": "Let's Go", - "@whatsNewGetStarted": { - "description": "Button text to dismiss What's New screen" - }, - "whatsNewPageIndicator": "{current} of {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } } } \ No newline at end of file From ebcdcf40dcbf5df1630be3a92e30a95e1c0b6385 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:31 +0700 Subject: [PATCH 062/104] New translations app_en.arb (Spanish) --- lib/l10n/arb/app_es-ES.arb | 216 ------------------------------------- 1 file changed, 216 deletions(-) diff --git a/lib/l10n/arb/app_es-ES.arb b/lib/l10n/arb/app_es-ES.arb index 85cb5bc1..76b14618 100644 --- a/lib/l10n/arb/app_es-ES.arb +++ b/lib/l10n/arb/app_es-ES.arb @@ -4314,221 +4314,5 @@ "setupModeChangeableLater": "You can switch between modes anytime in Settings.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" - }, - "settingsSmartQueueTitle": "Smart Queue", - "@settingsSmartQueueTitle": { - "description": "Title for Smart Queue toggle in settings" - }, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", - "@settingsSmartQueueSubtitle": { - "description": "Subtitle for Smart Queue toggle in settings" - }, - "whatsNewTitle": "What's New in 4.0", - "@whatsNewTitle": { - "description": "Title for the What's New screen" - }, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", - "@whatsNewSubtitle": { - "description": "Subtitle for the What's New screen" - }, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": { - "description": "Welcome page title in What's New screen" - }, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", - "@whatsNewWelcomeDesc": { - "description": "Welcome page description in What's New screen" - }, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", - "@whatsNewWelcomeTip1": { - "description": "Welcome page tip 1" - }, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", - "@whatsNewWelcomeTip2": { - "description": "Welcome page tip 2" - }, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", - "@whatsNewWelcomeTip3": { - "description": "Welcome page tip 3" - }, - "whatsNewStreamingTitle": "Streaming Mode", - "@whatsNewStreamingTitle": { - "description": "What's New feature: Streaming Mode title" - }, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", - "@whatsNewStreamingDesc": { - "description": "What's New feature: Streaming Mode description" - }, - "whatsNewSmartQueueTitle": "Smart Queue", - "@whatsNewSmartQueueTitle": { - "description": "What's New feature: Smart Queue title" - }, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", - "@whatsNewSmartQueueDesc": { - "description": "What's New feature: Smart Queue description" - }, - "whatsNewDualModeTitle": "Dual Mode", - "@whatsNewDualModeTitle": { - "description": "What's New feature: Dual Mode title" - }, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", - "@whatsNewDualModeDesc": { - "description": "What's New feature: Dual Mode description" - }, - "whatsNewLibraryTitle": "Redesigned Library", - "@whatsNewLibraryTitle": { - "description": "What's New feature: Library redesign title" - }, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", - "@whatsNewLibraryDesc": { - "description": "What's New feature: Library redesign description" - }, - "whatsNewPlayerTitle": "Full-Screen Player", - "@whatsNewPlayerTitle": { - "description": "What's New feature: Full-Screen Player title" - }, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", - "@whatsNewPlayerDesc": { - "description": "What's New feature: Full-Screen Player description" - }, - "whatsNewContextMenuTitle": "Long-Press Menus", - "@whatsNewContextMenuTitle": { - "description": "What's New feature: Context Menus title" - }, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", - "@whatsNewContextMenuDesc": { - "description": "What's New feature: Context Menus description" - }, - "whatsNewPerformanceTitle": "Performance", - "@whatsNewPerformanceTitle": { - "description": "What's New feature: Performance title" - }, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", - "@whatsNewPerformanceDesc": { - "description": "What's New feature: Performance description" - }, - "whatsNewBatchToolsTitle": "Batch Tools", - "@whatsNewBatchToolsTitle": { - "description": "What's New feature: Batch Tools title" - }, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", - "@whatsNewBatchToolsDesc": { - "description": "What's New feature: Batch Tools description" - }, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", - "@whatsNewStreamingTip1": { - "description": "What's New tip: streaming instant play" - }, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", - "@whatsNewStreamingTip2": { - "description": "What's New tip: streaming synced lyrics" - }, - "whatsNewStreamingTip3": "Download tracks directly from the player", - "@whatsNewStreamingTip3": { - "description": "What's New tip: streaming download from player" - }, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", - "@whatsNewSmartQueueTip1": { - "description": "What's New tip: smart queue auto-fill" - }, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", - "@whatsNewSmartQueueTip2": { - "description": "What's New tip: smart queue artist discovery" - }, - "whatsNewSmartQueueTip3": "Never run out of music to play", - "@whatsNewSmartQueueTip3": { - "description": "What's New tip: smart queue endless" - }, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", - "@whatsNewDualModeTip1": { - "description": "What's New tip: dual mode switch" - }, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", - "@whatsNewDualModeTip2": { - "description": "What's New tip: dual mode adaptive UI" - }, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", - "@whatsNewDualModeTip3": { - "description": "What's New tip: dual mode use cases" - }, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", - "@whatsNewLibraryTip1": { - "description": "What's New tip: library drag and drop" - }, - "whatsNewLibraryTip2": "Set custom cover images for playlists", - "@whatsNewLibraryTip2": { - "description": "What's New tip: library custom covers" - }, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", - "@whatsNewLibraryTip3": { - "description": "What's New tip: library multi-select" - }, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", - "@whatsNewPlayerTip1": { - "description": "What's New tip: player parallax" - }, - "whatsNewPlayerTip2": "Playback persists across app restarts", - "@whatsNewPlayerTip2": { - "description": "What's New tip: player persistence" - }, - "whatsNewPlayerTip3": "Synced lyrics while you listen", - "@whatsNewPlayerTip3": { - "description": "What's New tip: player lyrics" - }, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", - "@whatsNewContextMenuTip1": { - "description": "What's New tip: context menu add to playlist" - }, - "whatsNewContextMenuTip2": "Share or convert with one tap", - "@whatsNewContextMenuTip2": { - "description": "What's New tip: context menu share/convert" - }, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", - "@whatsNewContextMenuTip3": { - "description": "What's New tip: context menu re-enrich" - }, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", - "@whatsNewBatchToolsTip1": { - "description": "What's New tip: batch share" - }, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", - "@whatsNewBatchToolsTip2": { - "description": "What's New tip: batch convert" - }, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", - "@whatsNewBatchToolsTip3": { - "description": "What's New tip: batch re-enrich" - }, - "whatsNewPerformanceTip1": "Faster app startup time", - "@whatsNewPerformanceTip1": { - "description": "What's New tip: performance startup" - }, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", - "@whatsNewPerformanceTip2": { - "description": "What's New tip: performance memory" - }, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", - "@whatsNewPerformanceTip3": { - "description": "What's New tip: performance SQLite" - }, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", - "@whatsNewReadyMessage": { - "description": "Ready card message on last What's New page" - }, - "whatsNewGetStarted": "Let's Go", - "@whatsNewGetStarted": { - "description": "Button text to dismiss What's New screen" - }, - "whatsNewPageIndicator": "{current} of {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } } } \ No newline at end of file From 6fb50cfc67fb7a86ddae3ab09a42907162abd517 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:32 +0700 Subject: [PATCH 063/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 290 +++++----------------------------------- 1 file changed, 37 insertions(+), 253 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 9e894fea..1d46bf1d 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -2443,7 +2443,7 @@ "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, - "extensionsNoDownloadProvider": "No extensions with download provider", + "extensionsNoDownloadProvider": "Keine Erweiterungen mit Download-Provider", "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" }, @@ -3198,7 +3198,7 @@ "@libraryScanSelectFolderFirst": { "description": "Message when trying to scan without folder" }, - "libraryCleanupMissingFiles": "Cleanup Missing Files", + "libraryCleanupMissingFiles": "Fehlende Dateien bereinigen", "@libraryCleanupMissingFiles": { "description": "Button to remove entries for missing files" }, @@ -3210,7 +3210,7 @@ "@libraryClear": { "description": "Button to clear all library entries" }, - "libraryClearSubtitle": "Remove all scanned tracks", + "libraryClearSubtitle": "Alle gescannten Titel entfernen", "@libraryClearSubtitle": { "description": "Subtitle for clear button" }, @@ -3265,7 +3265,7 @@ "@libraryScanning": { "description": "Status during scan" }, - "libraryScanProgress": "{progress}% of {total} files", + "libraryScanProgress": "{progress}% von {total} Dateien", "@libraryScanProgress": { "description": "Scan progress display", "placeholders": { @@ -3294,7 +3294,7 @@ "@libraryCleared": { "description": "Snackbar after clearing library" }, - "libraryStorageAccessRequired": "Storage Access Required", + "libraryStorageAccessRequired": "Speicherzugriff erforderlich", "@libraryStorageAccessRequired": { "description": "Dialog title for storage permission" }, @@ -3302,7 +3302,7 @@ "@libraryStorageAccessMessage": { "description": "Dialog message for storage permission" }, - "libraryFolderNotExist": "Selected folder does not exist", + "libraryFolderNotExist": "Der ausgewählte Ordner existiert nicht", "@libraryFolderNotExist": { "description": "Error when folder doesn't exist" }, @@ -3394,7 +3394,7 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} filter(s) active", + "libraryFilterActive": "{count} Filter aktiv", "@libraryFilterActive": { "description": "Badge showing number of active filters", "placeholders": { @@ -3429,11 +3429,11 @@ "@storageSwitchTitle": { "description": "Dialog title when switching storage mode" }, - "storageSwitchToSafTitle": "Switch to SAF Storage?", + "storageSwitchToSafTitle": "Zum SAF-Speicher wechseln?", "@storageSwitchToSafTitle": { "description": "Dialog title when switching to SAF" }, - "storageSwitchToAppTitle": "Switch to App Storage?", + "storageSwitchToAppTitle": "Zum App Speicher wechseln?", "@storageSwitchToAppTitle": { "description": "Dialog title when switching to app storage" }, @@ -3449,7 +3449,7 @@ "@storageSwitchExistingDownloads": { "description": "Section header for existing downloads info" }, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", + "storageSwitchExistingDownloadsInfo": "{count} Titel im {mode} Speicher", "@storageSwitchExistingDownloadsInfo": { "description": "Info about existing downloads count", "placeholders": { @@ -3465,7 +3465,7 @@ "@storageSwitchNewDownloads": { "description": "Section header for new downloads info" }, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", + "storageSwitchNewDownloadsLocation": "Wird unter gespeichert: {location}", "@storageSwitchNewDownloadsLocation": { "description": "Shows where new downloads will go", "placeholders": { @@ -3512,7 +3512,7 @@ } } }, - "storageStatsSafCount": "{count} tracks in SAF Storage", + "storageStatsSafCount": "{count} Titel im SAF Speicher", "@storageStatsSafCount": { "description": "Count of tracks in SAF storage", "placeholders": { @@ -3525,7 +3525,7 @@ "@storageModeInfo": { "description": "Info when user has files in both storage modes" }, - "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", + "tutorialWelcomeTitle": "Willkommen bei SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" }, @@ -3625,7 +3625,7 @@ "@tutorialExtensionsTip3": { "description": "Tutorial extensions tip 3" }, - "tutorialSettingsTitle": "Customize Your Experience", + "tutorialSettingsTitle": "Passe deine Benutzererfahrung an", "@tutorialSettingsTitle": { "description": "Tutorial settings page title" }, @@ -3641,7 +3641,7 @@ "@tutorialSettingsTip2": { "description": "Tutorial settings tip 2" }, - "tutorialSettingsTip3": "Customize app theme and appearance", + "tutorialSettingsTip3": "App-Design und Aussehen anpassen", "@tutorialSettingsTip3": { "description": "Tutorial settings tip 3" }, @@ -3657,11 +3657,11 @@ "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" }, - "libraryForceFullScanSubtitle": "Rescan all files, ignoring cache", + "libraryForceFullScanSubtitle": "Alle Dateien erneut scannen und Cache ignorieren", "@libraryForceFullScanSubtitle": { "description": "Subtitle for force full scan button" }, - "cleanupOrphanedDownloads": "Cleanup Orphaned Downloads", + "cleanupOrphanedDownloads": "Verwaiste Downloads bereinigen", "@cleanupOrphanedDownloads": { "description": "Button to remove history entries for deleted files" }, @@ -3678,7 +3678,7 @@ } } }, - "cleanupOrphanedDownloadsNone": "No orphaned entries found", + "cleanupOrphanedDownloadsNone": "Keine verwaisten Einträge gefunden", "@cleanupOrphanedDownloadsNone": { "description": "Snackbar when no orphans found" }, @@ -3904,7 +3904,7 @@ "@trackCoverNoSource": { "description": "Snackbar when no cover art URL or embedded cover" }, - "trackLyricsSaved": "Lyrics saved to {fileName}", + "trackLyricsSaved": "Lyrics in {fileName} gespeichert", "@trackLyricsSaved": { "description": "Snackbar after lyrics saved", "placeholders": { @@ -3913,7 +3913,7 @@ } } }, - "trackReEnrichProgress": "Re-enriching metadata...", + "trackReEnrichProgress": "Metadaten neu anreichern...", "@trackReEnrichProgress": { "description": "Snackbar while re-enriching metadata" }, @@ -3921,11 +3921,11 @@ "@trackReEnrichSearching": { "description": "Snackbar while searching metadata from internet for local items" }, - "trackReEnrichSuccess": "Metadata re-enriched successfully", + "trackReEnrichSuccess": "Metadaten erfolgreich neu angereichert", "@trackReEnrichSuccess": { "description": "Snackbar after successful re-enrichment" }, - "trackReEnrichFfmpegFailed": "FFmpeg metadata embed failed", + "trackReEnrichFfmpegFailed": "FFmpeg Metadaten-Einbettung fehlgeschlagen", "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, @@ -3942,7 +3942,7 @@ "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "trackConvertFormatSubtitle": "In MP3 oder Opus konvertieren", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, @@ -3981,7 +3981,7 @@ "@trackConvertConverting": { "description": "Snackbar while converting" }, - "trackConvertSuccess": "Converted to {format} successfully", + "trackConvertSuccess": "Konvertiert in {format} erfolgreich", "@trackConvertSuccess": { "description": "Snackbar after successful conversion", "placeholders": { @@ -4043,7 +4043,7 @@ } } }, - "collectionAddedToPlaylist": "Added to \"{playlistName}\"", + "collectionAddedToPlaylist": "Zu \"{playlistName} \" hinzugefügt", "@collectionAddedToPlaylist": { "description": "Snackbar after adding track to playlist", "placeholders": { @@ -4052,7 +4052,7 @@ } } }, - "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", + "collectionAlreadyInPlaylist": "Bereits in \"{playlistName}\"", "@collectionAlreadyInPlaylist": { "description": "Snackbar when track already exists in playlist", "placeholders": { @@ -4069,7 +4069,7 @@ "@collectionPlaylistNameHint": { "description": "Hint text for playlist name input" }, - "collectionPlaylistNameRequired": "Playlist name is required", + "collectionPlaylistNameRequired": "Playlist-Name ist erforderlich", "@collectionPlaylistNameRequired": { "description": "Validation error for empty playlist name" }, @@ -4106,7 +4106,7 @@ "@collectionWishlistEmptySubtitle": { "description": "Wishlist empty state subtitle" }, - "collectionLovedEmptyTitle": "Loved folder is empty", + "collectionLovedEmptyTitle": "Lieblingssongs sind leer", "@collectionLovedEmptyTitle": { "description": "Loved empty state title" }, @@ -4118,7 +4118,7 @@ "@collectionPlaylistEmptyTitle": { "description": "Playlist empty state title" }, - "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", + "collectionPlaylistEmptySubtitle": "Drücke lange + auf einem beliebigen Titel, um ihn hier hinzuzufügen", "@collectionPlaylistEmptySubtitle": { "description": "Playlist empty state subtitle" }, @@ -4130,7 +4130,7 @@ "@collectionRemoveFromFolder": { "description": "Tooltip for removing track from wishlist/loved folder" }, - "collectionRemoved": "\"{trackName}\" removed", + "collectionRemoved": "\"{trackName}\" entfernt", "@collectionRemoved": { "description": "Snackbar after removing a track from a collection", "placeholders": { @@ -4139,7 +4139,7 @@ } } }, - "collectionAddedToLoved": "\"{trackName}\" added to Loved", + "collectionAddedToLoved": "\"{trackName}\" zu Lieblingssongs hinzugefügt", "@collectionAddedToLoved": { "description": "Snackbar after adding track to loved folder", "placeholders": { @@ -4148,7 +4148,7 @@ } } }, - "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", + "collectionRemovedFromLoved": "\"{trackName}\" aus Lieblingssongs entfernt", "@collectionRemovedFromLoved": { "description": "Snackbar after removing track from loved folder", "placeholders": { @@ -4157,7 +4157,7 @@ } } }, - "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", + "collectionAddedToWishlist": "\"{trackName}\" zur Wunschliste hinzugefügt", "@collectionAddedToWishlist": { "description": "Snackbar after adding track to wishlist", "placeholders": { @@ -4166,7 +4166,7 @@ } } }, - "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", + "collectionRemovedFromWishlist": "\"{trackName}\" aus der Wunschliste entfernt", "@collectionRemovedFromWishlist": { "description": "Snackbar after removing track from wishlist", "placeholders": { @@ -4179,7 +4179,7 @@ "@trackOptionAddToLoved": { "description": "Bottom sheet action label - add track to loved folder" }, - "trackOptionRemoveFromLoved": "Aus der Wunschliste entfernt", + "trackOptionRemoveFromLoved": "Aus Lieblingssongs entfernt", "@trackOptionRemoveFromLoved": { "description": "Bottom sheet action label - remove track from loved folder" }, @@ -4187,7 +4187,7 @@ "@trackOptionAddToWishlist": { "description": "Bottom sheet action label - add track to wishlist" }, - "trackOptionRemoveFromWishlist": "Remove from Wishlist", + "trackOptionRemoveFromWishlist": "Von der Wunschliste entfernen", "@trackOptionRemoveFromWishlist": { "description": "Bottom sheet action label - remove track from wishlist" }, @@ -4291,7 +4291,7 @@ "@setupModeDownloaderFeature2": { "description": "Downloader mode feature 2" }, - "setupModeDownloaderFeature3": "Manage your local music library", + "setupModeDownloaderFeature3": "Verwalte deine lokale Musikbibliothek", "@setupModeDownloaderFeature3": { "description": "Downloader mode feature 3" }, @@ -4314,221 +4314,5 @@ "setupModeChangeableLater": "Du kannst jederzeit in den Einstellungen zwischen den Modi wechseln.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" - }, - "settingsSmartQueueTitle": "Smarte Warteschlange", - "@settingsSmartQueueTitle": { - "description": "Title for Smart Queue toggle in settings" - }, - "settingsSmartQueueSubtitle": "Ähnliche Titel automatisch entdecken und zu deiner Wiedergabeliste hinzufügen", - "@settingsSmartQueueSubtitle": { - "description": "Subtitle for Smart Queue toggle in settings" - }, - "whatsNewTitle": "Was ist neu in 4.0", - "@whatsNewTitle": { - "description": "Title for the What's New screen" - }, - "whatsNewSubtitle": "SpotiFLAC hat sich weiterentwickelt hier ist was sich seit 3.x geändert hat", - "@whatsNewSubtitle": { - "description": "Subtitle for the What's New screen" - }, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": { - "description": "Welcome page title in What's New screen" - }, - "whatsNewWelcomeDesc": "Willkommen zurück! Dies ist ein großes Update mit neuen Features. Wische durch um zu sehen, was sich geändert hat.", - "@whatsNewWelcomeDesc": { - "description": "Welcome page description in What's New screen" - }, - "whatsNewWelcomeTip1": "Neuer Streaming-Modus mit Sofortwiedergabe", - "@whatsNewWelcomeTip1": { - "description": "Welcome page tip 1" - }, - "whatsNewWelcomeTip2": "Neue Bibliothek und Vollbild-Player", - "@whatsNewWelcomeTip2": { - "description": "Welcome page tip 2" - }, - "whatsNewWelcomeTip3": "Batch-Tools, Performance-Boosts und mehr", - "@whatsNewWelcomeTip3": { - "description": "Welcome page tip 3" - }, - "whatsNewStreamingTitle": "Streaming-Modus", - "@whatsNewStreamingTitle": { - "description": "What's New feature: Streaming Mode title" - }, - "whatsNewStreamingDesc": "Tippe auf einen Titel, um sofort abzuspielen kein Download nötig. Vollbild-Player mit synchronisierten Songtexten und Mediensteuerungen.", - "@whatsNewStreamingDesc": { - "description": "What's New feature: Streaming Mode description" - }, - "whatsNewSmartQueueTitle": "Smarte Warteschlange", - "@whatsNewSmartQueueTitle": { - "description": "What's New feature: Smart Queue title" - }, - "whatsNewSmartQueueDesc": "Deine Warteschlange automatisch kuratiert mit verwandten Tracks und Künstlern Entdeckung. Nie geht die Musik aus.", - "@whatsNewSmartQueueDesc": { - "description": "What's New feature: Smart Queue description" - }, - "whatsNewDualModeTitle": "Dual-Modus", - "@whatsNewDualModeTitle": { - "description": "What's New feature: Dual Mode title" - }, - "whatsNewDualModeDesc": "Wechsel jederzeit zwischen dem Downloader und dem Streaming-Modus. Alle Tasten passen sich automatisch an.", - "@whatsNewDualModeDesc": { - "description": "What's New feature: Dual Mode description" - }, - "whatsNewLibraryTitle": "Neu gestaltete Bibliothek", - "@whatsNewLibraryTitle": { - "description": "What's New feature: Library redesign title" - }, - "whatsNewLibraryDesc": "Playlist-erstes Layout mit Drag-and-Drop Kategorisierung, benutzerdefinierten Covern und Mehrfachauswahl-Aktionen.", - "@whatsNewLibraryDesc": { - "description": "What's New feature: Library redesign description" - }, - "whatsNewPlayerTitle": "Vollbild-Player", - "@whatsNewPlayerTitle": { - "description": "What's New feature: Full-Screen Player title" - }, - "whatsNewPlayerDesc": "Cover art parallax, synchronisierte Songtexte, Wiedergabe der Persistenz bei Neustarts und Download Button im Player.", - "@whatsNewPlayerDesc": { - "description": "What's New feature: Full-Screen Player description" - }, - "whatsNewContextMenuTitle": "Menü lange drücken", - "@whatsNewContextMenuTitle": { - "description": "What's New feature: Context Menus title" - }, - "whatsNewContextMenuDesc": "Drücke lange auf einen Titel für schnelle Aktionen fügen Sie zu Wiedergabeliste, Teilen, Konvertieren oder erneuern Sie Metadaten hinzu.", - "@whatsNewContextMenuDesc": { - "description": "What's New feature: Context Menus description" - }, - "whatsNewPerformanceTitle": "Performance", - "@whatsNewPerformanceTitle": { - "description": "What's New feature: Performance title" - }, - "whatsNewPerformanceDesc": "Schnelleres Starten, reduzierte Speicherauslastung, SQLite-gestützte Dauerhaftigkeit und granulare UI-Updates.", - "@whatsNewPerformanceDesc": { - "description": "What's New feature: Performance description" - }, - "whatsNewBatchToolsTitle": "Batch-Tools", - "@whatsNewBatchToolsTitle": { - "description": "What's New feature: Batch Tools title" - }, - "whatsNewBatchToolsDesc": "Mehrfachauswahl-Freigabe, Batch-Konvertierung in MP3/Opus und Batch-Bereicherung von Metadaten in Ihrer gesamten Bibliothek.", - "@whatsNewBatchToolsDesc": { - "description": "What's New feature: Batch Tools description" - }, - "whatsNewStreamingTip1": "Tippe auf irgendeinen Titel, um ihn sofort abzuspielen", - "@whatsNewStreamingTip1": { - "description": "What's New tip: streaming instant play" - }, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", - "@whatsNewStreamingTip2": { - "description": "What's New tip: streaming synced lyrics" - }, - "whatsNewStreamingTip3": "Titel direkt vom Player herunterladen", - "@whatsNewStreamingTip3": { - "description": "What's New tip: streaming download from player" - }, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", - "@whatsNewSmartQueueTip1": { - "description": "What's New tip: smart queue auto-fill" - }, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", - "@whatsNewSmartQueueTip2": { - "description": "What's New tip: smart queue artist discovery" - }, - "whatsNewSmartQueueTip3": "Never run out of music to play", - "@whatsNewSmartQueueTip3": { - "description": "What's New tip: smart queue endless" - }, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", - "@whatsNewDualModeTip1": { - "description": "What's New tip: dual mode switch" - }, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", - "@whatsNewDualModeTip2": { - "description": "What's New tip: dual mode adaptive UI" - }, - "whatsNewDualModeTip3": "Download für Offline, Stream für Sofortwiedergabe", - "@whatsNewDualModeTip3": { - "description": "What's New tip: dual mode use cases" - }, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", - "@whatsNewLibraryTip1": { - "description": "What's New tip: library drag and drop" - }, - "whatsNewLibraryTip2": "Set custom cover images for playlists", - "@whatsNewLibraryTip2": { - "description": "What's New tip: library custom covers" - }, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", - "@whatsNewLibraryTip3": { - "description": "What's New tip: library multi-select" - }, - "whatsNewPlayerTip1": "Cover mit Parallaxen-Scrolling-Effekt", - "@whatsNewPlayerTip1": { - "description": "What's New tip: player parallax" - }, - "whatsNewPlayerTip2": "Playback persists across app restarts", - "@whatsNewPlayerTip2": { - "description": "What's New tip: player persistence" - }, - "whatsNewPlayerTip3": "Synced lyrics while you listen", - "@whatsNewPlayerTip3": { - "description": "What's New tip: player lyrics" - }, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", - "@whatsNewContextMenuTip1": { - "description": "What's New tip: context menu add to playlist" - }, - "whatsNewContextMenuTip2": "Mit einem Klick teilen oder konvertieren", - "@whatsNewContextMenuTip2": { - "description": "What's New tip: context menu share/convert" - }, - "whatsNewContextMenuTip3": "Metadaten bei Bedarf neu anreichern", - "@whatsNewContextMenuTip3": { - "description": "What's New tip: context menu re-enrich" - }, - "whatsNewBatchToolsTip1": "Mehrere Titel gleichzeitig teilen", - "@whatsNewBatchToolsTip1": { - "description": "What's New tip: batch share" - }, - "whatsNewBatchToolsTip2": "Batch-Konvertierung in MP3 oder Opus Format", - "@whatsNewBatchToolsTip2": { - "description": "What's New tip: batch convert" - }, - "whatsNewBatchToolsTip3": "Metadaten in Ihrer Bibliothek neu anreichern", - "@whatsNewBatchToolsTip3": { - "description": "What's New tip: batch re-enrich" - }, - "whatsNewPerformanceTip1": "Schnellere App Startzeit", - "@whatsNewPerformanceTip1": { - "description": "What's New tip: performance startup" - }, - "whatsNewPerformanceTip2": "Verringerte Speicherauslastung während der Wiedergabe", - "@whatsNewPerformanceTip2": { - "description": "What's New tip: performance memory" - }, - "whatsNewPerformanceTip3": "SQLite-gesicherter Speicher für Zuverlässigkeit", - "@whatsNewPerformanceTip3": { - "description": "What's New tip: performance SQLite" - }, - "whatsNewReadyMessage": "Alles ist bereit, viel Spaß mit dem neuen SpotiFLAC!", - "@whatsNewReadyMessage": { - "description": "Ready card message on last What's New page" - }, - "whatsNewGetStarted": "Los geht's", - "@whatsNewGetStarted": { - "description": "Button text to dismiss What's New screen" - }, - "whatsNewPageIndicator": "{current} von {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } } } \ No newline at end of file From c929f8d0a6bb5cc95f7e22c65dc4ddea688d4f2f Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:33 +0700 Subject: [PATCH 064/104] New translations app_en.arb (Japanese) --- lib/l10n/arb/app_ja.arb | 216 ---------------------------------------- 1 file changed, 216 deletions(-) diff --git a/lib/l10n/arb/app_ja.arb b/lib/l10n/arb/app_ja.arb index 2cf76c9d..375344dc 100644 --- a/lib/l10n/arb/app_ja.arb +++ b/lib/l10n/arb/app_ja.arb @@ -4314,221 +4314,5 @@ "setupModeChangeableLater": "You can switch between modes anytime in Settings.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" - }, - "settingsSmartQueueTitle": "Smart Queue", - "@settingsSmartQueueTitle": { - "description": "Title for Smart Queue toggle in settings" - }, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", - "@settingsSmartQueueSubtitle": { - "description": "Subtitle for Smart Queue toggle in settings" - }, - "whatsNewTitle": "What's New in 4.0", - "@whatsNewTitle": { - "description": "Title for the What's New screen" - }, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", - "@whatsNewSubtitle": { - "description": "Subtitle for the What's New screen" - }, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": { - "description": "Welcome page title in What's New screen" - }, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", - "@whatsNewWelcomeDesc": { - "description": "Welcome page description in What's New screen" - }, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", - "@whatsNewWelcomeTip1": { - "description": "Welcome page tip 1" - }, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", - "@whatsNewWelcomeTip2": { - "description": "Welcome page tip 2" - }, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", - "@whatsNewWelcomeTip3": { - "description": "Welcome page tip 3" - }, - "whatsNewStreamingTitle": "Streaming Mode", - "@whatsNewStreamingTitle": { - "description": "What's New feature: Streaming Mode title" - }, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", - "@whatsNewStreamingDesc": { - "description": "What's New feature: Streaming Mode description" - }, - "whatsNewSmartQueueTitle": "Smart Queue", - "@whatsNewSmartQueueTitle": { - "description": "What's New feature: Smart Queue title" - }, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", - "@whatsNewSmartQueueDesc": { - "description": "What's New feature: Smart Queue description" - }, - "whatsNewDualModeTitle": "Dual Mode", - "@whatsNewDualModeTitle": { - "description": "What's New feature: Dual Mode title" - }, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", - "@whatsNewDualModeDesc": { - "description": "What's New feature: Dual Mode description" - }, - "whatsNewLibraryTitle": "Redesigned Library", - "@whatsNewLibraryTitle": { - "description": "What's New feature: Library redesign title" - }, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", - "@whatsNewLibraryDesc": { - "description": "What's New feature: Library redesign description" - }, - "whatsNewPlayerTitle": "Full-Screen Player", - "@whatsNewPlayerTitle": { - "description": "What's New feature: Full-Screen Player title" - }, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", - "@whatsNewPlayerDesc": { - "description": "What's New feature: Full-Screen Player description" - }, - "whatsNewContextMenuTitle": "Long-Press Menus", - "@whatsNewContextMenuTitle": { - "description": "What's New feature: Context Menus title" - }, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", - "@whatsNewContextMenuDesc": { - "description": "What's New feature: Context Menus description" - }, - "whatsNewPerformanceTitle": "Performance", - "@whatsNewPerformanceTitle": { - "description": "What's New feature: Performance title" - }, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", - "@whatsNewPerformanceDesc": { - "description": "What's New feature: Performance description" - }, - "whatsNewBatchToolsTitle": "Batch Tools", - "@whatsNewBatchToolsTitle": { - "description": "What's New feature: Batch Tools title" - }, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", - "@whatsNewBatchToolsDesc": { - "description": "What's New feature: Batch Tools description" - }, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", - "@whatsNewStreamingTip1": { - "description": "What's New tip: streaming instant play" - }, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", - "@whatsNewStreamingTip2": { - "description": "What's New tip: streaming synced lyrics" - }, - "whatsNewStreamingTip3": "Download tracks directly from the player", - "@whatsNewStreamingTip3": { - "description": "What's New tip: streaming download from player" - }, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", - "@whatsNewSmartQueueTip1": { - "description": "What's New tip: smart queue auto-fill" - }, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", - "@whatsNewSmartQueueTip2": { - "description": "What's New tip: smart queue artist discovery" - }, - "whatsNewSmartQueueTip3": "Never run out of music to play", - "@whatsNewSmartQueueTip3": { - "description": "What's New tip: smart queue endless" - }, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", - "@whatsNewDualModeTip1": { - "description": "What's New tip: dual mode switch" - }, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", - "@whatsNewDualModeTip2": { - "description": "What's New tip: dual mode adaptive UI" - }, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", - "@whatsNewDualModeTip3": { - "description": "What's New tip: dual mode use cases" - }, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", - "@whatsNewLibraryTip1": { - "description": "What's New tip: library drag and drop" - }, - "whatsNewLibraryTip2": "Set custom cover images for playlists", - "@whatsNewLibraryTip2": { - "description": "What's New tip: library custom covers" - }, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", - "@whatsNewLibraryTip3": { - "description": "What's New tip: library multi-select" - }, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", - "@whatsNewPlayerTip1": { - "description": "What's New tip: player parallax" - }, - "whatsNewPlayerTip2": "Playback persists across app restarts", - "@whatsNewPlayerTip2": { - "description": "What's New tip: player persistence" - }, - "whatsNewPlayerTip3": "Synced lyrics while you listen", - "@whatsNewPlayerTip3": { - "description": "What's New tip: player lyrics" - }, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", - "@whatsNewContextMenuTip1": { - "description": "What's New tip: context menu add to playlist" - }, - "whatsNewContextMenuTip2": "Share or convert with one tap", - "@whatsNewContextMenuTip2": { - "description": "What's New tip: context menu share/convert" - }, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", - "@whatsNewContextMenuTip3": { - "description": "What's New tip: context menu re-enrich" - }, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", - "@whatsNewBatchToolsTip1": { - "description": "What's New tip: batch share" - }, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", - "@whatsNewBatchToolsTip2": { - "description": "What's New tip: batch convert" - }, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", - "@whatsNewBatchToolsTip3": { - "description": "What's New tip: batch re-enrich" - }, - "whatsNewPerformanceTip1": "Faster app startup time", - "@whatsNewPerformanceTip1": { - "description": "What's New tip: performance startup" - }, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", - "@whatsNewPerformanceTip2": { - "description": "What's New tip: performance memory" - }, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", - "@whatsNewPerformanceTip3": { - "description": "What's New tip: performance SQLite" - }, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", - "@whatsNewReadyMessage": { - "description": "Ready card message on last What's New page" - }, - "whatsNewGetStarted": "Let's Go", - "@whatsNewGetStarted": { - "description": "Button text to dismiss What's New screen" - }, - "whatsNewPageIndicator": "{current} of {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } } } \ No newline at end of file From 030d66fd6532a211e81eebab6ce51db479c6389b Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:35 +0700 Subject: [PATCH 065/104] New translations app_en.arb (Korean) --- lib/l10n/arb/app_ko.arb | 216 ---------------------------------------- 1 file changed, 216 deletions(-) diff --git a/lib/l10n/arb/app_ko.arb b/lib/l10n/arb/app_ko.arb index 7739b0dd..9cfbefe8 100644 --- a/lib/l10n/arb/app_ko.arb +++ b/lib/l10n/arb/app_ko.arb @@ -4314,221 +4314,5 @@ "setupModeChangeableLater": "You can switch between modes anytime in Settings.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" - }, - "settingsSmartQueueTitle": "Smart Queue", - "@settingsSmartQueueTitle": { - "description": "Title for Smart Queue toggle in settings" - }, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", - "@settingsSmartQueueSubtitle": { - "description": "Subtitle for Smart Queue toggle in settings" - }, - "whatsNewTitle": "What's New in 4.0", - "@whatsNewTitle": { - "description": "Title for the What's New screen" - }, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", - "@whatsNewSubtitle": { - "description": "Subtitle for the What's New screen" - }, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": { - "description": "Welcome page title in What's New screen" - }, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", - "@whatsNewWelcomeDesc": { - "description": "Welcome page description in What's New screen" - }, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", - "@whatsNewWelcomeTip1": { - "description": "Welcome page tip 1" - }, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", - "@whatsNewWelcomeTip2": { - "description": "Welcome page tip 2" - }, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", - "@whatsNewWelcomeTip3": { - "description": "Welcome page tip 3" - }, - "whatsNewStreamingTitle": "Streaming Mode", - "@whatsNewStreamingTitle": { - "description": "What's New feature: Streaming Mode title" - }, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", - "@whatsNewStreamingDesc": { - "description": "What's New feature: Streaming Mode description" - }, - "whatsNewSmartQueueTitle": "Smart Queue", - "@whatsNewSmartQueueTitle": { - "description": "What's New feature: Smart Queue title" - }, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", - "@whatsNewSmartQueueDesc": { - "description": "What's New feature: Smart Queue description" - }, - "whatsNewDualModeTitle": "Dual Mode", - "@whatsNewDualModeTitle": { - "description": "What's New feature: Dual Mode title" - }, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", - "@whatsNewDualModeDesc": { - "description": "What's New feature: Dual Mode description" - }, - "whatsNewLibraryTitle": "Redesigned Library", - "@whatsNewLibraryTitle": { - "description": "What's New feature: Library redesign title" - }, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", - "@whatsNewLibraryDesc": { - "description": "What's New feature: Library redesign description" - }, - "whatsNewPlayerTitle": "Full-Screen Player", - "@whatsNewPlayerTitle": { - "description": "What's New feature: Full-Screen Player title" - }, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", - "@whatsNewPlayerDesc": { - "description": "What's New feature: Full-Screen Player description" - }, - "whatsNewContextMenuTitle": "Long-Press Menus", - "@whatsNewContextMenuTitle": { - "description": "What's New feature: Context Menus title" - }, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", - "@whatsNewContextMenuDesc": { - "description": "What's New feature: Context Menus description" - }, - "whatsNewPerformanceTitle": "Performance", - "@whatsNewPerformanceTitle": { - "description": "What's New feature: Performance title" - }, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", - "@whatsNewPerformanceDesc": { - "description": "What's New feature: Performance description" - }, - "whatsNewBatchToolsTitle": "Batch Tools", - "@whatsNewBatchToolsTitle": { - "description": "What's New feature: Batch Tools title" - }, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", - "@whatsNewBatchToolsDesc": { - "description": "What's New feature: Batch Tools description" - }, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", - "@whatsNewStreamingTip1": { - "description": "What's New tip: streaming instant play" - }, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", - "@whatsNewStreamingTip2": { - "description": "What's New tip: streaming synced lyrics" - }, - "whatsNewStreamingTip3": "Download tracks directly from the player", - "@whatsNewStreamingTip3": { - "description": "What's New tip: streaming download from player" - }, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", - "@whatsNewSmartQueueTip1": { - "description": "What's New tip: smart queue auto-fill" - }, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", - "@whatsNewSmartQueueTip2": { - "description": "What's New tip: smart queue artist discovery" - }, - "whatsNewSmartQueueTip3": "Never run out of music to play", - "@whatsNewSmartQueueTip3": { - "description": "What's New tip: smart queue endless" - }, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", - "@whatsNewDualModeTip1": { - "description": "What's New tip: dual mode switch" - }, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", - "@whatsNewDualModeTip2": { - "description": "What's New tip: dual mode adaptive UI" - }, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", - "@whatsNewDualModeTip3": { - "description": "What's New tip: dual mode use cases" - }, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", - "@whatsNewLibraryTip1": { - "description": "What's New tip: library drag and drop" - }, - "whatsNewLibraryTip2": "Set custom cover images for playlists", - "@whatsNewLibraryTip2": { - "description": "What's New tip: library custom covers" - }, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", - "@whatsNewLibraryTip3": { - "description": "What's New tip: library multi-select" - }, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", - "@whatsNewPlayerTip1": { - "description": "What's New tip: player parallax" - }, - "whatsNewPlayerTip2": "Playback persists across app restarts", - "@whatsNewPlayerTip2": { - "description": "What's New tip: player persistence" - }, - "whatsNewPlayerTip3": "Synced lyrics while you listen", - "@whatsNewPlayerTip3": { - "description": "What's New tip: player lyrics" - }, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", - "@whatsNewContextMenuTip1": { - "description": "What's New tip: context menu add to playlist" - }, - "whatsNewContextMenuTip2": "Share or convert with one tap", - "@whatsNewContextMenuTip2": { - "description": "What's New tip: context menu share/convert" - }, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", - "@whatsNewContextMenuTip3": { - "description": "What's New tip: context menu re-enrich" - }, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", - "@whatsNewBatchToolsTip1": { - "description": "What's New tip: batch share" - }, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", - "@whatsNewBatchToolsTip2": { - "description": "What's New tip: batch convert" - }, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", - "@whatsNewBatchToolsTip3": { - "description": "What's New tip: batch re-enrich" - }, - "whatsNewPerformanceTip1": "Faster app startup time", - "@whatsNewPerformanceTip1": { - "description": "What's New tip: performance startup" - }, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", - "@whatsNewPerformanceTip2": { - "description": "What's New tip: performance memory" - }, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", - "@whatsNewPerformanceTip3": { - "description": "What's New tip: performance SQLite" - }, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", - "@whatsNewReadyMessage": { - "description": "Ready card message on last What's New page" - }, - "whatsNewGetStarted": "Let's Go", - "@whatsNewGetStarted": { - "description": "Button text to dismiss What's New screen" - }, - "whatsNewPageIndicator": "{current} of {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } } } \ No newline at end of file From 5af4bb7adeb7c3a92be1de0dbff965f4b1da5c39 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:36 +0700 Subject: [PATCH 066/104] New translations app_en.arb (Dutch) --- lib/l10n/arb/app_nl.arb | 216 ---------------------------------------- 1 file changed, 216 deletions(-) diff --git a/lib/l10n/arb/app_nl.arb b/lib/l10n/arb/app_nl.arb index b96df20f..a960b2aa 100644 --- a/lib/l10n/arb/app_nl.arb +++ b/lib/l10n/arb/app_nl.arb @@ -4314,221 +4314,5 @@ "setupModeChangeableLater": "You can switch between modes anytime in Settings.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" - }, - "settingsSmartQueueTitle": "Smart Queue", - "@settingsSmartQueueTitle": { - "description": "Title for Smart Queue toggle in settings" - }, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", - "@settingsSmartQueueSubtitle": { - "description": "Subtitle for Smart Queue toggle in settings" - }, - "whatsNewTitle": "What's New in 4.0", - "@whatsNewTitle": { - "description": "Title for the What's New screen" - }, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", - "@whatsNewSubtitle": { - "description": "Subtitle for the What's New screen" - }, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": { - "description": "Welcome page title in What's New screen" - }, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", - "@whatsNewWelcomeDesc": { - "description": "Welcome page description in What's New screen" - }, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", - "@whatsNewWelcomeTip1": { - "description": "Welcome page tip 1" - }, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", - "@whatsNewWelcomeTip2": { - "description": "Welcome page tip 2" - }, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", - "@whatsNewWelcomeTip3": { - "description": "Welcome page tip 3" - }, - "whatsNewStreamingTitle": "Streaming Mode", - "@whatsNewStreamingTitle": { - "description": "What's New feature: Streaming Mode title" - }, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", - "@whatsNewStreamingDesc": { - "description": "What's New feature: Streaming Mode description" - }, - "whatsNewSmartQueueTitle": "Smart Queue", - "@whatsNewSmartQueueTitle": { - "description": "What's New feature: Smart Queue title" - }, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", - "@whatsNewSmartQueueDesc": { - "description": "What's New feature: Smart Queue description" - }, - "whatsNewDualModeTitle": "Dual Mode", - "@whatsNewDualModeTitle": { - "description": "What's New feature: Dual Mode title" - }, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", - "@whatsNewDualModeDesc": { - "description": "What's New feature: Dual Mode description" - }, - "whatsNewLibraryTitle": "Redesigned Library", - "@whatsNewLibraryTitle": { - "description": "What's New feature: Library redesign title" - }, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", - "@whatsNewLibraryDesc": { - "description": "What's New feature: Library redesign description" - }, - "whatsNewPlayerTitle": "Full-Screen Player", - "@whatsNewPlayerTitle": { - "description": "What's New feature: Full-Screen Player title" - }, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", - "@whatsNewPlayerDesc": { - "description": "What's New feature: Full-Screen Player description" - }, - "whatsNewContextMenuTitle": "Long-Press Menus", - "@whatsNewContextMenuTitle": { - "description": "What's New feature: Context Menus title" - }, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", - "@whatsNewContextMenuDesc": { - "description": "What's New feature: Context Menus description" - }, - "whatsNewPerformanceTitle": "Performance", - "@whatsNewPerformanceTitle": { - "description": "What's New feature: Performance title" - }, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", - "@whatsNewPerformanceDesc": { - "description": "What's New feature: Performance description" - }, - "whatsNewBatchToolsTitle": "Batch Tools", - "@whatsNewBatchToolsTitle": { - "description": "What's New feature: Batch Tools title" - }, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", - "@whatsNewBatchToolsDesc": { - "description": "What's New feature: Batch Tools description" - }, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", - "@whatsNewStreamingTip1": { - "description": "What's New tip: streaming instant play" - }, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", - "@whatsNewStreamingTip2": { - "description": "What's New tip: streaming synced lyrics" - }, - "whatsNewStreamingTip3": "Download tracks directly from the player", - "@whatsNewStreamingTip3": { - "description": "What's New tip: streaming download from player" - }, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", - "@whatsNewSmartQueueTip1": { - "description": "What's New tip: smart queue auto-fill" - }, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", - "@whatsNewSmartQueueTip2": { - "description": "What's New tip: smart queue artist discovery" - }, - "whatsNewSmartQueueTip3": "Never run out of music to play", - "@whatsNewSmartQueueTip3": { - "description": "What's New tip: smart queue endless" - }, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", - "@whatsNewDualModeTip1": { - "description": "What's New tip: dual mode switch" - }, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", - "@whatsNewDualModeTip2": { - "description": "What's New tip: dual mode adaptive UI" - }, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", - "@whatsNewDualModeTip3": { - "description": "What's New tip: dual mode use cases" - }, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", - "@whatsNewLibraryTip1": { - "description": "What's New tip: library drag and drop" - }, - "whatsNewLibraryTip2": "Set custom cover images for playlists", - "@whatsNewLibraryTip2": { - "description": "What's New tip: library custom covers" - }, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", - "@whatsNewLibraryTip3": { - "description": "What's New tip: library multi-select" - }, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", - "@whatsNewPlayerTip1": { - "description": "What's New tip: player parallax" - }, - "whatsNewPlayerTip2": "Playback persists across app restarts", - "@whatsNewPlayerTip2": { - "description": "What's New tip: player persistence" - }, - "whatsNewPlayerTip3": "Synced lyrics while you listen", - "@whatsNewPlayerTip3": { - "description": "What's New tip: player lyrics" - }, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", - "@whatsNewContextMenuTip1": { - "description": "What's New tip: context menu add to playlist" - }, - "whatsNewContextMenuTip2": "Share or convert with one tap", - "@whatsNewContextMenuTip2": { - "description": "What's New tip: context menu share/convert" - }, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", - "@whatsNewContextMenuTip3": { - "description": "What's New tip: context menu re-enrich" - }, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", - "@whatsNewBatchToolsTip1": { - "description": "What's New tip: batch share" - }, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", - "@whatsNewBatchToolsTip2": { - "description": "What's New tip: batch convert" - }, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", - "@whatsNewBatchToolsTip3": { - "description": "What's New tip: batch re-enrich" - }, - "whatsNewPerformanceTip1": "Faster app startup time", - "@whatsNewPerformanceTip1": { - "description": "What's New tip: performance startup" - }, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", - "@whatsNewPerformanceTip2": { - "description": "What's New tip: performance memory" - }, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", - "@whatsNewPerformanceTip3": { - "description": "What's New tip: performance SQLite" - }, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", - "@whatsNewReadyMessage": { - "description": "Ready card message on last What's New page" - }, - "whatsNewGetStarted": "Let's Go", - "@whatsNewGetStarted": { - "description": "Button text to dismiss What's New screen" - }, - "whatsNewPageIndicator": "{current} of {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } } } \ No newline at end of file From 6d812c76c20eed5e6e4d6ccf513a3b4c484be275 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:38 +0700 Subject: [PATCH 067/104] New translations app_en.arb (Portuguese) --- lib/l10n/arb/app_pt-PT.arb | 216 ------------------------------------- 1 file changed, 216 deletions(-) diff --git a/lib/l10n/arb/app_pt-PT.arb b/lib/l10n/arb/app_pt-PT.arb index 2cb659a1..180e8d7e 100644 --- a/lib/l10n/arb/app_pt-PT.arb +++ b/lib/l10n/arb/app_pt-PT.arb @@ -4314,221 +4314,5 @@ "setupModeChangeableLater": "You can switch between modes anytime in Settings.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" - }, - "settingsSmartQueueTitle": "Smart Queue", - "@settingsSmartQueueTitle": { - "description": "Title for Smart Queue toggle in settings" - }, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", - "@settingsSmartQueueSubtitle": { - "description": "Subtitle for Smart Queue toggle in settings" - }, - "whatsNewTitle": "What's New in 4.0", - "@whatsNewTitle": { - "description": "Title for the What's New screen" - }, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", - "@whatsNewSubtitle": { - "description": "Subtitle for the What's New screen" - }, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": { - "description": "Welcome page title in What's New screen" - }, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", - "@whatsNewWelcomeDesc": { - "description": "Welcome page description in What's New screen" - }, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", - "@whatsNewWelcomeTip1": { - "description": "Welcome page tip 1" - }, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", - "@whatsNewWelcomeTip2": { - "description": "Welcome page tip 2" - }, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", - "@whatsNewWelcomeTip3": { - "description": "Welcome page tip 3" - }, - "whatsNewStreamingTitle": "Streaming Mode", - "@whatsNewStreamingTitle": { - "description": "What's New feature: Streaming Mode title" - }, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", - "@whatsNewStreamingDesc": { - "description": "What's New feature: Streaming Mode description" - }, - "whatsNewSmartQueueTitle": "Smart Queue", - "@whatsNewSmartQueueTitle": { - "description": "What's New feature: Smart Queue title" - }, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", - "@whatsNewSmartQueueDesc": { - "description": "What's New feature: Smart Queue description" - }, - "whatsNewDualModeTitle": "Dual Mode", - "@whatsNewDualModeTitle": { - "description": "What's New feature: Dual Mode title" - }, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", - "@whatsNewDualModeDesc": { - "description": "What's New feature: Dual Mode description" - }, - "whatsNewLibraryTitle": "Redesigned Library", - "@whatsNewLibraryTitle": { - "description": "What's New feature: Library redesign title" - }, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", - "@whatsNewLibraryDesc": { - "description": "What's New feature: Library redesign description" - }, - "whatsNewPlayerTitle": "Full-Screen Player", - "@whatsNewPlayerTitle": { - "description": "What's New feature: Full-Screen Player title" - }, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", - "@whatsNewPlayerDesc": { - "description": "What's New feature: Full-Screen Player description" - }, - "whatsNewContextMenuTitle": "Long-Press Menus", - "@whatsNewContextMenuTitle": { - "description": "What's New feature: Context Menus title" - }, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", - "@whatsNewContextMenuDesc": { - "description": "What's New feature: Context Menus description" - }, - "whatsNewPerformanceTitle": "Performance", - "@whatsNewPerformanceTitle": { - "description": "What's New feature: Performance title" - }, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", - "@whatsNewPerformanceDesc": { - "description": "What's New feature: Performance description" - }, - "whatsNewBatchToolsTitle": "Batch Tools", - "@whatsNewBatchToolsTitle": { - "description": "What's New feature: Batch Tools title" - }, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", - "@whatsNewBatchToolsDesc": { - "description": "What's New feature: Batch Tools description" - }, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", - "@whatsNewStreamingTip1": { - "description": "What's New tip: streaming instant play" - }, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", - "@whatsNewStreamingTip2": { - "description": "What's New tip: streaming synced lyrics" - }, - "whatsNewStreamingTip3": "Download tracks directly from the player", - "@whatsNewStreamingTip3": { - "description": "What's New tip: streaming download from player" - }, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", - "@whatsNewSmartQueueTip1": { - "description": "What's New tip: smart queue auto-fill" - }, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", - "@whatsNewSmartQueueTip2": { - "description": "What's New tip: smart queue artist discovery" - }, - "whatsNewSmartQueueTip3": "Never run out of music to play", - "@whatsNewSmartQueueTip3": { - "description": "What's New tip: smart queue endless" - }, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", - "@whatsNewDualModeTip1": { - "description": "What's New tip: dual mode switch" - }, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", - "@whatsNewDualModeTip2": { - "description": "What's New tip: dual mode adaptive UI" - }, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", - "@whatsNewDualModeTip3": { - "description": "What's New tip: dual mode use cases" - }, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", - "@whatsNewLibraryTip1": { - "description": "What's New tip: library drag and drop" - }, - "whatsNewLibraryTip2": "Set custom cover images for playlists", - "@whatsNewLibraryTip2": { - "description": "What's New tip: library custom covers" - }, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", - "@whatsNewLibraryTip3": { - "description": "What's New tip: library multi-select" - }, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", - "@whatsNewPlayerTip1": { - "description": "What's New tip: player parallax" - }, - "whatsNewPlayerTip2": "Playback persists across app restarts", - "@whatsNewPlayerTip2": { - "description": "What's New tip: player persistence" - }, - "whatsNewPlayerTip3": "Synced lyrics while you listen", - "@whatsNewPlayerTip3": { - "description": "What's New tip: player lyrics" - }, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", - "@whatsNewContextMenuTip1": { - "description": "What's New tip: context menu add to playlist" - }, - "whatsNewContextMenuTip2": "Share or convert with one tap", - "@whatsNewContextMenuTip2": { - "description": "What's New tip: context menu share/convert" - }, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", - "@whatsNewContextMenuTip3": { - "description": "What's New tip: context menu re-enrich" - }, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", - "@whatsNewBatchToolsTip1": { - "description": "What's New tip: batch share" - }, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", - "@whatsNewBatchToolsTip2": { - "description": "What's New tip: batch convert" - }, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", - "@whatsNewBatchToolsTip3": { - "description": "What's New tip: batch re-enrich" - }, - "whatsNewPerformanceTip1": "Faster app startup time", - "@whatsNewPerformanceTip1": { - "description": "What's New tip: performance startup" - }, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", - "@whatsNewPerformanceTip2": { - "description": "What's New tip: performance memory" - }, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", - "@whatsNewPerformanceTip3": { - "description": "What's New tip: performance SQLite" - }, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", - "@whatsNewReadyMessage": { - "description": "Ready card message on last What's New page" - }, - "whatsNewGetStarted": "Let's Go", - "@whatsNewGetStarted": { - "description": "Button text to dismiss What's New screen" - }, - "whatsNewPageIndicator": "{current} of {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } } } \ No newline at end of file From e82e3a83430ca9ffa7915786e3fac8846882f99e Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:39 +0700 Subject: [PATCH 068/104] New translations app_en.arb (Russian) --- lib/l10n/arb/app_ru.arb | 216 ---------------------------------------- 1 file changed, 216 deletions(-) diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index caab1247..fbdf0c06 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -4314,221 +4314,5 @@ "setupModeChangeableLater": "Вы можете переключаться между режимами в любое время в Настройках.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" - }, - "settingsSmartQueueTitle": "Умная очередь", - "@settingsSmartQueueTitle": { - "description": "Title for Smart Queue toggle in settings" - }, - "settingsSmartQueueSubtitle": "Автоматически искать и добавлять похожие треки в очередь", - "@settingsSmartQueueSubtitle": { - "description": "Subtitle for Smart Queue toggle in settings" - }, - "whatsNewTitle": "Что нового в 4.0", - "@whatsNewTitle": { - "description": "Title for the What's New screen" - }, - "whatsNewSubtitle": "В SpotiFLAC развивается — вот что изменилось с 3.x", - "@whatsNewSubtitle": { - "description": "Subtitle for the What's New screen" - }, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": { - "description": "Welcome page title in What's New screen" - }, - "whatsNewWelcomeDesc": "С возвращением! Это крупное обновление с новыми возможностями. Смахните влево для того, чтобы увидеть, что изменилось.", - "@whatsNewWelcomeDesc": { - "description": "Welcome page description in What's New screen" - }, - "whatsNewWelcomeTip1": "Новый режим трансляции с мгновенным воспроизведением", - "@whatsNewWelcomeTip1": { - "description": "Welcome page tip 1" - }, - "whatsNewWelcomeTip2": "Переработанный дизайн библиотеки и полноэкранный плеер", - "@whatsNewWelcomeTip2": { - "description": "Welcome page tip 2" - }, - "whatsNewWelcomeTip3": "Пакетные инструменты, повышение производительности и многое другое", - "@whatsNewWelcomeTip3": { - "description": "Welcome page tip 3" - }, - "whatsNewStreamingTitle": "Режим трансляции", - "@whatsNewStreamingTitle": { - "description": "What's New feature: Streaming Mode title" - }, - "whatsNewStreamingDesc": "Коснитесь трека для мгновенного воспроизведения — загрузка не требуется. Полноэкранный проигрыватель с синхронизированными текстами и мультимедиа элементами.", - "@whatsNewStreamingDesc": { - "description": "What's New feature: Streaming Mode description" - }, - "whatsNewSmartQueueTitle": "Умная очередь", - "@whatsNewSmartQueueTitle": { - "description": "What's New feature: Smart Queue title" - }, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", - "@whatsNewSmartQueueDesc": { - "description": "What's New feature: Smart Queue description" - }, - "whatsNewDualModeTitle": "Двойной режим", - "@whatsNewDualModeTitle": { - "description": "What's New feature: Dual Mode title" - }, - "whatsNewDualModeDesc": "Переключение между режимами загрузчика и стриминга в любое время. Все кнопки адаптируются автоматически.", - "@whatsNewDualModeDesc": { - "description": "What's New feature: Dual Mode description" - }, - "whatsNewLibraryTitle": "Переработанная библиотека", - "@whatsNewLibraryTitle": { - "description": "What's New feature: Library redesign title" - }, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", - "@whatsNewLibraryDesc": { - "description": "What's New feature: Library redesign description" - }, - "whatsNewPlayerTitle": "Полноэкранный плеер", - "@whatsNewPlayerTitle": { - "description": "What's New feature: Full-Screen Player title" - }, - "whatsNewPlayerDesc": "Параллакс обложки, синхронизированные тексты, сохранение воспроизведения при перезапуске и кнопка скачать в плеере.", - "@whatsNewPlayerDesc": { - "description": "What's New feature: Full-Screen Player description" - }, - "whatsNewContextMenuTitle": "Меню длинного нажатия", - "@whatsNewContextMenuTitle": { - "description": "What's New feature: Context Menus title" - }, - "whatsNewContextMenuDesc": "Долгое нажатие на трек для быстрых действий — добавить в плейлист, поделиться, конвертировать или обновить метаданные.", - "@whatsNewContextMenuDesc": { - "description": "What's New feature: Context Menus description" - }, - "whatsNewPerformanceTitle": "Производительность", - "@whatsNewPerformanceTitle": { - "description": "What's New feature: Performance title" - }, - "whatsNewPerformanceDesc": "Более быстрый запуск, меньшее использование памяти, поддержка SQLite и детальное обновление пользовательского интерфейса.", - "@whatsNewPerformanceDesc": { - "description": "What's New feature: Performance description" - }, - "whatsNewBatchToolsTitle": "Пакетные инструменты", - "@whatsNewBatchToolsTitle": { - "description": "What's New feature: Batch Tools title" - }, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", - "@whatsNewBatchToolsDesc": { - "description": "What's New feature: Batch Tools description" - }, - "whatsNewStreamingTip1": "Коснитесь трека, чтобы мгновенно его воспроизвести", - "@whatsNewStreamingTip1": { - "description": "What's New tip: streaming instant play" - }, - "whatsNewStreamingTip2": "Синхронизированные тексты песен в полноэкранном плеере", - "@whatsNewStreamingTip2": { - "description": "What's New tip: streaming synced lyrics" - }, - "whatsNewStreamingTip3": "Скачивайте треки прямо из плеера", - "@whatsNewStreamingTip3": { - "description": "What's New tip: streaming download from player" - }, - "whatsNewSmartQueueTip1": "Автозаполнение очереди соответствующими треками", - "@whatsNewSmartQueueTip1": { - "description": "What's New tip: smart queue auto-fill" - }, - "whatsNewSmartQueueTip2": "Откройте для себя новых исполнителей слушая музыку", - "@whatsNewSmartQueueTip2": { - "description": "What's New tip: smart queue artist discovery" - }, - "whatsNewSmartQueueTip3": "Музыка для прослушивания никогда не закончится", - "@whatsNewSmartQueueTip3": { - "description": "What's New tip: smart queue endless" - }, - "whatsNewDualModeTip1": "Переключайте режимы в любое время в настройках", - "@whatsNewDualModeTip1": { - "description": "What's New tip: dual mode switch" - }, - "whatsNewDualModeTip2": "Кнопки интерфейса адаптируются к текущему режиму", - "@whatsNewDualModeTip2": { - "description": "What's New tip: dual mode adaptive UI" - }, - "whatsNewDualModeTip3": "Скачать для автономной трансляции и мгновенного воспроизведения", - "@whatsNewDualModeTip3": { - "description": "What's New tip: dual mode use cases" - }, - "whatsNewLibraryTip1": "Перетащите для организации плейлистов", - "@whatsNewLibraryTip1": { - "description": "What's New tip: library drag and drop" - }, - "whatsNewLibraryTip2": "Установить пользовательские обложки для плейлистов", - "@whatsNewLibraryTip2": { - "description": "What's New tip: library custom covers" - }, - "whatsNewLibraryTip3": "Мульти-выбор треков для пакетных действий", - "@whatsNewLibraryTip3": { - "description": "What's New tip: library multi-select" - }, - "whatsNewPlayerTip1": "Обложки с эффектом параллакса", - "@whatsNewPlayerTip1": { - "description": "What's New tip: player parallax" - }, - "whatsNewPlayerTip2": "Воспроизведение продолжится после перезапуска приложения", - "@whatsNewPlayerTip2": { - "description": "What's New tip: player persistence" - }, - "whatsNewPlayerTip3": "Синхронизированные тексты песен во время прослушивания", - "@whatsNewPlayerTip3": { - "description": "What's New tip: player lyrics" - }, - "whatsNewContextMenuTip1": "Мгновенно добавляйте треки в любой плейлист", - "@whatsNewContextMenuTip1": { - "description": "What's New tip: context menu add to playlist" - }, - "whatsNewContextMenuTip2": "Поделиться или конвертировать одним нажатием", - "@whatsNewContextMenuTip2": { - "description": "What's New tip: context menu share/convert" - }, - "whatsNewContextMenuTip3": "При необходимости восстановить метаданные", - "@whatsNewContextMenuTip3": { - "description": "What's New tip: context menu re-enrich" - }, - "whatsNewBatchToolsTip1": "Поделиться сразу несколькими треками", - "@whatsNewBatchToolsTip1": { - "description": "What's New tip: batch share" - }, - "whatsNewBatchToolsTip2": "Пакетное конвертирование в формат MP3 или Opus", - "@whatsNewBatchToolsTip2": { - "description": "What's New tip: batch convert" - }, - "whatsNewBatchToolsTip3": "Повторное обновление метаданных в вашей библиотеке", - "@whatsNewBatchToolsTip3": { - "description": "What's New tip: batch re-enrich" - }, - "whatsNewPerformanceTip1": "Быстрый запуск приложения", - "@whatsNewPerformanceTip1": { - "description": "What's New tip: performance startup" - }, - "whatsNewPerformanceTip2": "Уменьшение использования памяти во время воспроизведения", - "@whatsNewPerformanceTip2": { - "description": "What's New tip: performance memory" - }, - "whatsNewPerformanceTip3": "Надежное хранилище с поддержкой SQLite", - "@whatsNewPerformanceTip3": { - "description": "What's New tip: performance SQLite" - }, - "whatsNewReadyMessage": "Всё готово — наслаждайтесь новым SpotiFLAC!", - "@whatsNewReadyMessage": { - "description": "Ready card message on last What's New page" - }, - "whatsNewGetStarted": "Поехали", - "@whatsNewGetStarted": { - "description": "Button text to dismiss What's New screen" - }, - "whatsNewPageIndicator": "{current} из {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } } } \ No newline at end of file From e6a857335f587c75754c817319d5e3eab7e21e6a Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:40 +0700 Subject: [PATCH 069/104] New translations app_en.arb (Chinese Simplified) --- lib/l10n/arb/app_zh_CN.arb | 216 ------------------------------------- 1 file changed, 216 deletions(-) diff --git a/lib/l10n/arb/app_zh_CN.arb b/lib/l10n/arb/app_zh_CN.arb index 9dbae850..60bd8b49 100644 --- a/lib/l10n/arb/app_zh_CN.arb +++ b/lib/l10n/arb/app_zh_CN.arb @@ -4314,221 +4314,5 @@ "setupModeChangeableLater": "You can switch between modes anytime in Settings.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" - }, - "settingsSmartQueueTitle": "Smart Queue", - "@settingsSmartQueueTitle": { - "description": "Title for Smart Queue toggle in settings" - }, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", - "@settingsSmartQueueSubtitle": { - "description": "Subtitle for Smart Queue toggle in settings" - }, - "whatsNewTitle": "What's New in 4.0", - "@whatsNewTitle": { - "description": "Title for the What's New screen" - }, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", - "@whatsNewSubtitle": { - "description": "Subtitle for the What's New screen" - }, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": { - "description": "Welcome page title in What's New screen" - }, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", - "@whatsNewWelcomeDesc": { - "description": "Welcome page description in What's New screen" - }, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", - "@whatsNewWelcomeTip1": { - "description": "Welcome page tip 1" - }, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", - "@whatsNewWelcomeTip2": { - "description": "Welcome page tip 2" - }, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", - "@whatsNewWelcomeTip3": { - "description": "Welcome page tip 3" - }, - "whatsNewStreamingTitle": "Streaming Mode", - "@whatsNewStreamingTitle": { - "description": "What's New feature: Streaming Mode title" - }, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", - "@whatsNewStreamingDesc": { - "description": "What's New feature: Streaming Mode description" - }, - "whatsNewSmartQueueTitle": "Smart Queue", - "@whatsNewSmartQueueTitle": { - "description": "What's New feature: Smart Queue title" - }, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", - "@whatsNewSmartQueueDesc": { - "description": "What's New feature: Smart Queue description" - }, - "whatsNewDualModeTitle": "Dual Mode", - "@whatsNewDualModeTitle": { - "description": "What's New feature: Dual Mode title" - }, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", - "@whatsNewDualModeDesc": { - "description": "What's New feature: Dual Mode description" - }, - "whatsNewLibraryTitle": "Redesigned Library", - "@whatsNewLibraryTitle": { - "description": "What's New feature: Library redesign title" - }, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", - "@whatsNewLibraryDesc": { - "description": "What's New feature: Library redesign description" - }, - "whatsNewPlayerTitle": "Full-Screen Player", - "@whatsNewPlayerTitle": { - "description": "What's New feature: Full-Screen Player title" - }, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", - "@whatsNewPlayerDesc": { - "description": "What's New feature: Full-Screen Player description" - }, - "whatsNewContextMenuTitle": "Long-Press Menus", - "@whatsNewContextMenuTitle": { - "description": "What's New feature: Context Menus title" - }, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", - "@whatsNewContextMenuDesc": { - "description": "What's New feature: Context Menus description" - }, - "whatsNewPerformanceTitle": "Performance", - "@whatsNewPerformanceTitle": { - "description": "What's New feature: Performance title" - }, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", - "@whatsNewPerformanceDesc": { - "description": "What's New feature: Performance description" - }, - "whatsNewBatchToolsTitle": "Batch Tools", - "@whatsNewBatchToolsTitle": { - "description": "What's New feature: Batch Tools title" - }, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", - "@whatsNewBatchToolsDesc": { - "description": "What's New feature: Batch Tools description" - }, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", - "@whatsNewStreamingTip1": { - "description": "What's New tip: streaming instant play" - }, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", - "@whatsNewStreamingTip2": { - "description": "What's New tip: streaming synced lyrics" - }, - "whatsNewStreamingTip3": "Download tracks directly from the player", - "@whatsNewStreamingTip3": { - "description": "What's New tip: streaming download from player" - }, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", - "@whatsNewSmartQueueTip1": { - "description": "What's New tip: smart queue auto-fill" - }, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", - "@whatsNewSmartQueueTip2": { - "description": "What's New tip: smart queue artist discovery" - }, - "whatsNewSmartQueueTip3": "Never run out of music to play", - "@whatsNewSmartQueueTip3": { - "description": "What's New tip: smart queue endless" - }, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", - "@whatsNewDualModeTip1": { - "description": "What's New tip: dual mode switch" - }, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", - "@whatsNewDualModeTip2": { - "description": "What's New tip: dual mode adaptive UI" - }, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", - "@whatsNewDualModeTip3": { - "description": "What's New tip: dual mode use cases" - }, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", - "@whatsNewLibraryTip1": { - "description": "What's New tip: library drag and drop" - }, - "whatsNewLibraryTip2": "Set custom cover images for playlists", - "@whatsNewLibraryTip2": { - "description": "What's New tip: library custom covers" - }, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", - "@whatsNewLibraryTip3": { - "description": "What's New tip: library multi-select" - }, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", - "@whatsNewPlayerTip1": { - "description": "What's New tip: player parallax" - }, - "whatsNewPlayerTip2": "Playback persists across app restarts", - "@whatsNewPlayerTip2": { - "description": "What's New tip: player persistence" - }, - "whatsNewPlayerTip3": "Synced lyrics while you listen", - "@whatsNewPlayerTip3": { - "description": "What's New tip: player lyrics" - }, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", - "@whatsNewContextMenuTip1": { - "description": "What's New tip: context menu add to playlist" - }, - "whatsNewContextMenuTip2": "Share or convert with one tap", - "@whatsNewContextMenuTip2": { - "description": "What's New tip: context menu share/convert" - }, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", - "@whatsNewContextMenuTip3": { - "description": "What's New tip: context menu re-enrich" - }, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", - "@whatsNewBatchToolsTip1": { - "description": "What's New tip: batch share" - }, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", - "@whatsNewBatchToolsTip2": { - "description": "What's New tip: batch convert" - }, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", - "@whatsNewBatchToolsTip3": { - "description": "What's New tip: batch re-enrich" - }, - "whatsNewPerformanceTip1": "Faster app startup time", - "@whatsNewPerformanceTip1": { - "description": "What's New tip: performance startup" - }, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", - "@whatsNewPerformanceTip2": { - "description": "What's New tip: performance memory" - }, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", - "@whatsNewPerformanceTip3": { - "description": "What's New tip: performance SQLite" - }, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", - "@whatsNewReadyMessage": { - "description": "Ready card message on last What's New page" - }, - "whatsNewGetStarted": "Let's Go", - "@whatsNewGetStarted": { - "description": "Button text to dismiss What's New screen" - }, - "whatsNewPageIndicator": "{current} of {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } } } \ No newline at end of file From d6cb9fc26184ede58cdca846b0d444f920205d1b Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:42 +0700 Subject: [PATCH 070/104] New translations app_en.arb (Chinese Traditional) --- lib/l10n/arb/app_zh_TW.arb | 216 ------------------------------------- 1 file changed, 216 deletions(-) diff --git a/lib/l10n/arb/app_zh_TW.arb b/lib/l10n/arb/app_zh_TW.arb index 6b2af6ab..0d983390 100644 --- a/lib/l10n/arb/app_zh_TW.arb +++ b/lib/l10n/arb/app_zh_TW.arb @@ -4314,221 +4314,5 @@ "setupModeChangeableLater": "You can switch between modes anytime in Settings.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" - }, - "settingsSmartQueueTitle": "Smart Queue", - "@settingsSmartQueueTitle": { - "description": "Title for Smart Queue toggle in settings" - }, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", - "@settingsSmartQueueSubtitle": { - "description": "Subtitle for Smart Queue toggle in settings" - }, - "whatsNewTitle": "What's New in 4.0", - "@whatsNewTitle": { - "description": "Title for the What's New screen" - }, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", - "@whatsNewSubtitle": { - "description": "Subtitle for the What's New screen" - }, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": { - "description": "Welcome page title in What's New screen" - }, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", - "@whatsNewWelcomeDesc": { - "description": "Welcome page description in What's New screen" - }, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", - "@whatsNewWelcomeTip1": { - "description": "Welcome page tip 1" - }, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", - "@whatsNewWelcomeTip2": { - "description": "Welcome page tip 2" - }, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", - "@whatsNewWelcomeTip3": { - "description": "Welcome page tip 3" - }, - "whatsNewStreamingTitle": "Streaming Mode", - "@whatsNewStreamingTitle": { - "description": "What's New feature: Streaming Mode title" - }, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", - "@whatsNewStreamingDesc": { - "description": "What's New feature: Streaming Mode description" - }, - "whatsNewSmartQueueTitle": "Smart Queue", - "@whatsNewSmartQueueTitle": { - "description": "What's New feature: Smart Queue title" - }, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", - "@whatsNewSmartQueueDesc": { - "description": "What's New feature: Smart Queue description" - }, - "whatsNewDualModeTitle": "Dual Mode", - "@whatsNewDualModeTitle": { - "description": "What's New feature: Dual Mode title" - }, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", - "@whatsNewDualModeDesc": { - "description": "What's New feature: Dual Mode description" - }, - "whatsNewLibraryTitle": "Redesigned Library", - "@whatsNewLibraryTitle": { - "description": "What's New feature: Library redesign title" - }, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", - "@whatsNewLibraryDesc": { - "description": "What's New feature: Library redesign description" - }, - "whatsNewPlayerTitle": "Full-Screen Player", - "@whatsNewPlayerTitle": { - "description": "What's New feature: Full-Screen Player title" - }, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", - "@whatsNewPlayerDesc": { - "description": "What's New feature: Full-Screen Player description" - }, - "whatsNewContextMenuTitle": "Long-Press Menus", - "@whatsNewContextMenuTitle": { - "description": "What's New feature: Context Menus title" - }, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", - "@whatsNewContextMenuDesc": { - "description": "What's New feature: Context Menus description" - }, - "whatsNewPerformanceTitle": "Performance", - "@whatsNewPerformanceTitle": { - "description": "What's New feature: Performance title" - }, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", - "@whatsNewPerformanceDesc": { - "description": "What's New feature: Performance description" - }, - "whatsNewBatchToolsTitle": "Batch Tools", - "@whatsNewBatchToolsTitle": { - "description": "What's New feature: Batch Tools title" - }, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", - "@whatsNewBatchToolsDesc": { - "description": "What's New feature: Batch Tools description" - }, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", - "@whatsNewStreamingTip1": { - "description": "What's New tip: streaming instant play" - }, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", - "@whatsNewStreamingTip2": { - "description": "What's New tip: streaming synced lyrics" - }, - "whatsNewStreamingTip3": "Download tracks directly from the player", - "@whatsNewStreamingTip3": { - "description": "What's New tip: streaming download from player" - }, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", - "@whatsNewSmartQueueTip1": { - "description": "What's New tip: smart queue auto-fill" - }, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", - "@whatsNewSmartQueueTip2": { - "description": "What's New tip: smart queue artist discovery" - }, - "whatsNewSmartQueueTip3": "Never run out of music to play", - "@whatsNewSmartQueueTip3": { - "description": "What's New tip: smart queue endless" - }, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", - "@whatsNewDualModeTip1": { - "description": "What's New tip: dual mode switch" - }, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", - "@whatsNewDualModeTip2": { - "description": "What's New tip: dual mode adaptive UI" - }, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", - "@whatsNewDualModeTip3": { - "description": "What's New tip: dual mode use cases" - }, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", - "@whatsNewLibraryTip1": { - "description": "What's New tip: library drag and drop" - }, - "whatsNewLibraryTip2": "Set custom cover images for playlists", - "@whatsNewLibraryTip2": { - "description": "What's New tip: library custom covers" - }, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", - "@whatsNewLibraryTip3": { - "description": "What's New tip: library multi-select" - }, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", - "@whatsNewPlayerTip1": { - "description": "What's New tip: player parallax" - }, - "whatsNewPlayerTip2": "Playback persists across app restarts", - "@whatsNewPlayerTip2": { - "description": "What's New tip: player persistence" - }, - "whatsNewPlayerTip3": "Synced lyrics while you listen", - "@whatsNewPlayerTip3": { - "description": "What's New tip: player lyrics" - }, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", - "@whatsNewContextMenuTip1": { - "description": "What's New tip: context menu add to playlist" - }, - "whatsNewContextMenuTip2": "Share or convert with one tap", - "@whatsNewContextMenuTip2": { - "description": "What's New tip: context menu share/convert" - }, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", - "@whatsNewContextMenuTip3": { - "description": "What's New tip: context menu re-enrich" - }, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", - "@whatsNewBatchToolsTip1": { - "description": "What's New tip: batch share" - }, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", - "@whatsNewBatchToolsTip2": { - "description": "What's New tip: batch convert" - }, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", - "@whatsNewBatchToolsTip3": { - "description": "What's New tip: batch re-enrich" - }, - "whatsNewPerformanceTip1": "Faster app startup time", - "@whatsNewPerformanceTip1": { - "description": "What's New tip: performance startup" - }, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", - "@whatsNewPerformanceTip2": { - "description": "What's New tip: performance memory" - }, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", - "@whatsNewPerformanceTip3": { - "description": "What's New tip: performance SQLite" - }, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", - "@whatsNewReadyMessage": { - "description": "Ready card message on last What's New page" - }, - "whatsNewGetStarted": "Let's Go", - "@whatsNewGetStarted": { - "description": "Button text to dismiss What's New screen" - }, - "whatsNewPageIndicator": "{current} of {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } } } \ No newline at end of file From 277e7719d3e7ad80ce6afc44958f85ae781f36d2 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:43 +0700 Subject: [PATCH 071/104] New translations app_en.arb (Indonesian) --- lib/l10n/arb/app_id.arb | 216 ---------------------------------------- 1 file changed, 216 deletions(-) diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index eb027b8d..171cb813 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -4314,221 +4314,5 @@ "setupModeChangeableLater": "You can switch between modes anytime in Settings.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" - }, - "settingsSmartQueueTitle": "Smart Queue", - "@settingsSmartQueueTitle": { - "description": "Title for Smart Queue toggle in settings" - }, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", - "@settingsSmartQueueSubtitle": { - "description": "Subtitle for Smart Queue toggle in settings" - }, - "whatsNewTitle": "What's New in 4.0", - "@whatsNewTitle": { - "description": "Title for the What's New screen" - }, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", - "@whatsNewSubtitle": { - "description": "Subtitle for the What's New screen" - }, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": { - "description": "Welcome page title in What's New screen" - }, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", - "@whatsNewWelcomeDesc": { - "description": "Welcome page description in What's New screen" - }, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", - "@whatsNewWelcomeTip1": { - "description": "Welcome page tip 1" - }, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", - "@whatsNewWelcomeTip2": { - "description": "Welcome page tip 2" - }, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", - "@whatsNewWelcomeTip3": { - "description": "Welcome page tip 3" - }, - "whatsNewStreamingTitle": "Streaming Mode", - "@whatsNewStreamingTitle": { - "description": "What's New feature: Streaming Mode title" - }, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", - "@whatsNewStreamingDesc": { - "description": "What's New feature: Streaming Mode description" - }, - "whatsNewSmartQueueTitle": "Smart Queue", - "@whatsNewSmartQueueTitle": { - "description": "What's New feature: Smart Queue title" - }, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", - "@whatsNewSmartQueueDesc": { - "description": "What's New feature: Smart Queue description" - }, - "whatsNewDualModeTitle": "Dual Mode", - "@whatsNewDualModeTitle": { - "description": "What's New feature: Dual Mode title" - }, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", - "@whatsNewDualModeDesc": { - "description": "What's New feature: Dual Mode description" - }, - "whatsNewLibraryTitle": "Redesigned Library", - "@whatsNewLibraryTitle": { - "description": "What's New feature: Library redesign title" - }, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", - "@whatsNewLibraryDesc": { - "description": "What's New feature: Library redesign description" - }, - "whatsNewPlayerTitle": "Full-Screen Player", - "@whatsNewPlayerTitle": { - "description": "What's New feature: Full-Screen Player title" - }, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", - "@whatsNewPlayerDesc": { - "description": "What's New feature: Full-Screen Player description" - }, - "whatsNewContextMenuTitle": "Long-Press Menus", - "@whatsNewContextMenuTitle": { - "description": "What's New feature: Context Menus title" - }, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", - "@whatsNewContextMenuDesc": { - "description": "What's New feature: Context Menus description" - }, - "whatsNewPerformanceTitle": "Performance", - "@whatsNewPerformanceTitle": { - "description": "What's New feature: Performance title" - }, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", - "@whatsNewPerformanceDesc": { - "description": "What's New feature: Performance description" - }, - "whatsNewBatchToolsTitle": "Batch Tools", - "@whatsNewBatchToolsTitle": { - "description": "What's New feature: Batch Tools title" - }, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", - "@whatsNewBatchToolsDesc": { - "description": "What's New feature: Batch Tools description" - }, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", - "@whatsNewStreamingTip1": { - "description": "What's New tip: streaming instant play" - }, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", - "@whatsNewStreamingTip2": { - "description": "What's New tip: streaming synced lyrics" - }, - "whatsNewStreamingTip3": "Download tracks directly from the player", - "@whatsNewStreamingTip3": { - "description": "What's New tip: streaming download from player" - }, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", - "@whatsNewSmartQueueTip1": { - "description": "What's New tip: smart queue auto-fill" - }, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", - "@whatsNewSmartQueueTip2": { - "description": "What's New tip: smart queue artist discovery" - }, - "whatsNewSmartQueueTip3": "Never run out of music to play", - "@whatsNewSmartQueueTip3": { - "description": "What's New tip: smart queue endless" - }, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", - "@whatsNewDualModeTip1": { - "description": "What's New tip: dual mode switch" - }, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", - "@whatsNewDualModeTip2": { - "description": "What's New tip: dual mode adaptive UI" - }, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", - "@whatsNewDualModeTip3": { - "description": "What's New tip: dual mode use cases" - }, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", - "@whatsNewLibraryTip1": { - "description": "What's New tip: library drag and drop" - }, - "whatsNewLibraryTip2": "Set custom cover images for playlists", - "@whatsNewLibraryTip2": { - "description": "What's New tip: library custom covers" - }, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", - "@whatsNewLibraryTip3": { - "description": "What's New tip: library multi-select" - }, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", - "@whatsNewPlayerTip1": { - "description": "What's New tip: player parallax" - }, - "whatsNewPlayerTip2": "Playback persists across app restarts", - "@whatsNewPlayerTip2": { - "description": "What's New tip: player persistence" - }, - "whatsNewPlayerTip3": "Synced lyrics while you listen", - "@whatsNewPlayerTip3": { - "description": "What's New tip: player lyrics" - }, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", - "@whatsNewContextMenuTip1": { - "description": "What's New tip: context menu add to playlist" - }, - "whatsNewContextMenuTip2": "Share or convert with one tap", - "@whatsNewContextMenuTip2": { - "description": "What's New tip: context menu share/convert" - }, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", - "@whatsNewContextMenuTip3": { - "description": "What's New tip: context menu re-enrich" - }, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", - "@whatsNewBatchToolsTip1": { - "description": "What's New tip: batch share" - }, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", - "@whatsNewBatchToolsTip2": { - "description": "What's New tip: batch convert" - }, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", - "@whatsNewBatchToolsTip3": { - "description": "What's New tip: batch re-enrich" - }, - "whatsNewPerformanceTip1": "Faster app startup time", - "@whatsNewPerformanceTip1": { - "description": "What's New tip: performance startup" - }, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", - "@whatsNewPerformanceTip2": { - "description": "What's New tip: performance memory" - }, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", - "@whatsNewPerformanceTip3": { - "description": "What's New tip: performance SQLite" - }, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", - "@whatsNewReadyMessage": { - "description": "Ready card message on last What's New page" - }, - "whatsNewGetStarted": "Let's Go", - "@whatsNewGetStarted": { - "description": "Button text to dismiss What's New screen" - }, - "whatsNewPageIndicator": "{current} of {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } } } \ No newline at end of file From 5532d0a7d92c279bb29ce176a5c8419b4eeb382e Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:44 +0700 Subject: [PATCH 072/104] New translations app_en.arb (Hindi) --- lib/l10n/arb/app_hi.arb | 216 ---------------------------------------- 1 file changed, 216 deletions(-) diff --git a/lib/l10n/arb/app_hi.arb b/lib/l10n/arb/app_hi.arb index e236d3f6..c395c127 100644 --- a/lib/l10n/arb/app_hi.arb +++ b/lib/l10n/arb/app_hi.arb @@ -4314,221 +4314,5 @@ "setupModeChangeableLater": "You can switch between modes anytime in Settings.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" - }, - "settingsSmartQueueTitle": "Smart Queue", - "@settingsSmartQueueTitle": { - "description": "Title for Smart Queue toggle in settings" - }, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", - "@settingsSmartQueueSubtitle": { - "description": "Subtitle for Smart Queue toggle in settings" - }, - "whatsNewTitle": "What's New in 4.0", - "@whatsNewTitle": { - "description": "Title for the What's New screen" - }, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", - "@whatsNewSubtitle": { - "description": "Subtitle for the What's New screen" - }, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": { - "description": "Welcome page title in What's New screen" - }, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", - "@whatsNewWelcomeDesc": { - "description": "Welcome page description in What's New screen" - }, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", - "@whatsNewWelcomeTip1": { - "description": "Welcome page tip 1" - }, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", - "@whatsNewWelcomeTip2": { - "description": "Welcome page tip 2" - }, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", - "@whatsNewWelcomeTip3": { - "description": "Welcome page tip 3" - }, - "whatsNewStreamingTitle": "Streaming Mode", - "@whatsNewStreamingTitle": { - "description": "What's New feature: Streaming Mode title" - }, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", - "@whatsNewStreamingDesc": { - "description": "What's New feature: Streaming Mode description" - }, - "whatsNewSmartQueueTitle": "Smart Queue", - "@whatsNewSmartQueueTitle": { - "description": "What's New feature: Smart Queue title" - }, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", - "@whatsNewSmartQueueDesc": { - "description": "What's New feature: Smart Queue description" - }, - "whatsNewDualModeTitle": "Dual Mode", - "@whatsNewDualModeTitle": { - "description": "What's New feature: Dual Mode title" - }, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", - "@whatsNewDualModeDesc": { - "description": "What's New feature: Dual Mode description" - }, - "whatsNewLibraryTitle": "Redesigned Library", - "@whatsNewLibraryTitle": { - "description": "What's New feature: Library redesign title" - }, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", - "@whatsNewLibraryDesc": { - "description": "What's New feature: Library redesign description" - }, - "whatsNewPlayerTitle": "Full-Screen Player", - "@whatsNewPlayerTitle": { - "description": "What's New feature: Full-Screen Player title" - }, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", - "@whatsNewPlayerDesc": { - "description": "What's New feature: Full-Screen Player description" - }, - "whatsNewContextMenuTitle": "Long-Press Menus", - "@whatsNewContextMenuTitle": { - "description": "What's New feature: Context Menus title" - }, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", - "@whatsNewContextMenuDesc": { - "description": "What's New feature: Context Menus description" - }, - "whatsNewPerformanceTitle": "Performance", - "@whatsNewPerformanceTitle": { - "description": "What's New feature: Performance title" - }, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", - "@whatsNewPerformanceDesc": { - "description": "What's New feature: Performance description" - }, - "whatsNewBatchToolsTitle": "Batch Tools", - "@whatsNewBatchToolsTitle": { - "description": "What's New feature: Batch Tools title" - }, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", - "@whatsNewBatchToolsDesc": { - "description": "What's New feature: Batch Tools description" - }, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", - "@whatsNewStreamingTip1": { - "description": "What's New tip: streaming instant play" - }, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", - "@whatsNewStreamingTip2": { - "description": "What's New tip: streaming synced lyrics" - }, - "whatsNewStreamingTip3": "Download tracks directly from the player", - "@whatsNewStreamingTip3": { - "description": "What's New tip: streaming download from player" - }, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", - "@whatsNewSmartQueueTip1": { - "description": "What's New tip: smart queue auto-fill" - }, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", - "@whatsNewSmartQueueTip2": { - "description": "What's New tip: smart queue artist discovery" - }, - "whatsNewSmartQueueTip3": "Never run out of music to play", - "@whatsNewSmartQueueTip3": { - "description": "What's New tip: smart queue endless" - }, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", - "@whatsNewDualModeTip1": { - "description": "What's New tip: dual mode switch" - }, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", - "@whatsNewDualModeTip2": { - "description": "What's New tip: dual mode adaptive UI" - }, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", - "@whatsNewDualModeTip3": { - "description": "What's New tip: dual mode use cases" - }, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", - "@whatsNewLibraryTip1": { - "description": "What's New tip: library drag and drop" - }, - "whatsNewLibraryTip2": "Set custom cover images for playlists", - "@whatsNewLibraryTip2": { - "description": "What's New tip: library custom covers" - }, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", - "@whatsNewLibraryTip3": { - "description": "What's New tip: library multi-select" - }, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", - "@whatsNewPlayerTip1": { - "description": "What's New tip: player parallax" - }, - "whatsNewPlayerTip2": "Playback persists across app restarts", - "@whatsNewPlayerTip2": { - "description": "What's New tip: player persistence" - }, - "whatsNewPlayerTip3": "Synced lyrics while you listen", - "@whatsNewPlayerTip3": { - "description": "What's New tip: player lyrics" - }, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", - "@whatsNewContextMenuTip1": { - "description": "What's New tip: context menu add to playlist" - }, - "whatsNewContextMenuTip2": "Share or convert with one tap", - "@whatsNewContextMenuTip2": { - "description": "What's New tip: context menu share/convert" - }, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", - "@whatsNewContextMenuTip3": { - "description": "What's New tip: context menu re-enrich" - }, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", - "@whatsNewBatchToolsTip1": { - "description": "What's New tip: batch share" - }, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", - "@whatsNewBatchToolsTip2": { - "description": "What's New tip: batch convert" - }, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", - "@whatsNewBatchToolsTip3": { - "description": "What's New tip: batch re-enrich" - }, - "whatsNewPerformanceTip1": "Faster app startup time", - "@whatsNewPerformanceTip1": { - "description": "What's New tip: performance startup" - }, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", - "@whatsNewPerformanceTip2": { - "description": "What's New tip: performance memory" - }, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", - "@whatsNewPerformanceTip3": { - "description": "What's New tip: performance SQLite" - }, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", - "@whatsNewReadyMessage": { - "description": "Ready card message on last What's New page" - }, - "whatsNewGetStarted": "Let's Go", - "@whatsNewGetStarted": { - "description": "Button text to dismiss What's New screen" - }, - "whatsNewPageIndicator": "{current} of {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } } } \ No newline at end of file From ab2d671760afe1eefb296c4738ea60067e44ed2c Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:19:46 +0700 Subject: [PATCH 073/104] New translations app_en.arb (Turkish) --- lib/l10n/arb/app_tr-TR.arb | 216 ------------------------------------- 1 file changed, 216 deletions(-) diff --git a/lib/l10n/arb/app_tr-TR.arb b/lib/l10n/arb/app_tr-TR.arb index 86dd938c..62bdd10c 100644 --- a/lib/l10n/arb/app_tr-TR.arb +++ b/lib/l10n/arb/app_tr-TR.arb @@ -4314,221 +4314,5 @@ "setupModeChangeableLater": "You can switch between modes anytime in Settings.", "@setupModeChangeableLater": { "description": "Hint that mode can be changed later" - }, - "settingsSmartQueueTitle": "Smart Queue", - "@settingsSmartQueueTitle": { - "description": "Title for Smart Queue toggle in settings" - }, - "settingsSmartQueueSubtitle": "Automatically discover and add similar tracks to your queue", - "@settingsSmartQueueSubtitle": { - "description": "Subtitle for Smart Queue toggle in settings" - }, - "whatsNewTitle": "What's New in 4.0", - "@whatsNewTitle": { - "description": "Title for the What's New screen" - }, - "whatsNewSubtitle": "SpotiFLAC has evolved — here's what changed since 3.x", - "@whatsNewSubtitle": { - "description": "Subtitle for the What's New screen" - }, - "whatsNewWelcomeTitle": "SpotiFLAC Mobile 4.0", - "@whatsNewWelcomeTitle": { - "description": "Welcome page title in What's New screen" - }, - "whatsNewWelcomeDesc": "Welcome back! This is a major update packed with new features. Swipe through to see what's changed.", - "@whatsNewWelcomeDesc": { - "description": "Welcome page description in What's New screen" - }, - "whatsNewWelcomeTip1": "New streaming mode with instant playback", - "@whatsNewWelcomeTip1": { - "description": "Welcome page tip 1" - }, - "whatsNewWelcomeTip2": "Redesigned library and full-screen player", - "@whatsNewWelcomeTip2": { - "description": "Welcome page tip 2" - }, - "whatsNewWelcomeTip3": "Batch tools, performance boosts, and more", - "@whatsNewWelcomeTip3": { - "description": "Welcome page tip 3" - }, - "whatsNewStreamingTitle": "Streaming Mode", - "@whatsNewStreamingTitle": { - "description": "What's New feature: Streaming Mode title" - }, - "whatsNewStreamingDesc": "Tap any track to play instantly — no download needed. Full-screen player with synced lyrics and media controls.", - "@whatsNewStreamingDesc": { - "description": "What's New feature: Streaming Mode description" - }, - "whatsNewSmartQueueTitle": "Smart Queue", - "@whatsNewSmartQueueTitle": { - "description": "What's New feature: Smart Queue title" - }, - "whatsNewSmartQueueDesc": "Your queue auto-curates with related tracks and artist discovery. Never run out of music.", - "@whatsNewSmartQueueDesc": { - "description": "What's New feature: Smart Queue description" - }, - "whatsNewDualModeTitle": "Dual Mode", - "@whatsNewDualModeTitle": { - "description": "What's New feature: Dual Mode title" - }, - "whatsNewDualModeDesc": "Switch between Downloader and Streaming modes anytime. All buttons adapt automatically.", - "@whatsNewDualModeDesc": { - "description": "What's New feature: Dual Mode description" - }, - "whatsNewLibraryTitle": "Redesigned Library", - "@whatsNewLibraryTitle": { - "description": "What's New feature: Library redesign title" - }, - "whatsNewLibraryDesc": "Playlist-first layout with drag-and-drop categorization, custom covers, and multi-select batch actions.", - "@whatsNewLibraryDesc": { - "description": "What's New feature: Library redesign description" - }, - "whatsNewPlayerTitle": "Full-Screen Player", - "@whatsNewPlayerTitle": { - "description": "What's New feature: Full-Screen Player title" - }, - "whatsNewPlayerDesc": "Cover art parallax, synced lyrics, playback persistence across restarts, and download button in player.", - "@whatsNewPlayerDesc": { - "description": "What's New feature: Full-Screen Player description" - }, - "whatsNewContextMenuTitle": "Long-Press Menus", - "@whatsNewContextMenuTitle": { - "description": "What's New feature: Context Menus title" - }, - "whatsNewContextMenuDesc": "Long-press any track for quick actions — add to playlist, share, convert, or re-enrich metadata.", - "@whatsNewContextMenuDesc": { - "description": "What's New feature: Context Menus description" - }, - "whatsNewPerformanceTitle": "Performance", - "@whatsNewPerformanceTitle": { - "description": "What's New feature: Performance title" - }, - "whatsNewPerformanceDesc": "Faster startup, reduced memory usage, SQLite-backed persistence, and granular UI updates.", - "@whatsNewPerformanceDesc": { - "description": "What's New feature: Performance description" - }, - "whatsNewBatchToolsTitle": "Batch Tools", - "@whatsNewBatchToolsTitle": { - "description": "What's New feature: Batch Tools title" - }, - "whatsNewBatchToolsDesc": "Multi-select share, batch convert to MP3/Opus, and batch re-enrich metadata across your library.", - "@whatsNewBatchToolsDesc": { - "description": "What's New feature: Batch Tools description" - }, - "whatsNewStreamingTip1": "Tap any track to start playing instantly", - "@whatsNewStreamingTip1": { - "description": "What's New tip: streaming instant play" - }, - "whatsNewStreamingTip2": "Synced lyrics in the full-screen player", - "@whatsNewStreamingTip2": { - "description": "What's New tip: streaming synced lyrics" - }, - "whatsNewStreamingTip3": "Download tracks directly from the player", - "@whatsNewStreamingTip3": { - "description": "What's New tip: streaming download from player" - }, - "whatsNewSmartQueueTip1": "Queue auto-fills with related tracks", - "@whatsNewSmartQueueTip1": { - "description": "What's New tip: smart queue auto-fill" - }, - "whatsNewSmartQueueTip2": "Discover new artists as you listen", - "@whatsNewSmartQueueTip2": { - "description": "What's New tip: smart queue artist discovery" - }, - "whatsNewSmartQueueTip3": "Never run out of music to play", - "@whatsNewSmartQueueTip3": { - "description": "What's New tip: smart queue endless" - }, - "whatsNewDualModeTip1": "Switch modes anytime in Settings", - "@whatsNewDualModeTip1": { - "description": "What's New tip: dual mode switch" - }, - "whatsNewDualModeTip2": "UI buttons adapt to your current mode", - "@whatsNewDualModeTip2": { - "description": "What's New tip: dual mode adaptive UI" - }, - "whatsNewDualModeTip3": "Download for offline, stream for instant play", - "@whatsNewDualModeTip3": { - "description": "What's New tip: dual mode use cases" - }, - "whatsNewLibraryTip1": "Drag and drop to organize playlists", - "@whatsNewLibraryTip1": { - "description": "What's New tip: library drag and drop" - }, - "whatsNewLibraryTip2": "Set custom cover images for playlists", - "@whatsNewLibraryTip2": { - "description": "What's New tip: library custom covers" - }, - "whatsNewLibraryTip3": "Multi-select tracks for batch actions", - "@whatsNewLibraryTip3": { - "description": "What's New tip: library multi-select" - }, - "whatsNewPlayerTip1": "Cover art with parallax scrolling effect", - "@whatsNewPlayerTip1": { - "description": "What's New tip: player parallax" - }, - "whatsNewPlayerTip2": "Playback persists across app restarts", - "@whatsNewPlayerTip2": { - "description": "What's New tip: player persistence" - }, - "whatsNewPlayerTip3": "Synced lyrics while you listen", - "@whatsNewPlayerTip3": { - "description": "What's New tip: player lyrics" - }, - "whatsNewContextMenuTip1": "Add tracks to any playlist instantly", - "@whatsNewContextMenuTip1": { - "description": "What's New tip: context menu add to playlist" - }, - "whatsNewContextMenuTip2": "Share or convert with one tap", - "@whatsNewContextMenuTip2": { - "description": "What's New tip: context menu share/convert" - }, - "whatsNewContextMenuTip3": "Re-enrich metadata when needed", - "@whatsNewContextMenuTip3": { - "description": "What's New tip: context menu re-enrich" - }, - "whatsNewBatchToolsTip1": "Share multiple tracks at once", - "@whatsNewBatchToolsTip1": { - "description": "What's New tip: batch share" - }, - "whatsNewBatchToolsTip2": "Batch convert to MP3 or Opus format", - "@whatsNewBatchToolsTip2": { - "description": "What's New tip: batch convert" - }, - "whatsNewBatchToolsTip3": "Re-enrich metadata across your library", - "@whatsNewBatchToolsTip3": { - "description": "What's New tip: batch re-enrich" - }, - "whatsNewPerformanceTip1": "Faster app startup time", - "@whatsNewPerformanceTip1": { - "description": "What's New tip: performance startup" - }, - "whatsNewPerformanceTip2": "Reduced memory usage during playback", - "@whatsNewPerformanceTip2": { - "description": "What's New tip: performance memory" - }, - "whatsNewPerformanceTip3": "SQLite-backed storage for reliability", - "@whatsNewPerformanceTip3": { - "description": "What's New tip: performance SQLite" - }, - "whatsNewReadyMessage": "You're all set — enjoy the new SpotiFLAC!", - "@whatsNewReadyMessage": { - "description": "Ready card message on last What's New page" - }, - "whatsNewGetStarted": "Let's Go", - "@whatsNewGetStarted": { - "description": "Button text to dismiss What's New screen" - }, - "whatsNewPageIndicator": "{current} of {total}", - "@whatsNewPageIndicator": { - "description": "Page indicator text in What's New screen", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } } } \ No newline at end of file From 02c803385c9f1ce2e967613580070e6756fea81a Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:33 +0700 Subject: [PATCH 074/104] Update source file app_en.arb --- lib/l10n/arb/app_en.arb | 3508 +++++++++++++++++++++++---------------- 1 file changed, 2057 insertions(+), 1451 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 709c1213..ca416fa0 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1,897 +1,996 @@ { "@@locale": "en", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", - "@appName": {"description": "App name - DO NOT TRANSLATE"}, - "appDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", - "@appDescription": {"description": "App description shown in about page"}, - + "@appName": { + "description": "App name - DO NOT TRANSLATE" + }, "navHome": "Home", - "@navHome": {"description": "Bottom navigation - Home tab"}, + "@navHome": { + "description": "Bottom navigation - Home tab" + }, "navLibrary": "Library", - "@navLibrary": {"description": "Bottom navigation - Library tab"}, - "navHistory": "History", - "@navHistory": {"description": "Bottom navigation - History tab (legacy)"}, + "@navLibrary": { + "description": "Bottom navigation - Library tab" + }, "navSettings": "Settings", - "@navSettings": {"description": "Bottom navigation - Settings tab"}, + "@navSettings": { + "description": "Bottom navigation - Settings tab" + }, "navStore": "Store", - "@navStore": {"description": "Bottom navigation - Extension store tab"}, - + "@navStore": { + "description": "Bottom navigation - Extension store tab" + }, "homeTitle": "Home", - "@homeTitle": {"description": "Home screen title"}, - "homeSearchHint": "Paste Spotify URL or search...", - "@homeSearchHint": {"description": "Placeholder text in search box"}, - "homeSearchHintExtension": "Search with {extensionName}...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": {"type": "String", "description": "Name of the active extension"} - } + "@homeTitle": { + "description": "Home screen title" }, "homeSubtitle": "Paste a Spotify link or search by name", - "@homeSubtitle": {"description": "Subtitle shown below search box"}, + "@homeSubtitle": { + "description": "Subtitle shown below search box" + }, "homeSupports": "Supports: Track, Album, Playlist, Artist URLs", - "@homeSupports": {"description": "Info text about supported URL types"}, + "@homeSupports": { + "description": "Info text about supported URL types" + }, "homeRecent": "Recent", - "@homeRecent": {"description": "Section header for recent searches"}, - - "historyTitle": "History", - "@historyTitle": {"description": "History screen title"}, - "historyDownloading": "Downloading ({count})", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": {"type": "int", "description": "Number of active downloads"} - } + "@homeRecent": { + "description": "Section header for recent searches" }, - "historyDownloaded": "Downloaded", - "@historyDownloaded": {"description": "Tab showing completed downloads"}, "historyFilterAll": "All", - "@historyFilterAll": {"description": "Filter chip - show all items"}, + "@historyFilterAll": { + "description": "Filter chip - show all items" + }, "historyFilterAlbums": "Albums", - "@historyFilterAlbums": {"description": "Filter chip - show albums only"}, + "@historyFilterAlbums": { + "description": "Filter chip - show albums only" + }, "historyFilterSingles": "Singles", - "@historyFilterSingles": {"description": "Filter chip - show singles only"}, - "historyTracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": {"type": "int"} - } + "@historyFilterSingles": { + "description": "Filter chip - show singles only" }, - "historyAlbumsCount": "{count, plural, =1{1 album} other{{count} albums}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": {"type": "int"} - } - }, - "historyNoDownloads": "No download history", - "@historyNoDownloads": {"description": "Empty state title"}, - "historyNoDownloadsSubtitle": "Downloaded tracks will appear here", - "@historyNoDownloadsSubtitle": {"description": "Empty state subtitle"}, - "historyNoAlbums": "No album downloads", - "@historyNoAlbums": {"description": "Empty state when filtering albums"}, - "historyNoAlbumsSubtitle": "Download multiple tracks from an album to see them here", - "@historyNoAlbumsSubtitle": {"description": "Empty state subtitle for albums filter"}, - "historyNoSingles": "No single downloads", - "@historyNoSingles": {"description": "Empty state when filtering singles"}, -"historyNoSinglesSubtitle": "Single track downloads will appear here", - "@historyNoSinglesSubtitle": {"description": "Empty state subtitle for singles filter"}, "historySearchHint": "Search history...", - "@historySearchHint": {"description": "Search bar placeholder in history"}, - + "@historySearchHint": { + "description": "Search bar placeholder in history" + }, "settingsTitle": "Settings", - "@settingsTitle": {"description": "Settings screen title"}, + "@settingsTitle": { + "description": "Settings screen title" + }, "settingsDownload": "Download", - "@settingsDownload": {"description": "Settings section - download options"}, + "@settingsDownload": { + "description": "Settings section - download options" + }, "settingsAppearance": "Appearance", - "@settingsAppearance": {"description": "Settings section - visual customization"}, + "@settingsAppearance": { + "description": "Settings section - visual customization" + }, "settingsOptions": "Options", - "@settingsOptions": {"description": "Settings section - app options"}, + "@settingsOptions": { + "description": "Settings section - app options" + }, "settingsExtensions": "Extensions", - "@settingsExtensions": {"description": "Settings section - extension management"}, + "@settingsExtensions": { + "description": "Settings section - extension management" + }, "settingsAbout": "About", - "@settingsAbout": {"description": "Settings section - app info"}, - + "@settingsAbout": { + "description": "Settings section - app info" + }, "downloadTitle": "Download", - "@downloadTitle": {"description": "Download settings page title"}, - "downloadLocation": "Download Location", - "@downloadLocation": {"description": "Setting for download folder"}, - "downloadLocationSubtitle": "Choose where to save files", - "@downloadLocationSubtitle": {"description": "Subtitle for download location"}, - "downloadLocationDefault": "Default location", - "@downloadLocationDefault": {"description": "Shown when using default folder"}, - "downloadDefaultService": "Default Service", - "@downloadDefaultService": {"description": "Setting for preferred download service (Tidal/Qobuz/Amazon)"}, - "downloadDefaultServiceSubtitle": "Service used for downloads", - "@downloadDefaultServiceSubtitle": {"description": "Subtitle for default service"}, - "downloadDefaultQuality": "Default Quality", - "@downloadDefaultQuality": {"description": "Setting for audio quality"}, - "downloadAskQuality": "Ask Quality Before Download", - "@downloadAskQuality": {"description": "Toggle to show quality picker"}, + "@downloadTitle": { + "description": "Download settings page title" + }, "downloadAskQualitySubtitle": "Show quality picker for each download", - "@downloadAskQualitySubtitle": {"description": "Subtitle for ask quality toggle"}, + "@downloadAskQualitySubtitle": { + "description": "Subtitle for ask quality toggle" + }, "downloadFilenameFormat": "Filename Format", - "@downloadFilenameFormat": {"description": "Setting for output filename pattern"}, + "@downloadFilenameFormat": { + "description": "Setting for output filename pattern" + }, "downloadFolderOrganization": "Folder Organization", - "@downloadFolderOrganization": {"description": "Setting for folder structure"}, - "downloadSeparateSingles": "Separate Singles", - "@downloadSeparateSingles": {"description": "Toggle to separate single tracks"}, - "downloadSeparateSinglesSubtitle": "Put single tracks in a separate folder", - "@downloadSeparateSinglesSubtitle": {"description": "Subtitle for separate singles toggle"}, - - "qualityBest": "Best Available", - "@qualityBest": {"description": "Audio quality option - highest available"}, - "qualityFlac": "FLAC", - "@qualityFlac": {"description": "Audio quality option - FLAC lossless"}, - "quality320": "320 kbps", - "@quality320": {"description": "Audio quality option - 320kbps MP3"}, - "quality128": "128 kbps", - "@quality128": {"description": "Audio quality option - 128kbps MP3"}, - + "@downloadFolderOrganization": { + "description": "Setting for folder structure" + }, "appearanceTitle": "Appearance", - "@appearanceTitle": {"description": "Appearance settings page title"}, - "appearanceTheme": "Theme", - "@appearanceTheme": {"description": "Theme mode setting"}, + "@appearanceTitle": { + "description": "Appearance settings page title" + }, "appearanceThemeSystem": "System", - "@appearanceThemeSystem": {"description": "Follow system theme"}, + "@appearanceThemeSystem": { + "description": "Follow system theme" + }, "appearanceThemeLight": "Light", - "@appearanceThemeLight": {"description": "Light theme"}, + "@appearanceThemeLight": { + "description": "Light theme" + }, "appearanceThemeDark": "Dark", - "@appearanceThemeDark": {"description": "Dark theme"}, + "@appearanceThemeDark": { + "description": "Dark theme" + }, "appearanceDynamicColor": "Dynamic Color", - "@appearanceDynamicColor": {"description": "Material You dynamic colors"}, + "@appearanceDynamicColor": { + "description": "Material You dynamic colors" + }, "appearanceDynamicColorSubtitle": "Use colors from your wallpaper", - "@appearanceDynamicColorSubtitle": {"description": "Subtitle for dynamic color"}, - "appearanceAccentColor": "Accent Color", - "@appearanceAccentColor": {"description": "Custom accent color picker"}, + "@appearanceDynamicColorSubtitle": { + "description": "Subtitle for dynamic color" + }, "appearanceHistoryView": "History View", - "@appearanceHistoryView": {"description": "Layout style for history"}, + "@appearanceHistoryView": { + "description": "Layout style for history" + }, "appearanceHistoryViewList": "List", - "@appearanceHistoryViewList": {"description": "List layout option"}, + "@appearanceHistoryViewList": { + "description": "List layout option" + }, "appearanceHistoryViewGrid": "Grid", - "@appearanceHistoryViewGrid": {"description": "Grid layout option"}, - + "@appearanceHistoryViewGrid": { + "description": "Grid layout option" + }, "optionsTitle": "Options", - "@optionsTitle": {"description": "Options settings page title"}, - "optionsSearchSource": "Search Source", - "@optionsSearchSource": {"description": "Section for search provider settings"}, + "@optionsTitle": { + "description": "Options settings page title" + }, "optionsPrimaryProvider": "Primary Provider", - "@optionsPrimaryProvider": {"description": "Main search provider setting"}, + "@optionsPrimaryProvider": { + "description": "Main search provider setting" + }, "optionsPrimaryProviderSubtitle": "Service used when searching by track name.", - "@optionsPrimaryProviderSubtitle": {"description": "Subtitle for primary provider"}, + "@optionsPrimaryProviderSubtitle": { + "description": "Subtitle for primary provider" + }, "optionsUsingExtension": "Using extension: {extensionName}", "@optionsUsingExtension": { "description": "Shows active extension name", "placeholders": { - "extensionName": {"type": "String"} + "extensionName": { + "type": "String" + } } }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", - "@optionsSwitchBack": {"description": "Hint to switch back to built-in providers"}, + "@optionsSwitchBack": { + "description": "Hint to switch back to built-in providers" + }, "optionsAutoFallback": "Auto Fallback", - "@optionsAutoFallback": {"description": "Auto-retry with other services"}, + "@optionsAutoFallback": { + "description": "Auto-retry with other services" + }, "optionsAutoFallbackSubtitle": "Try other services if download fails", - "@optionsAutoFallbackSubtitle": {"description": "Subtitle for auto fallback"}, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", - "@optionsAutoSkipUnavailableTracks": {"description": "Toggle to skip to the next queue track when current track stream resolution fails"}, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": {"description": "Subtitle when auto skip on resolve failure is enabled"}, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": {"description": "Subtitle when auto skip on resolve failure is disabled"}, - "optionsInteractionMode": "Interaction Mode", - "@optionsInteractionMode": {"description": "Tap behavior mode for track lists"}, - "modeDownloader": "Downloader Mode", - "@modeDownloader": {"description": "Interaction mode where taps queue downloads"}, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", - "@modeDownloaderSubtitle": {"description": "Subtitle for downloader interaction mode"}, - "modeStreaming": "Streaming Mode", - "@modeStreaming": {"description": "Interaction mode where taps start playback"}, - "modeStreamingSubtitle": "Tap tracks to play instantly", - "@modeStreamingSubtitle": {"description": "Subtitle for streaming interaction mode"}, + "@optionsAutoFallbackSubtitle": { + "description": "Subtitle for auto fallback" + }, "optionsUseExtensionProviders": "Use Extension Providers", - "@optionsUseExtensionProviders": {"description": "Enable extension download providers"}, + "@optionsUseExtensionProviders": { + "description": "Enable extension download providers" + }, "optionsUseExtensionProvidersOn": "Extensions will be tried first", - "@optionsUseExtensionProvidersOn": {"description": "Status when extension providers enabled"}, + "@optionsUseExtensionProvidersOn": { + "description": "Status when extension providers enabled" + }, "optionsUseExtensionProvidersOff": "Using built-in providers only", - "@optionsUseExtensionProvidersOff": {"description": "Status when extension providers disabled"}, + "@optionsUseExtensionProvidersOff": { + "description": "Status when extension providers disabled" + }, "optionsEmbedLyrics": "Embed Lyrics", - "@optionsEmbedLyrics": {"description": "Embed lyrics in audio files"}, + "@optionsEmbedLyrics": { + "description": "Embed lyrics in audio files" + }, "optionsEmbedLyricsSubtitle": "Embed synced lyrics into FLAC files", - "@optionsEmbedLyricsSubtitle": {"description": "Subtitle for embed lyrics"}, + "@optionsEmbedLyricsSubtitle": { + "description": "Subtitle for embed lyrics" + }, "optionsMaxQualityCover": "Max Quality Cover", - "@optionsMaxQualityCover": {"description": "Download highest quality album art"}, + "@optionsMaxQualityCover": { + "description": "Download highest quality album art" + }, "optionsMaxQualityCoverSubtitle": "Download highest resolution cover art", - "@optionsMaxQualityCoverSubtitle": {"description": "Subtitle for max quality cover"}, + "@optionsMaxQualityCoverSubtitle": { + "description": "Subtitle for max quality cover" + }, "optionsConcurrentDownloads": "Concurrent Downloads", - "@optionsConcurrentDownloads": {"description": "Number of parallel downloads"}, + "@optionsConcurrentDownloads": { + "description": "Number of parallel downloads" + }, "optionsConcurrentSequential": "Sequential (1 at a time)", - "@optionsConcurrentSequential": {"description": "Download one at a time"}, + "@optionsConcurrentSequential": { + "description": "Download one at a time" + }, "optionsConcurrentParallel": "{count} parallel downloads", "@optionsConcurrentParallel": { "description": "Multiple parallel downloads", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "optionsConcurrentWarning": "Parallel downloads may trigger rate limiting", - "@optionsConcurrentWarning": {"description": "Warning about rate limits"}, + "@optionsConcurrentWarning": { + "description": "Warning about rate limits" + }, "optionsExtensionStore": "Extension Store", - "@optionsExtensionStore": {"description": "Show/hide store tab"}, + "@optionsExtensionStore": { + "description": "Show/hide store tab" + }, "optionsExtensionStoreSubtitle": "Show Store tab in navigation", - "@optionsExtensionStoreSubtitle": {"description": "Subtitle for extension store toggle"}, + "@optionsExtensionStoreSubtitle": { + "description": "Subtitle for extension store toggle" + }, "optionsCheckUpdates": "Check for Updates", - "@optionsCheckUpdates": {"description": "Auto update check toggle"}, + "@optionsCheckUpdates": { + "description": "Auto update check toggle" + }, "optionsCheckUpdatesSubtitle": "Notify when new version is available", - "@optionsCheckUpdatesSubtitle": {"description": "Subtitle for update check"}, + "@optionsCheckUpdatesSubtitle": { + "description": "Subtitle for update check" + }, "optionsUpdateChannel": "Update Channel", - "@optionsUpdateChannel": {"description": "Stable vs preview releases"}, + "@optionsUpdateChannel": { + "description": "Stable vs preview releases" + }, "optionsUpdateChannelStable": "Stable releases only", - "@optionsUpdateChannelStable": {"description": "Only stable updates"}, + "@optionsUpdateChannelStable": { + "description": "Only stable updates" + }, "optionsUpdateChannelPreview": "Get preview releases", - "@optionsUpdateChannelPreview": {"description": "Include beta/preview updates"}, + "@optionsUpdateChannelPreview": { + "description": "Include beta/preview updates" + }, "optionsUpdateChannelWarning": "Preview may contain bugs or incomplete features", - "@optionsUpdateChannelWarning": {"description": "Warning about preview channel"}, + "@optionsUpdateChannelWarning": { + "description": "Warning about preview channel" + }, "optionsClearHistory": "Clear Download History", - "@optionsClearHistory": {"description": "Delete all download history"}, + "@optionsClearHistory": { + "description": "Delete all download history" + }, "optionsClearHistorySubtitle": "Remove all downloaded tracks from history", - "@optionsClearHistorySubtitle": {"description": "Subtitle for clear history"}, + "@optionsClearHistorySubtitle": { + "description": "Subtitle for clear history" + }, "optionsDetailedLogging": "Detailed Logging", - "@optionsDetailedLogging": {"description": "Enable verbose logs for debugging"}, + "@optionsDetailedLogging": { + "description": "Enable verbose logs for debugging" + }, "optionsDetailedLoggingOn": "Detailed logs are being recorded", - "@optionsDetailedLoggingOn": {"description": "Status when logging enabled"}, + "@optionsDetailedLoggingOn": { + "description": "Status when logging enabled" + }, "optionsDetailedLoggingOff": "Enable for bug reports", - "@optionsDetailedLoggingOff": {"description": "Status when logging disabled"}, + "@optionsDetailedLoggingOff": { + "description": "Status when logging disabled" + }, "optionsSpotifyCredentials": "Spotify Credentials", - "@optionsSpotifyCredentials": {"description": "Spotify API credentials setting"}, + "@optionsSpotifyCredentials": { + "description": "Spotify API credentials setting" + }, "optionsSpotifyCredentialsConfigured": "Client ID: {clientId}...", "@optionsSpotifyCredentialsConfigured": { "description": "Shows configured client ID preview", "placeholders": { - "clientId": {"type": "String"} + "clientId": { + "type": "String" + } } }, "optionsSpotifyCredentialsRequired": "Required - tap to configure", - "@optionsSpotifyCredentialsRequired": {"description": "Prompt to set up credentials"}, + "@optionsSpotifyCredentialsRequired": { + "description": "Prompt to set up credentials" + }, "optionsSpotifyWarning": "Spotify requires your own API credentials. Get them free from developer.spotify.com", - "@optionsSpotifyWarning": {"description": "Info about Spotify API requirement"}, + "@optionsSpotifyWarning": { + "description": "Info about Spotify API requirement" + }, "optionsSpotifyDeprecationWarning": "Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.", - "@optionsSpotifyDeprecationWarning": {"description": "Warning about Spotify API deprecation"}, - + "@optionsSpotifyDeprecationWarning": { + "description": "Warning about Spotify API deprecation" + }, "extensionsTitle": "Extensions", - "@extensionsTitle": {"description": "Extensions page title"}, - "extensionsInstalled": "Installed Extensions", - "@extensionsInstalled": {"description": "Section header for installed extensions"}, - "extensionsNone": "No extensions installed", - "@extensionsNone": {"description": "Empty state title"}, - "extensionsNoneSubtitle": "Install extensions from the Store tab", - "@extensionsNoneSubtitle": {"description": "Empty state subtitle"}, - "extensionsEnabled": "Enabled", - "@extensionsEnabled": {"description": "Extension status - active"}, + "@extensionsTitle": { + "description": "Extensions page title" + }, "extensionsDisabled": "Disabled", - "@extensionsDisabled": {"description": "Extension status - inactive"}, + "@extensionsDisabled": { + "description": "Extension status - inactive" + }, "extensionsVersion": "Version {version}", "@extensionsVersion": { "description": "Extension version display", "placeholders": { - "version": {"type": "String"} + "version": { + "type": "String" + } } }, "extensionsAuthor": "by {author}", "@extensionsAuthor": { "description": "Extension author credit", "placeholders": { - "author": {"type": "String"} + "author": { + "type": "String" + } } }, "extensionsUninstall": "Uninstall", - "@extensionsUninstall": {"description": "Uninstall extension button"}, - "extensionsSetAsSearch": "Set as Search Provider", - "@extensionsSetAsSearch": {"description": "Use extension for search"}, - + "@extensionsUninstall": { + "description": "Uninstall extension button" + }, "storeTitle": "Extension Store", - "@storeTitle": {"description": "Store screen title"}, + "@storeTitle": { + "description": "Store screen title" + }, "storeSearch": "Search extensions...", - "@storeSearch": {"description": "Store search placeholder"}, + "@storeSearch": { + "description": "Store search placeholder" + }, "storeInstall": "Install", - "@storeInstall": {"description": "Install extension button"}, + "@storeInstall": { + "description": "Install extension button" + }, "storeInstalled": "Installed", - "@storeInstalled": {"description": "Already installed badge"}, + "@storeInstalled": { + "description": "Already installed badge" + }, "storeUpdate": "Update", - "@storeUpdate": {"description": "Update available button"}, - + "@storeUpdate": { + "description": "Update available button" + }, "aboutTitle": "About", - "@aboutTitle": {"description": "About page title"}, + "@aboutTitle": { + "description": "About page title" + }, "aboutContributors": "Contributors", - "@aboutContributors": {"description": "Section for contributors"}, + "@aboutContributors": { + "description": "Section for contributors" + }, "aboutMobileDeveloper": "Mobile version developer", - "@aboutMobileDeveloper": {"description": "Role description for mobile dev"}, + "@aboutMobileDeveloper": { + "description": "Role description for mobile dev" + }, "aboutOriginalCreator": "Creator of the original SpotiFLAC", - "@aboutOriginalCreator": {"description": "Role description for original creator"}, + "@aboutOriginalCreator": { + "description": "Role description for original creator" + }, "aboutLogoArtist": "The talented artist who created our beautiful app logo!", - "@aboutLogoArtist": {"description": "Role description for logo artist"}, + "@aboutLogoArtist": { + "description": "Role description for logo artist" + }, "aboutTranslators": "Translators", - "@aboutTranslators": {"description": "Section for translators"}, + "@aboutTranslators": { + "description": "Section for translators" + }, "aboutSpecialThanks": "Special Thanks", - "@aboutSpecialThanks": {"description": "Section for special thanks"}, + "@aboutSpecialThanks": { + "description": "Section for special thanks" + }, "aboutLinks": "Links", - "@aboutLinks": {"description": "Section for external links"}, + "@aboutLinks": { + "description": "Section for external links" + }, "aboutMobileSource": "Mobile source code", - "@aboutMobileSource": {"description": "Link to mobile GitHub repo"}, + "@aboutMobileSource": { + "description": "Link to mobile GitHub repo" + }, "aboutPCSource": "PC source code", - "@aboutPCSource": {"description": "Link to PC GitHub repo"}, + "@aboutPCSource": { + "description": "Link to PC GitHub repo" + }, "aboutReportIssue": "Report an issue", - "@aboutReportIssue": {"description": "Link to report bugs"}, + "@aboutReportIssue": { + "description": "Link to report bugs" + }, "aboutReportIssueSubtitle": "Report any problems you encounter", - "@aboutReportIssueSubtitle": {"description": "Subtitle for report issue"}, -"aboutFeatureRequest": "Feature request", - "@aboutFeatureRequest": {"description": "Link to suggest features"}, + "@aboutReportIssueSubtitle": { + "description": "Subtitle for report issue" + }, + "aboutFeatureRequest": "Feature request", + "@aboutFeatureRequest": { + "description": "Link to suggest features" + }, "aboutFeatureRequestSubtitle": "Suggest new features for the app", - "@aboutFeatureRequestSubtitle": {"description": "Subtitle for feature request"}, + "@aboutFeatureRequestSubtitle": { + "description": "Subtitle for feature request" + }, "aboutTelegramChannel": "Telegram Channel", - "@aboutTelegramChannel": {"description": "Link to Telegram channel"}, + "@aboutTelegramChannel": { + "description": "Link to Telegram channel" + }, "aboutTelegramChannelSubtitle": "Announcements and updates", - "@aboutTelegramChannelSubtitle": {"description": "Subtitle for Telegram channel"}, + "@aboutTelegramChannelSubtitle": { + "description": "Subtitle for Telegram channel" + }, "aboutTelegramChat": "Telegram Community", - "@aboutTelegramChat": {"description": "Link to Telegram chat group"}, + "@aboutTelegramChat": { + "description": "Link to Telegram chat group" + }, "aboutTelegramChatSubtitle": "Chat with other users", - "@aboutTelegramChatSubtitle": {"description": "Subtitle for Telegram chat"}, + "@aboutTelegramChatSubtitle": { + "description": "Subtitle for Telegram chat" + }, "aboutSocial": "Social", - "@aboutSocial": {"description": "Section for social links"}, - "aboutSupport": "Support", - "@aboutSupport": {"description": "Section for support/donation links"}, + "@aboutSocial": { + "description": "Section for social links" + }, "aboutApp": "App", - "@aboutApp": {"description": "Section for app info"}, + "@aboutApp": { + "description": "Section for app info" + }, "aboutVersion": "Version", - "@aboutVersion": {"description": "Version info label"}, + "@aboutVersion": { + "description": "Version info label" + }, "aboutBinimumDesc": "The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn't exist!", - "@aboutBinimumDesc": {"description": "Credit description for binimum"}, + "@aboutBinimumDesc": { + "description": "Credit description for binimum" + }, "aboutSachinsenalDesc": "The original HiFi project creator. The foundation of Tidal integration!", - "@aboutSachinsenalDesc": {"description": "Credit description for sachinsenal0x64"}, + "@aboutSachinsenalDesc": { + "description": "Credit description for sachinsenal0x64" + }, "aboutSjdonadoDesc": "Creator of I Don't Have Spotify (IDHS). The fallback link resolver that saves the day!", - "@aboutSjdonadoDesc": {"description": "Credit description for sjdonado"}, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": {"description": "Name of Amazon API service - DO NOT TRANSLATE"}, - "aboutDoubleDoubleDesc": "Amazing API for Amazon Music downloads. Thank you for making it free!", - "@aboutDoubleDoubleDesc": {"description": "Credit for DoubleDouble API"}, + "@aboutSjdonadoDesc": { + "description": "Credit description for sjdonado" + }, "aboutDabMusic": "DAB Music", - "@aboutDabMusic": {"description": "Name of Qobuz API service - DO NOT TRANSLATE"}, + "@aboutDabMusic": { + "description": "Name of Qobuz API service - DO NOT TRANSLATE" + }, "aboutDabMusicDesc": "The best Qobuz streaming API. Hi-Res downloads wouldn't be possible without this!", - "@aboutDabMusicDesc": {"description": "Credit for DAB Music API"}, + "@aboutDabMusicDesc": { + "description": "Credit for DAB Music API" + }, "aboutSpotiSaver": "SpotiSaver", - "@aboutSpotiSaver": {"description": "Name of SpotiSaver API service - DO NOT TRANSLATE"}, + "@aboutSpotiSaver": { + "description": "Name of SpotiSaver API service - DO NOT TRANSLATE" + }, "aboutSpotiSaverDesc": "Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!", - "@aboutSpotiSaverDesc": {"description": "Credit for SpotiSaver API"}, + "@aboutSpotiSaverDesc": { + "description": "Credit for SpotiSaver API" + }, "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", - "@aboutAppDescription": {"description": "App description in header card"}, - - "albumTitle": "Album", - "@albumTitle": {"description": "Album screen title"}, - "albumTracks": "{count, plural, =1{1 track} other{{count} tracks}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": {"type": "int"} - } + "@aboutAppDescription": { + "description": "App description in header card" }, - "albumDownloadAll": "Download All", - "@albumDownloadAll": {"description": "Button to download all tracks"}, - "albumDownloadRemaining": "Download Remaining", - "@albumDownloadRemaining": {"description": "Button to download remaining tracks"}, - - "playlistTitle": "Playlist", - "@playlistTitle": {"description": "Playlist screen title"}, - "artistTitle": "Artist", - "@artistTitle": {"description": "Artist screen title"}, "artistAlbums": "Albums", - "@artistAlbums": {"description": "Section header for artist albums"}, + "@artistAlbums": { + "description": "Section header for artist albums" + }, "artistSingles": "Singles & EPs", - "@artistSingles": {"description": "Section header for singles/EPs"}, + "@artistSingles": { + "description": "Section header for singles/EPs" + }, "artistCompilations": "Compilations", - "@artistCompilations": {"description": "Section header for compilations"}, - "artistReleases": "{count, plural, =1{1 release} other{{count} releases}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": {"type": "int"} - } + "@artistCompilations": { + "description": "Section header for compilations" }, "artistPopular": "Popular", - "@artistPopular": {"description": "Section header for popular/top tracks"}, + "@artistPopular": { + "description": "Section header for popular/top tracks" + }, "artistMonthlyListeners": "{count} monthly listeners", "@artistMonthlyListeners": { "description": "Monthly listener count display", "placeholders": { - "count": {"type": "String", "description": "Formatted listener count"} + "count": { + "type": "String", + "description": "Formatted listener count" + } } }, - - "trackMetadataTitle": "Track Info", - "@trackMetadataTitle": {"description": "Track metadata screen title"}, - "trackMetadataArtist": "Artist", - "@trackMetadataArtist": {"description": "Metadata field - artist name"}, - "trackMetadataAlbum": "Album", - "@trackMetadataAlbum": {"description": "Metadata field - album name"}, - "trackMetadataDuration": "Duration", - "@trackMetadataDuration": {"description": "Metadata field - track length"}, - "trackMetadataQuality": "Quality", - "@trackMetadataQuality": {"description": "Metadata field - audio quality"}, - "trackMetadataPath": "File Path", - "@trackMetadataPath": {"description": "Metadata field - file location"}, - "trackMetadataDownloadedAt": "Downloaded", - "@trackMetadataDownloadedAt": {"description": "Metadata field - download date"}, "trackMetadataService": "Service", - "@trackMetadataService": {"description": "Metadata field - download service used"}, + "@trackMetadataService": { + "description": "Metadata field - download service used" + }, "trackMetadataPlay": "Play", - "@trackMetadataPlay": {"description": "Action button - play track"}, + "@trackMetadataPlay": { + "description": "Action button - play track" + }, "trackMetadataShare": "Share", - "@trackMetadataShare": {"description": "Action button - share track"}, + "@trackMetadataShare": { + "description": "Action button - share track" + }, "trackMetadataDelete": "Delete", - "@trackMetadataDelete": {"description": "Action button - delete track"}, - "trackMetadataRedownload": "Re-download", - "@trackMetadataRedownload": {"description": "Action button - download again"}, - "trackMetadataOpenFolder": "Open Folder", - "@trackMetadataOpenFolder": {"description": "Action button - open containing folder"}, - - "setupTitle": "Welcome to SpotiFLAC", - "@setupTitle": {"description": "Setup wizard title"}, - "setupSubtitle": "Let's get you started", - "@setupSubtitle": {"description": "Setup wizard subtitle"}, - "setupStoragePermission": "Storage Permission", - "@setupStoragePermission": {"description": "Storage permission step title"}, - "setupStoragePermissionSubtitle": "Required to save downloaded files", - "@setupStoragePermissionSubtitle": {"description": "Explanation for storage permission"}, - "setupStoragePermissionGranted": "Permission granted", - "@setupStoragePermissionGranted": {"description": "Status when permission granted"}, - "setupStoragePermissionDenied": "Permission denied", - "@setupStoragePermissionDenied": {"description": "Status when permission denied"}, + "@trackMetadataDelete": { + "description": "Action button - delete track" + }, "setupGrantPermission": "Grant Permission", - "@setupGrantPermission": {"description": "Button to request permission"}, - "setupDownloadLocation": "Download Location", - "@setupDownloadLocation": {"description": "Download folder step title"}, - "setupChooseFolder": "Choose Folder", - "@setupChooseFolder": {"description": "Button to pick folder"}, - "setupContinue": "Continue", - "@setupContinue": {"description": "Continue to next step button"}, + "@setupGrantPermission": { + "description": "Button to request permission" + }, "setupSkip": "Skip for now", - "@setupSkip": {"description": "Skip current step button"}, + "@setupSkip": { + "description": "Skip current step button" + }, "setupStorageAccessRequired": "Storage Access Required", - "@setupStorageAccessRequired": {"description": "Title when storage access needed"}, - "setupStorageAccessMessage": "SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.", - "@setupStorageAccessMessage": {"description": "Explanation for storage access"}, + "@setupStorageAccessRequired": { + "description": "Title when storage access needed" + }, "setupStorageAccessMessageAndroid11": "Android 11+ requires \"All files access\" permission to save files to your chosen download folder.", - "@setupStorageAccessMessageAndroid11": {"description": "Android 11+ specific explanation"}, + "@setupStorageAccessMessageAndroid11": { + "description": "Android 11+ specific explanation" + }, "setupOpenSettings": "Open Settings", - "@setupOpenSettings": {"description": "Button to open system settings"}, + "@setupOpenSettings": { + "description": "Button to open system settings" + }, "setupPermissionDeniedMessage": "Permission denied. Please grant all permissions to continue.", - "@setupPermissionDeniedMessage": {"description": "Error when permission denied"}, + "@setupPermissionDeniedMessage": { + "description": "Error when permission denied" + }, "setupPermissionRequired": "{permissionType} Permission Required", "@setupPermissionRequired": { "description": "Generic permission required title", "placeholders": { - "permissionType": {"type": "String", "description": "Type of permission (Storage/Notification)"} + "permissionType": { + "type": "String", + "description": "Type of permission (Storage/Notification)" + } } }, "setupPermissionRequiredMessage": "{permissionType} permission is required for the best experience. You can change this later in Settings.", "@setupPermissionRequiredMessage": { "description": "Generic permission required message", "placeholders": { - "permissionType": {"type": "String"} + "permissionType": { + "type": "String" + } } }, - "setupSelectDownloadFolder": "Select Download Folder", - "@setupSelectDownloadFolder": {"description": "Folder selection step title"}, "setupUseDefaultFolder": "Use Default Folder?", - "@setupUseDefaultFolder": {"description": "Dialog title for default folder"}, + "@setupUseDefaultFolder": { + "description": "Dialog title for default folder" + }, "setupNoFolderSelected": "No folder selected. Would you like to use the default Music folder?", - "@setupNoFolderSelected": {"description": "Prompt when no folder selected"}, + "@setupNoFolderSelected": { + "description": "Prompt when no folder selected" + }, "setupUseDefault": "Use Default", - "@setupUseDefault": {"description": "Button to use default folder"}, + "@setupUseDefault": { + "description": "Button to use default folder" + }, "setupDownloadLocationTitle": "Download Location", - "@setupDownloadLocationTitle": {"description": "Download location dialog title"}, + "@setupDownloadLocationTitle": { + "description": "Download location dialog title" + }, "setupDownloadLocationIosMessage": "On iOS, downloads are saved to the app's Documents folder. You can access them via the Files app.", - "@setupDownloadLocationIosMessage": {"description": "iOS-specific folder info"}, + "@setupDownloadLocationIosMessage": { + "description": "iOS-specific folder info" + }, "setupAppDocumentsFolder": "App Documents Folder", - "@setupAppDocumentsFolder": {"description": "iOS documents folder option"}, + "@setupAppDocumentsFolder": { + "description": "iOS documents folder option" + }, "setupAppDocumentsFolderSubtitle": "Recommended - accessible via Files app", - "@setupAppDocumentsFolderSubtitle": {"description": "Subtitle for documents folder"}, + "@setupAppDocumentsFolderSubtitle": { + "description": "Subtitle for documents folder" + }, "setupChooseFromFiles": "Choose from Files", - "@setupChooseFromFiles": {"description": "iOS file picker option"}, + "@setupChooseFromFiles": { + "description": "iOS file picker option" + }, "setupChooseFromFilesSubtitle": "Select iCloud or other location", - "@setupChooseFromFilesSubtitle": {"description": "Subtitle for file picker"}, -"setupIosEmptyFolderWarning": "iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.", - "@setupIosEmptyFolderWarning": {"description": "iOS folder selection warning"}, + "@setupChooseFromFilesSubtitle": { + "description": "Subtitle for file picker" + }, + "setupIosEmptyFolderWarning": "iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.", + "@setupIosEmptyFolderWarning": { + "description": "iOS folder selection warning" + }, "setupIcloudNotSupported": "iCloud Drive is not supported. Please use the app Documents folder.", - "@setupIcloudNotSupported": {"description": "Error when user selects iCloud Drive on iOS"}, + "@setupIcloudNotSupported": { + "description": "Error when user selects iCloud Drive on iOS" + }, "setupDownloadInFlac": "Download Spotify tracks in FLAC", - "@setupDownloadInFlac": {"description": "App tagline in setup"}, - "setupStepStorage": "Storage", - "@setupStepStorage": {"description": "Setup step indicator - storage"}, - "setupStepNotification": "Notification", - "@setupStepNotification": {"description": "Setup step indicator - notification"}, - "setupStepFolder": "Folder", - "@setupStepFolder": {"description": "Setup step indicator - folder"}, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": {"description": "Setup step indicator - Spotify API"}, - "setupStepPermission": "Permission", - "@setupStepPermission": {"description": "Setup step indicator - permission"}, + "@setupDownloadInFlac": { + "description": "App tagline in setup" + }, "setupStorageGranted": "Storage Permission Granted!", - "@setupStorageGranted": {"description": "Success message for storage permission"}, + "@setupStorageGranted": { + "description": "Success message for storage permission" + }, "setupStorageRequired": "Storage Permission Required", - "@setupStorageRequired": {"description": "Title when storage permission needed"}, + "@setupStorageRequired": { + "description": "Title when storage permission needed" + }, "setupStorageDescription": "SpotiFLAC needs storage permission to save your downloaded music files.", - "@setupStorageDescription": {"description": "Explanation for storage permission"}, + "@setupStorageDescription": { + "description": "Explanation for storage permission" + }, "setupNotificationGranted": "Notification Permission Granted!", - "@setupNotificationGranted": {"description": "Success message for notification permission"}, + "@setupNotificationGranted": { + "description": "Success message for notification permission" + }, "setupNotificationEnable": "Enable Notifications", - "@setupNotificationEnable": {"description": "Button to enable notifications"}, - "setupNotificationDescription": "Get notified when downloads complete or require attention.", - "@setupNotificationDescription": {"description": "Explanation for notifications"}, - "setupFolderSelected": "Download Folder Selected!", - "@setupFolderSelected": {"description": "Success message for folder selection"}, + "@setupNotificationEnable": { + "description": "Button to enable notifications" + }, "setupFolderChoose": "Choose Download Folder", - "@setupFolderChoose": {"description": "Button to choose folder"}, + "@setupFolderChoose": { + "description": "Button to choose folder" + }, "setupFolderDescription": "Select a folder where your downloaded music will be saved.", - "@setupFolderDescription": {"description": "Explanation for folder selection"}, - "setupChangeFolder": "Change Folder", - "@setupChangeFolder": {"description": "Button to change selected folder"}, + "@setupFolderDescription": { + "description": "Explanation for folder selection" + }, "setupSelectFolder": "Select Folder", - "@setupSelectFolder": {"description": "Button to select folder"}, - "setupSpotifyApiOptional": "Spotify API (Optional)", - "@setupSpotifyApiOptional": {"description": "Spotify API step title"}, - "setupSpotifyApiDescription": "Add your Spotify API credentials for better search results and access to Spotify-exclusive content.", - "@setupSpotifyApiDescription": {"description": "Explanation for Spotify API"}, - "setupUseSpotifyApi": "Use Spotify API", - "@setupUseSpotifyApi": {"description": "Toggle to enable Spotify API"}, - "setupEnterCredentialsBelow": "Enter your credentials below", - "@setupEnterCredentialsBelow": {"description": "Prompt to enter credentials"}, - "setupUsingDeezer": "Using Deezer (no account needed)", - "@setupUsingDeezer": {"description": "Status when using Deezer"}, - "setupEnterClientId": "Enter Spotify Client ID", - "@setupEnterClientId": {"description": "Placeholder for client ID field"}, - "setupEnterClientSecret": "Enter Spotify Client Secret", - "@setupEnterClientSecret": {"description": "Placeholder for client secret field"}, - "setupGetFreeCredentials": "Get your free API credentials from the Spotify Developer Dashboard.", - "@setupGetFreeCredentials": {"description": "Info about getting Spotify credentials"}, + "@setupSelectFolder": { + "description": "Button to select folder" + }, "setupEnableNotifications": "Enable Notifications", - "@setupEnableNotifications": {"description": "Button to enable notifications"}, - "setupProceedToNextStep": "You can now proceed to the next step.", - "@setupProceedToNextStep": {"description": "Message after completing a step"}, - "setupNotificationProgressDescription": "You will receive download progress notifications.", - "@setupNotificationProgressDescription": {"description": "Info about notification usage"}, + "@setupEnableNotifications": { + "description": "Button to enable notifications" + }, "setupNotificationBackgroundDescription": "Get notified about download progress and completion. This helps you track downloads when the app is in background.", - "@setupNotificationBackgroundDescription": {"description": "Detailed notification explanation"}, + "@setupNotificationBackgroundDescription": { + "description": "Detailed notification explanation" + }, "setupSkipForNow": "Skip for now", - "@setupSkipForNow": {"description": "Skip button text"}, - "setupBack": "Back", - "@setupBack": {"description": "Back button text"}, + "@setupSkipForNow": { + "description": "Skip button text" + }, "setupNext": "Next", - "@setupNext": {"description": "Next button text"}, + "@setupNext": { + "description": "Next button text" + }, "setupGetStarted": "Get Started", - "@setupGetStarted": {"description": "Final setup button"}, - "setupSkipAndStart": "Skip & Start", - "@setupSkipAndStart": {"description": "Skip setup and start app"}, + "@setupGetStarted": { + "description": "Final setup button" + }, "setupAllowAccessToManageFiles": "Please enable \"Allow access to manage all files\" in the next screen.", - "@setupAllowAccessToManageFiles": {"description": "Instruction for file access permission"}, - "setupGetCredentialsFromSpotify": "Get credentials from developer.spotify.com", - "@setupGetCredentialsFromSpotify": {"description": "Link text for Spotify developer portal"}, - + "@setupAllowAccessToManageFiles": { + "description": "Instruction for file access permission" + }, "dialogCancel": "Cancel", - "@dialogCancel": {"description": "Dialog button - cancel action"}, - "dialogOk": "OK", - "@dialogOk": {"description": "Dialog button - confirm/acknowledge"}, + "@dialogCancel": { + "description": "Dialog button - cancel action" + }, "dialogSave": "Save", - "@dialogSave": {"description": "Dialog button - save changes"}, + "@dialogSave": { + "description": "Dialog button - save changes" + }, "dialogDelete": "Delete", - "@dialogDelete": {"description": "Dialog button - delete item"}, + "@dialogDelete": { + "description": "Dialog button - delete item" + }, "dialogRetry": "Retry", - "@dialogRetry": {"description": "Dialog button - retry action"}, - "dialogClose": "Close", - "@dialogClose": {"description": "Dialog button - close dialog"}, - "dialogYes": "Yes", - "@dialogYes": {"description": "Dialog button - confirm yes"}, - "dialogNo": "No", - "@dialogNo": {"description": "Dialog button - confirm no"}, + "@dialogRetry": { + "description": "Dialog button - retry action" + }, "dialogClear": "Clear", - "@dialogClear": {"description": "Dialog button - clear items"}, - "dialogConfirm": "Confirm", - "@dialogConfirm": {"description": "Dialog button - confirm action"}, + "@dialogClear": { + "description": "Dialog button - clear items" + }, "dialogDone": "Done", - "@dialogDone": {"description": "Dialog button - action completed"}, + "@dialogDone": { + "description": "Dialog button - action completed" + }, "dialogImport": "Import", - "@dialogImport": {"description": "Dialog button - import data"}, + "@dialogImport": { + "description": "Dialog button - import data" + }, "dialogDiscard": "Discard", - "@dialogDiscard": {"description": "Dialog button - discard changes"}, + "@dialogDiscard": { + "description": "Dialog button - discard changes" + }, "dialogRemove": "Remove", - "@dialogRemove": {"description": "Dialog button - remove item"}, + "@dialogRemove": { + "description": "Dialog button - remove item" + }, "dialogUninstall": "Uninstall", - "@dialogUninstall": {"description": "Dialog button - uninstall extension"}, + "@dialogUninstall": { + "description": "Dialog button - uninstall extension" + }, "dialogDiscardChanges": "Discard Changes?", - "@dialogDiscardChanges": {"description": "Dialog title - unsaved changes warning"}, + "@dialogDiscardChanges": { + "description": "Dialog title - unsaved changes warning" + }, "dialogUnsavedChanges": "You have unsaved changes. Do you want to discard them?", - "@dialogUnsavedChanges": {"description": "Dialog message - unsaved changes"}, - "dialogDownloadFailed": "Download Failed", - "@dialogDownloadFailed": {"description": "Dialog title - download error"}, - "dialogTrackLabel": "Track:", - "@dialogTrackLabel": {"description": "Label for track name in error dialog"}, - "dialogArtistLabel": "Artist:", - "@dialogArtistLabel": {"description": "Label for artist name in error dialog"}, - "dialogErrorLabel": "Error:", - "@dialogErrorLabel": {"description": "Label for error message"}, + "@dialogUnsavedChanges": { + "description": "Dialog message - unsaved changes" + }, "dialogClearAll": "Clear All", - "@dialogClearAll": {"description": "Dialog title - clear all items"}, - "dialogClearAllDownloads": "Are you sure you want to clear all downloads?", - "@dialogClearAllDownloads": {"description": "Dialog message - clear downloads confirmation"}, - "dialogRemoveFromDevice": "Remove from device?", - "@dialogRemoveFromDevice": {"description": "Dialog title - delete file confirmation"}, + "@dialogClearAll": { + "description": "Dialog title - clear all items" + }, "dialogRemoveExtension": "Remove Extension", - "@dialogRemoveExtension": {"description": "Dialog title - uninstall extension"}, + "@dialogRemoveExtension": { + "description": "Dialog title - uninstall extension" + }, "dialogRemoveExtensionMessage": "Are you sure you want to remove this extension? This cannot be undone.", - "@dialogRemoveExtensionMessage": {"description": "Dialog message - uninstall confirmation"}, + "@dialogRemoveExtensionMessage": { + "description": "Dialog message - uninstall confirmation" + }, "dialogUninstallExtension": "Uninstall Extension?", - "@dialogUninstallExtension": {"description": "Dialog title - uninstall extension"}, + "@dialogUninstallExtension": { + "description": "Dialog title - uninstall extension" + }, "dialogUninstallExtensionMessage": "Are you sure you want to remove {extensionName}?", "@dialogUninstallExtensionMessage": { "description": "Dialog message - uninstall specific extension", "placeholders": { - "extensionName": {"type": "String"} + "extensionName": { + "type": "String" + } } }, "dialogClearHistoryTitle": "Clear History", - "@dialogClearHistoryTitle": {"description": "Dialog title - clear download history"}, + "@dialogClearHistoryTitle": { + "description": "Dialog title - clear download history" + }, "dialogClearHistoryMessage": "Are you sure you want to clear all download history? This cannot be undone.", - "@dialogClearHistoryMessage": {"description": "Dialog message - clear history confirmation"}, + "@dialogClearHistoryMessage": { + "description": "Dialog message - clear history confirmation" + }, "dialogDeleteSelectedTitle": "Delete Selected", - "@dialogDeleteSelectedTitle": {"description": "Dialog title - delete selected items"}, + "@dialogDeleteSelectedTitle": { + "description": "Dialog title - delete selected items" + }, "dialogDeleteSelectedMessage": "Delete {count} {count, plural, =1{track} other{tracks}} from history?\n\nThis will also delete the files from storage.", "@dialogDeleteSelectedMessage": { "description": "Dialog message - delete selected tracks", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "dialogImportPlaylistTitle": "Import Playlist", - "@dialogImportPlaylistTitle": {"description": "Dialog title - import CSV playlist"}, + "@dialogImportPlaylistTitle": { + "description": "Dialog title - import CSV playlist" + }, "dialogImportPlaylistMessage": "Found {count} tracks in CSV. Add them to download queue?", "csvImportTracks": "{count} tracks from CSV", "@csvImportTracks": { "description": "Label shown in quality picker for CSV import", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "@dialogImportPlaylistMessage": { "description": "Dialog message - import playlist confirmation", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, - "snackbarAddedToQueue": "Added \"{trackName}\" to queue", "@snackbarAddedToQueue": { "description": "Snackbar - track added to download queue", "placeholders": { - "trackName": {"type": "String"} + "trackName": { + "type": "String" + } } }, "snackbarAddedTracksToQueue": "Added {count} tracks to queue", "@snackbarAddedTracksToQueue": { "description": "Snackbar - multiple tracks added to queue", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "snackbarAlreadyDownloaded": "\"{trackName}\" already downloaded", "@snackbarAlreadyDownloaded": { "description": "Snackbar - track already exists", "placeholders": { - "trackName": {"type": "String"} + "trackName": { + "type": "String" + } } }, "snackbarAlreadyInLibrary": "\"{trackName}\" already exists in your library", "@snackbarAlreadyInLibrary": { "description": "Snackbar - track already exists in local library", "placeholders": { - "trackName": {"type": "String"} + "trackName": { + "type": "String" + } } }, "snackbarHistoryCleared": "History cleared", - "@snackbarHistoryCleared": {"description": "Snackbar - history deleted"}, + "@snackbarHistoryCleared": { + "description": "Snackbar - history deleted" + }, "snackbarCredentialsSaved": "Credentials saved", - "@snackbarCredentialsSaved": {"description": "Snackbar - Spotify credentials saved"}, + "@snackbarCredentialsSaved": { + "description": "Snackbar - Spotify credentials saved" + }, "snackbarCredentialsCleared": "Credentials cleared", - "@snackbarCredentialsCleared": {"description": "Snackbar - Spotify credentials removed"}, + "@snackbarCredentialsCleared": { + "description": "Snackbar - Spotify credentials removed" + }, "snackbarDeletedTracks": "Deleted {count} {count, plural, =1{track} other{tracks}}", "@snackbarDeletedTracks": { "description": "Snackbar - tracks deleted", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "snackbarCannotOpenFile": "Cannot open file: {error}", "@snackbarCannotOpenFile": { "description": "Snackbar - file open error", "placeholders": { - "error": {"type": "String"} + "error": { + "type": "String" + } } }, "snackbarFillAllFields": "Please fill all fields", - "@snackbarFillAllFields": {"description": "Snackbar - validation error"}, + "@snackbarFillAllFields": { + "description": "Snackbar - validation error" + }, "snackbarViewQueue": "View Queue", - "@snackbarViewQueue": {"description": "Snackbar action - view download queue"}, - "snackbarFailedToLoad": "Failed to load: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": {"type": "String"} - } + "@snackbarViewQueue": { + "description": "Snackbar action - view download queue" }, "snackbarUrlCopied": "{platform} URL copied to clipboard", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", "placeholders": { - "platform": {"type": "String", "description": "Platform name (Spotify/Deezer)"} + "platform": { + "type": "String", + "description": "Platform name (Spotify/Deezer)" + } } }, "snackbarFileNotFound": "File not found", - "@snackbarFileNotFound": {"description": "Snackbar - file doesn't exist"}, + "@snackbarFileNotFound": { + "description": "Snackbar - file doesn't exist" + }, "snackbarSelectExtFile": "Please select a .spotiflac-ext file", - "@snackbarSelectExtFile": {"description": "Snackbar - wrong file type selected"}, + "@snackbarSelectExtFile": { + "description": "Snackbar - wrong file type selected" + }, "snackbarProviderPrioritySaved": "Provider priority saved", - "@snackbarProviderPrioritySaved": {"description": "Snackbar - provider order saved"}, + "@snackbarProviderPrioritySaved": { + "description": "Snackbar - provider order saved" + }, "snackbarMetadataProviderSaved": "Metadata provider priority saved", - "@snackbarMetadataProviderSaved": {"description": "Snackbar - metadata provider order saved"}, + "@snackbarMetadataProviderSaved": { + "description": "Snackbar - metadata provider order saved" + }, "snackbarExtensionInstalled": "{extensionName} installed.", "@snackbarExtensionInstalled": { "description": "Snackbar - extension installed successfully", "placeholders": { - "extensionName": {"type": "String"} + "extensionName": { + "type": "String" + } } }, "snackbarExtensionUpdated": "{extensionName} updated.", "@snackbarExtensionUpdated": { "description": "Snackbar - extension updated successfully", "placeholders": { - "extensionName": {"type": "String"} + "extensionName": { + "type": "String" + } } }, "snackbarFailedToInstall": "Failed to install extension", - "@snackbarFailedToInstall": {"description": "Snackbar - extension install error"}, + "@snackbarFailedToInstall": { + "description": "Snackbar - extension install error" + }, "snackbarFailedToUpdate": "Failed to update extension", - "@snackbarFailedToUpdate": {"description": "Snackbar - extension update error"}, - + "@snackbarFailedToUpdate": { + "description": "Snackbar - extension update error" + }, "errorRateLimited": "Rate Limited", - "@errorRateLimited": {"description": "Error title - too many requests"}, + "@errorRateLimited": { + "description": "Error title - too many requests" + }, "errorRateLimitedMessage": "Too many requests. Please wait a moment before searching again.", - "@errorRateLimitedMessage": {"description": "Error message - rate limit explanation"}, - "errorFailedToLoad": "Failed to load {item}", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": {"type": "String", "description": "Item that failed to load (album/playlist/etc)"} - } + "@errorRateLimitedMessage": { + "description": "Error message - rate limit explanation" }, "errorNoTracksFound": "No tracks found", - "@errorNoTracksFound": {"description": "Error - search returned no results"}, - "errorSeekNotSupported": "Seeking is not supported for this live stream", - "@errorSeekNotSupported": {"description": "Error - seek disabled for live decrypted stream"}, + "@errorNoTracksFound": { + "description": "Error - search returned no results" + }, "errorMissingExtensionSource": "Cannot load {item}: missing extension source", "@errorMissingExtensionSource": { "description": "Error - extension source not available", "placeholders": { - "item": {"type": "String"} + "item": { + "type": "String" + } } }, - - "statusQueued": "Queued", - "@statusQueued": {"description": "Download status - waiting in queue"}, - "statusDownloading": "Downloading", - "@statusDownloading": {"description": "Download status - in progress"}, - "statusFinalizing": "Finalizing", - "@statusFinalizing": {"description": "Download status - writing metadata"}, - "statusCompleted": "Completed", - "@statusCompleted": {"description": "Download status - finished"}, - "statusFailed": "Failed", - "@statusFailed": {"description": "Download status - error occurred"}, - "statusSkipped": "Skipped", - "@statusSkipped": {"description": "Download status - already exists"}, - "statusPaused": "Paused", - "@statusPaused": {"description": "Download status - paused"}, - "actionPause": "Pause", - "@actionPause": {"description": "Action button - pause download"}, + "@actionPause": { + "description": "Action button - pause download" + }, "actionResume": "Resume", - "@actionResume": {"description": "Action button - resume download"}, + "@actionResume": { + "description": "Action button - resume download" + }, "actionCancel": "Cancel", - "@actionCancel": {"description": "Action button - cancel operation"}, - "actionStop": "Stop", - "@actionStop": {"description": "Action button - stop operation"}, - "actionSelect": "Select", - "@actionSelect": {"description": "Action button - enter selection mode"}, + "@actionCancel": { + "description": "Action button - cancel operation" + }, "actionSelectAll": "Select All", - "@actionSelectAll": {"description": "Action button - select all items"}, + "@actionSelectAll": { + "description": "Action button - select all items" + }, "actionDeselect": "Deselect", - "@actionDeselect": {"description": "Action button - deselect all"}, - "actionPaste": "Paste", - "@actionPaste": {"description": "Action button - paste from clipboard"}, - "actionImportCsv": "Import CSV", - "@actionImportCsv": {"description": "Action button - import CSV file"}, + "@actionDeselect": { + "description": "Action button - deselect all" + }, "actionRemoveCredentials": "Remove Credentials", - "@actionRemoveCredentials": {"description": "Action button - delete Spotify credentials"}, + "@actionRemoveCredentials": { + "description": "Action button - delete Spotify credentials" + }, "actionSaveCredentials": "Save Credentials", - "@actionSaveCredentials": {"description": "Action button - save Spotify credentials"}, - + "@actionSaveCredentials": { + "description": "Action button - save Spotify credentials" + }, "selectionSelected": "{count} selected", "@selectionSelected": { "description": "Selection count indicator", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "selectionAllSelected": "All tracks selected", - "@selectionAllSelected": {"description": "Status - all items selected"}, - "selectionTapToSelect": "Tap tracks to select", - "@selectionTapToSelect": {"description": "Hint - how to select items"}, - "selectionDeleteTracks": "Delete {count} {count, plural, =1{track} other{tracks}}", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": {"type": "int"} - } + "@selectionAllSelected": { + "description": "Status - all items selected" }, "selectionSelectToDelete": "Select tracks to delete", - "@selectionSelectToDelete": {"description": "Placeholder when nothing selected"}, - + "@selectionSelectToDelete": { + "description": "Placeholder when nothing selected" + }, "progressFetchingMetadata": "Fetching metadata... {current}/{total}", "@progressFetchingMetadata": { "description": "Progress indicator - loading track info", "placeholders": { - "current": {"type": "int"}, - "total": {"type": "int"} + "current": { + "type": "int" + }, + "total": { + "type": "int" + } } }, "progressReadingCsv": "Reading CSV...", - "@progressReadingCsv": {"description": "Progress indicator - parsing CSV file"}, - + "@progressReadingCsv": { + "description": "Progress indicator - parsing CSV file" + }, "searchSongs": "Songs", - "@searchSongs": {"description": "Search result category - songs"}, + "@searchSongs": { + "description": "Search result category - songs" + }, "searchArtists": "Artists", - "@searchArtists": {"description": "Search result category - artists"}, + "@searchArtists": { + "description": "Search result category - artists" + }, "searchAlbums": "Albums", - "@searchAlbums": {"description": "Search result category - albums"}, + "@searchAlbums": { + "description": "Search result category - albums" + }, "searchPlaylists": "Playlists", - "@searchPlaylists": {"description": "Search result category - playlists"}, - + "@searchPlaylists": { + "description": "Search result category - playlists" + }, "tooltipPlay": "Play", - "@tooltipPlay": {"description": "Tooltip - play button"}, - "tooltipCancel": "Cancel", - "@tooltipCancel": {"description": "Tooltip - cancel button"}, - "tooltipStop": "Stop", - "@tooltipStop": {"description": "Tooltip - stop button"}, - "tooltipRetry": "Retry", - "@tooltipRetry": {"description": "Tooltip - retry button"}, - "tooltipRemove": "Remove", - "@tooltipRemove": {"description": "Tooltip - remove button"}, - "tooltipClear": "Clear", - "@tooltipClear": {"description": "Tooltip - clear button"}, - "tooltipPaste": "Paste", - "@tooltipPaste": {"description": "Tooltip - paste button"}, - + "@tooltipPlay": { + "description": "Tooltip - play button" + }, "filenameFormat": "Filename Format", - "@filenameFormat": {"description": "Setting title - filename pattern"}, - "filenameFormatPreview": "Preview: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": {"type": "String"} - } + "@filenameFormat": { + "description": "Setting title - filename pattern" }, - "filenameAvailablePlaceholders": "Available placeholders:", - "@filenameAvailablePlaceholders": {"description": "Label for placeholder list"}, - "filenameHint": "{artist} - {title}", - "@filenameHint": {"description": "Default filename format hint"}, "filenameShowAdvancedTags": "Show advanced tags", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -900,1600 +999,2107 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - - "folderOrganization": "Folder Organization", - "@folderOrganization": {"description": "Setting title - folder structure"}, "folderOrganizationNone": "No organization", - "@folderOrganizationNone": {"description": "Folder option - flat structure"}, + "@folderOrganizationNone": { + "description": "Folder option - flat structure" + }, "folderOrganizationByArtist": "By Artist", - "@folderOrganizationByArtist": {"description": "Folder option - artist folders"}, + "@folderOrganizationByArtist": { + "description": "Folder option - artist folders" + }, "folderOrganizationByAlbum": "By Album", - "@folderOrganizationByAlbum": {"description": "Folder option - album folders"}, + "@folderOrganizationByAlbum": { + "description": "Folder option - album folders" + }, "folderOrganizationByArtistAlbum": "Artist/Album", - "@folderOrganizationByArtistAlbum": {"description": "Folder option - nested folders"}, + "@folderOrganizationByArtistAlbum": { + "description": "Folder option - nested folders" + }, "folderOrganizationDescription": "Organize downloaded files into folders", - "@folderOrganizationDescription": {"description": "Folder organization sheet description"}, + "@folderOrganizationDescription": { + "description": "Folder organization sheet description" + }, "folderOrganizationNoneSubtitle": "All files in download folder", - "@folderOrganizationNoneSubtitle": {"description": "Subtitle for no organization option"}, + "@folderOrganizationNoneSubtitle": { + "description": "Subtitle for no organization option" + }, "folderOrganizationByArtistSubtitle": "Separate folder for each artist", - "@folderOrganizationByArtistSubtitle": {"description": "Subtitle for artist folder option"}, + "@folderOrganizationByArtistSubtitle": { + "description": "Subtitle for artist folder option" + }, "folderOrganizationByAlbumSubtitle": "Separate folder for each album", - "@folderOrganizationByAlbumSubtitle": {"description": "Subtitle for album folder option"}, + "@folderOrganizationByAlbumSubtitle": { + "description": "Subtitle for album folder option" + }, "folderOrganizationByArtistAlbumSubtitle": "Nested folders for artist and album", - "@folderOrganizationByArtistAlbumSubtitle": {"description": "Subtitle for nested folder option"}, - + "@folderOrganizationByArtistAlbumSubtitle": { + "description": "Subtitle for nested folder option" + }, "updateAvailable": "Update Available", - "@updateAvailable": {"description": "Update dialog title"}, - "updateNewVersion": "Version {version} is available", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": {"type": "String"} - } + "@updateAvailable": { + "description": "Update dialog title" }, - "updateDownload": "Download", - "@updateDownload": {"description": "Update button - download update"}, "updateLater": "Later", - "@updateLater": {"description": "Update button - dismiss"}, - "updateChangelog": "Changelog", - "@updateChangelog": {"description": "Link to changelog"}, - "updateStartingDownload": "Starting download...", - "@updateStartingDownload": {"description": "Update status - initializing"}, - "updateDownloadFailed": "Download failed", - "@updateDownloadFailed": {"description": "Update error title"}, - "updateFailedMessage": "Failed to download update", - "@updateFailedMessage": {"description": "Update error message"}, - "updateNewVersionReady": "A new version is ready", - "@updateNewVersionReady": {"description": "Update subtitle"}, - "updateCurrent": "Current", - "@updateCurrent": {"description": "Label for current version"}, - "updateNew": "New", - "@updateNew": {"description": "Label for new version"}, - "updateDownloading": "Downloading...", - "@updateDownloading": {"description": "Update status - downloading"}, - "updateWhatsNew": "What's New", - "@updateWhatsNew": {"description": "Changelog section title"}, - "updateDownloadInstall": "Download & Install", - "@updateDownloadInstall": {"description": "Update button - download and install"}, - "updateDontRemind": "Don't remind", - "@updateDontRemind": {"description": "Update button - skip this version"}, - - "providerPriority": "Provider Priority", - "@providerPriority": {"description": "Setting title - download provider order"}, - "providerPrioritySubtitle": "Drag to reorder download providers", - "@providerPrioritySubtitle": {"description": "Subtitle for provider priority"}, - "providerPriorityTitle": "Provider Priority", - "@providerPriorityTitle": {"description": "Provider priority page title"}, - "providerPriorityDescription": "Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.", - "@providerPriorityDescription": {"description": "Provider priority page description"}, - "providerPriorityInfo": "If a track is not available on the first provider, the app will automatically try the next one.", - "@providerPriorityInfo": {"description": "Info tip about fallback behavior"}, - "providerBuiltIn": "Built-in", - "@providerBuiltIn": {"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"}, - "providerExtension": "Extension", - "@providerExtension": {"description": "Label for extension-provided providers"}, - - "metadataProviderPriority": "Metadata Provider Priority", - "@metadataProviderPriority": {"description": "Setting title - metadata provider order"}, - "metadataProviderPrioritySubtitle": "Order used when fetching track metadata", - "@metadataProviderPrioritySubtitle": {"description": "Subtitle for metadata priority"}, - "metadataProviderPriorityTitle": "Metadata Priority", - "@metadataProviderPriorityTitle": {"description": "Metadata priority page title"}, - "metadataProviderPriorityDescription": "Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.", - "@metadataProviderPriorityDescription": {"description": "Metadata priority page description"}, - "metadataProviderPriorityInfo": "Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.", - "@metadataProviderPriorityInfo": {"description": "Info tip about rate limits"}, - "metadataNoRateLimits": "No rate limits", - "@metadataNoRateLimits": {"description": "Deezer provider description"}, - "metadataMayRateLimit": "May rate limit", - "@metadataMayRateLimit": {"description": "Spotify provider description"}, - - "logTitle": "Logs", - "@logTitle": {"description": "Logs screen title"}, - "logCopy": "Copy Logs", - "@logCopy": {"description": "Action - copy logs to clipboard"}, - "logClear": "Clear Logs", - "@logClear": {"description": "Action - delete all logs"}, - "logShare": "Share Logs", - "@logShare": {"description": "Action - share logs file"}, - "logEmpty": "No logs yet", - "@logEmpty": {"description": "Empty state title"}, - "logCopied": "Logs copied to clipboard", - "@logCopied": {"description": "Snackbar - logs copied"}, - "logSearchHint": "Search logs...", - "@logSearchHint": {"description": "Log search placeholder"}, - "logFilterLevel": "Level", - "@logFilterLevel": {"description": "Filter by log level"}, - "logFilterSection": "Filter", - "@logFilterSection": {"description": "Filter section title"}, - "logShareLogs": "Share logs", - "@logShareLogs": {"description": "Share button tooltip"}, - "logClearLogs": "Clear logs", - "@logClearLogs": {"description": "Clear button tooltip"}, - "logClearLogsTitle": "Clear Logs", - "@logClearLogsTitle": {"description": "Clear logs dialog title"}, - "logClearLogsMessage": "Are you sure you want to clear all logs?", - "@logClearLogsMessage": {"description": "Clear logs confirmation message"}, - "logIspBlocking": "ISP BLOCKING DETECTED", - "@logIspBlocking": {"description": "Error category - ISP blocking"}, - "logRateLimited": "RATE LIMITED", - "@logRateLimited": {"description": "Error category - rate limiting"}, - "logNetworkError": "NETWORK ERROR", - "@logNetworkError": {"description": "Error category - network issues"}, - "logTrackNotFound": "TRACK NOT FOUND", - "@logTrackNotFound": {"description": "Error category - missing tracks"}, - "logFilterBySeverity": "Filter logs by severity", - "@logFilterBySeverity": {"description": "Filter dialog title"}, - "logNoLogsYet": "No logs yet", - "@logNoLogsYet": {"description": "Empty state title"}, - "logNoLogsYetSubtitle": "Logs will appear here as you use the app", - "@logNoLogsYetSubtitle": {"description": "Empty state subtitle"}, - "logIssueSummary": "Issue Summary", - "@logIssueSummary": {"description": "Section header for error summary"}, - "logIspBlockingDescription": "Your ISP may be blocking access to download services", - "@logIspBlockingDescription": {"description": "ISP blocking explanation"}, - "logIspBlockingSuggestion": "Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8", - "@logIspBlockingSuggestion": {"description": "ISP blocking fix suggestion"}, - "logRateLimitedDescription": "Too many requests to the service", - "@logRateLimitedDescription": {"description": "Rate limit explanation"}, - "logRateLimitedSuggestion": "Wait a few minutes before trying again", - "@logRateLimitedSuggestion": {"description": "Rate limit fix suggestion"}, - "logNetworkErrorDescription": "Connection issues detected", - "@logNetworkErrorDescription": {"description": "Network error explanation"}, - "logNetworkErrorSuggestion": "Check your internet connection", - "@logNetworkErrorSuggestion": {"description": "Network error fix suggestion"}, - "logTrackNotFoundDescription": "Some tracks could not be found on download services", - "@logTrackNotFoundDescription": {"description": "Track not found explanation"}, - "logTrackNotFoundSuggestion": "The track may not be available in lossless quality", - "@logTrackNotFoundSuggestion": {"description": "Track not found explanation"}, - "logTotalErrors": "Total errors: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": {"type": "int"} - } + "@updateLater": { + "description": "Update button - dismiss" }, - "logAffected": "Affected: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": {"type": "String"} - } + "updateStartingDownload": "Starting download...", + "@updateStartingDownload": { + "description": "Update status - initializing" + }, + "updateDownloadFailed": "Download failed", + "@updateDownloadFailed": { + "description": "Update error title" + }, + "updateFailedMessage": "Failed to download update", + "@updateFailedMessage": { + "description": "Update error message" + }, + "updateNewVersionReady": "A new version is ready", + "@updateNewVersionReady": { + "description": "Update subtitle" + }, + "updateCurrent": "Current", + "@updateCurrent": { + "description": "Label for current version" + }, + "updateNew": "New", + "@updateNew": { + "description": "Label for new version" + }, + "updateDownloading": "Downloading...", + "@updateDownloading": { + "description": "Update status - downloading" + }, + "updateWhatsNew": "What's New", + "@updateWhatsNew": { + "description": "Changelog section title" + }, + "updateDownloadInstall": "Download & Install", + "@updateDownloadInstall": { + "description": "Update button - download and install" + }, + "updateDontRemind": "Don't remind", + "@updateDontRemind": { + "description": "Update button - skip this version" + }, + "providerPriorityTitle": "Provider Priority", + "@providerPriorityTitle": { + "description": "Provider priority page title" + }, + "providerPriorityDescription": "Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.", + "@providerPriorityDescription": { + "description": "Provider priority page description" + }, + "providerPriorityInfo": "If a track is not available on the first provider, the app will automatically try the next one.", + "@providerPriorityInfo": { + "description": "Info tip about fallback behavior" + }, + "providerBuiltIn": "Built-in", + "@providerBuiltIn": { + "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + }, + "providerExtension": "Extension", + "@providerExtension": { + "description": "Label for extension-provided providers" + }, + "metadataProviderPriorityTitle": "Metadata Priority", + "@metadataProviderPriorityTitle": { + "description": "Metadata priority page title" + }, + "metadataProviderPriorityDescription": "Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.", + "@metadataProviderPriorityDescription": { + "description": "Metadata priority page description" + }, + "metadataProviderPriorityInfo": "Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.", + "@metadataProviderPriorityInfo": { + "description": "Info tip about rate limits" + }, + "metadataNoRateLimits": "No rate limits", + "@metadataNoRateLimits": { + "description": "Deezer provider description" + }, + "metadataMayRateLimit": "May rate limit", + "@metadataMayRateLimit": { + "description": "Spotify provider description" + }, + "logTitle": "Logs", + "@logTitle": { + "description": "Logs screen title" + }, + "logCopied": "Logs copied to clipboard", + "@logCopied": { + "description": "Snackbar - logs copied" + }, + "logSearchHint": "Search logs...", + "@logSearchHint": { + "description": "Log search placeholder" + }, + "logFilterLevel": "Level", + "@logFilterLevel": { + "description": "Filter by log level" + }, + "logFilterSection": "Filter", + "@logFilterSection": { + "description": "Filter section title" + }, + "logShareLogs": "Share logs", + "@logShareLogs": { + "description": "Share button tooltip" + }, + "logClearLogs": "Clear logs", + "@logClearLogs": { + "description": "Clear button tooltip" + }, + "logClearLogsTitle": "Clear Logs", + "@logClearLogsTitle": { + "description": "Clear logs dialog title" + }, + "logClearLogsMessage": "Are you sure you want to clear all logs?", + "@logClearLogsMessage": { + "description": "Clear logs confirmation message" + }, + "logFilterBySeverity": "Filter logs by severity", + "@logFilterBySeverity": { + "description": "Filter dialog title" + }, + "logNoLogsYet": "No logs yet", + "@logNoLogsYet": { + "description": "Empty state title" + }, + "logNoLogsYetSubtitle": "Logs will appear here as you use the app", + "@logNoLogsYetSubtitle": { + "description": "Empty state subtitle" }, "logEntriesFiltered": "Entries ({count} filtered)", "@logEntriesFiltered": { "description": "Log count with filter active", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "logEntries": "Entries ({count})", "@logEntries": { "description": "Total log count", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, - "credentialsTitle": "Spotify Credentials", - "@credentialsTitle": {"description": "Credentials dialog title"}, + "@credentialsTitle": { + "description": "Credentials dialog title" + }, "credentialsDescription": "Enter your Client ID and Secret to use your own Spotify application quota.", - "@credentialsDescription": {"description": "Credentials dialog explanation"}, + "@credentialsDescription": { + "description": "Credentials dialog explanation" + }, "credentialsClientId": "Client ID", - "@credentialsClientId": {"description": "Client ID field label - DO NOT TRANSLATE"}, + "@credentialsClientId": { + "description": "Client ID field label - DO NOT TRANSLATE" + }, "credentialsClientIdHint": "Paste Client ID", - "@credentialsClientIdHint": {"description": "Client ID placeholder"}, + "@credentialsClientIdHint": { + "description": "Client ID placeholder" + }, "credentialsClientSecret": "Client Secret", - "@credentialsClientSecret": {"description": "Client Secret field label - DO NOT TRANSLATE"}, + "@credentialsClientSecret": { + "description": "Client Secret field label - DO NOT TRANSLATE" + }, "credentialsClientSecretHint": "Paste Client Secret", - "@credentialsClientSecretHint": {"description": "Client Secret placeholder"}, - + "@credentialsClientSecretHint": { + "description": "Client Secret placeholder" + }, "channelStable": "Stable", - "@channelStable": {"description": "Update channel - stable releases"}, + "@channelStable": { + "description": "Update channel - stable releases" + }, "channelPreview": "Preview", - "@channelPreview": {"description": "Update channel - beta/preview releases"}, - + "@channelPreview": { + "description": "Update channel - beta/preview releases" + }, "sectionSearchSource": "Search Source", - "@sectionSearchSource": {"description": "Settings section header"}, + "@sectionSearchSource": { + "description": "Settings section header" + }, "sectionDownload": "Download", - "@sectionDownload": {"description": "Settings section header"}, + "@sectionDownload": { + "description": "Settings section header" + }, "sectionPerformance": "Performance", - "@sectionPerformance": {"description": "Settings section header"}, + "@sectionPerformance": { + "description": "Settings section header" + }, "sectionApp": "App", - "@sectionApp": {"description": "Settings section header"}, + "@sectionApp": { + "description": "Settings section header" + }, "sectionData": "Data", - "@sectionData": {"description": "Settings section header"}, + "@sectionData": { + "description": "Settings section header" + }, "sectionDebug": "Debug", - "@sectionDebug": {"description": "Settings section header"}, + "@sectionDebug": { + "description": "Settings section header" + }, "sectionService": "Service", - "@sectionService": {"description": "Settings section header"}, + "@sectionService": { + "description": "Settings section header" + }, "sectionAudioQuality": "Audio Quality", - "@sectionAudioQuality": {"description": "Settings section header"}, + "@sectionAudioQuality": { + "description": "Settings section header" + }, "sectionFileSettings": "File Settings", - "@sectionFileSettings": {"description": "Settings section header"}, + "@sectionFileSettings": { + "description": "Settings section header" + }, "sectionLyrics": "Lyrics", - "@sectionLyrics": {"description": "Settings section header"}, - + "@sectionLyrics": { + "description": "Settings section header" + }, "lyricsMode": "Lyrics Mode", - "@lyricsMode": {"description": "Setting - how to save lyrics"}, + "@lyricsMode": { + "description": "Setting - how to save lyrics" + }, "lyricsModeDescription": "Choose how lyrics are saved with your downloads", - "@lyricsModeDescription": {"description": "Lyrics mode picker description"}, + "@lyricsModeDescription": { + "description": "Lyrics mode picker description" + }, "lyricsModeEmbed": "Embed in file", - "@lyricsModeEmbed": {"description": "Lyrics mode option - embed in audio file"}, + "@lyricsModeEmbed": { + "description": "Lyrics mode option - embed in audio file" + }, "lyricsModeEmbedSubtitle": "Lyrics stored inside FLAC metadata", - "@lyricsModeEmbedSubtitle": {"description": "Subtitle for embed option"}, + "@lyricsModeEmbedSubtitle": { + "description": "Subtitle for embed option" + }, "lyricsModeExternal": "External .lrc file", - "@lyricsModeExternal": {"description": "Lyrics mode option - separate LRC file"}, + "@lyricsModeExternal": { + "description": "Lyrics mode option - separate LRC file" + }, "lyricsModeExternalSubtitle": "Separate .lrc file for players like Samsung Music", - "@lyricsModeExternalSubtitle": {"description": "Subtitle for external option"}, + "@lyricsModeExternalSubtitle": { + "description": "Subtitle for external option" + }, "lyricsModeBoth": "Both", - "@lyricsModeBoth": {"description": "Lyrics mode option - embed and external"}, + "@lyricsModeBoth": { + "description": "Lyrics mode option - embed and external" + }, "lyricsModeBothSubtitle": "Embed and save .lrc file", - "@lyricsModeBothSubtitle": {"description": "Subtitle for both option"}, - + "@lyricsModeBothSubtitle": { + "description": "Subtitle for both option" + }, "sectionColor": "Color", - "@sectionColor": {"description": "Settings section header"}, + "@sectionColor": { + "description": "Settings section header" + }, "sectionTheme": "Theme", - "@sectionTheme": {"description": "Settings section header"}, + "@sectionTheme": { + "description": "Settings section header" + }, "sectionLayout": "Layout", -"@sectionLayout": {"description": "Settings section header"}, + "@sectionLayout": { + "description": "Settings section header" + }, "sectionLanguage": "Language", - "@sectionLanguage": {"description": "Settings section header for language"}, + "@sectionLanguage": { + "description": "Settings section header for language" + }, "appearanceLanguage": "App Language", - "@appearanceLanguage": {"description": "Language setting title"}, - "appearanceLanguageSubtitle": "Choose your preferred language", - "@appearanceLanguageSubtitle": {"description": "Language setting subtitle"}, - + "@appearanceLanguage": { + "description": "Language setting title" + }, "settingsAppearanceSubtitle": "Theme, colors, display", - "@settingsAppearanceSubtitle": {"description": "Appearance settings description"}, + "@settingsAppearanceSubtitle": { + "description": "Appearance settings description" + }, "settingsDownloadSubtitle": "Service, quality, filename format", - "@settingsDownloadSubtitle": {"description": "Download settings description"}, + "@settingsDownloadSubtitle": { + "description": "Download settings description" + }, "settingsOptionsSubtitle": "Fallback, lyrics, cover art, updates", - "@settingsOptionsSubtitle": {"description": "Options settings description"}, + "@settingsOptionsSubtitle": { + "description": "Options settings description" + }, "settingsExtensionsSubtitle": "Manage download providers", - "@settingsExtensionsSubtitle": {"description": "Extensions settings description"}, + "@settingsExtensionsSubtitle": { + "description": "Extensions settings description" + }, "settingsLogsSubtitle": "View app logs for debugging", - "@settingsLogsSubtitle": {"description": "Logs settings description"}, - + "@settingsLogsSubtitle": { + "description": "Logs settings description" + }, "loadingSharedLink": "Loading shared link...", - "@loadingSharedLink": {"description": "Status when opening shared URL"}, + "@loadingSharedLink": { + "description": "Status when opening shared URL" + }, "pressBackAgainToExit": "Press back again to exit", - "@pressBackAgainToExit": {"description": "Exit confirmation message"}, - - "tracksHeader": "Tracks", - "@tracksHeader": {"description": "Section header for track list"}, + "@pressBackAgainToExit": { + "description": "Exit confirmation message" + }, "downloadAllCount": "Download All ({count})", "@downloadAllCount": { "description": "Download all button with count", "placeholders": { - "count": {"type": "int"} - } - }, - "playAllCount": "Play All ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, - "trackCopyFilePath": "Copy file path", - "@trackCopyFilePath": {"description": "Action - copy file path"}, + "@trackCopyFilePath": { + "description": "Action - copy file path" + }, "trackRemoveFromDevice": "Remove from device", - "@trackRemoveFromDevice": {"description": "Action - delete downloaded file"}, + "@trackRemoveFromDevice": { + "description": "Action - delete downloaded file" + }, "trackLoadLyrics": "Load Lyrics", - "@trackLoadLyrics": {"description": "Action - fetch lyrics"}, + "@trackLoadLyrics": { + "description": "Action - fetch lyrics" + }, "trackMetadata": "Metadata", - "@trackMetadata": {"description": "Tab title - track metadata"}, + "@trackMetadata": { + "description": "Tab title - track metadata" + }, "trackFileInfo": "File Info", - "@trackFileInfo": {"description": "Tab title - file information"}, + "@trackFileInfo": { + "description": "Tab title - file information" + }, "trackLyrics": "Lyrics", - "@trackLyrics": {"description": "Tab title - lyrics"}, + "@trackLyrics": { + "description": "Tab title - lyrics" + }, "trackFileNotFound": "File not found", - "@trackFileNotFound": {"description": "Error - file doesn't exist"}, + "@trackFileNotFound": { + "description": "Error - file doesn't exist" + }, "trackOpenInDeezer": "Open in Deezer", - "@trackOpenInDeezer": {"description": "Action - open track in Deezer app"}, + "@trackOpenInDeezer": { + "description": "Action - open track in Deezer app" + }, "trackOpenInSpotify": "Open in Spotify", - "@trackOpenInSpotify": {"description": "Action - open track in Spotify app"}, + "@trackOpenInSpotify": { + "description": "Action - open track in Spotify app" + }, "trackTrackName": "Track name", - "@trackTrackName": {"description": "Metadata label - track title"}, + "@trackTrackName": { + "description": "Metadata label - track title" + }, "trackArtist": "Artist", - "@trackArtist": {"description": "Metadata label - artist name"}, + "@trackArtist": { + "description": "Metadata label - artist name" + }, "trackAlbumArtist": "Album artist", - "@trackAlbumArtist": {"description": "Metadata label - album artist"}, + "@trackAlbumArtist": { + "description": "Metadata label - album artist" + }, "trackAlbum": "Album", - "@trackAlbum": {"description": "Metadata label - album name"}, + "@trackAlbum": { + "description": "Metadata label - album name" + }, "trackTrackNumber": "Track number", - "@trackTrackNumber": {"description": "Metadata label - track number"}, + "@trackTrackNumber": { + "description": "Metadata label - track number" + }, "trackDiscNumber": "Disc number", - "@trackDiscNumber": {"description": "Metadata label - disc number"}, + "@trackDiscNumber": { + "description": "Metadata label - disc number" + }, "trackDuration": "Duration", - "@trackDuration": {"description": "Metadata label - track length"}, + "@trackDuration": { + "description": "Metadata label - track length" + }, "trackAudioQuality": "Audio quality", - "@trackAudioQuality": {"description": "Metadata label - audio quality"}, + "@trackAudioQuality": { + "description": "Metadata label - audio quality" + }, "trackReleaseDate": "Release date", - "@trackReleaseDate": {"description": "Metadata label - release date"}, + "@trackReleaseDate": { + "description": "Metadata label - release date" + }, "trackGenre": "Genre", - "@trackGenre": {"description": "Metadata label - music genre"}, + "@trackGenre": { + "description": "Metadata label - music genre" + }, "trackLabel": "Label", - "@trackLabel": {"description": "Metadata label - record label"}, + "@trackLabel": { + "description": "Metadata label - record label" + }, "trackCopyright": "Copyright", - "@trackCopyright": {"description": "Metadata label - copyright information"}, + "@trackCopyright": { + "description": "Metadata label - copyright information" + }, "trackDownloaded": "Downloaded", - "@trackDownloaded": {"description": "Metadata label - download date"}, + "@trackDownloaded": { + "description": "Metadata label - download date" + }, "trackCopyLyrics": "Copy lyrics", - "@trackCopyLyrics": {"description": "Action - copy lyrics to clipboard"}, + "@trackCopyLyrics": { + "description": "Action - copy lyrics to clipboard" + }, "trackLyricsNotAvailable": "Lyrics not available for this track", - "@trackLyricsNotAvailable": {"description": "Message when lyrics not found"}, + "@trackLyricsNotAvailable": { + "description": "Message when lyrics not found" + }, "trackLyricsTimeout": "Request timed out. Try again later.", - "@trackLyricsTimeout": {"description": "Message when lyrics request times out"}, + "@trackLyricsTimeout": { + "description": "Message when lyrics request times out" + }, "trackLyricsLoadFailed": "Failed to load lyrics", - "@trackLyricsLoadFailed": {"description": "Message when lyrics loading fails"}, + "@trackLyricsLoadFailed": { + "description": "Message when lyrics loading fails" + }, "trackEmbedLyrics": "Embed Lyrics", - "@trackEmbedLyrics": {"description": "Action - embed lyrics into audio file"}, + "@trackEmbedLyrics": { + "description": "Action - embed lyrics into audio file" + }, "trackLyricsEmbedded": "Lyrics embedded successfully", - "@trackLyricsEmbedded": {"description": "Snackbar - lyrics saved to file"}, + "@trackLyricsEmbedded": { + "description": "Snackbar - lyrics saved to file" + }, "trackInstrumental": "Instrumental track", - "@trackInstrumental": {"description": "Message when track is instrumental (no lyrics)"}, + "@trackInstrumental": { + "description": "Message when track is instrumental (no lyrics)" + }, "trackCopiedToClipboard": "Copied to clipboard", - "@trackCopiedToClipboard": {"description": "Snackbar - content copied"}, + "@trackCopiedToClipboard": { + "description": "Snackbar - content copied" + }, "trackDeleteConfirmTitle": "Remove from device?", - "@trackDeleteConfirmTitle": {"description": "Delete confirmation title"}, + "@trackDeleteConfirmTitle": { + "description": "Delete confirmation title" + }, "trackDeleteConfirmMessage": "This will permanently delete the downloaded file and remove it from your history.", - "@trackDeleteConfirmMessage": {"description": "Delete confirmation message"}, - "trackCannotOpen": "Cannot open: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": {"type": "String"} - } + "@trackDeleteConfirmMessage": { + "description": "Delete confirmation message" }, - "dateToday": "Today", - "@dateToday": {"description": "Relative date - today"}, + "@dateToday": { + "description": "Relative date - today" + }, "dateYesterday": "Yesterday", - "@dateYesterday": {"description": "Relative date - yesterday"}, + "@dateYesterday": { + "description": "Relative date - yesterday" + }, "dateDaysAgo": "{count} days ago", "@dateDaysAgo": { "description": "Relative date - days ago", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "dateWeeksAgo": "{count} weeks ago", "@dateWeeksAgo": { "description": "Relative date - weeks ago", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "dateMonthsAgo": "{count} months ago", "@dateMonthsAgo": { "description": "Relative date - months ago", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, - - "concurrentSequential": "Sequential", - "@concurrentSequential": {"description": "Download mode - one at a time"}, - "concurrentParallel2": "2 Parallel", - "@concurrentParallel2": {"description": "Download mode - 2 simultaneous"}, - "concurrentParallel3": "3 Parallel", - "@concurrentParallel3": {"description": "Download mode - 3 simultaneous"}, - - "tapToSeeError": "Tap to see error details", - "@tapToSeeError": {"description": "Tooltip for failed download"}, - "storeFilterAll": "All", - "@storeFilterAll": {"description": "Store filter - all extensions"}, + "@storeFilterAll": { + "description": "Store filter - all extensions" + }, "storeFilterMetadata": "Metadata", - "@storeFilterMetadata": {"description": "Store filter - metadata providers"}, + "@storeFilterMetadata": { + "description": "Store filter - metadata providers" + }, "storeFilterDownload": "Download", - "@storeFilterDownload": {"description": "Store filter - download providers"}, + "@storeFilterDownload": { + "description": "Store filter - download providers" + }, "storeFilterUtility": "Utility", - "@storeFilterUtility": {"description": "Store filter - utility extensions"}, + "@storeFilterUtility": { + "description": "Store filter - utility extensions" + }, "storeFilterLyrics": "Lyrics", - "@storeFilterLyrics": {"description": "Store filter - lyrics providers"}, + "@storeFilterLyrics": { + "description": "Store filter - lyrics providers" + }, "storeFilterIntegration": "Integration", - "@storeFilterIntegration": {"description": "Store filter - integrations"}, + "@storeFilterIntegration": { + "description": "Store filter - integrations" + }, "storeClearFilters": "Clear filters", - "@storeClearFilters": {"description": "Button to clear all filters"}, - "storeNoResults": "No extensions found", - "@storeNoResults": {"description": "Empty state when no extensions match filters"}, - - "extensionProviderPriority": "Provider Priority", - "@extensionProviderPriority": {"description": "Extension capability - provider priority"}, - "extensionInstallButton": "Install Extension", - "@extensionInstallButton": {"description": "Button to install extension"}, + "@storeClearFilters": { + "description": "Button to clear all filters" + }, "extensionDefaultProvider": "Default (Deezer/Spotify)", - "@extensionDefaultProvider": {"description": "Default search provider option"}, + "@extensionDefaultProvider": { + "description": "Default search provider option" + }, "extensionDefaultProviderSubtitle": "Use built-in search", - "@extensionDefaultProviderSubtitle": {"description": "Subtitle for default provider"}, + "@extensionDefaultProviderSubtitle": { + "description": "Subtitle for default provider" + }, "extensionAuthor": "Author", - "@extensionAuthor": {"description": "Extension detail - author"}, + "@extensionAuthor": { + "description": "Extension detail - author" + }, "extensionId": "ID", - "@extensionId": {"description": "Extension detail - unique ID"}, + "@extensionId": { + "description": "Extension detail - unique ID" + }, "extensionError": "Error", - "@extensionError": {"description": "Extension detail - error message"}, + "@extensionError": { + "description": "Extension detail - error message" + }, "extensionCapabilities": "Capabilities", - "@extensionCapabilities": {"description": "Section header - extension features"}, + "@extensionCapabilities": { + "description": "Section header - extension features" + }, "extensionMetadataProvider": "Metadata Provider", - "@extensionMetadataProvider": {"description": "Capability - provides metadata"}, + "@extensionMetadataProvider": { + "description": "Capability - provides metadata" + }, "extensionDownloadProvider": "Download Provider", - "@extensionDownloadProvider": {"description": "Capability - provides downloads"}, + "@extensionDownloadProvider": { + "description": "Capability - provides downloads" + }, "extensionLyricsProvider": "Lyrics Provider", - "@extensionLyricsProvider": {"description": "Capability - provides lyrics"}, + "@extensionLyricsProvider": { + "description": "Capability - provides lyrics" + }, "extensionUrlHandler": "URL Handler", - "@extensionUrlHandler": {"description": "Capability - handles URLs"}, + "@extensionUrlHandler": { + "description": "Capability - handles URLs" + }, "extensionQualityOptions": "Quality Options", - "@extensionQualityOptions": {"description": "Capability - quality selection"}, + "@extensionQualityOptions": { + "description": "Capability - quality selection" + }, "extensionPostProcessingHooks": "Post-Processing Hooks", - "@extensionPostProcessingHooks": {"description": "Capability - post-processing"}, + "@extensionPostProcessingHooks": { + "description": "Capability - post-processing" + }, "extensionPermissions": "Permissions", - "@extensionPermissions": {"description": "Section header - required permissions"}, + "@extensionPermissions": { + "description": "Section header - required permissions" + }, "extensionSettings": "Settings", - "@extensionSettings": {"description": "Section header - extension settings"}, + "@extensionSettings": { + "description": "Section header - extension settings" + }, "extensionRemoveButton": "Remove Extension", - "@extensionRemoveButton": {"description": "Button to uninstall extension"}, + "@extensionRemoveButton": { + "description": "Button to uninstall extension" + }, "extensionUpdated": "Updated", - "@extensionUpdated": {"description": "Extension detail - last update"}, + "@extensionUpdated": { + "description": "Extension detail - last update" + }, "extensionMinAppVersion": "Min App Version", - "@extensionMinAppVersion": {"description": "Extension detail - minimum app version"}, + "@extensionMinAppVersion": { + "description": "Extension detail - minimum app version" + }, "extensionCustomTrackMatching": "Custom Track Matching", - "@extensionCustomTrackMatching": {"description": "Capability - custom track matching algorithm"}, + "@extensionCustomTrackMatching": { + "description": "Capability - custom track matching algorithm" + }, "extensionPostProcessing": "Post-Processing", - "@extensionPostProcessing": {"description": "Capability - post-download processing"}, + "@extensionPostProcessing": { + "description": "Capability - post-download processing" + }, "extensionHooksAvailable": "{count} hook(s) available", "@extensionHooksAvailable": { "description": "Post-processing hooks count", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "extensionPatternsCount": "{count} pattern(s)", "@extensionPatternsCount": { "description": "URL patterns count", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "extensionStrategy": "Strategy: {strategy}", "@extensionStrategy": { "description": "Track matching strategy name", "placeholders": { - "strategy": {"type": "String"} + "strategy": { + "type": "String" + } } }, "extensionsProviderPrioritySection": "Provider Priority", - "@extensionsProviderPrioritySection": {"description": "Section header - provider priority"}, + "@extensionsProviderPrioritySection": { + "description": "Section header - provider priority" + }, "extensionsInstalledSection": "Installed Extensions", - "@extensionsInstalledSection": {"description": "Section header - installed extensions"}, + "@extensionsInstalledSection": { + "description": "Section header - installed extensions" + }, "extensionsNoExtensions": "No extensions installed", - "@extensionsNoExtensions": {"description": "Empty state - no extensions"}, + "@extensionsNoExtensions": { + "description": "Empty state - no extensions" + }, "extensionsNoExtensionsSubtitle": "Install .spotiflac-ext files to add new providers", - "@extensionsNoExtensionsSubtitle": {"description": "Empty state subtitle"}, + "@extensionsNoExtensionsSubtitle": { + "description": "Empty state subtitle" + }, "extensionsInstallButton": "Install Extension", - "@extensionsInstallButton": {"description": "Button to install extension from file"}, + "@extensionsInstallButton": { + "description": "Button to install extension from file" + }, "extensionsInfoTip": "Extensions can add new metadata and download providers. Only install extensions from trusted sources.", - "@extensionsInfoTip": {"description": "Security warning about extensions"}, + "@extensionsInfoTip": { + "description": "Security warning about extensions" + }, "extensionsInstalledSuccess": "Extension installed successfully", - "@extensionsInstalledSuccess": {"description": "Success message after install"}, + "@extensionsInstalledSuccess": { + "description": "Success message after install" + }, "extensionsDownloadPriority": "Download Priority", - "@extensionsDownloadPriority": {"description": "Setting - download provider order"}, + "@extensionsDownloadPriority": { + "description": "Setting - download provider order" + }, "extensionsDownloadPrioritySubtitle": "Set download service order", - "@extensionsDownloadPrioritySubtitle": {"description": "Subtitle for download priority"}, + "@extensionsDownloadPrioritySubtitle": { + "description": "Subtitle for download priority" + }, "extensionsNoDownloadProvider": "No extensions with download provider", - "@extensionsNoDownloadProvider": {"description": "Empty state - no download providers"}, + "@extensionsNoDownloadProvider": { + "description": "Empty state - no download providers" + }, "extensionsMetadataPriority": "Metadata Priority", - "@extensionsMetadataPriority": {"description": "Setting - metadata provider order"}, + "@extensionsMetadataPriority": { + "description": "Setting - metadata provider order" + }, "extensionsMetadataPrioritySubtitle": "Set search & metadata source order", - "@extensionsMetadataPrioritySubtitle": {"description": "Subtitle for metadata priority"}, + "@extensionsMetadataPrioritySubtitle": { + "description": "Subtitle for metadata priority" + }, "extensionsNoMetadataProvider": "No extensions with metadata provider", - "@extensionsNoMetadataProvider": {"description": "Empty state - no metadata providers"}, + "@extensionsNoMetadataProvider": { + "description": "Empty state - no metadata providers" + }, "extensionsSearchProvider": "Search Provider", - "@extensionsSearchProvider": {"description": "Setting - search provider selection"}, + "@extensionsSearchProvider": { + "description": "Setting - search provider selection" + }, "extensionsNoCustomSearch": "No extensions with custom search", - "@extensionsNoCustomSearch": {"description": "Empty state - no search providers"}, + "@extensionsNoCustomSearch": { + "description": "Empty state - no search providers" + }, "extensionsSearchProviderDescription": "Choose which service to use for searching tracks", - "@extensionsSearchProviderDescription": {"description": "Search provider setting description"}, + "@extensionsSearchProviderDescription": { + "description": "Search provider setting description" + }, "extensionsCustomSearch": "Custom search", - "@extensionsCustomSearch": {"description": "Label for custom search provider"}, + "@extensionsCustomSearch": { + "description": "Label for custom search provider" + }, "extensionsErrorLoading": "Error loading extension", - "@extensionsErrorLoading": {"description": "Error message when extension fails to load"}, - + "@extensionsErrorLoading": { + "description": "Error message when extension fails to load" + }, "qualityFlacLossless": "FLAC Lossless", - "@qualityFlacLossless": {"description": "Quality option - CD quality FLAC"}, + "@qualityFlacLossless": { + "description": "Quality option - CD quality FLAC" + }, "qualityFlacLosslessSubtitle": "16-bit / 44.1kHz", - "@qualityFlacLosslessSubtitle": {"description": "Technical spec for lossless"}, + "@qualityFlacLosslessSubtitle": { + "description": "Technical spec for lossless" + }, "qualityHiResFlac": "Hi-Res FLAC", - "@qualityHiResFlac": {"description": "Quality option - high resolution FLAC"}, + "@qualityHiResFlac": { + "description": "Quality option - high resolution FLAC" + }, "qualityHiResFlacSubtitle": "24-bit / up to 96kHz", - "@qualityHiResFlacSubtitle": {"description": "Technical spec for hi-res"}, + "@qualityHiResFlacSubtitle": { + "description": "Technical spec for hi-res" + }, "qualityHiResFlacMax": "Hi-Res FLAC Max", - "@qualityHiResFlacMax": {"description": "Quality option - maximum resolution FLAC"}, + "@qualityHiResFlacMax": { + "description": "Quality option - maximum resolution FLAC" + }, "qualityHiResFlacMaxSubtitle": "24-bit / up to 192kHz", - "@qualityHiResFlacMaxSubtitle": {"description": "Technical spec for hi-res max"}, - "qualityLossy": "Lossy", - "@qualityLossy": {"description": "Quality option - lossy format (MP3/Opus)"}, - "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", - "@qualityLossyMp3Subtitle": {"description": "Technical spec for lossy MP3"}, - "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", - "@qualityLossyOpusSubtitle": {"description": "Technical spec for lossy Opus"}, - "enableLossyOption": "Enable Lossy Option", - "@enableLossyOption": {"description": "Setting - enable lossy quality option"}, - "enableLossyOptionSubtitleOn": "Lossy quality option is available", - "@enableLossyOptionSubtitleOn": {"description": "Subtitle when lossy is enabled"}, - "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", - "@enableLossyOptionSubtitleOff": {"description": "Subtitle when lossy is disabled"}, - "lossyFormat": "Lossy Format", - "@lossyFormat": {"description": "Setting - choose lossy format"}, - "lossyFormatDescription": "Choose the lossy format for conversion", - "@lossyFormatDescription": {"description": "Description for lossy format picker"}, - "lossyFormatMp3Subtitle": "320kbps, best compatibility", - "@lossyFormatMp3Subtitle": {"description": "MP3 format description"}, - "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", - "@lossyFormatOpusSubtitle": {"description": "Opus format description"}, + "@qualityHiResFlacMaxSubtitle": { + "description": "Technical spec for hi-res max" + }, "qualityNote": "Actual quality depends on track availability from the service", - "@qualityNote": {"description": "Note about quality availability"}, + "@qualityNote": { + "description": "Note about quality availability" + }, "youtubeQualityNote": "YouTube provides lossy audio only. Not part of lossless fallback.", - "@youtubeQualityNote": {"description": "Note for YouTube service explaining lossy-only quality"}, + "@youtubeQualityNote": { + "description": "Note for YouTube service explaining lossy-only quality" + }, "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", - "@youtubeOpusBitrateTitle": {"description": "Title for YouTube Opus bitrate setting"}, + "@youtubeOpusBitrateTitle": { + "description": "Title for YouTube Opus bitrate setting" + }, "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", - "@youtubeMp3BitrateTitle": {"description": "Title for YouTube MP3 bitrate setting"}, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": {"type": "int"}, - "min": {"type": "int"}, - "max": {"type": "int"} - } + "@youtubeMp3BitrateTitle": { + "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": {"type": "int"}, - "max": {"type": "int"} - } - }, - "youtubeBitrateFieldLabel": "Bitrate (kbps)", - "@youtubeBitrateFieldLabel": {"description": "Label for YouTube bitrate input field"}, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": {"type": "int"}, - "max": {"type": "int"} - } - }, - "downloadAskBeforeDownload": "Ask Before Download", - "@downloadAskBeforeDownload": {"description": "Setting - show quality picker"}, + "@downloadAskBeforeDownload": { + "description": "Setting - show quality picker" + }, "downloadDirectory": "Download Directory", - "@downloadDirectory": {"description": "Setting - download folder"}, + "@downloadDirectory": { + "description": "Setting - download folder" + }, "downloadSeparateSinglesFolder": "Separate Singles Folder", - "@downloadSeparateSinglesFolder": {"description": "Setting - separate folder for singles"}, + "@downloadSeparateSinglesFolder": { + "description": "Setting - separate folder for singles" + }, "downloadAlbumFolderStructure": "Album Folder Structure", - "@downloadAlbumFolderStructure": {"description": "Setting - album folder organization"}, + "@downloadAlbumFolderStructure": { + "description": "Setting - album folder organization" + }, "downloadUseAlbumArtistForFolders": "Use Album Artist for folders", - "@downloadUseAlbumArtistForFolders": {"description": "Setting - choose whether artist folders use Album Artist or Track Artist"}, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": {"description": "Subtitle when Album Artist is used for folder naming"}, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": {"description": "Subtitle when Track Artist is used for folder naming"}, + "@downloadUseAlbumArtistForFolders": { + "description": "Setting - choose whether artist folders use Album Artist or Track Artist" + }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", - "@downloadUsePrimaryArtistOnly": {"description": "Setting - strip featured artists from folder name"}, + "@downloadUsePrimaryArtistOnly": { + "description": "Setting - strip featured artists from folder name" + }, "downloadUsePrimaryArtistOnlyEnabled": "Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)", - "@downloadUsePrimaryArtistOnlyEnabled": {"description": "Subtitle when primary artist only is enabled"}, + "@downloadUsePrimaryArtistOnlyEnabled": { + "description": "Subtitle when primary artist only is enabled" + }, "downloadUsePrimaryArtistOnlyDisabled": "Full artist string used for folder name", - "@downloadUsePrimaryArtistOnlyDisabled": {"description": "Subtitle when primary artist only is disabled"}, - "downloadSaveFormat": "Save Format", - "@downloadSaveFormat": {"description": "Setting - output file format"}, - "downloadSelectService": "Select Service", - "@downloadSelectService": {"description": "Dialog title - choose download service"}, + "@downloadUsePrimaryArtistOnlyDisabled": { + "description": "Subtitle when primary artist only is disabled" + }, "downloadSelectQuality": "Select Quality", - "@downloadSelectQuality": {"description": "Dialog title - choose audio quality"}, + "@downloadSelectQuality": { + "description": "Dialog title - choose audio quality" + }, "downloadFrom": "Download From", - "@downloadFrom": {"description": "Label - download source"}, - "downloadDefaultQualityLabel": "Default Quality", - "@downloadDefaultQualityLabel": {"description": "Label - default quality setting"}, - "downloadBestAvailable": "Best available", - "@downloadBestAvailable": {"description": "Quality option - highest available"}, - - "folderNone": "None", - "@folderNone": {"description": "Folder option - no organization"}, - "folderNoneSubtitle": "Save all files directly to download folder", - "@folderNoneSubtitle": {"description": "Subtitle for no folder organization"}, - "folderArtist": "Artist", - "@folderArtist": {"description": "Folder option - by artist"}, - "folderArtistSubtitle": "Artist Name/filename", - "@folderArtistSubtitle": {"description": "Folder structure example"}, - "folderAlbum": "Album", - "@folderAlbum": {"description": "Folder option - by album"}, - "folderAlbumSubtitle": "Album Name/filename", - "@folderAlbumSubtitle": {"description": "Folder structure example"}, - "folderArtistAlbum": "Artist/Album", - "@folderArtistAlbum": {"description": "Folder option - nested"}, - "folderArtistAlbumSubtitle": "Artist Name/Album Name/filename", - "@folderArtistAlbumSubtitle": {"description": "Folder structure example"}, - - "serviceTidal": "Tidal", - "@serviceTidal": {"description": "Service name - DO NOT TRANSLATE"}, - "serviceQobuz": "Qobuz", - "@serviceQobuz": {"description": "Service name - DO NOT TRANSLATE"}, - "serviceAmazon": "Amazon", - "@serviceAmazon": {"description": "Service name - DO NOT TRANSLATE"}, - "serviceDeezer": "Deezer", - "@serviceDeezer": {"description": "Service name - DO NOT TRANSLATE"}, - "serviceSpotify": "Spotify", - "@serviceSpotify": {"description": "Service name - DO NOT TRANSLATE"}, - + "@downloadFrom": { + "description": "Label - download source" + }, "appearanceAmoledDark": "AMOLED Dark", - "@appearanceAmoledDark": {"description": "Theme option - pure black"}, + "@appearanceAmoledDark": { + "description": "Theme option - pure black" + }, "appearanceAmoledDarkSubtitle": "Pure black background", - "@appearanceAmoledDarkSubtitle": {"description": "Subtitle for AMOLED dark"}, - "appearanceChooseAccentColor": "Choose Accent Color", - "@appearanceChooseAccentColor": {"description": "Color picker dialog title"}, - "appearanceChooseTheme": "Theme Mode", - "@appearanceChooseTheme": {"description": "Theme picker dialog title"}, - - "queueTitle": "Download Queue", - "@queueTitle": {"description": "Queue screen title"}, -"queueClearAll": "Clear All", - "@queueClearAll": {"description": "Button - clear all queue items"}, + "@appearanceAmoledDarkSubtitle": { + "description": "Subtitle for AMOLED dark" + }, + "queueClearAll": "Clear All", + "@queueClearAll": { + "description": "Button - clear all queue items" + }, "queueClearAllMessage": "Are you sure you want to clear all downloads?", - "@queueClearAllMessage": {"description": "Clear queue confirmation"}, - "queueExportFailed": "Export", - "@queueExportFailed": {"description": "Button - export failed downloads to TXT"}, - "queueExportFailedSuccess": "Failed downloads exported to TXT file", - "@queueExportFailedSuccess": {"description": "Success message after exporting failed downloads"}, - "queueExportFailedClear": "Clear Failed", - "@queueExportFailedClear": {"description": "Action to clear failed downloads after export"}, - "queueExportFailedError": "Failed to export downloads", - "@queueExportFailedError": {"description": "Error message when export fails"}, + "@queueClearAllMessage": { + "description": "Clear queue confirmation" + }, "settingsAutoExportFailed": "Auto-export failed downloads", - "@settingsAutoExportFailed": {"description": "Setting toggle for auto-export"}, + "@settingsAutoExportFailed": { + "description": "Setting toggle for auto-export" + }, "settingsAutoExportFailedSubtitle": "Save failed downloads to TXT file automatically", - "@settingsAutoExportFailedSubtitle": {"description": "Subtitle for auto-export setting"}, - + "@settingsAutoExportFailedSubtitle": { + "description": "Subtitle for auto-export setting" + }, "settingsDownloadNetwork": "Download Network", - "@settingsDownloadNetwork": {"description": "Setting for network type preference"}, + "@settingsDownloadNetwork": { + "description": "Setting for network type preference" + }, "settingsDownloadNetworkAny": "WiFi + Mobile Data", - "@settingsDownloadNetworkAny": {"description": "Network option - use any connection"}, + "@settingsDownloadNetworkAny": { + "description": "Network option - use any connection" + }, "settingsDownloadNetworkWifiOnly": "WiFi Only", - "@settingsDownloadNetworkWifiOnly": {"description": "Network option - only use WiFi"}, + "@settingsDownloadNetworkWifiOnly": { + "description": "Network option - only use WiFi" + }, "settingsDownloadNetworkSubtitle": "Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.", - "@settingsDownloadNetworkSubtitle": {"description": "Subtitle explaining network preference"}, - - "queueEmpty": "No downloads in queue", - "@queueEmpty": {"description": "Empty queue state title"}, - "queueEmptySubtitle": "Add tracks from the home screen", - "@queueEmptySubtitle": {"description": "Empty queue state subtitle"}, - "queueClearCompleted": "Clear completed", - "@queueClearCompleted": {"description": "Button - clear finished downloads"}, - "queueDownloadFailed": "Download Failed", - "@queueDownloadFailed": {"description": "Error dialog title"}, - "queueTrackLabel": "Track:", - "@queueTrackLabel": {"description": "Label in error dialog"}, - "queueArtistLabel": "Artist:", - "@queueArtistLabel": {"description": "Label in error dialog"}, - "queueErrorLabel": "Error:", - "@queueErrorLabel": {"description": "Label in error dialog"}, - "queueUnknownError": "Unknown error", - "@queueUnknownError": {"description": "Fallback error message"}, - + "@settingsDownloadNetworkSubtitle": { + "description": "Subtitle explaining network preference" + }, "albumFolderArtistAlbum": "Artist / Album", - "@albumFolderArtistAlbum": {"description": "Album folder option"}, + "@albumFolderArtistAlbum": { + "description": "Album folder option" + }, "albumFolderArtistAlbumSubtitle": "Albums/Artist Name/Album Name/", - "@albumFolderArtistAlbumSubtitle": {"description": "Folder structure example"}, + "@albumFolderArtistAlbumSubtitle": { + "description": "Folder structure example" + }, "albumFolderArtistYearAlbum": "Artist / [Year] Album", - "@albumFolderArtistYearAlbum": {"description": "Album folder option with year"}, + "@albumFolderArtistYearAlbum": { + "description": "Album folder option with year" + }, "albumFolderArtistYearAlbumSubtitle": "Albums/Artist Name/[2005] Album Name/", - "@albumFolderArtistYearAlbumSubtitle": {"description": "Folder structure example"}, + "@albumFolderArtistYearAlbumSubtitle": { + "description": "Folder structure example" + }, "albumFolderAlbumOnly": "Album Only", - "@albumFolderAlbumOnly": {"description": "Album folder option"}, + "@albumFolderAlbumOnly": { + "description": "Album folder option" + }, "albumFolderAlbumOnlySubtitle": "Albums/Album Name/", - "@albumFolderAlbumOnlySubtitle": {"description": "Folder structure example"}, + "@albumFolderAlbumOnlySubtitle": { + "description": "Folder structure example" + }, "albumFolderYearAlbum": "[Year] Album", - "@albumFolderYearAlbum": {"description": "Album folder option with year"}, + "@albumFolderYearAlbum": { + "description": "Album folder option with year" + }, "albumFolderYearAlbumSubtitle": "Albums/[2005] Album Name/", - "@albumFolderYearAlbumSubtitle": {"description": "Folder structure example"}, + "@albumFolderYearAlbumSubtitle": { + "description": "Folder structure example" + }, "albumFolderArtistAlbumSingles": "Artist / Album + Singles", - "@albumFolderArtistAlbumSingles": {"description": "Album folder option with singles inside artist"}, + "@albumFolderArtistAlbumSingles": { + "description": "Album folder option with singles inside artist" + }, "albumFolderArtistAlbumSinglesSubtitle": "Artist/Album/ and Artist/Singles/", - "@albumFolderArtistAlbumSinglesSubtitle": {"description": "Folder structure example"}, - + "@albumFolderArtistAlbumSinglesSubtitle": { + "description": "Folder structure example" + }, "downloadedAlbumDeleteSelected": "Delete Selected", - "@downloadedAlbumDeleteSelected": {"description": "Button - delete selected tracks"}, + "@downloadedAlbumDeleteSelected": { + "description": "Button - delete selected tracks" + }, "downloadedAlbumDeleteMessage": "Delete {count} {count, plural, =1{track} other{tracks}} from this album?\n\nThis will also delete the files from storage.", "@downloadedAlbumDeleteMessage": { "description": "Delete confirmation with count", "placeholders": { - "count": {"type": "int"} - } - }, - "downloadedAlbumTracksHeader": "Tracks", - "@downloadedAlbumTracksHeader": {"description": "Section header for tracks"}, - "downloadedAlbumDownloadedCount": "{count} downloaded", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "downloadedAlbumSelectedCount": "{count} selected", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "downloadedAlbumAllSelected": "All tracks selected", - "@downloadedAlbumAllSelected": {"description": "Status - all items selected"}, + "@downloadedAlbumAllSelected": { + "description": "Status - all items selected" + }, "downloadedAlbumTapToSelect": "Tap tracks to select", - "@downloadedAlbumTapToSelect": {"description": "Selection hint"}, + "@downloadedAlbumTapToSelect": { + "description": "Selection hint" + }, "downloadedAlbumDeleteCount": "Delete {count} {count, plural, =1{track} other{tracks}}", "@downloadedAlbumDeleteCount": { "description": "Delete button text with count", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "downloadedAlbumSelectToDelete": "Select tracks to delete", - "@downloadedAlbumSelectToDelete": {"description": "Placeholder when nothing selected"}, + "@downloadedAlbumSelectToDelete": { + "description": "Placeholder when nothing selected" + }, "downloadedAlbumDiscHeader": "Disc {discNumber}", "@downloadedAlbumDiscHeader": { "description": "Header for disc separator in multi-disc albums", "placeholders": { - "discNumber": {"type": "int", "example": "1"} + "discNumber": { + "type": "int", + "example": "1" + } } }, - - "utilityFunctions": "Utility Functions", - "@utilityFunctions": {"description": "Extension capability - utility functions"}, - "recentTypeArtist": "Artist", - "@recentTypeArtist": {"description": "Recent access item type - artist"}, + "@recentTypeArtist": { + "description": "Recent access item type - artist" + }, "recentTypeAlbum": "Album", - "@recentTypeAlbum": {"description": "Recent access item type - album"}, + "@recentTypeAlbum": { + "description": "Recent access item type - album" + }, "recentTypeSong": "Song", - "@recentTypeSong": {"description": "Recent access item type - song/track"}, + "@recentTypeSong": { + "description": "Recent access item type - song/track" + }, "recentTypePlaylist": "Playlist", - "@recentTypePlaylist": {"description": "Recent access item type - playlist"}, + "@recentTypePlaylist": { + "description": "Recent access item type - playlist" + }, "recentEmpty": "No recent items yet", - "@recentEmpty": {"description": "Empty state text for recent access list"}, + "@recentEmpty": { + "description": "Empty state text for recent access list" + }, "recentShowAllDownloads": "Show All Downloads", "@recentShowAllDownloads": { "description": "Button label to unhide hidden downloads in recent access" }, - "recentPlaylistInfo": "Playlist: {name}", "@recentPlaylistInfo": { "description": "Snackbar message when tapping playlist in recent access", "placeholders": { - "name": {"type": "String", "description": "Playlist name"} + "name": { + "type": "String", + "description": "Playlist name" + } } }, - "errorGeneric": "Error: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": {"type": "String", "description": "Error message"} - } - }, - "discographyDownload": "Download Discography", - "@discographyDownload": {"description": "Button - download artist discography"}, - "discographyPlay": "Play Discography", - "@discographyPlay": {"description": "Button - play artist discography"}, + "@discographyDownload": { + "description": "Button - download artist discography" + }, "discographyDownloadAll": "Download All", - "@discographyDownloadAll": {"description": "Option - download entire discography"}, - "discographyPlayAll": "Play All", - "@discographyPlayAll": {"description": "Option - play entire discography"}, + "@discographyDownloadAll": { + "description": "Option - download entire discography" + }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", "placeholders": { - "count": {"type": "int"}, - "albumCount": {"type": "int"} + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } } }, "discographyAlbumsOnly": "Albums Only", - "@discographyAlbumsOnly": {"description": "Option - download only albums"}, + "@discographyAlbumsOnly": { + "description": "Option - download only albums" + }, "discographyAlbumsOnlySubtitle": "{count} tracks from {albumCount} albums", "@discographyAlbumsOnlySubtitle": { "description": "Subtitle showing album tracks count", "placeholders": { - "count": {"type": "int"}, - "albumCount": {"type": "int"} + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } } }, "discographySinglesOnly": "Singles & EPs Only", - "@discographySinglesOnly": {"description": "Option - download only singles"}, + "@discographySinglesOnly": { + "description": "Option - download only singles" + }, "discographySinglesOnlySubtitle": "{count} tracks from {albumCount} singles", "@discographySinglesOnlySubtitle": { "description": "Subtitle showing singles tracks count", "placeholders": { - "count": {"type": "int"}, - "albumCount": {"type": "int"} + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } } }, "discographySelectAlbums": "Select Albums...", - "@discographySelectAlbums": {"description": "Option - manually select albums to download"}, + "@discographySelectAlbums": { + "description": "Option - manually select albums to download" + }, "discographySelectAlbumsSubtitle": "Choose specific albums or singles", - "@discographySelectAlbumsSubtitle": {"description": "Subtitle for select albums option"}, + "@discographySelectAlbumsSubtitle": { + "description": "Subtitle for select albums option" + }, "discographyFetchingTracks": "Fetching tracks...", - "@discographyFetchingTracks": {"description": "Progress - fetching album tracks"}, + "@discographyFetchingTracks": { + "description": "Progress - fetching album tracks" + }, "discographyFetchingAlbum": "Fetching {current} of {total}...", "@discographyFetchingAlbum": { "description": "Progress - fetching specific album", "placeholders": { - "current": {"type": "int"}, - "total": {"type": "int"} + "current": { + "type": "int" + }, + "total": { + "type": "int" + } } }, "discographySelectedCount": "{count} selected", "@discographySelectedCount": { "description": "Selection count badge", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "discographyDownloadSelected": "Download Selected", - "@discographyDownloadSelected": {"description": "Button - download selected albums"}, - "discographyPlaySelected": "Play Selected", - "@discographyPlaySelected": {"description": "Button - play selected albums"}, + "@discographyDownloadSelected": { + "description": "Button - download selected albums" + }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "discographySkippedDownloaded": "{added} added, {skipped} already downloaded", "@discographySkippedDownloaded": { "description": "Snackbar - with skipped tracks count", "placeholders": { - "added": {"type": "int"}, - "skipped": {"type": "int"} + "added": { + "type": "int" + }, + "skipped": { + "type": "int" + } } }, "discographyNoAlbums": "No albums available", - "@discographyNoAlbums": {"description": "Error - no albums found for artist"}, + "@discographyNoAlbums": { + "description": "Error - no albums found for artist" + }, "discographyFailedToFetch": "Failed to fetch some albums", - "@discographyFailedToFetch": {"description": "Error - some albums failed to load"}, - + "@discographyFailedToFetch": { + "description": "Error - some albums failed to load" + }, "sectionStorageAccess": "Storage Access", - "@sectionStorageAccess": {"description": "Section header for storage access settings"}, + "@sectionStorageAccess": { + "description": "Section header for storage access settings" + }, "allFilesAccess": "All Files Access", - "@allFilesAccess": {"description": "Toggle for MANAGE_EXTERNAL_STORAGE permission"}, + "@allFilesAccess": { + "description": "Toggle for MANAGE_EXTERNAL_STORAGE permission" + }, "allFilesAccessEnabledSubtitle": "Can write to any folder", - "@allFilesAccessEnabledSubtitle": {"description": "Subtitle when all files access is enabled"}, + "@allFilesAccessEnabledSubtitle": { + "description": "Subtitle when all files access is enabled" + }, "allFilesAccessDisabledSubtitle": "Limited to media folders only", - "@allFilesAccessDisabledSubtitle": {"description": "Subtitle when all files access is disabled"}, + "@allFilesAccessDisabledSubtitle": { + "description": "Subtitle when all files access is disabled" + }, "allFilesAccessDescription": "Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.", - "@allFilesAccessDescription": {"description": "Description explaining when to enable all files access"}, + "@allFilesAccessDescription": { + "description": "Description explaining when to enable all files access" + }, "allFilesAccessDeniedMessage": "Permission was denied. Please enable 'All files access' manually in system settings.", - "@allFilesAccessDeniedMessage": {"description": "Message when permission is permanently denied"}, + "@allFilesAccessDeniedMessage": { + "description": "Message when permission is permanently denied" + }, "allFilesAccessDisabledMessage": "All Files Access disabled. The app will use limited storage access.", - "@allFilesAccessDisabledMessage": {"description": "Snackbar message when user disables all files access"}, - + "@allFilesAccessDisabledMessage": { + "description": "Snackbar message when user disables all files access" + }, "settingsLocalLibrary": "Local Library", - "@settingsLocalLibrary": {"description": "Settings menu item - local library"}, + "@settingsLocalLibrary": { + "description": "Settings menu item - local library" + }, "settingsLocalLibrarySubtitle": "Scan music & detect duplicates", - "@settingsLocalLibrarySubtitle": {"description": "Subtitle for local library settings"}, + "@settingsLocalLibrarySubtitle": { + "description": "Subtitle for local library settings" + }, "settingsCache": "Storage & Cache", - "@settingsCache": {"description": "Settings menu item - cache management"}, + "@settingsCache": { + "description": "Settings menu item - cache management" + }, "settingsCacheSubtitle": "View size and clear cached data", - "@settingsCacheSubtitle": {"description": "Subtitle for cache management menu"}, + "@settingsCacheSubtitle": { + "description": "Subtitle for cache management menu" + }, "libraryTitle": "Local Library", - "@libraryTitle": {"description": "Library settings page title"}, - "libraryStatus": "Library Status", - "@libraryStatus": {"description": "Section header for library status"}, + "@libraryTitle": { + "description": "Library settings page title" + }, "libraryScanSettings": "Scan Settings", - "@libraryScanSettings": {"description": "Section header for scan settings"}, + "@libraryScanSettings": { + "description": "Section header for scan settings" + }, "libraryEnableLocalLibrary": "Enable Local Library", - "@libraryEnableLocalLibrary": {"description": "Toggle to enable library scanning"}, + "@libraryEnableLocalLibrary": { + "description": "Toggle to enable library scanning" + }, "libraryEnableLocalLibrarySubtitle": "Scan and track your existing music", - "@libraryEnableLocalLibrarySubtitle": {"description": "Subtitle for enable toggle"}, + "@libraryEnableLocalLibrarySubtitle": { + "description": "Subtitle for enable toggle" + }, "libraryFolder": "Library Folder", - "@libraryFolder": {"description": "Folder selection setting"}, + "@libraryFolder": { + "description": "Folder selection setting" + }, "libraryFolderHint": "Tap to select folder", - "@libraryFolderHint": {"description": "Placeholder when no folder selected"}, + "@libraryFolderHint": { + "description": "Placeholder when no folder selected" + }, "libraryShowDuplicateIndicator": "Show Duplicate Indicator", - "@libraryShowDuplicateIndicator": {"description": "Toggle for duplicate indicator in search"}, + "@libraryShowDuplicateIndicator": { + "description": "Toggle for duplicate indicator in search" + }, "libraryShowDuplicateIndicatorSubtitle": "Show when searching for existing tracks", - "@libraryShowDuplicateIndicatorSubtitle": {"description": "Subtitle for duplicate indicator toggle"}, + "@libraryShowDuplicateIndicatorSubtitle": { + "description": "Subtitle for duplicate indicator toggle" + }, "libraryActions": "Actions", - "@libraryActions": {"description": "Section header for library actions"}, + "@libraryActions": { + "description": "Section header for library actions" + }, "libraryScan": "Scan Library", - "@libraryScan": {"description": "Button to start library scan"}, + "@libraryScan": { + "description": "Button to start library scan" + }, "libraryScanSubtitle": "Scan for audio files", - "@libraryScanSubtitle": {"description": "Subtitle for scan button"}, + "@libraryScanSubtitle": { + "description": "Subtitle for scan button" + }, "libraryScanSelectFolderFirst": "Select a folder first", - "@libraryScanSelectFolderFirst": {"description": "Message when trying to scan without folder"}, + "@libraryScanSelectFolderFirst": { + "description": "Message when trying to scan without folder" + }, "libraryCleanupMissingFiles": "Cleanup Missing Files", - "@libraryCleanupMissingFiles": {"description": "Button to remove entries for missing files"}, + "@libraryCleanupMissingFiles": { + "description": "Button to remove entries for missing files" + }, "libraryCleanupMissingFilesSubtitle": "Remove entries for files that no longer exist", - "@libraryCleanupMissingFilesSubtitle": {"description": "Subtitle for cleanup button"}, + "@libraryCleanupMissingFilesSubtitle": { + "description": "Subtitle for cleanup button" + }, "libraryClear": "Clear Library", - "@libraryClear": {"description": "Button to clear all library entries"}, + "@libraryClear": { + "description": "Button to clear all library entries" + }, "libraryClearSubtitle": "Remove all scanned tracks", - "@libraryClearSubtitle": {"description": "Subtitle for clear button"}, + "@libraryClearSubtitle": { + "description": "Subtitle for clear button" + }, "libraryClearConfirmTitle": "Clear Library", - "@libraryClearConfirmTitle": {"description": "Dialog title for clear confirmation"}, + "@libraryClearConfirmTitle": { + "description": "Dialog title for clear confirmation" + }, "libraryClearConfirmMessage": "This will remove all scanned tracks from your library. Your actual music files will not be deleted.", - "@libraryClearConfirmMessage": {"description": "Dialog message for clear confirmation"}, + "@libraryClearConfirmMessage": { + "description": "Dialog message for clear confirmation" + }, "libraryAbout": "About Local Library", - "@libraryAbout": {"description": "Section header for about info"}, + "@libraryAbout": { + "description": "Section header for about info" + }, "libraryAboutDescription": "Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.", - "@libraryAboutDescription": {"description": "Description of local library feature"}, - "libraryTracksCount": "{count} tracks", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": {"type": "int"} - } + "@libraryAboutDescription": { + "description": "Description of local library feature" }, "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", "placeholders": { - "time": {"type": "String"} + "time": { + "type": "String" + } } }, "libraryLastScannedNever": "Never", - "@libraryLastScannedNever": {"description": "Shown when library has never been scanned"}, + "@libraryLastScannedNever": { + "description": "Shown when library has never been scanned" + }, "libraryScanning": "Scanning...", - "@libraryScanning": {"description": "Status during scan"}, + "@libraryScanning": { + "description": "Status during scan" + }, "libraryScanProgress": "{progress}% of {total} files", "@libraryScanProgress": { "description": "Scan progress display", "placeholders": { - "progress": {"type": "String"}, - "total": {"type": "int"} + "progress": { + "type": "String" + }, + "total": { + "type": "int" + } } }, "libraryInLibrary": "In Library", - "@libraryInLibrary": {"description": "Badge shown on tracks that exist in local library"}, + "@libraryInLibrary": { + "description": "Badge shown on tracks that exist in local library" + }, "libraryRemovedMissingFiles": "Removed {count} missing files from library", "@libraryRemovedMissingFiles": { "description": "Snackbar after cleanup", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "libraryCleared": "Library cleared", - "@libraryCleared": {"description": "Snackbar after clearing library"}, + "@libraryCleared": { + "description": "Snackbar after clearing library" + }, "libraryStorageAccessRequired": "Storage Access Required", - "@libraryStorageAccessRequired": {"description": "Dialog title for storage permission"}, + "@libraryStorageAccessRequired": { + "description": "Dialog title for storage permission" + }, "libraryStorageAccessMessage": "SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.", - "@libraryStorageAccessMessage": {"description": "Dialog message for storage permission"}, + "@libraryStorageAccessMessage": { + "description": "Dialog message for storage permission" + }, "libraryFolderNotExist": "Selected folder does not exist", - "@libraryFolderNotExist": {"description": "Error when folder doesn't exist"}, + "@libraryFolderNotExist": { + "description": "Error when folder doesn't exist" + }, "librarySourceDownloaded": "Downloaded", - "@librarySourceDownloaded": {"description": "Badge for tracks downloaded via SpotiFLAC"}, + "@librarySourceDownloaded": { + "description": "Badge for tracks downloaded via SpotiFLAC" + }, "librarySourceLocal": "Local", - "@librarySourceLocal": {"description": "Badge for tracks from local library scan"}, + "@librarySourceLocal": { + "description": "Badge for tracks from local library scan" + }, "libraryFilterAll": "All", - "@libraryFilterAll": {"description": "Filter chip - show all library items"}, + "@libraryFilterAll": { + "description": "Filter chip - show all library items" + }, "libraryFilterDownloaded": "Downloaded", - "@libraryFilterDownloaded": {"description": "Filter chip - show only downloaded items"}, + "@libraryFilterDownloaded": { + "description": "Filter chip - show only downloaded items" + }, "libraryFilterLocal": "Local", - "@libraryFilterLocal": {"description": "Filter chip - show only local library items"}, - + "@libraryFilterLocal": { + "description": "Filter chip - show only local library items" + }, "libraryFilterTitle": "Filters", - "@libraryFilterTitle": {"description": "Filter bottom sheet title"}, + "@libraryFilterTitle": { + "description": "Filter bottom sheet title" + }, "libraryFilterReset": "Reset", - "@libraryFilterReset": {"description": "Reset all filters button"}, + "@libraryFilterReset": { + "description": "Reset all filters button" + }, "libraryFilterApply": "Apply", - "@libraryFilterApply": {"description": "Apply filters button"}, + "@libraryFilterApply": { + "description": "Apply filters button" + }, "libraryFilterSource": "Source", - "@libraryFilterSource": {"description": "Filter section - source type"}, + "@libraryFilterSource": { + "description": "Filter section - source type" + }, "libraryFilterQuality": "Quality", - "@libraryFilterQuality": {"description": "Filter section - audio quality"}, + "@libraryFilterQuality": { + "description": "Filter section - audio quality" + }, "libraryFilterQualityHiRes": "Hi-Res (24bit)", - "@libraryFilterQualityHiRes": {"description": "Filter option - high resolution audio"}, + "@libraryFilterQualityHiRes": { + "description": "Filter option - high resolution audio" + }, "libraryFilterQualityCD": "CD (16bit)", - "@libraryFilterQualityCD": {"description": "Filter option - CD quality audio"}, + "@libraryFilterQualityCD": { + "description": "Filter option - CD quality audio" + }, "libraryFilterQualityLossy": "Lossy", - "@libraryFilterQualityLossy": {"description": "Filter option - lossy compressed audio"}, + "@libraryFilterQualityLossy": { + "description": "Filter option - lossy compressed audio" + }, "libraryFilterFormat": "Format", - "@libraryFilterFormat": {"description": "Filter section - file format"}, - "libraryFilterDate": "Date Added", - "@libraryFilterDate": {"description": "Filter section - date range"}, - "libraryFilterDateToday": "Today", - "@libraryFilterDateToday": {"description": "Filter option - today only"}, - "libraryFilterDateWeek": "This Week", - "@libraryFilterDateWeek": {"description": "Filter option - this week"}, - "libraryFilterDateMonth": "This Month", - "@libraryFilterDateMonth": {"description": "Filter option - this month"}, - "libraryFilterDateYear": "This Year", - "@libraryFilterDateYear": {"description": "Filter option - this year"}, + "@libraryFilterFormat": { + "description": "Filter section - file format" + }, "libraryFilterSort": "Sort", - "@libraryFilterSort": {"description": "Filter section - sort order"}, + "@libraryFilterSort": { + "description": "Filter section - sort order" + }, "libraryFilterSortLatest": "Latest", - "@libraryFilterSortLatest": {"description": "Sort option - newest first"}, + "@libraryFilterSortLatest": { + "description": "Sort option - newest first" + }, "libraryFilterSortOldest": "Oldest", - "@libraryFilterSortOldest": {"description": "Sort option - oldest first"}, - "libraryFilterActive": "{count} filter(s) active", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": {"type": "int"} - } + "@libraryFilterSortOldest": { + "description": "Sort option - oldest first" }, - "timeJustNow": "Just now", - "@timeJustNow": {"description": "Relative time - less than a minute ago"}, + "@timeJustNow": { + "description": "Relative time - less than a minute ago" + }, "timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}", "@timeMinutesAgo": { "description": "Relative time - minutes ago", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}", "@timeHoursAgo": { "description": "Relative time - hours ago", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, - - "storageSwitchTitle": "Switch Storage Mode", - "@storageSwitchTitle": {"description": "Dialog title when switching storage mode"}, - "storageSwitchToSafTitle": "Switch to SAF Storage?", - "@storageSwitchToSafTitle": {"description": "Dialog title when switching to SAF"}, - "storageSwitchToAppTitle": "Switch to App Storage?", - "@storageSwitchToAppTitle": {"description": "Dialog title when switching to app storage"}, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", - "@storageSwitchToSafMessage": {"description": "Explanation when switching to SAF"}, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", - "@storageSwitchToAppMessage": {"description": "Explanation when switching to app storage"}, - "storageSwitchExistingDownloads": "Existing Downloads", - "@storageSwitchExistingDownloads": {"description": "Section header for existing downloads info"}, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": {"type": "int"}, - "mode": {"type": "String"} - } - }, - "storageSwitchNewDownloads": "New Downloads", - "@storageSwitchNewDownloads": {"description": "Section header for new downloads info"}, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": {"type": "String"} - } - }, - "storageSwitchContinue": "Continue", - "@storageSwitchContinue": {"description": "Button to proceed with storage switch"}, - "storageSwitchSelectFolder": "Select SAF Folder", - "@storageSwitchSelectFolder": {"description": "Button to select SAF folder"}, - "storageAppStorage": "App Storage", - "@storageAppStorage": {"description": "Label for app storage mode"}, - "storageSafStorage": "SAF Storage", - "@storageSafStorage": {"description": "Label for SAF storage mode"}, - "storageModeBadge": "Storage: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": {"type": "String"} - } - }, - "storageStatsTitle": "Storage Statistics", - "@storageStatsTitle": {"description": "Section title for storage stats"}, - "storageStatsAppCount": "{count} tracks in App Storage", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": {"type": "int"} - } - }, - "storageStatsSafCount": "{count} tracks in SAF Storage", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": {"type": "int"} - } - }, - "storageModeInfo": "Your files are stored in multiple locations", - "@storageModeInfo": {"description": "Info when user has files in both storage modes"}, - "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", - "@tutorialWelcomeTitle": {"description": "Tutorial welcome page title"}, + "@tutorialWelcomeTitle": { + "description": "Tutorial welcome page title" + }, "tutorialWelcomeDesc": "Let's learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.", - "@tutorialWelcomeDesc": {"description": "Tutorial welcome page description"}, + "@tutorialWelcomeDesc": { + "description": "Tutorial welcome page description" + }, "tutorialWelcomeTip1": "Download music from Spotify, Deezer, or paste any supported URL", - "@tutorialWelcomeTip1": {"description": "Tutorial welcome tip 1"}, + "@tutorialWelcomeTip1": { + "description": "Tutorial welcome tip 1" + }, "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", - "@tutorialWelcomeTip2": {"description": "Tutorial welcome tip 2"}, + "@tutorialWelcomeTip2": { + "description": "Tutorial welcome tip 2" + }, "tutorialWelcomeTip3": "Automatic metadata, cover art, and lyrics embedding", - "@tutorialWelcomeTip3": {"description": "Tutorial welcome tip 3"}, - + "@tutorialWelcomeTip3": { + "description": "Tutorial welcome tip 3" + }, "tutorialSearchTitle": "Finding Music", - "@tutorialSearchTitle": {"description": "Tutorial search page title"}, + "@tutorialSearchTitle": { + "description": "Tutorial search page title" + }, "tutorialSearchDesc": "There are two easy ways to find music you want to download.", - "@tutorialSearchDesc": {"description": "Tutorial search page description"}, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", - "@tutorialSearchTip1": {"description": "Tutorial search tip 1"}, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", - "@tutorialSearchTip2": {"description": "Tutorial search tip 2"}, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", - "@tutorialSearchTip3": {"description": "Tutorial search tip 3"}, - + "@tutorialSearchDesc": { + "description": "Tutorial search page description" + }, "tutorialDownloadTitle": "Downloading Music", - "@tutorialDownloadTitle": {"description": "Tutorial download page title"}, + "@tutorialDownloadTitle": { + "description": "Tutorial download page title" + }, "tutorialDownloadDesc": "Downloading music is simple and fast. Here's how it works.", - "@tutorialDownloadDesc": {"description": "Tutorial download page description"}, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", - "@tutorialDownloadTip1": {"description": "Tutorial download tip 1"}, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", - "@tutorialDownloadTip2": {"description": "Tutorial download tip 2"}, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", - "@tutorialDownloadTip3": {"description": "Tutorial download tip 3"}, - + "@tutorialDownloadDesc": { + "description": "Tutorial download page description" + }, "tutorialLibraryTitle": "Your Library", - "@tutorialLibraryTitle": {"description": "Tutorial library page title"}, + "@tutorialLibraryTitle": { + "description": "Tutorial library page title" + }, "tutorialLibraryDesc": "All your downloaded music is organized in the Library tab.", - "@tutorialLibraryDesc": {"description": "Tutorial library page description"}, + "@tutorialLibraryDesc": { + "description": "Tutorial library page description" + }, "tutorialLibraryTip1": "View download progress and queue in the Library tab", - "@tutorialLibraryTip1": {"description": "Tutorial library tip 1"}, + "@tutorialLibraryTip1": { + "description": "Tutorial library tip 1" + }, "tutorialLibraryTip2": "Tap any track to play it with your music player", - "@tutorialLibraryTip2": {"description": "Tutorial library tip 2"}, + "@tutorialLibraryTip2": { + "description": "Tutorial library tip 2" + }, "tutorialLibraryTip3": "Switch between list and grid view for better browsing", - "@tutorialLibraryTip3": {"description": "Tutorial library tip 3"}, - + "@tutorialLibraryTip3": { + "description": "Tutorial library tip 3" + }, "tutorialExtensionsTitle": "Extensions", - "@tutorialExtensionsTitle": {"description": "Tutorial extensions page title"}, + "@tutorialExtensionsTitle": { + "description": "Tutorial extensions page title" + }, "tutorialExtensionsDesc": "Extend the app's capabilities with community extensions.", - "@tutorialExtensionsDesc": {"description": "Tutorial extensions page description"}, + "@tutorialExtensionsDesc": { + "description": "Tutorial extensions page description" + }, "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", - "@tutorialExtensionsTip1": {"description": "Tutorial extensions tip 1"}, + "@tutorialExtensionsTip1": { + "description": "Tutorial extensions tip 1" + }, "tutorialExtensionsTip2": "Add new download providers or search sources", - "@tutorialExtensionsTip2": {"description": "Tutorial extensions tip 2"}, + "@tutorialExtensionsTip2": { + "description": "Tutorial extensions tip 2" + }, "tutorialExtensionsTip3": "Get lyrics, enhanced metadata, and more features", - "@tutorialExtensionsTip3": {"description": "Tutorial extensions tip 3"}, - + "@tutorialExtensionsTip3": { + "description": "Tutorial extensions tip 3" + }, "tutorialSettingsTitle": "Customize Your Experience", - "@tutorialSettingsTitle": {"description": "Tutorial settings page title"}, + "@tutorialSettingsTitle": { + "description": "Tutorial settings page title" + }, "tutorialSettingsDesc": "Personalize the app in Settings to match your preferences.", - "@tutorialSettingsDesc": {"description": "Tutorial settings page description"}, + "@tutorialSettingsDesc": { + "description": "Tutorial settings page description" + }, "tutorialSettingsTip1": "Change download location and folder organization", - "@tutorialSettingsTip1": {"description": "Tutorial settings tip 1"}, + "@tutorialSettingsTip1": { + "description": "Tutorial settings tip 1" + }, "tutorialSettingsTip2": "Set default audio quality and format preferences", - "@tutorialSettingsTip2": {"description": "Tutorial settings tip 2"}, + "@tutorialSettingsTip2": { + "description": "Tutorial settings tip 2" + }, "tutorialSettingsTip3": "Customize app theme and appearance", - "@tutorialSettingsTip3": {"description": "Tutorial settings tip 3"}, - + "@tutorialSettingsTip3": { + "description": "Tutorial settings tip 3" + }, "tutorialReadyMessage": "You're all set! Start downloading your favorite music now.", - "@tutorialReadyMessage": {"description": "Tutorial completion message"}, - "tutorialExample": "EXAMPLE", - "@tutorialExample": {"description": "Example label in tutorial"}, - + "@tutorialReadyMessage": { + "description": "Tutorial completion message" + }, "libraryForceFullScan": "Force Full Scan", - "@libraryForceFullScan": {"description": "Button to force a complete rescan of library"}, + "@libraryForceFullScan": { + "description": "Button to force a complete rescan of library" + }, "libraryForceFullScanSubtitle": "Rescan all files, ignoring cache", - "@libraryForceFullScanSubtitle": {"description": "Subtitle for force full scan button"}, - + "@libraryForceFullScanSubtitle": { + "description": "Subtitle for force full scan button" + }, "cleanupOrphanedDownloads": "Cleanup Orphaned Downloads", - "@cleanupOrphanedDownloads": {"description": "Button to remove history entries for deleted files"}, + "@cleanupOrphanedDownloads": { + "description": "Button to remove history entries for deleted files" + }, "cleanupOrphanedDownloadsSubtitle": "Remove history entries for files that no longer exist", - "@cleanupOrphanedDownloadsSubtitle": {"description": "Subtitle for orphaned cleanup button"}, + "@cleanupOrphanedDownloadsSubtitle": { + "description": "Subtitle for orphaned cleanup button" + }, "cleanupOrphanedDownloadsResult": "Removed {count} orphaned entries from history", "@cleanupOrphanedDownloadsResult": { "description": "Snackbar after orphan cleanup", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "cleanupOrphanedDownloadsNone": "No orphaned entries found", - "@cleanupOrphanedDownloadsNone": {"description": "Snackbar when no orphans found"}, - + "@cleanupOrphanedDownloadsNone": { + "description": "Snackbar when no orphans found" + }, "cacheTitle": "Storage & Cache", - "@cacheTitle": {"description": "Cache management page title"}, + "@cacheTitle": { + "description": "Cache management page title" + }, "cacheSummaryTitle": "Cache overview", - "@cacheSummaryTitle": {"description": "Heading for cache summary card"}, + "@cacheSummaryTitle": { + "description": "Heading for cache summary card" + }, "cacheSummarySubtitle": "Clearing cache will not remove downloaded music files.", - "@cacheSummarySubtitle": {"description": "Helper text for cache summary card"}, + "@cacheSummarySubtitle": { + "description": "Helper text for cache summary card" + }, "cacheEstimatedTotal": "Estimated cache usage: {size}", "@cacheEstimatedTotal": { "description": "Total cache size shown in summary", "placeholders": { - "size": {"type": "String"} + "size": { + "type": "String" + } } }, "cacheSectionStorage": "Cached Data", - "@cacheSectionStorage": {"description": "Section header for cache entries"}, + "@cacheSectionStorage": { + "description": "Section header for cache entries" + }, "cacheSectionMaintenance": "Maintenance", - "@cacheSectionMaintenance": {"description": "Section header for cleanup actions"}, + "@cacheSectionMaintenance": { + "description": "Section header for cleanup actions" + }, "cacheAppDirectory": "App cache directory", - "@cacheAppDirectory": {"description": "Cache item title for app cache directory"}, + "@cacheAppDirectory": { + "description": "Cache item title for app cache directory" + }, "cacheAppDirectoryDesc": "HTTP responses, WebView data, and other temporary app data.", - "@cacheAppDirectoryDesc": {"description": "Description of what app cache directory contains"}, + "@cacheAppDirectoryDesc": { + "description": "Description of what app cache directory contains" + }, "cacheTempDirectory": "Temporary directory", - "@cacheTempDirectory": {"description": "Cache item title for temporary files directory"}, + "@cacheTempDirectory": { + "description": "Cache item title for temporary files directory" + }, "cacheTempDirectoryDesc": "Temporary files from downloads and audio conversion.", - "@cacheTempDirectoryDesc": {"description": "Description of what temporary directory contains"}, + "@cacheTempDirectoryDesc": { + "description": "Description of what temporary directory contains" + }, "cacheCoverImage": "Cover image cache", - "@cacheCoverImage": {"description": "Cache item title for persistent cover images"}, + "@cacheCoverImage": { + "description": "Cache item title for persistent cover images" + }, "cacheCoverImageDesc": "Downloaded album and track cover art. Will re-download when viewed.", - "@cacheCoverImageDesc": {"description": "Description of what cover image cache contains"}, + "@cacheCoverImageDesc": { + "description": "Description of what cover image cache contains" + }, "cacheLibraryCover": "Library cover cache", - "@cacheLibraryCover": {"description": "Cache item title for local library cover art images"}, + "@cacheLibraryCover": { + "description": "Cache item title for local library cover art images" + }, "cacheLibraryCoverDesc": "Cover art extracted from local music files. Will re-extract on next scan.", - "@cacheLibraryCoverDesc": {"description": "Description of what library cover cache contains"}, + "@cacheLibraryCoverDesc": { + "description": "Description of what library cover cache contains" + }, "cacheExploreFeed": "Explore feed cache", - "@cacheExploreFeed": {"description": "Cache item title for explore home feed cache"}, + "@cacheExploreFeed": { + "description": "Cache item title for explore home feed cache" + }, "cacheExploreFeedDesc": "Explore tab content (new releases, trending). Will refresh on next visit.", - "@cacheExploreFeedDesc": {"description": "Description of what explore feed cache contains"}, + "@cacheExploreFeedDesc": { + "description": "Description of what explore feed cache contains" + }, "cacheTrackLookup": "Track lookup cache", - "@cacheTrackLookup": {"description": "Cache item title for track ID lookup cache"}, + "@cacheTrackLookup": { + "description": "Cache item title for track ID lookup cache" + }, "cacheTrackLookupDesc": "Spotify/Deezer track ID lookups. Clearing may slow next few searches.", - "@cacheTrackLookupDesc": {"description": "Description of what track lookup cache contains"}, + "@cacheTrackLookupDesc": { + "description": "Description of what track lookup cache contains" + }, "cacheCleanupUnusedDesc": "Remove orphaned download history and library entries for missing files.", - "@cacheCleanupUnusedDesc": {"description": "Description of what cleanup unused data does"}, + "@cacheCleanupUnusedDesc": { + "description": "Description of what cleanup unused data does" + }, "cacheNoData": "No cached data", - "@cacheNoData": {"description": "Label when cache category has no data"}, + "@cacheNoData": { + "description": "Label when cache category has no data" + }, "cacheSizeWithFiles": "{size} in {count} files", "@cacheSizeWithFiles": { "description": "Cache size and file count", "placeholders": { - "size": {"type": "String"}, - "count": {"type": "int"} + "size": { + "type": "String" + }, + "count": { + "type": "int" + } } }, "cacheSizeOnly": "{size}", "@cacheSizeOnly": { "description": "Cache size only", "placeholders": { - "size": {"type": "String"} + "size": { + "type": "String" + } } }, "cacheEntries": "{count} entries", "@cacheEntries": { "description": "Track cache entry count", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "cacheClearSuccess": "Cleared: {target}", "@cacheClearSuccess": { "description": "Snackbar after clearing selected cache", "placeholders": { - "target": {"type": "String"} + "target": { + "type": "String" + } } }, "cacheClearConfirmTitle": "Clear cache?", - "@cacheClearConfirmTitle": {"description": "Dialog title before clearing one cache category"}, + "@cacheClearConfirmTitle": { + "description": "Dialog title before clearing one cache category" + }, "cacheClearConfirmMessage": "This will clear cached data for {target}. Downloaded music files will not be deleted.", "@cacheClearConfirmMessage": { "description": "Dialog message before clearing selected cache", "placeholders": { - "target": {"type": "String"} + "target": { + "type": "String" + } } }, "cacheClearAllConfirmTitle": "Clear all cache?", - "@cacheClearAllConfirmTitle": {"description": "Dialog title before clearing all caches"}, + "@cacheClearAllConfirmTitle": { + "description": "Dialog title before clearing all caches" + }, "cacheClearAllConfirmMessage": "This will clear all cache categories on this page. Downloaded music files will not be deleted.", - "@cacheClearAllConfirmMessage": {"description": "Dialog message before clearing all caches"}, + "@cacheClearAllConfirmMessage": { + "description": "Dialog message before clearing all caches" + }, "cacheClearAll": "Clear all cache", - "@cacheClearAll": {"description": "Button label to clear all caches"}, + "@cacheClearAll": { + "description": "Button label to clear all caches" + }, "cacheCleanupUnused": "Cleanup unused data", - "@cacheCleanupUnused": {"description": "Action title for cleaning unused entries"}, + "@cacheCleanupUnused": { + "description": "Action title for cleaning unused entries" + }, "cacheCleanupUnusedSubtitle": "Remove orphaned download history and missing library entries", - "@cacheCleanupUnusedSubtitle": {"description": "Subtitle for cleanup unused data action"}, + "@cacheCleanupUnusedSubtitle": { + "description": "Subtitle for cleanup unused data action" + }, "cacheCleanupResult": "Cleanup completed: {downloadCount} orphaned downloads, {libraryCount} missing library entries", "@cacheCleanupResult": { "description": "Snackbar after unused data cleanup", "placeholders": { - "downloadCount": {"type": "int"}, - "libraryCount": {"type": "int"} + "downloadCount": { + "type": "int" + }, + "libraryCount": { + "type": "int" + } } }, "cacheRefreshStats": "Refresh stats", - "@cacheRefreshStats": {"description": "Button label to refresh cache statistics"}, - + "@cacheRefreshStats": { + "description": "Button label to refresh cache statistics" + }, "trackSaveCoverArt": "Save Cover Art", - "@trackSaveCoverArt": {"description": "Menu action - save album cover art as file"}, + "@trackSaveCoverArt": { + "description": "Menu action - save album cover art as file" + }, "trackSaveCoverArtSubtitle": "Save album art as .jpg file", - "@trackSaveCoverArtSubtitle": {"description": "Subtitle for save cover art action"}, + "@trackSaveCoverArtSubtitle": { + "description": "Subtitle for save cover art action" + }, "trackSaveLyrics": "Save Lyrics (.lrc)", - "@trackSaveLyrics": {"description": "Menu action - save lyrics as .lrc file"}, + "@trackSaveLyrics": { + "description": "Menu action - save lyrics as .lrc file" + }, "trackSaveLyricsSubtitle": "Fetch and save lyrics as .lrc file", - "@trackSaveLyricsSubtitle": {"description": "Subtitle for save lyrics action"}, + "@trackSaveLyricsSubtitle": { + "description": "Subtitle for save lyrics action" + }, "trackSaveLyricsProgress": "Saving lyrics...", - "@trackSaveLyricsProgress": {"description": "Snackbar while saving lyrics to file"}, + "@trackSaveLyricsProgress": { + "description": "Snackbar while saving lyrics to file" + }, "trackReEnrich": "Re-enrich", - "@trackReEnrich": {"description": "Menu action - re-embed metadata into audio file"}, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", - "@trackReEnrichSubtitle": {"description": "Subtitle for re-enrich metadata action"}, + "@trackReEnrich": { + "description": "Menu action - re-embed metadata into audio file" + }, "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", - "@trackReEnrichOnlineSubtitle": {"description": "Subtitle for re-enrich metadata action for local items"}, + "@trackReEnrichOnlineSubtitle": { + "description": "Subtitle for re-enrich metadata action for local items" + }, "trackEditMetadata": "Edit Metadata", - "@trackEditMetadata": {"description": "Menu action - edit embedded metadata"}, + "@trackEditMetadata": { + "description": "Menu action - edit embedded metadata" + }, "trackCoverSaved": "Cover art saved to {fileName}", "@trackCoverSaved": { "description": "Snackbar after cover art saved", "placeholders": { - "fileName": {"type": "String"} + "fileName": { + "type": "String" + } } }, "trackCoverNoSource": "No cover art source available", - "@trackCoverNoSource": {"description": "Snackbar when no cover art URL or embedded cover"}, + "@trackCoverNoSource": { + "description": "Snackbar when no cover art URL or embedded cover" + }, "trackLyricsSaved": "Lyrics saved to {fileName}", "@trackLyricsSaved": { "description": "Snackbar after lyrics saved", "placeholders": { - "fileName": {"type": "String"} + "fileName": { + "type": "String" + } } }, "trackReEnrichProgress": "Re-enriching metadata...", - "@trackReEnrichProgress": {"description": "Snackbar while re-enriching metadata"}, + "@trackReEnrichProgress": { + "description": "Snackbar while re-enriching metadata" + }, "trackReEnrichSearching": "Searching metadata online...", - "@trackReEnrichSearching": {"description": "Snackbar while searching metadata from internet for local items"}, + "@trackReEnrichSearching": { + "description": "Snackbar while searching metadata from internet for local items" + }, "trackReEnrichSuccess": "Metadata re-enriched successfully", - "@trackReEnrichSuccess": {"description": "Snackbar after successful re-enrichment"}, + "@trackReEnrichSuccess": { + "description": "Snackbar after successful re-enrichment" + }, "trackReEnrichFfmpegFailed": "FFmpeg metadata embed failed", - "@trackReEnrichFfmpegFailed": {"description": "Snackbar when FFmpeg embed fails for MP3/Opus"}, + "@trackReEnrichFfmpegFailed": { + "description": "Snackbar when FFmpeg embed fails for MP3/Opus" + }, "trackSaveFailed": "Failed: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", "placeholders": { - "error": {"type": "String"} + "error": { + "type": "String" + } } }, - "trackConvertFormat": "Convert Format", - "@trackConvertFormat": {"description": "Menu item - convert audio format"}, + "@trackConvertFormat": { + "description": "Menu item - convert audio format" + }, "trackConvertFormatSubtitle": "Convert to MP3 or Opus", - "@trackConvertFormatSubtitle": {"description": "Subtitle for convert format menu item"}, + "@trackConvertFormatSubtitle": { + "description": "Subtitle for convert format menu item" + }, "trackConvertTitle": "Convert Audio", - "@trackConvertTitle": {"description": "Title of convert bottom sheet"}, + "@trackConvertTitle": { + "description": "Title of convert bottom sheet" + }, "trackConvertTargetFormat": "Target Format", - "@trackConvertTargetFormat": {"description": "Label for format selection"}, + "@trackConvertTargetFormat": { + "description": "Label for format selection" + }, "trackConvertBitrate": "Bitrate", - "@trackConvertBitrate": {"description": "Label for bitrate selection"}, + "@trackConvertBitrate": { + "description": "Label for bitrate selection" + }, "trackConvertConfirmTitle": "Confirm Conversion", - "@trackConvertConfirmTitle": {"description": "Confirmation dialog title"}, + "@trackConvertConfirmTitle": { + "description": "Confirmation dialog title" + }, "trackConvertConfirmMessage": "Convert from {sourceFormat} to {targetFormat} at {bitrate}?\n\nThe original file will be deleted after conversion.", "@trackConvertConfirmMessage": { "description": "Confirmation dialog message", "placeholders": { - "sourceFormat": {"type": "String"}, - "targetFormat": {"type": "String"}, - "bitrate": {"type": "String"} + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + }, + "bitrate": { + "type": "String" + } } }, "trackConvertConverting": "Converting audio...", - "@trackConvertConverting": {"description": "Snackbar while converting"}, + "@trackConvertConverting": { + "description": "Snackbar while converting" + }, "trackConvertSuccess": "Converted to {format} successfully", "@trackConvertSuccess": { "description": "Snackbar after successful conversion", "placeholders": { - "format": {"type": "String"} + "format": { + "type": "String" + } } }, "trackConvertFailed": "Conversion failed", - "@trackConvertFailed": {"description": "Snackbar when conversion fails"}, - + "@trackConvertFailed": { + "description": "Snackbar when conversion fails" + }, "actionCreate": "Create", - "@actionCreate": {"description": "Generic action button - create"}, - + "@actionCreate": { + "description": "Generic action button - create" + }, "collectionFoldersTitle": "My folders", - "@collectionFoldersTitle": {"description": "Library section title for custom folders"}, + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, "collectionWishlist": "Wishlist", - "@collectionWishlist": {"description": "Custom folder for saved tracks to download later"}, + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, "collectionLoved": "Loved", - "@collectionLoved": {"description": "Custom folder for favorite tracks"}, + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, "collectionPlaylists": "Playlists", - "@collectionPlaylists": {"description": "Custom user playlists folder"}, + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, "collectionPlaylist": "Playlist", - "@collectionPlaylist": {"description": "Single playlist label"}, + "@collectionPlaylist": { + "description": "Single playlist label" + }, "collectionAddToPlaylist": "Add to playlist", - "@collectionAddToPlaylist": {"description": "Action to add a track to user playlist"}, + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, "collectionCreatePlaylist": "Create playlist", - "@collectionCreatePlaylist": {"description": "Action to create a new playlist"}, + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, "collectionNoPlaylistsYet": "No playlists yet", - "@collectionNoPlaylistsYet": {"description": "Empty state title when user has no playlists"}, + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", - "@collectionNoPlaylistsSubtitle": {"description": "Empty state subtitle when user has no playlists"}, + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", "@collectionPlaylistTracks": { "description": "Track count label for custom playlists", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "collectionAddedToPlaylist": "Added to \"{playlistName}\"", "@collectionAddedToPlaylist": { "description": "Snackbar after adding track to playlist", "placeholders": { - "playlistName": {"type": "String"} + "playlistName": { + "type": "String" + } } }, "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", "@collectionAlreadyInPlaylist": { "description": "Snackbar when track already exists in playlist", "placeholders": { - "playlistName": {"type": "String"} + "playlistName": { + "type": "String" + } } }, "collectionPlaylistCreated": "Playlist created", - "@collectionPlaylistCreated": {"description": "Snackbar after creating playlist"}, + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, "collectionPlaylistNameHint": "Playlist name", - "@collectionPlaylistNameHint": {"description": "Hint text for playlist name input"}, + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, "collectionPlaylistNameRequired": "Playlist name is required", - "@collectionPlaylistNameRequired": {"description": "Validation error for empty playlist name"}, + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, "collectionRenamePlaylist": "Rename playlist", - "@collectionRenamePlaylist": {"description": "Action to rename playlist"}, + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, "collectionDeletePlaylist": "Delete playlist", - "@collectionDeletePlaylist": {"description": "Action to delete playlist"}, + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", "@collectionDeletePlaylistMessage": { "description": "Confirmation message for deleting playlist", "placeholders": { - "playlistName": {"type": "String"} + "playlistName": { + "type": "String" + } } }, "collectionPlaylistDeleted": "Playlist deleted", - "@collectionPlaylistDeleted": {"description": "Snackbar after deleting playlist"}, + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, "collectionPlaylistRenamed": "Playlist renamed", - "@collectionPlaylistRenamed": {"description": "Snackbar after renaming playlist"}, + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, "collectionWishlistEmptyTitle": "Wishlist is empty", - "@collectionWishlistEmptyTitle": {"description": "Wishlist empty state title"}, + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", - "@collectionWishlistEmptySubtitle": {"description": "Wishlist empty state subtitle"}, + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, "collectionLovedEmptyTitle": "Loved folder is empty", - "@collectionLovedEmptyTitle": {"description": "Loved empty state title"}, + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", - "@collectionLovedEmptySubtitle": {"description": "Loved empty state subtitle"}, + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, "collectionPlaylistEmptyTitle": "Playlist is empty", - "@collectionPlaylistEmptyTitle": {"description": "Playlist empty state title"}, + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", - "@collectionPlaylistEmptySubtitle": {"description": "Playlist empty state subtitle"}, + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, "collectionRemoveFromPlaylist": "Remove from playlist", - "@collectionRemoveFromPlaylist": {"description": "Tooltip for removing track from playlist"}, + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, "collectionRemoveFromFolder": "Remove from folder", - "@collectionRemoveFromFolder": {"description": "Tooltip for removing track from wishlist/loved folder"}, + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, "collectionRemoved": "\"{trackName}\" removed", "@collectionRemoved": { "description": "Snackbar after removing a track from a collection", "placeholders": { - "trackName": {"type": "String"} + "trackName": { + "type": "String" + } } }, "collectionAddedToLoved": "\"{trackName}\" added to Loved", "@collectionAddedToLoved": { "description": "Snackbar after adding track to loved folder", "placeholders": { - "trackName": {"type": "String"} + "trackName": { + "type": "String" + } } }, "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", "@collectionRemovedFromLoved": { "description": "Snackbar after removing track from loved folder", "placeholders": { - "trackName": {"type": "String"} + "trackName": { + "type": "String" + } } }, "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", "@collectionAddedToWishlist": { "description": "Snackbar after adding track to wishlist", "placeholders": { - "trackName": {"type": "String"} + "trackName": { + "type": "String" + } } }, "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", "@collectionRemovedFromWishlist": { "description": "Snackbar after removing track from wishlist", "placeholders": { - "trackName": {"type": "String"} + "trackName": { + "type": "String" + } } }, - "trackOptionAddToLoved": "Add to Loved", - "@trackOptionAddToLoved": {"description": "Bottom sheet action label - add track to loved folder"}, + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, "trackOptionRemoveFromLoved": "Remove from Loved", - "@trackOptionRemoveFromLoved": {"description": "Bottom sheet action label - remove track from loved folder"}, + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, "trackOptionAddToWishlist": "Add to Wishlist", - "@trackOptionAddToWishlist": {"description": "Bottom sheet action label - add track to wishlist"}, + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, "trackOptionRemoveFromWishlist": "Remove from Wishlist", - "@trackOptionRemoveFromWishlist": {"description": "Bottom sheet action label - remove track from wishlist"}, - + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, "collectionPlaylistChangeCover": "Change cover image", - "@collectionPlaylistChangeCover": {"description": "Bottom sheet action to pick a custom cover image for a playlist"}, + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, "collectionPlaylistRemoveCover": "Remove cover image", - "@collectionPlaylistRemoveCover": {"description": "Bottom sheet action to remove custom cover image from a playlist"}, - + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", "@selectionShareCount": { "description": "Share button text with count in selection mode", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "selectionShareNoFiles": "No shareable files found", - "@selectionShareNoFiles": {"description": "Snackbar when no selected files exist on disk"}, + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", "@selectionConvertCount": { "description": "Convert button text with count in selection mode", "placeholders": { - "count": {"type": "int"} + "count": { + "type": "int" + } } }, "selectionConvertNoConvertible": "No convertible tracks selected", - "@selectionConvertNoConvertible": {"description": "Snackbar when no selected tracks support conversion"}, + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, "selectionBatchConvertConfirmTitle": "Batch Convert", - "@selectionBatchConvertConfirmTitle": {"description": "Confirmation dialog title for batch conversion"}, + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", "@selectionBatchConvertConfirmMessage": { "description": "Confirmation dialog message for batch conversion", "placeholders": { - "count": {"type": "int"}, - "format": {"type": "String"}, - "bitrate": {"type": "String"} + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } } }, "selectionBatchConvertProgress": "Converting {current} of {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", "placeholders": { - "current": {"type": "int"}, - "total": {"type": "int"} + "current": { + "type": "int" + }, + "total": { + "type": "int" + } } }, "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", "@selectionBatchConvertSuccess": { "description": "Snackbar after batch conversion completes", "placeholders": { - "success": {"type": "int"}, - "total": {"type": "int"}, - "format": {"type": "String"} + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } } }, - - "setupModeSelectionTitle": "Choose Your Mode", - "@setupModeSelectionTitle": {"description": "Title for mode selection step in setup wizard"}, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", - "@setupModeSelectionDescription": {"description": "Description for mode selection step"}, - "setupModeDownloaderTitle": "Downloader", - "@setupModeDownloaderTitle": {"description": "Title for downloader mode option"}, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", - "@setupModeDownloaderFeature1": {"description": "Downloader mode feature 1"}, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", - "@setupModeDownloaderFeature2": {"description": "Downloader mode feature 2"}, - "setupModeDownloaderFeature3": "Manage your local music library", - "@setupModeDownloaderFeature3": {"description": "Downloader mode feature 3"}, - "setupModeStreamingTitle": "Streaming", - "@setupModeStreamingTitle": {"description": "Title for streaming mode option"}, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", - "@setupModeStreamingFeature1": {"description": "Streaming mode feature 1"}, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", - "@setupModeStreamingFeature2": {"description": "Streaming mode feature 2"}, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", - "@setupModeStreamingFeature3": {"description": "Streaming mode feature 3"}, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", - "@setupModeChangeableLater": {"description": "Hint that mode can be changed later"} + "downloadedAlbumDownloadedCount": "{count} downloaded", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" + }, + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" + } } From 11bda9aae5e2d28ca5d67d9d3a63cbb2f83064cc Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:36 +0700 Subject: [PATCH 075/104] New translations app_en.arb (French) --- lib/l10n/arb/app_fr.arb | 1241 +-------------------------------------- 1 file changed, 14 insertions(+), 1227 deletions(-) diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index dacc6646..3ddb731a 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -5,10 +5,6 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "appDescription": "Téléchargez des pistes Spotify en qualité sans perte de Tidal, Qobuz et Amazon Music.", - "@appDescription": { - "description": "App description shown in about page" - }, "navHome": "Accueil", "@navHome": { "description": "Bottom navigation - Home tab" @@ -17,10 +13,6 @@ "@navLibrary": { "description": "Bottom navigation - Library tab" }, - "navHistory": "Historique", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, "navSettings": "Paramètres", "@navSettings": { "description": "Bottom navigation - Settings tab" @@ -33,20 +25,6 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSearchHint": "Coller l'URL Spotify ou rechercher...", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "Rechercher avec {extensionName}...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, "homeSubtitle": "Coller un lien Spotify ou rechercher par nom", "@homeSubtitle": { "description": "Subtitle shown below search box" @@ -59,24 +37,6 @@ "@homeRecent": { "description": "Section header for recent searches" }, - "historyTitle": "Historique", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "Téléchargement ({count})", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "Téléchargé", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, "historyFilterAll": "Tous", "@historyFilterAll": { "description": "Filter chip - show all items" @@ -89,48 +49,6 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural, =1{1 album} other{{count} albums}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "Pas d'historique de téléchargement", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "Les pistes téléchargées apparaîtront ici", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "Pas de téléchargement d'album", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "Téléchargez plusieurs titres d'un album pour les voir ici", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "Pas de téléchargements uniques", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "Les téléchargements de pistes uniques apparaîtront ici", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, "historySearchHint": "Historique de recherche...", "@historySearchHint": { "description": "Search bar placeholder in history" @@ -163,34 +81,6 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "Télécharger Localisation", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "Choisissez où enregistrer des fichiers", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "Localisation par défaut", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "Service par défaut", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "Service utilisé pour les téléchargements", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "Qualité par défaut", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "Demandez La Qualité Avant Le Téléchargement", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, "downloadAskQualitySubtitle": "Afficher le sélecteur de qualité pour chaque téléchargement", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" @@ -203,38 +93,10 @@ "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "Titres séparés", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "Mettre des pistes uniques dans un dossier séparé", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "Meilleur Disponible", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 kbps", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 kbps", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, "appearanceTitle": "Apparence", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "Thème", - "@appearanceTheme": { - "description": "Theme mode setting" - }, "appearanceThemeSystem": "Système", "@appearanceThemeSystem": { "description": "Follow system theme" @@ -255,10 +117,6 @@ "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "Couleur d'accent", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, "appearanceHistoryView": "Historique Vue", "@appearanceHistoryView": { "description": "Layout style for history" @@ -275,10 +133,6 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "Recherche Source", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, "optionsPrimaryProvider": "Fournisseur principal", "@optionsPrimaryProvider": { "description": "Main search provider setting" @@ -308,38 +162,6 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", - "@optionsAutoSkipUnavailableTracks": { - "description": "Toggle to skip to the next queue track when current track stream resolution fails" - }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": { - "description": "Subtitle when auto skip on resolve failure is enabled" - }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": { - "description": "Subtitle when auto skip on resolve failure is disabled" - }, - "optionsInteractionMode": "Interaction Mode", - "@optionsInteractionMode": { - "description": "Tap behavior mode for track lists" - }, - "modeDownloader": "Downloader Mode", - "@modeDownloader": { - "description": "Interaction mode where taps queue downloads" - }, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", - "@modeDownloaderSubtitle": { - "description": "Subtitle for downloader interaction mode" - }, - "modeStreaming": "Streaming Mode", - "@modeStreaming": { - "description": "Interaction mode where taps start playback" - }, - "modeStreamingSubtitle": "Tap tracks to play instantly", - "@modeStreamingSubtitle": { - "description": "Subtitle for streaming interaction mode" - }, "optionsUseExtensionProviders": "Utiliser des fournisseurs d'extension", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -470,22 +292,6 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "Installed Extensions", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "No extensions installed", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "Install extensions from the Store tab", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "Enabled", - "@extensionsEnabled": { - "description": "Extension status - active" - }, "extensionsDisabled": "Disabled", "@extensionsDisabled": { "description": "Extension status - inactive" @@ -512,10 +318,6 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "Défini comme fournisseur de recherche", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, "storeTitle": "Magasin d'extension", "@storeTitle": { "description": "Store screen title" @@ -612,10 +414,6 @@ "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "Support", - "@aboutSupport": { - "description": "Section for support/donation links" - }, "aboutApp": "App", "@aboutApp": { "description": "Section for app info" @@ -636,14 +434,6 @@ "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "Amazing API for Amazon Music downloads. Thank you for making it free!", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, "aboutDabMusic": "DAB Music", "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" @@ -664,35 +454,6 @@ "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "Album", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural, =1{1 track} other{{count} tracks}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "Download All", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "Download Remaining", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "Playlist", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "Artist", - "@artistTitle": { - "description": "Artist screen title" - }, "artistAlbums": "Albums", "@artistAlbums": { "description": "Section header for artist albums" @@ -705,15 +466,6 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, =1{1 release} other{{count} releases}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "artistPopular": "Popular", "@artistPopular": { "description": "Section header for popular/top tracks" @@ -728,34 +480,6 @@ } } }, - "trackMetadataTitle": "Track Info", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "Artist", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "Album", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "Duration", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "File Path", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "Downloaded", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, "trackMetadataService": "Service", "@trackMetadataService": { "description": "Metadata field - download service used" @@ -772,54 +496,10 @@ "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "Re-télécharger", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "Dossier ouvert", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "Bienvenue chez SpotiFLAC", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "On va commencer", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "Permission de stockage", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "Requis pour enregistrer les fichiers téléchargés", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "Permission accordée", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "Permission refusée", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, "setupGrantPermission": "Grant Permission", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "Download Location", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "Choose Folder", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "Continue", - "@setupContinue": { - "description": "Continue to next step button" - }, "setupSkip": "Skip for now", "@setupSkip": { "description": "Skip current step button" @@ -828,10 +508,6 @@ "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, "setupStorageAccessMessageAndroid11": "Android 11+ requires \"All files access\" permission to save files to your chosen download folder.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" @@ -863,10 +539,6 @@ } } }, - "setupSelectDownloadFolder": "Select Download Folder", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, "setupUseDefaultFolder": "Use Default Folder?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" @@ -915,26 +587,6 @@ "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "Storage", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "Notification", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "Folder", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "Permission", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, "setupStorageGranted": "Storage Permission Granted!", "@setupStorageGranted": { "description": "Success message for storage permission" @@ -955,14 +607,6 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "Get notified when downloads complete or require attention.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "Dossier de téléchargement sélectionné!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, "setupFolderChoose": "Choisissez le dossier pour télécharger", "@setupFolderChoose": { "description": "Button to choose folder" @@ -971,58 +615,14 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "Change Folder", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, "setupSelectFolder": "Select Folder", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "Spotify API (Optional)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Add your Spotify API credentials for better search results and access to Spotify-exclusive content.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Use Spotify API", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "Enter your credentials below", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Using Deezer (no account needed)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Enter Spotify Client ID", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Enter Spotify Client Secret", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Get your free API credentials from the Spotify Developer Dashboard.", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, "setupEnableNotifications": "Enable Notifications", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "You can now proceed to the next step.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "You will receive download progress notifications.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, "setupNotificationBackgroundDescription": "Get notified about download progress and completion. This helps you track downloads when the app is in background.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" @@ -1031,10 +631,6 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "Back", - "@setupBack": { - "description": "Back button text" - }, "setupNext": "Next", "@setupNext": { "description": "Next button text" @@ -1043,26 +639,14 @@ "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "Skip & Start", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, "setupAllowAccessToManageFiles": "Please enable \"Allow access to manage all files\" in the next screen.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "Get credentials from developer.spotify.com", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, "dialogCancel": "Cancel", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "OK", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, "dialogSave": "Save", "@dialogSave": { "description": "Dialog button - save changes" @@ -1075,26 +659,10 @@ "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "Close", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "Yes", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "No", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, "dialogClear": "Clear", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "Confirm", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, "dialogDone": "Done", "@dialogDone": { "description": "Dialog button - action completed" @@ -1123,34 +691,10 @@ "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "Download Failed", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "Track:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "Artist:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "Error:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, "dialogClearAll": "Clear All", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "Are you sure you want to clear all downloads?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "Remove from device?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, "dialogRemoveExtension": "Remove Extension", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" @@ -1289,15 +833,6 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "Failed to load: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, "snackbarUrlCopied": "{platform} URL copied to clipboard", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", @@ -1358,24 +893,10 @@ "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "Failed to load {item}", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, "errorNoTracksFound": "No tracks found", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Seeking is not supported for this live stream", - "@errorSeekNotSupported": { - "description": "Error - seek disabled for live decrypted stream" - }, "errorMissingExtensionSource": "Cannot load {item}: missing extension source", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1385,34 +906,6 @@ } } }, - "statusQueued": "Queued", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "Downloading", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "Finalizing", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "Completed", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "Failed", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "Skipped", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "Paused", - "@statusPaused": { - "description": "Download status - paused" - }, "actionPause": "Pause", "@actionPause": { "description": "Action button - pause download" @@ -1425,14 +918,6 @@ "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "Stop", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "Select", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, "actionSelectAll": "Select All", "@actionSelectAll": { "description": "Action button - select all items" @@ -1441,14 +926,6 @@ "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "Paste", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "Import CSV", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, "actionRemoveCredentials": "Remove Credentials", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" @@ -1470,19 +947,6 @@ "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "Tap tracks to select", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "Delete {count} {count, plural, =1{track} other{tracks}}", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "selectionSelectToDelete": "Select tracks to delete", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" @@ -1523,51 +987,10 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "Cancel", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "Stop", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "Retry", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "Remove", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "Clear", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "Paste", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, "filenameFormat": "Filename Format", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "Preview: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "Available placeholders:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, "filenameShowAdvancedTags": "Show advanced tags", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -1576,10 +999,6 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "Folder Organization", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, "folderOrganizationNone": "No organization", "@folderOrganizationNone": { "description": "Folder option - flat structure" @@ -1620,27 +1039,10 @@ "@updateAvailable": { "description": "Update dialog title" }, - "updateNewVersion": "Version {version} is available", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "Download", - "@updateDownload": { - "description": "Update button - download update" - }, "updateLater": "Later", "@updateLater": { "description": "Update button - dismiss" }, - "updateChangelog": "Changelog", - "@updateChangelog": { - "description": "Link to changelog" - }, "updateStartingDownload": "Starting download...", "@updateStartingDownload": { "description": "Update status - initializing" @@ -1681,14 +1083,6 @@ "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriority": "Provider Priority", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "Drag to reorder download providers", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, "providerPriorityTitle": "Provider Priority", "@providerPriorityTitle": { "description": "Provider priority page title" @@ -1709,14 +1103,6 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriority": "Metadata Provider Priority", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Order used when fetching track metadata", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, "metadataProviderPriorityTitle": "Metadata Priority", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" @@ -1741,22 +1127,6 @@ "@logTitle": { "description": "Logs screen title" }, - "logCopy": "Copy Logs", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "Clear Logs", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "Share Logs", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "No logs yet", - "@logEmpty": { - "description": "Empty state title" - }, "logCopied": "Logs copied to clipboard", "@logCopied": { "description": "Snackbar - logs copied" @@ -1789,22 +1159,6 @@ "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "ISP BLOCKING DETECTED", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "RATE LIMITED", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "NETWORK ERROR", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "TRACK NOT FOUND", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, "logFilterBySeverity": "Filter logs by severity", "@logFilterBySeverity": { "description": "Filter dialog title" @@ -1817,60 +1171,6 @@ "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "Issue Summary", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "Your ISP may be blocking access to download services", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "Too many requests to the service", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "Wait a few minutes before trying again", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "Connection issues detected", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "Check your internet connection", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "Some tracks could not be found on download services", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "The track may not be available in lossless quality", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "Total errors: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "Affected: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, "logEntriesFiltered": "Entries ({count} filtered)", "@logEntriesFiltered": { "description": "Log count with filter active", @@ -2013,10 +1313,6 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "Choose your preferred language", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, "settingsAppearanceSubtitle": "Theme, colors, display", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" @@ -2045,10 +1341,6 @@ "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "Tracks", - "@tracksHeader": { - "description": "Section header for track list" - }, "downloadAllCount": "Download All ({count})", "@downloadAllCount": { "description": "Download all button with count", @@ -2058,15 +1350,6 @@ } } }, - "playAllCount": "Play All ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2204,15 +1487,6 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "Cannot open: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, "dateToday": "Today", "@dateToday": { "description": "Relative date - today" @@ -2248,22 +1522,6 @@ } } }, - "concurrentSequential": "Sequential", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 Parallel", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 Parallel", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "Tap to see error details", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, "storeFilterAll": "All", "@storeFilterAll": { "description": "Store filter - all extensions" @@ -2292,18 +1550,6 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "No extensions found", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "Provider Priority", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "Install Extension", - "@extensionInstallButton": { - "description": "Button to install extension" - }, "extensionDefaultProvider": "Default (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -2503,46 +1749,6 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Lossy", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Enable Lossy Option", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "Lossy quality option is available", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Lossy Format", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Choose the lossy format for conversion", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320kbps, best compatibility", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, "qualityNote": "Actual quality depends on track availability from the service", "@qualityNote": { "description": "Note about quality availability" @@ -2559,49 +1765,6 @@ "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": { - "type": "int" - }, - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateFieldLabel": "Bitrate (kbps)", - "@youtubeBitrateFieldLabel": { - "description": "Label for YouTube bitrate input field" - }, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2622,14 +1785,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -2642,14 +1797,6 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "Save Format", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "Select Service", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, "downloadSelectQuality": "Select Quality", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" @@ -2658,66 +1805,6 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "Default Quality", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "Best available", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "None", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "Save all files directly to download folder", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "Artist", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "Artist Name/filename", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "Album", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "Album Name/filename", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "Artist/Album", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "Artist Name/Album Name/filename", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, "appearanceAmoledDark": "AMOLED Dark", "@appearanceAmoledDark": { "description": "Theme option - pure black" @@ -2726,18 +1813,6 @@ "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "Choose Accent Color", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "Theme Mode", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "Download Queue", - "@queueTitle": { - "description": "Queue screen title" - }, "queueClearAll": "Clear All", "@queueClearAll": { "description": "Button - clear all queue items" @@ -2746,22 +1821,6 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "Export", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Failed downloads exported to TXT file", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "Clear Failed", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "Failed to export downloads", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, "settingsAutoExportFailed": "Auto-export failed downloads", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" @@ -2786,38 +1845,6 @@ "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, - "queueEmpty": "No downloads in queue", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "Add tracks from the home screen", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "Clear completed", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "Download Failed", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "Track:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "Artist:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "Error:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "Unknown error", - "@queueUnknownError": { - "description": "Fallback error message" - }, "albumFolderArtistAlbum": "Artist / Album", "@albumFolderArtistAlbum": { "description": "Album folder option" @@ -2871,19 +1898,6 @@ } } }, - "downloadedAlbumTracksHeader": "Tracks", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} downloaded", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, "downloadedAlbumSelectedCount": "{count} selected", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", @@ -2924,10 +1938,6 @@ } } }, - "utilityFunctions": "Utility Functions", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, "recentTypeArtist": "Artist", "@recentTypeArtist": { "description": "Recent access item type - artist" @@ -2962,32 +1972,14 @@ } } }, - "errorGeneric": "Error: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, "discographyDownload": "Download Discography", "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Play Discography", - "@discographyPlay": { - "description": "Button - play artist discography" - }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Play All", - "@discographyPlayAll": { - "description": "Option - play entire discography" - }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3069,10 +2061,6 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Play Selected", - "@discographyPlaySelected": { - "description": "Button - play selected albums" - }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3150,10 +2138,6 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Library Status", - "@libraryStatus": { - "description": "Section header for library status" - }, "libraryScanSettings": "Scan Settings", "@libraryScanSettings": { "description": "Section header for scan settings" @@ -3230,15 +2214,6 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} tracks", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", @@ -3362,26 +2337,6 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Date Added", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "Today", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "This Week", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "This Month", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "This Year", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -3394,15 +2349,6 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} filter(s) active", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -3425,106 +2371,6 @@ } } }, - "storageSwitchTitle": "Switch Storage Mode", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "Switch to SAF Storage?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "Switch to App Storage?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Existing Downloads", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "New Downloads", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "Continue", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "Select SAF Folder", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "App Storage", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "SAF Storage", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "Storage: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "Storage Statistics", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "{count} tracks in App Storage", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "{count} tracks in SAF Storage", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Your files are stored in multiple locations", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" @@ -3553,18 +2399,6 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, "tutorialDownloadTitle": "Downloading Music", "@tutorialDownloadTitle": { "description": "Tutorial download page title" @@ -3573,18 +2407,6 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, "tutorialLibraryTitle": "Your Library", "@tutorialLibraryTitle": { "description": "Tutorial library page title" @@ -3649,10 +2471,6 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "EXAMPLE", - "@tutorialExample": { - "description": "Example label in tutorial" - }, "libraryForceFullScan": "Force Full Scan", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" @@ -3879,10 +2697,6 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" @@ -4271,48 +3085,21 @@ } } }, - "setupModeSelectionTitle": "Choose Your Mode", - "@setupModeSelectionTitle": { - "description": "Title for mode selection step in setup wizard" + "downloadedAlbumDownloadedCount": "{count} downloaded", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } }, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", - "@setupModeSelectionDescription": { - "description": "Description for mode selection step" + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" }, - "setupModeDownloaderTitle": "Downloader", - "@setupModeDownloaderTitle": { - "description": "Title for downloader mode option" - }, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", - "@setupModeDownloaderFeature1": { - "description": "Downloader mode feature 1" - }, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", - "@setupModeDownloaderFeature2": { - "description": "Downloader mode feature 2" - }, - "setupModeDownloaderFeature3": "Manage your local music library", - "@setupModeDownloaderFeature3": { - "description": "Downloader mode feature 3" - }, - "setupModeStreamingTitle": "Streaming", - "@setupModeStreamingTitle": { - "description": "Title for streaming mode option" - }, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", - "@setupModeStreamingFeature1": { - "description": "Streaming mode feature 1" - }, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", - "@setupModeStreamingFeature2": { - "description": "Streaming mode feature 2" - }, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", - "@setupModeStreamingFeature3": { - "description": "Streaming mode feature 3" - }, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", - "@setupModeChangeableLater": { - "description": "Hint that mode can be changed later" + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" } } \ No newline at end of file From 05674d9586dd6a02f618a79a9f28aeba450e3d18 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:37 +0700 Subject: [PATCH 076/104] New translations app_en.arb (Spanish) --- lib/l10n/arb/app_es-ES.arb | 1241 +----------------------------------- 1 file changed, 14 insertions(+), 1227 deletions(-) diff --git a/lib/l10n/arb/app_es-ES.arb b/lib/l10n/arb/app_es-ES.arb index 76b14618..c48b16f4 100644 --- a/lib/l10n/arb/app_es-ES.arb +++ b/lib/l10n/arb/app_es-ES.arb @@ -5,10 +5,6 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "appDescription": "Descargue pistas de Spotify con calidad sin pérdida de Tidal, Qobuz y Amazon Music.", - "@appDescription": { - "description": "App description shown in about page" - }, "navHome": "Inicio", "@navHome": { "description": "Bottom navigation - Home tab" @@ -17,10 +13,6 @@ "@navLibrary": { "description": "Bottom navigation - Library tab" }, - "navHistory": "Historial", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, "navSettings": "Ajustes", "@navSettings": { "description": "Bottom navigation - Settings tab" @@ -33,20 +25,6 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSearchHint": "Pegar URL Spotify o buscar...", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "Buscar con {extensionName}...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, "homeSubtitle": "Pegar enlace de Spotify o buscar por nombre", "@homeSubtitle": { "description": "Subtitle shown below search box" @@ -59,24 +37,6 @@ "@homeRecent": { "description": "Section header for recent searches" }, - "historyTitle": "Historial", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "Descargando ({count})", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "Descargado", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, "historyFilterAll": "Todo", "@historyFilterAll": { "description": "Filter chip - show all items" @@ -89,48 +49,6 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, one {}=1{1 pista} other{{count} pistas}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural, one {}=1{1 álbum} other{{count} álbumes}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "No hay historial de descargas", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "Las pistas descargadas aparecerán aquí", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "No hay descargas de álbum", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "Descargar múltiples pistas de un álbum para verlas aquí", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "No hay descargas", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "Las descargas de una sola pista aparecerán aquí", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, "historySearchHint": "Buscar en historial...", "@historySearchHint": { "description": "Search bar placeholder in history" @@ -163,34 +81,6 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "Ubicación de descarga", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "Elija dónde guardar los archivos", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "Ubicación predeterminada", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "Servicio por defecto", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "Servicio usado para descargas", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "Calidad por defecto", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "Preguntar calidad antes de descargar", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, "downloadAskQualitySubtitle": "Mostrar selector de calidad para cada descarga", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" @@ -203,38 +93,10 @@ "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "Separar Pistas", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "Colocar pistas individuales en una carpeta separada", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "Mejor disponible", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 kbps", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 kbps", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, "appearanceTitle": "Apariencia", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "Tema", - "@appearanceTheme": { - "description": "Theme mode setting" - }, "appearanceThemeSystem": "Sistema", "@appearanceThemeSystem": { "description": "Follow system theme" @@ -255,10 +117,6 @@ "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "Color Secundario", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, "appearanceHistoryView": "Vista de Historial", "@appearanceHistoryView": { "description": "Layout style for history" @@ -275,10 +133,6 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "Buscar Fuente", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, "optionsPrimaryProvider": "Proveedor Principal", "@optionsPrimaryProvider": { "description": "Main search provider setting" @@ -308,38 +162,6 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", - "@optionsAutoSkipUnavailableTracks": { - "description": "Toggle to skip to the next queue track when current track stream resolution fails" - }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": { - "description": "Subtitle when auto skip on resolve failure is enabled" - }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": { - "description": "Subtitle when auto skip on resolve failure is disabled" - }, - "optionsInteractionMode": "Interaction Mode", - "@optionsInteractionMode": { - "description": "Tap behavior mode for track lists" - }, - "modeDownloader": "Downloader Mode", - "@modeDownloader": { - "description": "Interaction mode where taps queue downloads" - }, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", - "@modeDownloaderSubtitle": { - "description": "Subtitle for downloader interaction mode" - }, - "modeStreaming": "Streaming Mode", - "@modeStreaming": { - "description": "Interaction mode where taps start playback" - }, - "modeStreamingSubtitle": "Tap tracks to play instantly", - "@modeStreamingSubtitle": { - "description": "Subtitle for streaming interaction mode" - }, "optionsUseExtensionProviders": "Usar proveedores de extensiones", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -470,22 +292,6 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "Extensiones instaladas", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "No hay extensiones instaladas", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "Instalar extensiones desde la pestaña Tienda", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "Habilitado", - "@extensionsEnabled": { - "description": "Extension status - active" - }, "extensionsDisabled": "Deshabilitado", "@extensionsDisabled": { "description": "Extension status - inactive" @@ -512,10 +318,6 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "Establecer como proveedor de búsqueda", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, "storeTitle": "Tienda de extensiones", "@storeTitle": { "description": "Store screen title" @@ -612,10 +414,6 @@ "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "Soporte", - "@aboutSupport": { - "description": "Section for support/donation links" - }, "aboutApp": "Aplicación", "@aboutApp": { "description": "Section for app info" @@ -636,14 +434,6 @@ "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "API increible para descargas de Amazon Music. ¡Gracias por hacerla gratis!", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, "aboutDabMusic": "Música DAB", "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" @@ -664,35 +454,6 @@ "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "Álbum", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural, one {}=1{1 pista} other{{count} pistas}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "Descargar Todo", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "Descargas Restantes", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "Lista de reproducción", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "Artista", - "@artistTitle": { - "description": "Artist screen title" - }, "artistAlbums": "Álbumes", "@artistAlbums": { "description": "Section header for artist albums" @@ -705,15 +466,6 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, one {}=1{1 lanzamiento} other{{count} lanzamientos}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "artistPopular": "Populares", "@artistPopular": { "description": "Section header for popular/top tracks" @@ -728,34 +480,6 @@ } } }, - "trackMetadataTitle": "Información de pista", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "Artista", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "Álbum", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "Duración", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "Calidad", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "Ruta del archivo", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "Descargado", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, "trackMetadataService": "Servicio", "@trackMetadataService": { "description": "Metadata field - download service used" @@ -772,54 +496,10 @@ "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "Volver a descargar", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "Abrir carpeta", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "Bienvenido a SpotiFLAC", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "Comencemos", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "Permiso de almacenamiento", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "Necesario para guardar los archivos descargados", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "Permiso aprobado", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "Permiso denegado", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, "setupGrantPermission": "Conceder permiso", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "Ubicación de descarga", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "Seleccionar Carpeta", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "Continuar", - "@setupContinue": { - "description": "Continue to next step button" - }, "setupSkip": "Omitir por ahora", "@setupSkip": { "description": "Skip current step button" @@ -828,10 +508,6 @@ "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "SpotiFLAC necesita permiso de \"Todos los archivos de acceso\" para guardar los archivos de música en la carpeta elegida.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, "setupStorageAccessMessageAndroid11": "Android 11+ requiere permiso \"Todos los archivos de acceso\" para guardar los archivos en la carpeta de descargas elegida.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" @@ -863,10 +539,6 @@ } } }, - "setupSelectDownloadFolder": "Seleccionar carpeta de descarga", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, "setupUseDefaultFolder": "¿Usar carpeta por defecto?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" @@ -915,26 +587,6 @@ "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "Almacenamiento", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "Notificación", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "Carpeta", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "Permiso", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, "setupStorageGranted": "¡Permiso de almacenamiento concedido!", "@setupStorageGranted": { "description": "Success message for storage permission" @@ -955,14 +607,6 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "Recibe notificaciones cuando las descargas completen o requieran atención.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "¡Carpeta de descarga seleccionada!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, "setupFolderChoose": "Cambiar carpeta de descargas", "@setupFolderChoose": { "description": "Button to choose folder" @@ -971,58 +615,14 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "Cambiar carpeta", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, "setupSelectFolder": "Seleccionar Carpeta", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "API de Spotify (opcional)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Añade tus credenciales de la API de Spotify para mejores resultados de búsqueda y acceso al contenido exclusivo de Spotify.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Usar API de Spotify", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "Ingresa tus credenciales a continuación", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Usando Deezer (no se necesita cuenta)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Introduzca el ID de cliente de Spotify", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Ingresa el Client Secret de Spotify", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Obtén tus credenciales gratuitas de la API desde el Spotify Developer Dashboard.", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, "setupEnableNotifications": "Activar notificaciones", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "Ahora puedes continuar con el siguiente paso.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "Recibirás notificaciones de progreso de descargas.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, "setupNotificationBackgroundDescription": "Recibe notificaciones sobre el progreso de la descarga y la finalización. Esto te ayuda a rastrear las descargas cuando la aplicación está en segundo plano.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" @@ -1031,10 +631,6 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "Atrás", - "@setupBack": { - "description": "Back button text" - }, "setupNext": "Siguiente", "@setupNext": { "description": "Next button text" @@ -1043,26 +639,14 @@ "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "Saltar y empezar", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, "setupAllowAccessToManageFiles": "Por favor, activa \"Permitir el acceso para gestionar todos los archivos\" en la siguiente pantalla.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "Obtener credenciales de developer.spotify.com", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, "dialogCancel": "Cancelar", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "Aceptar", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, "dialogSave": "Guardar", "@dialogSave": { "description": "Dialog button - save changes" @@ -1075,26 +659,10 @@ "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "Cerrar", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "Sí", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "No", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, "dialogClear": "Borrar", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "Confirmar", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, "dialogDone": "Hecho", "@dialogDone": { "description": "Dialog button - action completed" @@ -1123,34 +691,10 @@ "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "Descarga fallida", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "Pista:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "Artista:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "Error:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, "dialogClearAll": "Eliminar todo", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "¿Estás seguro de que quieres borrar todas las descargas?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "¿Eliminar del dispositivo?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, "dialogRemoveExtension": "Eliminar extensión", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" @@ -1289,15 +833,6 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "Error al cargar: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, "snackbarUrlCopied": "URL {platform} copiada al portapapeles", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", @@ -1358,24 +893,10 @@ "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "Error al cargar {item}", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, "errorNoTracksFound": "No se encontraron pistas", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Seeking is not supported for this live stream", - "@errorSeekNotSupported": { - "description": "Error - seek disabled for live decrypted stream" - }, "errorMissingExtensionSource": "No se puede cargar {item}: falta una fuente de extensión", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1385,34 +906,6 @@ } } }, - "statusQueued": "En cola", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "Descargando", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "Finalizando", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "Completado", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "Error", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "Omitido", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "Pausado", - "@statusPaused": { - "description": "Download status - paused" - }, "actionPause": "Pausar", "@actionPause": { "description": "Action button - pause download" @@ -1425,14 +918,6 @@ "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "Detener", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "Seleccionar", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, "actionSelectAll": "Seleccionar Todo", "@actionSelectAll": { "description": "Action button - select all items" @@ -1441,14 +926,6 @@ "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "Pegar", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "Importar CSV", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, "actionRemoveCredentials": "Eliminar credenciales", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" @@ -1470,19 +947,6 @@ "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "Toca las pistas para seleccionar", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "¡Eliminar {count} {count, plural, one {}=1{pista} other{pistas}}", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "selectionSelectToDelete": "Seleccionar pistas a eliminar", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" @@ -1523,51 +987,10 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "Cancelar", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "Detener", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "Volver a intentar", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "Eliminar", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "Borrar", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "Pegar", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, "filenameFormat": "Formato del nombre del archivo", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "Vista previa: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "Marcadores disponibles:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, "filenameShowAdvancedTags": "Show advanced tags", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -1576,10 +999,6 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "Organización de carpetas", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, "folderOrganizationNone": "Ninguna organización", "@folderOrganizationNone": { "description": "Folder option - flat structure" @@ -1620,27 +1039,10 @@ "@updateAvailable": { "description": "Update dialog title" }, - "updateNewVersion": "Versión {version} está disponible", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "Descargar", - "@updateDownload": { - "description": "Update button - download update" - }, "updateLater": "Más tarde", "@updateLater": { "description": "Update button - dismiss" }, - "updateChangelog": "Historial de cambios", - "@updateChangelog": { - "description": "Link to changelog" - }, "updateStartingDownload": "Iniciando descarga...", "@updateStartingDownload": { "description": "Update status - initializing" @@ -1681,14 +1083,6 @@ "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriority": "Prioridad del proveedor", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "Arrastre para reordenar los proveedores de descarga", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, "providerPriorityTitle": "Prioridad del proveedor", "@providerPriorityTitle": { "description": "Provider priority page title" @@ -1709,14 +1103,6 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriority": "Prioridad del proveedor de metadatos", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Orden usado al recuperar metadatos de la pista", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, "metadataProviderPriorityTitle": "Prioridad de los metadatos", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" @@ -1741,22 +1127,6 @@ "@logTitle": { "description": "Logs screen title" }, - "logCopy": "Copiar Registros", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "Limpiar registros", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "Compartir Registros", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "No hay registros aún", - "@logEmpty": { - "description": "Empty state title" - }, "logCopied": "Registros copiados al portapapeles", "@logCopied": { "description": "Snackbar - logs copied" @@ -1789,22 +1159,6 @@ "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "BLOQUEO POR EL ISP DETECTADO", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "TASA LIMITADA", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "ERROR DE RED", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "PISTA NO ENCONTRADA", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, "logFilterBySeverity": "Filtrar los registros por gravedad", "@logFilterBySeverity": { "description": "Filter dialog title" @@ -1817,60 +1171,6 @@ "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "Resumen de Incidencias", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "Tu ISP puede estar bloqueando el acceso a los servicios de descarga", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "Intente usar una VPN o cambie el DNS a 1.1.1.1 o 8.8.8.8", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "Demasiadas solicitudes al servicio", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "Espere unos minutos antes de volver a intentarlo", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "Problemas de conexión detectados", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "Comprueba tu conexión a internet", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "No se pudieron encontrar algunas pistas en los servicios de descarga", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "La pista puede no estar disponible en calidad sin pérdida", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "Total de errores: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "Afectado: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, "logEntriesFiltered": "Entradas ({count} filtradas)", "@logEntriesFiltered": { "description": "Log count with filter active", @@ -2013,10 +1313,6 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "Elija su idioma preferido", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, "settingsAppearanceSubtitle": "Tema, colores, pantalla", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" @@ -2045,10 +1341,6 @@ "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "Pistas", - "@tracksHeader": { - "description": "Section header for track list" - }, "downloadAllCount": "Descargar Todo ({count})", "@downloadAllCount": { "description": "Download all button with count", @@ -2058,15 +1350,6 @@ } } }, - "playAllCount": "Play All ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "tracksCount": "{count, plural, one {}=1{1 pista} other{{count} pistas}}", "@tracksCount": { "description": "Track count display", @@ -2204,15 +1487,6 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "No se puede abrir: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, "dateToday": "Hoy", "@dateToday": { "description": "Relative date - today" @@ -2248,22 +1522,6 @@ } } }, - "concurrentSequential": "Secuencial", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 simultáneamente", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 simultáneamente", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "Pulse para ver los detalles del error", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, "storeFilterAll": "Todo", "@storeFilterAll": { "description": "Store filter - all extensions" @@ -2292,18 +1550,6 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "No se encontraron extensiones", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "Prioridad del proveedor", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "Instalar extensión", - "@extensionInstallButton": { - "description": "Button to install extension" - }, "extensionDefaultProvider": "Por defecto (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -2503,46 +1749,6 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Con pérdidas", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "MP3 320kbps (convertido desde FLAC)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128kbps (convertido de FLAC)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Habilitar opción con pérdida", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "La opción de calidad con pérdida está disponible", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Descargas FLAC y luego se convierten en formato con pérdida", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Formato con Perdido", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Elegir el formato con pérdida para la conversión", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320kbps, mejor compatibilidad", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128kbps, mejor calidad a menor tamaño", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, "qualityNote": "La calidad real depende de la disponibilidad de la pista del servicio", "@qualityNote": { "description": "Note about quality availability" @@ -2559,49 +1765,6 @@ "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": { - "type": "int" - }, - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateFieldLabel": "Bitrate (kbps)", - "@youtubeBitrateFieldLabel": { - "description": "Label for YouTube bitrate input field" - }, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, "downloadAskBeforeDownload": "Preguntar antes de descargar", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2622,14 +1785,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -2642,14 +1797,6 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "Guardar Formato", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "Seleccionar Servicio", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, "downloadSelectQuality": "Seleccionar Calidad", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" @@ -2658,66 +1805,6 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "Calidad por Defecto", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "La mejor disponible", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "Ninguna", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "Guardar todos los archivos directamente para descargar la carpeta", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "Artista", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "Nombre del Artista/nombre de archivo", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "Álbum", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "Nombre del álbum/nombre de archivo", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "Artista/Álbum", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "Nombre del Artista/Nombre del Álbum/Nombre del Archivo", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, "appearanceAmoledDark": "AMOLED Oscuro", "@appearanceAmoledDark": { "description": "Theme option - pure black" @@ -2726,18 +1813,6 @@ "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "Elegir color principal", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "Modo de tema", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "Descargas en proceso", - "@queueTitle": { - "description": "Queue screen title" - }, "queueClearAll": "Eliminar todo", "@queueClearAll": { "description": "Button - clear all queue items" @@ -2746,22 +1821,6 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "Exportar", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Descarga fallida exportada al archivo TXT", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "Limpieza Fallida", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "Error al exportar descargas", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, "settingsAutoExportFailed": "Autoexportar descargas fallidas", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" @@ -2786,38 +1845,6 @@ "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, - "queueEmpty": "No hay descargas en cola", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "Añadir pistas desde la pantalla de inicio", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "Limpiar tareas finalizadas", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "Descarga fallida", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "Pista:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "Artista:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "Error:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "Error desconocido", - "@queueUnknownError": { - "description": "Fallback error message" - }, "albumFolderArtistAlbum": "Artista / Álbum", "@albumFolderArtistAlbum": { "description": "Album folder option" @@ -2871,19 +1898,6 @@ } } }, - "downloadedAlbumTracksHeader": "Pistas", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} descargado", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, "downloadedAlbumSelectedCount": "{count} seleccionado", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", @@ -2924,10 +1938,6 @@ } } }, - "utilityFunctions": "Funciones de utilidad", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, "recentTypeArtist": "Artista", "@recentTypeArtist": { "description": "Recent access item type - artist" @@ -2962,32 +1972,14 @@ } } }, - "errorGeneric": "Error: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, "discographyDownload": "Descargar Discografía", "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Play Discography", - "@discographyPlay": { - "description": "Button - play artist discography" - }, "discographyDownloadAll": "Descargar Todo", "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Play All", - "@discographyPlayAll": { - "description": "Option - play entire discography" - }, "discographyDownloadAllSubtitle": "{count} pistas de {albumCount} lanzamientos", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3069,10 +2061,6 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Play Selected", - "@discographyPlaySelected": { - "description": "Button - play selected albums" - }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3150,10 +2138,6 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Library Status", - "@libraryStatus": { - "description": "Section header for library status" - }, "libraryScanSettings": "Scan Settings", "@libraryScanSettings": { "description": "Section header for scan settings" @@ -3230,15 +2214,6 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} tracks", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", @@ -3362,26 +2337,6 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Date Added", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "Today", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "This Week", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "This Month", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "This Year", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -3394,15 +2349,6 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} filter(s) active", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -3425,106 +2371,6 @@ } } }, - "storageSwitchTitle": "Switch Storage Mode", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "Switch to SAF Storage?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "Switch to App Storage?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Existing Downloads", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "New Downloads", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "Continue", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "Select SAF Folder", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "App Storage", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "SAF Storage", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "Storage: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "Storage Statistics", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "{count} tracks in App Storage", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "{count} tracks in SAF Storage", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Your files are stored in multiple locations", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" @@ -3553,18 +2399,6 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, "tutorialDownloadTitle": "Downloading Music", "@tutorialDownloadTitle": { "description": "Tutorial download page title" @@ -3573,18 +2407,6 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, "tutorialLibraryTitle": "Your Library", "@tutorialLibraryTitle": { "description": "Tutorial library page title" @@ -3649,10 +2471,6 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "EXAMPLE", - "@tutorialExample": { - "description": "Example label in tutorial" - }, "libraryForceFullScan": "Force Full Scan", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" @@ -3879,10 +2697,6 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" @@ -4271,48 +3085,21 @@ } } }, - "setupModeSelectionTitle": "Choose Your Mode", - "@setupModeSelectionTitle": { - "description": "Title for mode selection step in setup wizard" + "downloadedAlbumDownloadedCount": "{count} descargado", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } }, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", - "@setupModeSelectionDescription": { - "description": "Description for mode selection step" + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" }, - "setupModeDownloaderTitle": "Downloader", - "@setupModeDownloaderTitle": { - "description": "Title for downloader mode option" - }, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", - "@setupModeDownloaderFeature1": { - "description": "Downloader mode feature 1" - }, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", - "@setupModeDownloaderFeature2": { - "description": "Downloader mode feature 2" - }, - "setupModeDownloaderFeature3": "Manage your local music library", - "@setupModeDownloaderFeature3": { - "description": "Downloader mode feature 3" - }, - "setupModeStreamingTitle": "Streaming", - "@setupModeStreamingTitle": { - "description": "Title for streaming mode option" - }, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", - "@setupModeStreamingFeature1": { - "description": "Streaming mode feature 1" - }, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", - "@setupModeStreamingFeature2": { - "description": "Streaming mode feature 2" - }, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", - "@setupModeStreamingFeature3": { - "description": "Streaming mode feature 3" - }, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", - "@setupModeChangeableLater": { - "description": "Hint that mode can be changed later" + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" } } \ No newline at end of file From 32c10c2b232a7d4b91602ca782ae588d68b8f57a Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:38 +0700 Subject: [PATCH 077/104] New translations app_en.arb (German) --- lib/l10n/arb/app_de.arb | 1265 +-------------------------------------- 1 file changed, 26 insertions(+), 1239 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 1d46bf1d..139b620e 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -5,10 +5,6 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "appDescription": "Lade Spotify-Titel in verlustfreier Qualität von Tidal, Qobuz und Amazon Music herunter.", - "@appDescription": { - "description": "App description shown in about page" - }, "navHome": "Startseite", "@navHome": { "description": "Bottom navigation - Home tab" @@ -17,10 +13,6 @@ "@navLibrary": { "description": "Bottom navigation - Library tab" }, - "navHistory": "Verlauf", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, "navSettings": "Einstellungen", "@navSettings": { "description": "Bottom navigation - Settings tab" @@ -33,20 +25,6 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSearchHint": "Spotify-URL einfügen oder suchen...", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "Mit {extensionName} suchen...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, "homeSubtitle": "Spotify-Link einfügen oder nach Namen suchen", "@homeSubtitle": { "description": "Subtitle shown below search box" @@ -59,24 +37,6 @@ "@homeRecent": { "description": "Section header for recent searches" }, - "historyTitle": "Verlauf", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "Wird heruntergeladen ({count})", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "Heruntergeladen", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, "historyFilterAll": "Alle", "@historyFilterAll": { "description": "Filter chip - show all items" @@ -89,48 +49,6 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, =1{1 Titel} other{{count} Titel}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural, =1{1 Album} other{{count} Alben}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "Kein Download-Verlauf", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "Heruntergeladene Titel werden hier angezeigt", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "Keine Album-Downloads", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "Lade mehrere Titel eines Albums herunter, um sie hier zu sehen", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "Keine Einzel-Downloads", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "Einzelne Titel-Downloads werden hier angezeigt", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, "historySearchHint": "Suchverlauf...", "@historySearchHint": { "description": "Search bar placeholder in history" @@ -163,34 +81,6 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "Download-Speicherort", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "Wähle den Speicherort der Dateien", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "Standard-Speicherort", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "Standard-Dienst", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "Dienst für Downloads", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "Standard-Qualität", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "Qualität vor Download abfragen", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, "downloadAskQualitySubtitle": "Qualitätsauswahl für jeden Download anzeigen", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" @@ -203,38 +93,10 @@ "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "Singles trennen", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "Einzelne Titel in separatem Ordner speichern", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "Beste Qualität", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 kbps", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 kbps", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, "appearanceTitle": "Erscheinungsbild", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "Design", - "@appearanceTheme": { - "description": "Theme mode setting" - }, "appearanceThemeSystem": "System", "@appearanceThemeSystem": { "description": "Follow system theme" @@ -255,10 +117,6 @@ "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "Akzentfarbe", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, "appearanceHistoryView": "Verlaufsansicht", "@appearanceHistoryView": { "description": "Layout style for history" @@ -275,10 +133,6 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "Suchquelle", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, "optionsPrimaryProvider": "Primärer Anbieter", "@optionsPrimaryProvider": { "description": "Main search provider setting" @@ -308,38 +162,6 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", - "@optionsAutoSkipUnavailableTracks": { - "description": "Toggle to skip to the next queue track when current track stream resolution fails" - }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatisch zum nächsten Queue-Track springen, wenn ein Stream nicht aufgebaut werden kann.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": { - "description": "Subtitle when auto skip on resolve failure is enabled" - }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Beim Fehlschlagen der Titelerkennung anhalten und einen Fehler anzeigen.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": { - "description": "Subtitle when auto skip on resolve failure is disabled" - }, - "optionsInteractionMode": "Interaktionsmodus", - "@optionsInteractionMode": { - "description": "Tap behavior mode for track lists" - }, - "modeDownloader": "Downloader-Modus", - "@modeDownloader": { - "description": "Interaction mode where taps queue downloads" - }, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", - "@modeDownloaderSubtitle": { - "description": "Subtitle for downloader interaction mode" - }, - "modeStreaming": "Streaming-Modus", - "@modeStreaming": { - "description": "Interaction mode where taps start playback" - }, - "modeStreamingSubtitle": "Tap tracks to play instantly", - "@modeStreamingSubtitle": { - "description": "Subtitle for streaming interaction mode" - }, "optionsUseExtensionProviders": "Erweiterungs-Anbieter verwenden", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -470,22 +292,6 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "Installierte Erweiterungen", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "Keine Erweiterungen installiert", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "Erweiterungen aus dem Store-Tab installieren", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "Aktiviert", - "@extensionsEnabled": { - "description": "Extension status - active" - }, "extensionsDisabled": "Deaktiviert", "@extensionsDisabled": { "description": "Extension status - inactive" @@ -512,10 +318,6 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "Als Suchanbieter festlegen", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, "storeTitle": "Erweiterungs-Store", "@storeTitle": { "description": "Store screen title" @@ -612,10 +414,6 @@ "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "Support", - "@aboutSupport": { - "description": "Section for support/donation links" - }, "aboutApp": "App", "@aboutApp": { "description": "Section for app info" @@ -636,14 +434,6 @@ "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "Wundervolle API für Amazon Musik-Downloads.", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, "aboutDabMusic": "DAB Music", "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" @@ -664,35 +454,6 @@ "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "Album", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural,=1{1 Song} other{{count} Songs}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "Alle Herunterladen", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "Downloads verbleibend", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "Playlist", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "Künstler", - "@artistTitle": { - "description": "Artist screen title" - }, "artistAlbums": "Alben", "@artistAlbums": { "description": "Section header for artist albums" @@ -705,15 +466,6 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural,=1{1 Veröffentlichung} other{{count} Veröffentlichungen}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "artistPopular": "Beliebt", "@artistPopular": { "description": "Section header for popular/top tracks" @@ -728,34 +480,6 @@ } } }, - "trackMetadataTitle": "Titel Info", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "Künstler", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "Album", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "Länge", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "Qualität", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "Dateipfad", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "Heruntergeladen", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, "trackMetadataService": "Anbieter", "@trackMetadataService": { "description": "Metadata field - download service used" @@ -772,54 +496,10 @@ "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "Erneut herunterladen", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "Ordner öffnen", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "Willkommen bei SpotiFLAC", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "Los geht's", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "Speicherberechtigung", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "Benötigt um heruntergeladene Dateien zu Speichern", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "Berechtigung erteilt", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "Berechtigung verweigert", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, "setupGrantPermission": "Berechtigung erlauben", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "Speicherort", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "Ordner wählen", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "Fortfahren", - "@setupContinue": { - "description": "Continue to next step button" - }, "setupSkip": "Vorerst überspringen", "@setupSkip": { "description": "Skip current step button" @@ -828,10 +508,6 @@ "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "SpotiFLAC benötigt die Berechtigung \"Auf alle Dateien zugreifen\", um Musikdateien in deinen gewählten Ordner zu speichern.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, "setupStorageAccessMessageAndroid11": "Android 11+ benötigt die Berechtigung „Auf alle Dateien“, um Dateien im ausgewählten Download-Ordner zu speichern.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" @@ -863,10 +539,6 @@ } } }, - "setupSelectDownloadFolder": "Wähle Download-Ordner aus", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, "setupUseDefaultFolder": "Als Standardordner verwenden?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" @@ -915,26 +587,6 @@ "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "Speicherort", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "Benachrichtigung", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "Ordner", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "Berechtigung", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, "setupStorageGranted": "Speicherberechtigung erlaubt!", "@setupStorageGranted": { "description": "Success message for storage permission" @@ -955,14 +607,6 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "Benachrichtigt werden, wenn Downloads abgeschlossen sind.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "Download Ordner ausgewählt!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, "setupFolderChoose": "Speicherort auwählen", "@setupFolderChoose": { "description": "Button to choose folder" @@ -971,58 +615,14 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "Ordner ändern", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, "setupSelectFolder": "Ordner wählen", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "Spotify-API (optional)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Füge deine Spotify-API-Zugangsdaten für bessere Suchergebnisse und den Zugriff auf Spotify-exklusive Inhalte hinzu.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Spotify-API verwenden", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "Gib deine Anmeldedaten unten ein", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Deezer verwenden (kein Konto erforderlich)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Spotify-Client-ID eingeben", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Spotify Client-Secret eingeben", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Hole dir kostenlose API-Anmeldeinformationen aus dem Spotify-Entwickler-Dashboard.", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, "setupEnableNotifications": "Benachrichtigungen aktivieren", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "Du kannst mit dem nächsten Schritt fortfahren.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "Du erhältst Benachrichtigungen über den Download-Fortschritt.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, "setupNotificationBackgroundDescription": "Erhalte Benachrichtigungen über den Fortschritt und die Fertigstellung deiner Downloads, selbst wenn die App im Hintergrund läuft.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" @@ -1031,10 +631,6 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "Zurück", - "@setupBack": { - "description": "Back button text" - }, "setupNext": "Weiter", "@setupNext": { "description": "Next button text" @@ -1043,26 +639,14 @@ "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "Überspringen & Starten", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, "setupAllowAccessToManageFiles": "Bitte aktiviere \"Zugriff auf alle Dateien erlauben\" auf dem nächsten Bildschirm.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "Zugangsdaten von developer.spotify.com erhalten", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, "dialogCancel": "Abbrechen", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "OK", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, "dialogSave": "Speichern", "@dialogSave": { "description": "Dialog button - save changes" @@ -1075,26 +659,10 @@ "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "Schließen", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "Ja", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "Nein", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, "dialogClear": "Leeren", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "Bestätigen", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, "dialogDone": "Fertig", "@dialogDone": { "description": "Dialog button - action completed" @@ -1123,34 +691,10 @@ "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "Download fehlgeschlagen", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "Titel:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "Künstler:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "Fehler:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, "dialogClearAll": "Alles löschen", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "Bist du dir sicher, dass du alle Downloads löschen möchten?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "Vom Gerät entfernen?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, "dialogRemoveExtension": "Erweiterung entfernen", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" @@ -1289,15 +833,6 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "Fehler beim Laden: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, "snackbarUrlCopied": "{platform} URL in die Zwischenablage kopiert", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", @@ -1358,24 +893,10 @@ "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "Fehler beim Laden von: {item}", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, "errorNoTracksFound": "Keine Titel gefunden", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Suche wird für diesen Live-Stream nicht unterstützt", - "@errorSeekNotSupported": { - "description": "Error - seek disabled for live decrypted stream" - }, "errorMissingExtensionSource": "Kann {item} nicht lade wegen fehlender Erweiterungsquelle", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1385,34 +906,6 @@ } } }, - "statusQueued": "In Warteschlange", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "Wird heruntergeladen", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "Wird fertiggestellt", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "Beendet", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "Fehlgeschlagen", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "Übersprungen", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "Pausiert", - "@statusPaused": { - "description": "Download status - paused" - }, "actionPause": "Pause", "@actionPause": { "description": "Action button - pause download" @@ -1425,14 +918,6 @@ "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "Beenden", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "Wähle", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, "actionSelectAll": "Alles Auswählen", "@actionSelectAll": { "description": "Action button - select all items" @@ -1441,14 +926,6 @@ "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "Einfügen", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "CSV-Datei importieren", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, "actionRemoveCredentials": "Anmeldedaten entfernen", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" @@ -1470,19 +947,6 @@ "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "Tippe auf Titel zum Auswählen", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "Lösche {count} {count, plural, one {}=1{Titel}other{Titel}}", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "selectionSelectToDelete": "Titel zum Löschen auswählen", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" @@ -1523,51 +987,10 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "Abbrechen", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "Beenden", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "Wiederholen", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "Entfernen", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "Leeren", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "Einfügen", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, "filenameFormat": "Dateinamenformat", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "Vorschau: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "Verfügbare Platzhalter:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, "filenameShowAdvancedTags": "Erweiterte Tags anzeigen", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -1576,10 +999,6 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "Ordnerstruktur", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, "folderOrganizationNone": "Keine Organisation", "@folderOrganizationNone": { "description": "Folder option - flat structure" @@ -1620,27 +1039,10 @@ "@updateAvailable": { "description": "Update dialog title" }, - "updateNewVersion": "Version {version} ist verfügbar", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "Herunterladen", - "@updateDownload": { - "description": "Update button - download update" - }, "updateLater": "Später", "@updateLater": { "description": "Update button - dismiss" }, - "updateChangelog": "Änderungsverlauf", - "@updateChangelog": { - "description": "Link to changelog" - }, "updateStartingDownload": "Download wird gestartet...", "@updateStartingDownload": { "description": "Update status - initializing" @@ -1681,14 +1083,6 @@ "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriority": "Anbieterpriorität", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "Ziehen, um Download-Anbieter neu anzuordnen", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, "providerPriorityTitle": "Anbieterpriorität", "@providerPriorityTitle": { "description": "Provider priority page title" @@ -1709,14 +1103,6 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriority": "Priorität des Metadaten-Anbieters", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Reihenfolge beim Abrufen von Titelmetadaten", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, "metadataProviderPriorityTitle": "Metadaten Priorität", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" @@ -1741,22 +1127,6 @@ "@logTitle": { "description": "Logs screen title" }, - "logCopy": "Protokolle kopieren", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "Protokolle löschen", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "Protokolle teilen", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "Keine Protokolle bisher", - "@logEmpty": { - "description": "Empty state title" - }, "logCopied": "Protokolle in Zwischenablage kopiert", "@logCopied": { "description": "Snackbar - logs copied" @@ -1789,22 +1159,6 @@ "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "ISP BLOCKIERUNG ERKANNT", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "LIMIT ERKANNT", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "NETZWERKFEHLER", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "TITEL NICHT GEFUNDEN", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, "logFilterBySeverity": "Protokolle nach Schweregrad filtern", "@logFilterBySeverity": { "description": "Filter dialog title" @@ -1817,60 +1171,6 @@ "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "Problemübersicht", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "Ihr ISP blockiert möglicherweise den Zugriff auf den Download Dienst", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "Versuche es einem VPN oder ändere DNS auf 1.1.1.1 oder 8.8.8.8", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "Zu viele Anfragen an den Dienst", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "Warte ein paar Minuten, bevor du es erneut versuchst", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "Verbindungsprobleme erkannt", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "Überprüfe deine Internetverbindung", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "Einige Titel konnten auf Download-Diensten nicht gefunden werden", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "Der Titel ist möglicherweise nicht in verlustfreier Qualität verfügbar", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "Gesamte Fehler: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "Betroffen: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, "logEntriesFiltered": "Einträge ({count} gefiltert)", "@logEntriesFiltered": { "description": "Log count with filter active", @@ -2013,10 +1313,6 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "Deine bevorzugte Sprache wählen", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, "settingsAppearanceSubtitle": "Design, Farben, Anzeige", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" @@ -2045,10 +1341,6 @@ "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "Titel", - "@tracksHeader": { - "description": "Section header for track list" - }, "downloadAllCount": "Alle {count} Titel herunterladen", "@downloadAllCount": { "description": "Download all button with count", @@ -2058,15 +1350,6 @@ } } }, - "playAllCount": "Alle abspielen ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "tracksCount": "{count, plural, =1{1 Titel} other{{count} Titel}}", "@tracksCount": { "description": "Track count display", @@ -2204,15 +1487,6 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "Fehler beim Öffnen von: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, "dateToday": "Heute", "@dateToday": { "description": "Relative date - today" @@ -2248,22 +1522,6 @@ } } }, - "concurrentSequential": "Sequenziell", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 Parallel", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 Parallel", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "Tippe, um Fehlerdetails zu sehen", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, "storeFilterAll": "Alle", "@storeFilterAll": { "description": "Store filter - all extensions" @@ -2292,18 +1550,6 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "Keine Erweiterungen gefunden", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "Angebotspriorität", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "Erweiterung installieren", - "@extensionInstallButton": { - "description": "Button to install extension" - }, "extensionDefaultProvider": "Standard (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -2439,7 +1685,7 @@ "@extensionsDownloadPriority": { "description": "Setting - download provider order" }, - "extensionsDownloadPrioritySubtitle": "Set download service order", + "extensionsDownloadPrioritySubtitle": "Download-Service-Reihenfolge festlegen", "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, @@ -2451,11 +1697,11 @@ "@extensionsMetadataPriority": { "description": "Setting - metadata provider order" }, - "extensionsMetadataPrioritySubtitle": "Set search & metadata source order", + "extensionsMetadataPrioritySubtitle": "Reihenfolge der Such- und Metadaten quellen festlegen", "@extensionsMetadataPrioritySubtitle": { "description": "Subtitle for metadata priority" }, - "extensionsNoMetadataProvider": "No extensions with metadata provider", + "extensionsNoMetadataProvider": "Keine Erweiterungen mit Metadaten-Anbieter", "@extensionsNoMetadataProvider": { "description": "Empty state - no metadata providers" }, @@ -2463,7 +1709,7 @@ "@extensionsSearchProvider": { "description": "Setting - search provider selection" }, - "extensionsNoCustomSearch": "No extensions with custom search", + "extensionsNoCustomSearch": "Keine Erweiterungen mit benutzerdefinierter Suche", "@extensionsNoCustomSearch": { "description": "Empty state - no search providers" }, @@ -2475,7 +1721,7 @@ "@extensionsCustomSearch": { "description": "Label for custom search provider" }, - "extensionsErrorLoading": "Error loading extension", + "extensionsErrorLoading": "Fehler beim Laden der Erweiterung", "@extensionsErrorLoading": { "description": "Error message when extension fails to load" }, @@ -2503,46 +1749,6 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Verlustbehaftet", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "MP3 320kbps (konvertiert von FLAC)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128kbps (von FLAC konvertiert)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Lossy-Option aktivieren", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "Lossy quality option is available", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Lädt eine FLAC herunter und konvertiert sie dann in ein verlustbehaftetes Format", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Verlustbehaftetes Format", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Choose the lossy format for conversion", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320kbps, best compatibility", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, "qualityNote": "Die eigentliche Qualität hängt von der Verfügbarkeit des Dienstes ab", "@qualityNote": { "description": "Note about quality availability" @@ -2559,49 +1765,6 @@ "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": { - "type": "int" - }, - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateFieldLabel": "Bitrate (kbps)", - "@youtubeBitrateFieldLabel": { - "description": "Label for YouTube bitrate input field" - }, - "youtubeBitrateValidationError": "Bitrate muss zwischen {min} und {max} kbps liegen", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, "downloadAskBeforeDownload": "Qualität vor Download fragen", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2610,7 +1773,7 @@ "@downloadDirectory": { "description": "Setting - download folder" }, - "downloadSeparateSinglesFolder": "Separate Singles Folder", + "downloadSeparateSinglesFolder": "Singles Ordner trennen", "@downloadSeparateSinglesFolder": { "description": "Setting - separate folder for singles" }, @@ -2622,14 +1785,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Künstlerordner verwenden den Album-Interpreten, wenn verfügbar", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -2642,14 +1797,6 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "Speicherformat", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "Provider auswählen", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, "downloadSelectQuality": "Qualität wählen", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" @@ -2658,86 +1805,14 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "Standard-Qualität", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "Beste Qualität", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "Keine Ausgewählt", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "Alle Dateien direkt im Download-Ordner speichern", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "Künstler", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "Künstler Name/Dateiname", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "Album", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "Album Name/Dateiname", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "Künstler/Album", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "Artist Name/Album Name/filename", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, "appearanceAmoledDark": "AMOLED Schwarz", "@appearanceAmoledDark": { "description": "Theme option - pure black" }, - "appearanceAmoledDarkSubtitle": "Pure black background", + "appearanceAmoledDarkSubtitle": "AMOLED Hintergrund", "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "Akzentfarbe wählen", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "Designmodus", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "Download-Warteliste", - "@queueTitle": { - "description": "Queue screen title" - }, "queueClearAll": "Alles löschen", "@queueClearAll": { "description": "Button - clear all queue items" @@ -2746,22 +1821,6 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "Export", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Failed downloads exported to TXT file", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "Löschung fehlgeschlagen", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "Failed to export downloads", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, "settingsAutoExportFailed": "Auto-export failed downloads", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" @@ -2786,38 +1845,6 @@ "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, - "queueEmpty": "No downloads in queue", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "Add tracks from the home screen", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "Löschung Erledigt", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "Download fehlgeschlagen", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "Titel:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "Künstler:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "Fehler:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "Unbekannter Fehler", - "@queueUnknownError": { - "description": "Fallback error message" - }, "albumFolderArtistAlbum": "Künstler/Album", "@albumFolderArtistAlbum": { "description": "Album folder option" @@ -2871,19 +1898,6 @@ } } }, - "downloadedAlbumTracksHeader": "Titel", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} heruntergeladen", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, "downloadedAlbumSelectedCount": "{count} ausgewählt", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", @@ -2924,10 +1938,6 @@ } } }, - "utilityFunctions": "Hilfsfunktionen", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, "recentTypeArtist": "Künstler", "@recentTypeArtist": { "description": "Recent access item type - artist" @@ -2962,32 +1972,14 @@ } } }, - "errorGeneric": "Fehler: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, "discographyDownload": "Diskographie herunterladen", "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Diskographie abspielen", - "@discographyPlay": { - "description": "Button - play artist discography" - }, "discographyDownloadAll": "Alle Herunterladen", "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Alle abspielen", - "@discographyPlayAll": { - "description": "Option - play entire discography" - }, "discographyDownloadAllSubtitle": "{count} Titel von {albumCount} Releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3069,10 +2061,6 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Ausgewählte abspielen", - "@discographyPlaySelected": { - "description": "Button - play selected albums" - }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3150,10 +2138,6 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Bibliotheksstatus", - "@libraryStatus": { - "description": "Section header for library status" - }, "libraryScanSettings": "Scan Einstellungen", "@libraryScanSettings": { "description": "Section header for scan settings" @@ -3194,7 +2178,7 @@ "@libraryScanSubtitle": { "description": "Subtitle for scan button" }, - "libraryScanSelectFolderFirst": "Select a folder first", + "libraryScanSelectFolderFirst": "Wähle zuerst einen Ordner", "@libraryScanSelectFolderFirst": { "description": "Message when trying to scan without folder" }, @@ -3230,15 +2214,6 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} Titel", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, "libraryTracksUnit": "{count, plural, =1{1 Titel} other{{count} Titel}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", @@ -3362,26 +2337,6 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Datum Hinzugefügt", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "Heute", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "Diese Woche", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "In diesem Monat", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "Dieses Jahr", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, "libraryFilterSort": "Sortieren", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -3394,15 +2349,6 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} Filter aktiv", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, "timeJustNow": "Gerade eben", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -3425,106 +2371,6 @@ } } }, - "storageSwitchTitle": "Speichermodus wechseln", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "Zum SAF-Speicher wechseln?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "Zum App Speicher wechseln?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Deine vorhandenen Downloads bleiben im aktuellen Verzeichnis erhalten und bleiben zugänglich.\n\nNeue Downloads werden dann aber in den ausgewählten SAF-Ordner gespeichert.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Deine vorhandenen Downloads bleiben im aktuellen SAF-Verzeichnis erhalten und bleiben zugänglich.\n\nNeue Downloads werden dann aber in den Music/SpotiFLAC Ordner gespeichert.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Vorhandene Downloads", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count} Titel im {mode} Speicher", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "Neue Downloads", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Wird unter gespeichert: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "Fortfahren", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "SAF-Ordner wählen", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "App-Speicher", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "App-Speicher", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "Speicher: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "Speicherstatistik", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "{count} Titel im App-Speicher", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "{count} Titel im SAF Speicher", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Deine Dateien werden an mehreren Orten gespeichert", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, "tutorialWelcomeTitle": "Willkommen bei SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" @@ -3553,18 +2399,6 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Füge eine Spotify-oder Deezer-URL direkt in das Suchfeld ein", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Oder gebe den Song-Namen, Interpret oder Album ein um nach ihm zu suchen", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Unterstützt Tracks, Alben, Wiedergabelisten und Künstlerseiten", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, "tutorialDownloadTitle": "Musik wird heruntergeladen", "@tutorialDownloadTitle": { "description": "Tutorial download page title" @@ -3573,18 +2407,6 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Tippe auf den Download-Button neben einem Titel, um mit dem Download zu beginnen", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Wähle deine bevorzugte Qualität (FLAC, Hi-Res oder MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Lade ganze Alben oder Playlists mit einem Klick herunter", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, "tutorialLibraryTitle": "Deine Bibliothek", "@tutorialLibraryTitle": { "description": "Tutorial library page title" @@ -3649,10 +2471,6 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "BEISPIEL", - "@tutorialExample": { - "description": "Example label in tutorial" - }, "libraryForceFullScan": "Vollen Neu-Scan erzwingen", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" @@ -3694,7 +2512,7 @@ "@cacheSummarySubtitle": { "description": "Helper text for cache summary card" }, - "cacheEstimatedTotal": "Estimated cache usage: {size}", + "cacheEstimatedTotal": "Geschätzte Cache-Größe: {size}", "@cacheEstimatedTotal": { "description": "Total cache size shown in summary", "placeholders": { @@ -3767,7 +2585,7 @@ "@cacheNoData": { "description": "Label when cache category has no data" }, - "cacheSizeWithFiles": "{size} in {count} files", + "cacheSizeWithFiles": "{size} in {count} Dateien", "@cacheSizeWithFiles": { "description": "Cache size and file count", "placeholders": { @@ -3859,7 +2677,7 @@ "@trackSaveCoverArt": { "description": "Menu action - save album cover art as file" }, - "trackSaveCoverArtSubtitle": "Save album art as .jpg file", + "trackSaveCoverArtSubtitle": "Albumcover als .jpg Datei speichern", "@trackSaveCoverArtSubtitle": { "description": "Subtitle for save cover art action" }, @@ -3867,7 +2685,7 @@ "@trackSaveLyrics": { "description": "Menu action - save lyrics as .lrc file" }, - "trackSaveLyricsSubtitle": "Fetch and save lyrics as .lrc file", + "trackSaveLyricsSubtitle": "Lade Lyrics als .lrc Datei", "@trackSaveLyricsSubtitle": { "description": "Subtitle for save lyrics action" }, @@ -3879,10 +2697,6 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Metadaten neu einbetten ohne erneuten herunterladen", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, "trackReEnrichOnlineSubtitle": "Metadaten online suchen und in Datei einbinden", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" @@ -4271,48 +3085,21 @@ } } }, - "setupModeSelectionTitle": "Modus wählen", - "@setupModeSelectionTitle": { - "description": "Title for mode selection step in setup wizard" + "downloadedAlbumDownloadedCount": "{count} heruntergeladen", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } }, - "setupModeSelectionDescription": "Wie möchtest du SpotiFLAC verwenden? Sie können dies jederzeit in den Einstellungen ändern.", - "@setupModeSelectionDescription": { - "description": "Description for mode selection step" + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Künstlerordner verwenden den Album-Interpreten, wenn verfügbar", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" }, - "setupModeDownloaderTitle": "Downloader", - "@setupModeDownloaderTitle": { - "description": "Title for downloader mode option" - }, - "setupModeDownloaderFeature1": "Titel in verlustfreier FLAC-Qualität herunterladen", - "@setupModeDownloaderFeature1": { - "description": "Downloader mode feature 1" - }, - "setupModeDownloaderFeature2": "Speichere Musik auf deinem Gerät, um sie offline anzuhören", - "@setupModeDownloaderFeature2": { - "description": "Downloader mode feature 2" - }, - "setupModeDownloaderFeature3": "Verwalte deine lokale Musikbibliothek", - "@setupModeDownloaderFeature3": { - "description": "Downloader mode feature 3" - }, - "setupModeStreamingTitle": "Streamen", - "@setupModeStreamingTitle": { - "description": "Title for streaming mode option" - }, - "setupModeStreamingFeature1": "Streame Titel sofort ohne Download", - "@setupModeStreamingFeature1": { - "description": "Streaming mode feature 1" - }, - "setupModeStreamingFeature2": "Die smarte Warteschlange entdeckt automatisch neue Musik für dich", - "@setupModeStreamingFeature2": { - "description": "Streaming mode feature 2" - }, - "setupModeStreamingFeature3": "Bei Bedarf jeden Track mit Wiedergabesteuerungen abspielen", - "@setupModeStreamingFeature3": { - "description": "Streaming mode feature 3" - }, - "setupModeChangeableLater": "Du kannst jederzeit in den Einstellungen zwischen den Modi wechseln.", - "@setupModeChangeableLater": { - "description": "Hint that mode can be changed later" + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" } } \ No newline at end of file From f66ccb4741c186579c7c4c8fc626947d5eb96a21 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:40 +0700 Subject: [PATCH 078/104] New translations app_en.arb (Japanese) --- lib/l10n/arb/app_ja.arb | 1241 +-------------------------------------- 1 file changed, 14 insertions(+), 1227 deletions(-) diff --git a/lib/l10n/arb/app_ja.arb b/lib/l10n/arb/app_ja.arb index 375344dc..268d8950 100644 --- a/lib/l10n/arb/app_ja.arb +++ b/lib/l10n/arb/app_ja.arb @@ -5,10 +5,6 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "appDescription": "Tidal、Qobuz、Amazon Music から Spotify のトラックをロスレス品質でダウンロードします。", - "@appDescription": { - "description": "App description shown in about page" - }, "navHome": "ホーム", "@navHome": { "description": "Bottom navigation - Home tab" @@ -17,10 +13,6 @@ "@navLibrary": { "description": "Bottom navigation - Library tab" }, - "navHistory": "履歴", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, "navSettings": "設定", "@navSettings": { "description": "Bottom navigation - Settings tab" @@ -33,20 +25,6 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSearchHint": "Spotify の URL を貼り付けまたは検索...", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "{extensionName} で検索...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, "homeSubtitle": "Spotify のリンクを貼り付けるか、名前で検索します", "@homeSubtitle": { "description": "Subtitle shown below search box" @@ -59,24 +37,6 @@ "@homeRecent": { "description": "Section header for recent searches" }, - "historyTitle": "履歴", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "ダウンロード中 ({count})", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "ダウンロード済み", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, "historyFilterAll": "すべて", "@historyFilterAll": { "description": "Filter chip - show all items" @@ -89,48 +49,6 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, =1{1 個のトラック} other{{count} 個のトラック}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural, =1{1 個のアルバム} other{{count} 個のアルバム}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "ダウンロード履歴はありません", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "ダウンロードしたトラックはここに表示されます", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "アルバムのダウンロードはありません", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "Download multiple tracks from an album to see them here", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "シングルのダウンロードはありません", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "Single track downloads will appear here", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, "historySearchHint": "検索履歴...", "@historySearchHint": { "description": "Search bar placeholder in history" @@ -163,34 +81,6 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "ダウンロード先", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "ファイルの保存先を選択", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "デフォルトの場所", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "デフォルトのサービス", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "ダウンロードに使用したサービス", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "デフォルトの品質", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "ダウンロード前に品質を確認する", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, "downloadAskQualitySubtitle": "Show quality picker for each download", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" @@ -203,38 +93,10 @@ "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "シングルを分割", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "Put single tracks in a separate folder", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "おすすめ", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 kbps", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 kbps", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, "appearanceTitle": "外観", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "テーマ", - "@appearanceTheme": { - "description": "Theme mode setting" - }, "appearanceThemeSystem": "システム", "@appearanceThemeSystem": { "description": "Follow system theme" @@ -255,10 +117,6 @@ "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "アクセントカラー", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, "appearanceHistoryView": "履歴の表示", "@appearanceHistoryView": { "description": "Layout style for history" @@ -275,10 +133,6 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "検索ソース", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, "optionsPrimaryProvider": "プライマリーのプロバイダー", "@optionsPrimaryProvider": { "description": "Main search provider setting" @@ -308,38 +162,6 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", - "@optionsAutoSkipUnavailableTracks": { - "description": "Toggle to skip to the next queue track when current track stream resolution fails" - }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": { - "description": "Subtitle when auto skip on resolve failure is enabled" - }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": { - "description": "Subtitle when auto skip on resolve failure is disabled" - }, - "optionsInteractionMode": "Interaction Mode", - "@optionsInteractionMode": { - "description": "Tap behavior mode for track lists" - }, - "modeDownloader": "Downloader Mode", - "@modeDownloader": { - "description": "Interaction mode where taps queue downloads" - }, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", - "@modeDownloaderSubtitle": { - "description": "Subtitle for downloader interaction mode" - }, - "modeStreaming": "Streaming Mode", - "@modeStreaming": { - "description": "Interaction mode where taps start playback" - }, - "modeStreamingSubtitle": "Tap tracks to play instantly", - "@modeStreamingSubtitle": { - "description": "Subtitle for streaming interaction mode" - }, "optionsUseExtensionProviders": "拡張のプロバイダーを使用する", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -470,22 +292,6 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "インストール済みの拡張", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "拡張はインストールされていません", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "ストアタブから拡張をインストール", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "有効", - "@extensionsEnabled": { - "description": "Extension status - active" - }, "extensionsDisabled": "無効", "@extensionsDisabled": { "description": "Extension status - inactive" @@ -512,10 +318,6 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "検索のプロバイダーを設定", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, "storeTitle": "拡張ストア", "@storeTitle": { "description": "Store screen title" @@ -612,10 +414,6 @@ "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "サポート", - "@aboutSupport": { - "description": "Section for support/donation links" - }, "aboutApp": "アプリ", "@aboutApp": { "description": "Section for app info" @@ -636,14 +434,6 @@ "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "Amazing API for Amazon Music downloads. Thank you for making it free!", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, "aboutDabMusic": "DAB Music", "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" @@ -664,35 +454,6 @@ "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "アルバム", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural, =1{1 個のトラック} other{{count} 個のトラック}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "すべてダウンロード", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "Download Remaining", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "プレイリスト", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "アーティスト", - "@artistTitle": { - "description": "Artist screen title" - }, "artistAlbums": "アルバム", "@artistAlbums": { "description": "Section header for artist albums" @@ -705,15 +466,6 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, =1{1 個のリリース} other{{count} 個のリリース}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "artistPopular": "人気", "@artistPopular": { "description": "Section header for popular/top tracks" @@ -728,34 +480,6 @@ } } }, - "trackMetadataTitle": "トラック情報", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "アーティスト", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "アルバム", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "再生時間", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "品質", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "ファイルパス", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "ダウンロード済み", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, "trackMetadataService": "サービス", "@trackMetadataService": { "description": "Metadata field - download service used" @@ -772,54 +496,10 @@ "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "再ダウンロード", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "フォルダを開く", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "SpotiFLAC へようこそ", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "Let's get you started", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "ストレージの権限", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "ダウンロードしたファイルを保存するために必要です", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "権限を許可しました", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "権限が拒否されました", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, "setupGrantPermission": "権限を許可", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "ダウンロード先", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "フォルダを選択", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "続行", - "@setupContinue": { - "description": "Continue to next step button" - }, "setupSkip": "今はスキップ", "@setupSkip": { "description": "Skip current step button" @@ -828,10 +508,6 @@ "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, "setupStorageAccessMessageAndroid11": "Android 11+ requires \"All files access\" permission to save files to your chosen download folder.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" @@ -863,10 +539,6 @@ } } }, - "setupSelectDownloadFolder": "ダウンロードフォルダを選択", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, "setupUseDefaultFolder": "デフォルトのフォルダを使用しますか?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" @@ -915,26 +587,6 @@ "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "ストレージ", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "通知", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "フォルダ", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "権限", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, "setupStorageGranted": "ストレージの権限が許可されました!", "@setupStorageGranted": { "description": "Success message for storage permission" @@ -955,14 +607,6 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "Get notified when downloads complete or require attention.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "ダウンロードフォルダが選択済みです!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, "setupFolderChoose": "ダウンロードフォルダを選択", "@setupFolderChoose": { "description": "Button to choose folder" @@ -971,58 +615,14 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "フォルダを変更", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, "setupSelectFolder": "フォルダを選択", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "Spotify API (任意)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Add your Spotify API credentials for better search results and access to Spotify-exclusive content.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Spotify API を使用する", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "以下に認証情報を入力してください", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Deezer を使用中 (アカウントは不要です)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Spotify クライアント ID を入力", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Spotify クライアントシークレットを入力", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Spotify 開発者ダッシュボードから無料の API 認証情報を取得します。", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, "setupEnableNotifications": "通知を有効化する", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "You can now proceed to the next step.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "You will receive download progress notifications.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, "setupNotificationBackgroundDescription": "Get notified about download progress and completion. This helps you track downloads when the app is in background.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" @@ -1031,10 +631,6 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "戻る", - "@setupBack": { - "description": "Back button text" - }, "setupNext": "次へ", "@setupNext": { "description": "Next button text" @@ -1043,26 +639,14 @@ "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "スキップと開始", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, "setupAllowAccessToManageFiles": "Please enable \"Allow access to manage all files\" in the next screen.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "developer.spotify.com から認証情報を取得します", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, "dialogCancel": "キャンセル", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "OK", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, "dialogSave": "保存", "@dialogSave": { "description": "Dialog button - save changes" @@ -1075,26 +659,10 @@ "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "閉じる", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "はい", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "いいえ", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, "dialogClear": "消去", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "続行", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, "dialogDone": "完了", "@dialogDone": { "description": "Dialog button - action completed" @@ -1123,34 +691,10 @@ "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "ダウンロードに失敗しました", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "トラック:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "アーティスト:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "エラー:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, "dialogClearAll": "すべて消去", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "Are you sure you want to clear all downloads?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "デバイスから削除しますか?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, "dialogRemoveExtension": "拡張を削除", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" @@ -1289,15 +833,6 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "読み込みに失敗: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, "snackbarUrlCopied": "{platform} の URL をクリップボードにコピーしました", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", @@ -1358,24 +893,10 @@ "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "Failed to load {item}", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, "errorNoTracksFound": "トラックがありません", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Seeking is not supported for this live stream", - "@errorSeekNotSupported": { - "description": "Error - seek disabled for live decrypted stream" - }, "errorMissingExtensionSource": "{item} を読み込めません: 拡張ソースがありません", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1385,34 +906,6 @@ } } }, - "statusQueued": "キュー済み", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "ダウンロード中", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "終了処理中", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "完了しました", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "失敗しました", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "スキップしました", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "一時停止中", - "@statusPaused": { - "description": "Download status - paused" - }, "actionPause": "一時停止", "@actionPause": { "description": "Action button - pause download" @@ -1425,14 +918,6 @@ "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "停止", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "選択", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, "actionSelectAll": "すべて選択", "@actionSelectAll": { "description": "Action button - select all items" @@ -1441,14 +926,6 @@ "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "貼り付け", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "CSV をインポート", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, "actionRemoveCredentials": "認証情報を削除", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" @@ -1470,19 +947,6 @@ "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "トラックをタップで選択", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "{count} {count, plural, =1{個のトラック} other{個のトラック}}を削除", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "selectionSelectToDelete": "トラックを選択で削除", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" @@ -1523,51 +987,10 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "キャンセル", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "停止", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "再試行", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "削除", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "消去", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "貼り付け", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, "filenameFormat": "ファイル名の形式", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "プレビュー: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "利用可能なプレースホルダー:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, "filenameShowAdvancedTags": "Show advanced tags", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -1576,10 +999,6 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "フォルダ構成", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, "folderOrganizationNone": "構成がありません", "@folderOrganizationNone": { "description": "Folder option - flat structure" @@ -1620,27 +1039,10 @@ "@updateAvailable": { "description": "Update dialog title" }, - "updateNewVersion": "バージョン {version} が利用可能です", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "ダウンロード", - "@updateDownload": { - "description": "Update button - download update" - }, "updateLater": "後で", "@updateLater": { "description": "Update button - dismiss" }, - "updateChangelog": "更新履歴", - "@updateChangelog": { - "description": "Link to changelog" - }, "updateStartingDownload": "ダウンロードを開始中...", "@updateStartingDownload": { "description": "Update status - initializing" @@ -1681,14 +1083,6 @@ "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriority": "プロバイダーの優先度", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "ドラッグでダウンロードプロバイダーを並べ替え", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, "providerPriorityTitle": "プロバイダーの優先度", "@providerPriorityTitle": { "description": "Provider priority page title" @@ -1709,14 +1103,6 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriority": "メタデータプロバイダーの優先度", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Order used when fetching track metadata", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, "metadataProviderPriorityTitle": "メタデータの優先度", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" @@ -1741,22 +1127,6 @@ "@logTitle": { "description": "Logs screen title" }, - "logCopy": "ログをコピー", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "ログを消去", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "ログを共有", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "まだログはありません", - "@logEmpty": { - "description": "Empty state title" - }, "logCopied": "ログをクリップボードにコピーしました", "@logCopied": { "description": "Snackbar - logs copied" @@ -1789,22 +1159,6 @@ "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "ISP のブロックを検出しました", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "レート制限", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "ネットワークエラー", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "トラックがありません", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, "logFilterBySeverity": "Filter logs by severity", "@logFilterBySeverity": { "description": "Filter dialog title" @@ -1817,60 +1171,6 @@ "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "問題の概要", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "ISP がダウンロードサービスのアクセスをブロックしている可能性があります", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "VPN を使用するか DNS を 1.1.1.1 または 8.8.8.8 に変更をお試しください", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "サービスへのリクエストが多すぎます", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "Wait a few minutes before trying again", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "接続の問題が検出されました", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "インターネット接続を確認してください", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "Some tracks could not be found on download services", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "The track may not be available in lossless quality", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "エラーの合計: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "影響: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, "logEntriesFiltered": "エントリー ({count} 個をフィルター済み)", "@logEntriesFiltered": { "description": "Log count with filter active", @@ -2013,10 +1313,6 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "お好みの言語を選択してください", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, "settingsAppearanceSubtitle": "テーマ、カラー、画面", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" @@ -2045,10 +1341,6 @@ "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "トラック", - "@tracksHeader": { - "description": "Section header for track list" - }, "downloadAllCount": "すべてダウンロード ({count})", "@downloadAllCount": { "description": "Download all button with count", @@ -2058,15 +1350,6 @@ } } }, - "playAllCount": "Play All ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "tracksCount": "{count, plural, =1{1 個のトラック} other{{count} 個のトラック}}", "@tracksCount": { "description": "Track count display", @@ -2204,15 +1487,6 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "開けません: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, "dateToday": "今日", "@dateToday": { "description": "Relative date - today" @@ -2248,22 +1522,6 @@ } } }, - "concurrentSequential": "Sequential", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 並列", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 並列", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "タップでエラーの詳細を表示", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, "storeFilterAll": "すべて", "@storeFilterAll": { "description": "Store filter - all extensions" @@ -2292,18 +1550,6 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "拡張がありません", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "プロバイダーの優先度", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "拡張をインストール", - "@extensionInstallButton": { - "description": "Button to install extension" - }, "extensionDefaultProvider": "デフォルト (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -2503,46 +1749,6 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Lossy", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "MP3 320kbps (FLAC から変換)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128kbps (FLAC から変換)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Enable Lossy Option", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "Lossy quality option is available", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Lossy Format", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Choose the lossy format for conversion", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320kbps, best compatibility", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, "qualityNote": "実際の品質はサービスからのトラックの可用性に依存します", "@qualityNote": { "description": "Note about quality availability" @@ -2559,49 +1765,6 @@ "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": { - "type": "int" - }, - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateFieldLabel": "ビットレート (kbps)", - "@youtubeBitrateFieldLabel": { - "description": "Label for YouTube bitrate input field" - }, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, "downloadAskBeforeDownload": "ダウンロード前に確認する", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2622,14 +1785,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -2642,14 +1797,6 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "形式を保存", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "サービスを選択", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, "downloadSelectQuality": "品質を選択", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" @@ -2658,66 +1805,6 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "デフォルトの品質", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "おすすめ", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "なし", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "すべてのファイルをダウンロードフォルダに保存します", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "アーティスト", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "アーティスト名/ファイル名", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "アルバム", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "アルバム名/ファイル名", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "アーティスト/アルバム", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "アーティスト名/アルバム名/ファイル名", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, "appearanceAmoledDark": "AMOLED ダーク", "@appearanceAmoledDark": { "description": "Theme option - pure black" @@ -2726,18 +1813,6 @@ "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "アクセントカラーを選択", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "テーマモード", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "ダウンロードキュー", - "@queueTitle": { - "description": "Queue screen title" - }, "queueClearAll": "すべて消去", "@queueClearAll": { "description": "Button - clear all queue items" @@ -2746,22 +1821,6 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "エクスポート", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Failed downloads exported to TXT file", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "消去に失敗しました", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "ダウンロードのエクスポートに失敗しました", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, "settingsAutoExportFailed": "ダウンロードの自動エクスポートに失敗しました", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" @@ -2786,38 +1845,6 @@ "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, - "queueEmpty": "キューにダウンロードがありません", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "ホーム画面からトラックを追加", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "完了済みを消去", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "ダウンロードに失敗しました", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "トラック:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "アーティスト:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "エラー:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "不明なエラー", - "@queueUnknownError": { - "description": "Fallback error message" - }, "albumFolderArtistAlbum": "アーティスト / アルバム", "@albumFolderArtistAlbum": { "description": "Album folder option" @@ -2871,19 +1898,6 @@ } } }, - "downloadedAlbumTracksHeader": "トラック", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} 個をダウンロード済み", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, "downloadedAlbumSelectedCount": "{count} 個を選択済み", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", @@ -2924,10 +1938,6 @@ } } }, - "utilityFunctions": "ユーティリティ機能", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, "recentTypeArtist": "アーティスト", "@recentTypeArtist": { "description": "Recent access item type - artist" @@ -2962,32 +1972,14 @@ } } }, - "errorGeneric": "エラー: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, "discographyDownload": "ディスコグラフィをダウンロード", "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Play Discography", - "@discographyPlay": { - "description": "Button - play artist discography" - }, "discographyDownloadAll": "すべてダウンロード", "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Play All", - "@discographyPlayAll": { - "description": "Option - play entire discography" - }, "discographyDownloadAllSubtitle": "{albumCount} 個のリリースから {count} 個のトラック", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3069,10 +2061,6 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Play Selected", - "@discographyPlaySelected": { - "description": "Button - play selected albums" - }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3150,10 +2138,6 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "ライブラリの状態", - "@libraryStatus": { - "description": "Section header for library status" - }, "libraryScanSettings": "スキャン設定", "@libraryScanSettings": { "description": "Section header for scan settings" @@ -3230,15 +2214,6 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} 個のトラック", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", @@ -3362,26 +2337,6 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "日付を追加しました", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "今日", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "This Week", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "This Month", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "This Year", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -3394,15 +2349,6 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} 個のフィルターが有効", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -3425,106 +2371,6 @@ } } }, - "storageSwitchTitle": "ストレージモードを切り替え", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "SAF ストレージに切り替えますか?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "アプリストレージに切り替えますか?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Existing Downloads", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "新規ダウンロード", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "続行", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "SAF フォルダを選択", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "アプリストレージ", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "SAF ストレージ", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "ストレージ: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "ストレージの統計", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "アプリストレージ内の {count} 個のトラック", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "SAF ストレージ内の {count} 個のトラック", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Your files are stored in multiple locations", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, "tutorialWelcomeTitle": "SpotiFLAC へようこそ!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" @@ -3553,18 +2399,6 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, "tutorialDownloadTitle": "音楽をダウンロード中", "@tutorialDownloadTitle": { "description": "Tutorial download page title" @@ -3573,18 +2407,6 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, "tutorialLibraryTitle": "あなたのライブラリ", "@tutorialLibraryTitle": { "description": "Tutorial library page title" @@ -3649,10 +2471,6 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "例", - "@tutorialExample": { - "description": "Example label in tutorial" - }, "libraryForceFullScan": "強制フルスキャン", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" @@ -3879,10 +2697,6 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" @@ -4271,48 +3085,21 @@ } } }, - "setupModeSelectionTitle": "Choose Your Mode", - "@setupModeSelectionTitle": { - "description": "Title for mode selection step in setup wizard" + "downloadedAlbumDownloadedCount": "{count} 個をダウンロード済み", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } }, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", - "@setupModeSelectionDescription": { - "description": "Description for mode selection step" + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" }, - "setupModeDownloaderTitle": "Downloader", - "@setupModeDownloaderTitle": { - "description": "Title for downloader mode option" - }, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", - "@setupModeDownloaderFeature1": { - "description": "Downloader mode feature 1" - }, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", - "@setupModeDownloaderFeature2": { - "description": "Downloader mode feature 2" - }, - "setupModeDownloaderFeature3": "Manage your local music library", - "@setupModeDownloaderFeature3": { - "description": "Downloader mode feature 3" - }, - "setupModeStreamingTitle": "Streaming", - "@setupModeStreamingTitle": { - "description": "Title for streaming mode option" - }, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", - "@setupModeStreamingFeature1": { - "description": "Streaming mode feature 1" - }, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", - "@setupModeStreamingFeature2": { - "description": "Streaming mode feature 2" - }, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", - "@setupModeStreamingFeature3": { - "description": "Streaming mode feature 3" - }, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", - "@setupModeChangeableLater": { - "description": "Hint that mode can be changed later" + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" } } \ No newline at end of file From f2fdead6d3a183e6b6c7f3719b139c70d67d0abd Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:41 +0700 Subject: [PATCH 079/104] New translations app_en.arb (Korean) --- lib/l10n/arb/app_ko.arb | 1241 +-------------------------------------- 1 file changed, 14 insertions(+), 1227 deletions(-) diff --git a/lib/l10n/arb/app_ko.arb b/lib/l10n/arb/app_ko.arb index 9cfbefe8..f31eb1b9 100644 --- a/lib/l10n/arb/app_ko.arb +++ b/lib/l10n/arb/app_ko.arb @@ -5,10 +5,6 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "appDescription": "Spotify 트랙을 Tidal, Qobuz, Amazon Music에서 무손실 음질로 다운로드하세요.", - "@appDescription": { - "description": "App description shown in about page" - }, "navHome": "Home", "@navHome": { "description": "Bottom navigation - Home tab" @@ -17,10 +13,6 @@ "@navLibrary": { "description": "Bottom navigation - Library tab" }, - "navHistory": "History", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, "navSettings": "Settings", "@navSettings": { "description": "Bottom navigation - Settings tab" @@ -33,20 +25,6 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSearchHint": "Spotify URL을 붙여 넣거나 검색", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "{extensionName}에서 검색", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, "homeSubtitle": "Spotify URL을 붙여 넣거나 검색", "@homeSubtitle": { "description": "Subtitle shown below search box" @@ -59,24 +37,6 @@ "@homeRecent": { "description": "Section header for recent searches" }, - "historyTitle": "기록", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "다운로드 중... {count}", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "다운로드 목록", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, "historyFilterAll": "All", "@historyFilterAll": { "description": "Filter chip - show all items" @@ -89,48 +49,6 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural,=1{1 track}other{{count}tracks}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural,=1{1 album}other{{count} albums}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "다운로드 기록 없음", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "다운로드한 음반은 여기에 표시됩니다", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "다운로드한 앨범 없음", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "다운로드한 앨범의 수록곡은 여기에 표시됩니다", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "다운로드한 싱글 음반 없음", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "다운로드한 싱글 음반은 여기에 표시됩니다", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, "historySearchHint": "검색 기록...", "@historySearchHint": { "description": "Search bar placeholder in history" @@ -163,34 +81,6 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "저장 경로", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "파일을 어떤 경로에 저장할까요?", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "기본 경로", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "기본 서비스", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "다운로드에 사용할 서비스", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "기본 품질", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "다운로드할 때 품질 선택", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, "downloadAskQualitySubtitle": "다운로드를 할 때마다 품질을 선택하도록 합니다", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" @@ -203,38 +93,10 @@ "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "싱글 음반으로 분리", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "싱글 음반을 별도의 폴더로 분리합니다.", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "최상의 품질", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 kbps", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 kbps", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, "appearanceTitle": "외관", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "테마", - "@appearanceTheme": { - "description": "Theme mode setting" - }, "appearanceThemeSystem": "System", "@appearanceThemeSystem": { "description": "Follow system theme" @@ -255,10 +117,6 @@ "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "강조 색상", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, "appearanceHistoryView": "기록 정렬 방식", "@appearanceHistoryView": { "description": "Layout style for history" @@ -275,10 +133,6 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "검색 출처", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, "optionsPrimaryProvider": "기본 제공자", "@optionsPrimaryProvider": { "description": "Main search provider setting" @@ -308,38 +162,6 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", - "@optionsAutoSkipUnavailableTracks": { - "description": "Toggle to skip to the next queue track when current track stream resolution fails" - }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": { - "description": "Subtitle when auto skip on resolve failure is enabled" - }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": { - "description": "Subtitle when auto skip on resolve failure is disabled" - }, - "optionsInteractionMode": "Interaction Mode", - "@optionsInteractionMode": { - "description": "Tap behavior mode for track lists" - }, - "modeDownloader": "Downloader Mode", - "@modeDownloader": { - "description": "Interaction mode where taps queue downloads" - }, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", - "@modeDownloaderSubtitle": { - "description": "Subtitle for downloader interaction mode" - }, - "modeStreaming": "Streaming Mode", - "@modeStreaming": { - "description": "Interaction mode where taps start playback" - }, - "modeStreamingSubtitle": "Tap tracks to play instantly", - "@modeStreamingSubtitle": { - "description": "Subtitle for streaming interaction mode" - }, "optionsUseExtensionProviders": "확장 기능 사용", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -470,22 +292,6 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "확장 기능 설치", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "설치된 확장 기능 없음", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "스토어 탭에서 확장 프로그램을 설치하세요", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "활성화", - "@extensionsEnabled": { - "description": "Extension status - active" - }, "extensionsDisabled": "비활성화", "@extensionsDisabled": { "description": "Extension status - inactive" @@ -512,10 +318,6 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "검색 제공자", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, "storeTitle": "확장 기능 스토어", "@storeTitle": { "description": "Store screen title" @@ -612,10 +414,6 @@ "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "지원", - "@aboutSupport": { - "description": "Section for support/donation links" - }, "aboutApp": "App", "@aboutApp": { "description": "Section for app info" @@ -636,14 +434,6 @@ "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "아마존 뮤직 다운로드를 위한 훌륭한 API네요. 무료로 제공해주셔서 감사합니다!", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, "aboutDabMusic": "DAB Music", "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" @@ -664,35 +454,6 @@ "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "앨범", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural,=1{1 track}other{{count} tracks}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "모두 다운로드", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "이어서 다운로드", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "재생 목록", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "아티스트", - "@artistTitle": { - "description": "Artist screen title" - }, "artistAlbums": "앨범", "@artistAlbums": { "description": "Section header for artist albums" @@ -705,15 +466,6 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural,=1{1 release}other{{count} releases}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "artistPopular": "인기순", "@artistPopular": { "description": "Section header for popular/top tracks" @@ -728,34 +480,6 @@ } } }, - "trackMetadataTitle": "트랙 정보", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "아티스트", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "앨범", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "길이", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "품질", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "파일 경로", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "다운로드 일자", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, "trackMetadataService": "제공업체", "@trackMetadataService": { "description": "Metadata field - download service used" @@ -772,54 +496,10 @@ "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "다시 다운로드", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "경로 열기", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "Welcome to SpotiFLAC", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "시작해 볼까요?", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "스토리지 접근 권한", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "다운로드한 파일을 저장하려면 필요합니다.", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "권한이 부여됨", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "권한이 거부됨", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, "setupGrantPermission": "권한을 제공해 주세요.", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "다운로드 경로", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "폴더 선택", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "계속", - "@setupContinue": { - "description": "Continue to next step button" - }, "setupSkip": "다음에 할래요", "@setupSkip": { "description": "Skip current step button" @@ -828,10 +508,6 @@ "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "SpotiFLAC이 선택한 폴더에 음악 파일을 저장하려면 \"모든 파일 접근\" 권한이 필요합니다.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, "setupStorageAccessMessageAndroid11": "Android 11 이상 버전에서는 선택한 다운로드 폴더에 파일을 저장하려면 \"모든 파일 접근\" 권한이 필요합니다.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" @@ -863,10 +539,6 @@ } } }, - "setupSelectDownloadFolder": "다운로드 경로 선택", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, "setupUseDefaultFolder": "기본 폴더를 사용하시겠습니까?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" @@ -915,26 +587,6 @@ "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "저장소", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "알림", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "폴더", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "권한", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, "setupStorageGranted": "저장소 접근 권한이 부여되었습니다!", "@setupStorageGranted": { "description": "Success message for storage permission" @@ -955,14 +607,6 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "다운로드가 완료되거나 주의가 필요할 때 알림을 받으세요.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "다운로드 폴더가 선택되었습니다!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, "setupFolderChoose": "다운로드 폴더를 선택하세요", "@setupFolderChoose": { "description": "Button to choose folder" @@ -971,58 +615,14 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "폴더 변경", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, "setupSelectFolder": "폴더 선택", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "Spotify API (선택 사항)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Spotify API 자격 증명을 추가하면 검색 결과가 향상되고 Spotify 독점 콘텐츠에 액세스할 수 있습니다.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Spotify API 사용", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "아래에 자격 증명을 입력하세요", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Deezer 사용 (계정 필요 없음)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Spotify 클라이언트 ID를 입력하세요", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Spotify 클라이언트 비밀번호를 입력하세요", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Spotify 개발자 대시보드에서 무료 API 자격 증명을 받으세요.", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, "setupEnableNotifications": "알림 활성화", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "이제 다음 단계로 진행하실 수 있습니다.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "다운로드 진행 상황 알림을 받게 됩니다.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, "setupNotificationBackgroundDescription": "알림으로 다운로드 진행 상황을 확인하세요. 앱이 백그라운드에서 실행 중일 때 다운로드 상태와 완료 여부를 확인할 수 있습니다.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" @@ -1031,10 +631,6 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "이전", - "@setupBack": { - "description": "Back button text" - }, "setupNext": "다음", "@setupNext": { "description": "Next button text" @@ -1043,26 +639,14 @@ "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "건너뛰고 시작", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, "setupAllowAccessToManageFiles": "다음 화면에서 \"모든 파일 관리 권한 허용\"을 활성화해 주세요.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "developer.spotify.com에서 자격 증명을 받으세요.", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, "dialogCancel": "취소", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "확인", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, "dialogSave": "저장", "@dialogSave": { "description": "Dialog button - save changes" @@ -1075,26 +659,10 @@ "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "닫기", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "확인", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "거절", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, "dialogClear": "지우기", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "확정", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, "dialogDone": "완료", "@dialogDone": { "description": "Dialog button - action completed" @@ -1123,34 +691,10 @@ "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "다운로드 실패", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "트랙:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "아티스트:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "오류:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, "dialogClearAll": "모두 제거:", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "다운로드한 파일을 모두 삭제하시겠습니까?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "기기에서 삭제하시겠습니까?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, "dialogRemoveExtension": "확장 프로그램 제거", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" @@ -1289,15 +833,6 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "불러오기 실패: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, "snackbarUrlCopied": "{platform} 링크가 클립보드에 저장됨", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", @@ -1358,24 +893,10 @@ "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "{item} 로드 실패", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, "errorNoTracksFound": "트랙을 찾을 수 없습니다", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Seeking is not supported for this live stream", - "@errorSeekNotSupported": { - "description": "Error - seek disabled for live decrypted stream" - }, "errorMissingExtensionSource": "확장 소스가 누락되어, {item}(을)를 로드할 수 없습니다", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1385,34 +906,6 @@ } } }, - "statusQueued": "대기열에 추가됨", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "다운로드 중", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "마무리 중", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "완료됨", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "실패", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "생략됨", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "중단됨", - "@statusPaused": { - "description": "Download status - paused" - }, "actionPause": "멈추기", "@actionPause": { "description": "Action button - pause download" @@ -1425,14 +918,6 @@ "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "정지", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "선택", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, "actionSelectAll": "모두 선택", "@actionSelectAll": { "description": "Action button - select all items" @@ -1441,14 +926,6 @@ "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "붙여넣기", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "CSV 불러오기", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, "actionRemoveCredentials": "자격 증명 제거", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" @@ -1470,19 +947,6 @@ "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "트랙을 탭하여 선택하세요", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "{count} {count, plural, =1{track} other{tracks}}개 제거됨", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "selectionSelectToDelete": "삭제할 트랙을 선택", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" @@ -1523,51 +987,10 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "취소", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "정지", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "재시도", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "제거", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "지우기", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, "filenameFormat": "", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "사용 가능한 표현: ", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, "filenameShowAdvancedTags": "고급 태그 표시", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -1576,10 +999,6 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "폴더 정리", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, "folderOrganizationNone": "정리하지 않음", "@folderOrganizationNone": { "description": "Folder option - flat structure" @@ -1620,27 +1039,10 @@ "@updateAvailable": { "description": "Update dialog title" }, - "updateNewVersion": "Version {version} is available", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "Download", - "@updateDownload": { - "description": "Update button - download update" - }, "updateLater": "Later", "@updateLater": { "description": "Update button - dismiss" }, - "updateChangelog": "Changelog", - "@updateChangelog": { - "description": "Link to changelog" - }, "updateStartingDownload": "Starting download...", "@updateStartingDownload": { "description": "Update status - initializing" @@ -1681,14 +1083,6 @@ "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriority": "Provider Priority", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "Drag to reorder download providers", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, "providerPriorityTitle": "Provider Priority", "@providerPriorityTitle": { "description": "Provider priority page title" @@ -1709,14 +1103,6 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriority": "Metadata Provider Priority", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Order used when fetching track metadata", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, "metadataProviderPriorityTitle": "Metadata Priority", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" @@ -1741,22 +1127,6 @@ "@logTitle": { "description": "Logs screen title" }, - "logCopy": "Copy Logs", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "Clear Logs", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "Share Logs", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "No logs yet", - "@logEmpty": { - "description": "Empty state title" - }, "logCopied": "Logs copied to clipboard", "@logCopied": { "description": "Snackbar - logs copied" @@ -1789,22 +1159,6 @@ "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "ISP 차단이 감지되었습니다", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "요금 제한", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "네트워크 오류", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "트랙을 찾을 수 없습니다", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, "logFilterBySeverity": "심각성에 따라 로그 분류", "@logFilterBySeverity": { "description": "Filter dialog title" @@ -1817,60 +1171,6 @@ "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "문제 요약", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "인터넷 서비스 제공업체(ISP)에서 다운로드 서비스 접속을 차단했을 수 있습니다.", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "VPN을 사용하거나 DNS를 1.1.1.1 또는 8.8.8.8로 변경해 보세요.", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "서비스에 대한 요청이 너무 많습니다", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "몇 분 정도 기다렸다가 다시 시도해 보세요.", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "연결 문제가 감지되었습니다.", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "인터넷 연결 상태를 확인하세요.", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "일부 트랙은 다운로드 서비스 제공자에서 찾을 수 없었습니다.", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "해당 트랙은 무손실 음질로 제공되지 않을 수 있습니다.", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "총 오류 수: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "영향을 받는 도메인: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, "logEntriesFiltered": "({count} filtered)개 항목 필터링", "@logEntriesFiltered": { "description": "Log count with filter active", @@ -2013,10 +1313,6 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "Choose your preferred language", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, "settingsAppearanceSubtitle": "Theme, colors, display", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" @@ -2045,10 +1341,6 @@ "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "Tracks", - "@tracksHeader": { - "description": "Section header for track list" - }, "downloadAllCount": "Download All ({count})", "@downloadAllCount": { "description": "Download all button with count", @@ -2058,15 +1350,6 @@ } } }, - "playAllCount": "Play All ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2204,15 +1487,6 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "Cannot open: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, "dateToday": "Today", "@dateToday": { "description": "Relative date - today" @@ -2248,22 +1522,6 @@ } } }, - "concurrentSequential": "Sequential", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 Parallel", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 Parallel", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "Tap to see error details", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, "storeFilterAll": "All", "@storeFilterAll": { "description": "Store filter - all extensions" @@ -2292,18 +1550,6 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "No extensions found", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "Provider Priority", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "Install Extension", - "@extensionInstallButton": { - "description": "Button to install extension" - }, "extensionDefaultProvider": "Default (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -2503,46 +1749,6 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Lossy", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Enable Lossy Option", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "Lossy quality option is available", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Lossy Format", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Choose the lossy format for conversion", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320kbps, best compatibility", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, "qualityNote": "Actual quality depends on track availability from the service", "@qualityNote": { "description": "Note about quality availability" @@ -2559,49 +1765,6 @@ "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": { - "type": "int" - }, - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateFieldLabel": "Bitrate (kbps)", - "@youtubeBitrateFieldLabel": { - "description": "Label for YouTube bitrate input field" - }, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2622,14 +1785,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -2642,14 +1797,6 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "Save Format", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "Select Service", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, "downloadSelectQuality": "Select Quality", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" @@ -2658,66 +1805,6 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "Default Quality", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "Best available", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "None", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "Save all files directly to download folder", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "Artist", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "Artist Name/filename", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "Album", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "Album Name/filename", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "Artist/Album", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "Artist Name/Album Name/filename", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, "appearanceAmoledDark": "AMOLED Dark", "@appearanceAmoledDark": { "description": "Theme option - pure black" @@ -2726,18 +1813,6 @@ "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "Choose Accent Color", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "Theme Mode", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "Download Queue", - "@queueTitle": { - "description": "Queue screen title" - }, "queueClearAll": "Clear All", "@queueClearAll": { "description": "Button - clear all queue items" @@ -2746,22 +1821,6 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "Export", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Failed downloads exported to TXT file", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "Clear Failed", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "Failed to export downloads", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, "settingsAutoExportFailed": "Auto-export failed downloads", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" @@ -2786,38 +1845,6 @@ "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, - "queueEmpty": "No downloads in queue", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "Add tracks from the home screen", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "Clear completed", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "Download Failed", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "Track:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "Artist:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "Error:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "Unknown error", - "@queueUnknownError": { - "description": "Fallback error message" - }, "albumFolderArtistAlbum": "Artist / Album", "@albumFolderArtistAlbum": { "description": "Album folder option" @@ -2871,19 +1898,6 @@ } } }, - "downloadedAlbumTracksHeader": "Tracks", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} downloaded", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, "downloadedAlbumSelectedCount": "{count} selected", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", @@ -2924,10 +1938,6 @@ } } }, - "utilityFunctions": "Utility Functions", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, "recentTypeArtist": "Artist", "@recentTypeArtist": { "description": "Recent access item type - artist" @@ -2962,32 +1972,14 @@ } } }, - "errorGeneric": "Error: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, "discographyDownload": "Download Discography", "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Play Discography", - "@discographyPlay": { - "description": "Button - play artist discography" - }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Play All", - "@discographyPlayAll": { - "description": "Option - play entire discography" - }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3069,10 +2061,6 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Play Selected", - "@discographyPlaySelected": { - "description": "Button - play selected albums" - }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3150,10 +2138,6 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Library Status", - "@libraryStatus": { - "description": "Section header for library status" - }, "libraryScanSettings": "Scan Settings", "@libraryScanSettings": { "description": "Section header for scan settings" @@ -3230,15 +2214,6 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} tracks", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", @@ -3362,26 +2337,6 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Date Added", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "Today", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "This Week", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "This Month", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "This Year", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -3394,15 +2349,6 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} filter(s) active", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -3425,106 +2371,6 @@ } } }, - "storageSwitchTitle": "Switch Storage Mode", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "Switch to SAF Storage?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "Switch to App Storage?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Existing Downloads", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "New Downloads", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "Continue", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "Select SAF Folder", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "App Storage", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "SAF Storage", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "Storage: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "Storage Statistics", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "{count} tracks in App Storage", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "{count} tracks in SAF Storage", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Your files are stored in multiple locations", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" @@ -3553,18 +2399,6 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, "tutorialDownloadTitle": "Downloading Music", "@tutorialDownloadTitle": { "description": "Tutorial download page title" @@ -3573,18 +2407,6 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, "tutorialLibraryTitle": "Your Library", "@tutorialLibraryTitle": { "description": "Tutorial library page title" @@ -3649,10 +2471,6 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "EXAMPLE", - "@tutorialExample": { - "description": "Example label in tutorial" - }, "libraryForceFullScan": "Force Full Scan", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" @@ -3879,10 +2697,6 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" @@ -4271,48 +3085,21 @@ } } }, - "setupModeSelectionTitle": "Choose Your Mode", - "@setupModeSelectionTitle": { - "description": "Title for mode selection step in setup wizard" + "downloadedAlbumDownloadedCount": "{count} downloaded", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } }, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", - "@setupModeSelectionDescription": { - "description": "Description for mode selection step" + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" }, - "setupModeDownloaderTitle": "Downloader", - "@setupModeDownloaderTitle": { - "description": "Title for downloader mode option" - }, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", - "@setupModeDownloaderFeature1": { - "description": "Downloader mode feature 1" - }, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", - "@setupModeDownloaderFeature2": { - "description": "Downloader mode feature 2" - }, - "setupModeDownloaderFeature3": "Manage your local music library", - "@setupModeDownloaderFeature3": { - "description": "Downloader mode feature 3" - }, - "setupModeStreamingTitle": "Streaming", - "@setupModeStreamingTitle": { - "description": "Title for streaming mode option" - }, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", - "@setupModeStreamingFeature1": { - "description": "Streaming mode feature 1" - }, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", - "@setupModeStreamingFeature2": { - "description": "Streaming mode feature 2" - }, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", - "@setupModeStreamingFeature3": { - "description": "Streaming mode feature 3" - }, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", - "@setupModeChangeableLater": { - "description": "Hint that mode can be changed later" + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" } } \ No newline at end of file From bc279dd7fdb66717ea51ac0fcc8f7447c50c80e8 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:42 +0700 Subject: [PATCH 080/104] New translations app_en.arb (Dutch) --- lib/l10n/arb/app_nl.arb | 1241 +-------------------------------------- 1 file changed, 14 insertions(+), 1227 deletions(-) diff --git a/lib/l10n/arb/app_nl.arb b/lib/l10n/arb/app_nl.arb index a960b2aa..f2b8bb02 100644 --- a/lib/l10n/arb/app_nl.arb +++ b/lib/l10n/arb/app_nl.arb @@ -5,10 +5,6 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "appDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", - "@appDescription": { - "description": "App description shown in about page" - }, "navHome": "Home", "@navHome": { "description": "Bottom navigation - Home tab" @@ -17,10 +13,6 @@ "@navLibrary": { "description": "Bottom navigation - Library tab" }, - "navHistory": "History", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, "navSettings": "Settings", "@navSettings": { "description": "Bottom navigation - Settings tab" @@ -33,20 +25,6 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSearchHint": "Paste Spotify URL or search...", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "Search with {extensionName}...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, "homeSubtitle": "Paste a Spotify link or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" @@ -59,24 +37,6 @@ "@homeRecent": { "description": "Section header for recent searches" }, - "historyTitle": "History", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "Downloading ({count})", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "Downloaded", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, "historyFilterAll": "All", "@historyFilterAll": { "description": "Filter chip - show all items" @@ -89,48 +49,6 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural, =1{1 album} other{{count} albums}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "No download history", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "Downloaded tracks will appear here", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "No album downloads", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "Download multiple tracks from an album to see them here", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "No single downloads", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "Single track downloads will appear here", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, "historySearchHint": "Search history...", "@historySearchHint": { "description": "Search bar placeholder in history" @@ -163,34 +81,6 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "Download Location", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "Choose where to save files", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "Default location", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "Default Service", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "Service used for downloads", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "Default Quality", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "Ask Quality Before Download", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, "downloadAskQualitySubtitle": "Show quality picker for each download", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" @@ -203,38 +93,10 @@ "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "Separate Singles", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "Put single tracks in a separate folder", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "Best Available", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 kbps", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 kbps", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, "appearanceTitle": "Appearance", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "Theme", - "@appearanceTheme": { - "description": "Theme mode setting" - }, "appearanceThemeSystem": "System", "@appearanceThemeSystem": { "description": "Follow system theme" @@ -255,10 +117,6 @@ "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "Accent Color", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, "appearanceHistoryView": "History View", "@appearanceHistoryView": { "description": "Layout style for history" @@ -275,10 +133,6 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "Search Source", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, "optionsPrimaryProvider": "Primary Provider", "@optionsPrimaryProvider": { "description": "Main search provider setting" @@ -308,38 +162,6 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", - "@optionsAutoSkipUnavailableTracks": { - "description": "Toggle to skip to the next queue track when current track stream resolution fails" - }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": { - "description": "Subtitle when auto skip on resolve failure is enabled" - }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": { - "description": "Subtitle when auto skip on resolve failure is disabled" - }, - "optionsInteractionMode": "Interaction Mode", - "@optionsInteractionMode": { - "description": "Tap behavior mode for track lists" - }, - "modeDownloader": "Downloader Mode", - "@modeDownloader": { - "description": "Interaction mode where taps queue downloads" - }, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", - "@modeDownloaderSubtitle": { - "description": "Subtitle for downloader interaction mode" - }, - "modeStreaming": "Streaming Mode", - "@modeStreaming": { - "description": "Interaction mode where taps start playback" - }, - "modeStreamingSubtitle": "Tap tracks to play instantly", - "@modeStreamingSubtitle": { - "description": "Subtitle for streaming interaction mode" - }, "optionsUseExtensionProviders": "Use Extension Providers", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -470,22 +292,6 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "Installed Extensions", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "No extensions installed", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "Install extensions from the Store tab", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "Enabled", - "@extensionsEnabled": { - "description": "Extension status - active" - }, "extensionsDisabled": "Disabled", "@extensionsDisabled": { "description": "Extension status - inactive" @@ -512,10 +318,6 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "Set as Search Provider", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, "storeTitle": "Extension Store", "@storeTitle": { "description": "Store screen title" @@ -612,10 +414,6 @@ "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "Support", - "@aboutSupport": { - "description": "Section for support/donation links" - }, "aboutApp": "App", "@aboutApp": { "description": "Section for app info" @@ -636,14 +434,6 @@ "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "Amazing API for Amazon Music downloads. Thank you for making it free!", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, "aboutDabMusic": "DAB Music", "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" @@ -664,35 +454,6 @@ "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "Album", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural, =1{1 track} other{{count} tracks}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "Download All", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "Download Remaining", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "Playlist", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "Artist", - "@artistTitle": { - "description": "Artist screen title" - }, "artistAlbums": "Albums", "@artistAlbums": { "description": "Section header for artist albums" @@ -705,15 +466,6 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, =1{1 release} other{{count} releases}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "artistPopular": "Popular", "@artistPopular": { "description": "Section header for popular/top tracks" @@ -728,34 +480,6 @@ } } }, - "trackMetadataTitle": "Track Info", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "Artist", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "Album", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "Duration", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "Quality", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "File Path", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "Downloaded", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, "trackMetadataService": "Service", "@trackMetadataService": { "description": "Metadata field - download service used" @@ -772,54 +496,10 @@ "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "Re-download", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "Open Folder", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "Welcome to SpotiFLAC", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "Let's get you started", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "Storage Permission", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "Required to save downloaded files", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "Permission granted", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "Permission denied", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, "setupGrantPermission": "Grant Permission", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "Download Location", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "Choose Folder", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "Continue", - "@setupContinue": { - "description": "Continue to next step button" - }, "setupSkip": "Skip for now", "@setupSkip": { "description": "Skip current step button" @@ -828,10 +508,6 @@ "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, "setupStorageAccessMessageAndroid11": "Android 11+ requires \"All files access\" permission to save files to your chosen download folder.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" @@ -863,10 +539,6 @@ } } }, - "setupSelectDownloadFolder": "Select Download Folder", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, "setupUseDefaultFolder": "Use Default Folder?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" @@ -915,26 +587,6 @@ "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "Storage", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "Notification", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "Folder", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "Permission", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, "setupStorageGranted": "Storage Permission Granted!", "@setupStorageGranted": { "description": "Success message for storage permission" @@ -955,14 +607,6 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "Get notified when downloads complete or require attention.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "Download Folder Selected!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, "setupFolderChoose": "Choose Download Folder", "@setupFolderChoose": { "description": "Button to choose folder" @@ -971,58 +615,14 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "Change Folder", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, "setupSelectFolder": "Select Folder", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "Spotify API (Optional)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Add your Spotify API credentials for better search results and access to Spotify-exclusive content.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Use Spotify API", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "Enter your credentials below", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Using Deezer (no account needed)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Enter Spotify Client ID", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Enter Spotify Client Secret", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Get your free API credentials from the Spotify Developer Dashboard.", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, "setupEnableNotifications": "Enable Notifications", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "You can now proceed to the next step.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "You will receive download progress notifications.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, "setupNotificationBackgroundDescription": "Get notified about download progress and completion. This helps you track downloads when the app is in background.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" @@ -1031,10 +631,6 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "Back", - "@setupBack": { - "description": "Back button text" - }, "setupNext": "Next", "@setupNext": { "description": "Next button text" @@ -1043,26 +639,14 @@ "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "Skip & Start", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, "setupAllowAccessToManageFiles": "Please enable \"Allow access to manage all files\" in the next screen.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "Get credentials from developer.spotify.com", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, "dialogCancel": "Cancel", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "OK", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, "dialogSave": "Save", "@dialogSave": { "description": "Dialog button - save changes" @@ -1075,26 +659,10 @@ "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "Close", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "Yes", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "No", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, "dialogClear": "Clear", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "Confirm", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, "dialogDone": "Done", "@dialogDone": { "description": "Dialog button - action completed" @@ -1123,34 +691,10 @@ "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "Download Failed", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "Track:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "Artist:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "Error:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, "dialogClearAll": "Clear All", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "Are you sure you want to clear all downloads?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "Remove from device?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, "dialogRemoveExtension": "Remove Extension", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" @@ -1289,15 +833,6 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "Failed to load: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, "snackbarUrlCopied": "{platform} URL copied to clipboard", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", @@ -1358,24 +893,10 @@ "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "Failed to load {item}", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, "errorNoTracksFound": "No tracks found", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Seeking is not supported for this live stream", - "@errorSeekNotSupported": { - "description": "Error - seek disabled for live decrypted stream" - }, "errorMissingExtensionSource": "Cannot load {item}: missing extension source", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1385,34 +906,6 @@ } } }, - "statusQueued": "Queued", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "Downloading", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "Finalizing", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "Completed", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "Failed", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "Skipped", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "Paused", - "@statusPaused": { - "description": "Download status - paused" - }, "actionPause": "Pause", "@actionPause": { "description": "Action button - pause download" @@ -1425,14 +918,6 @@ "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "Stop", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "Select", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, "actionSelectAll": "Select All", "@actionSelectAll": { "description": "Action button - select all items" @@ -1441,14 +926,6 @@ "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "Paste", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "Import CSV", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, "actionRemoveCredentials": "Remove Credentials", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" @@ -1470,19 +947,6 @@ "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "Tap tracks to select", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "Delete {count} {count, plural, =1{track} other{tracks}}", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "selectionSelectToDelete": "Select tracks to delete", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" @@ -1523,51 +987,10 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "Cancel", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "Stop", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "Retry", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "Remove", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "Clear", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "Paste", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, "filenameFormat": "Filename Format", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "Preview: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "Available placeholders:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, "filenameShowAdvancedTags": "Show advanced tags", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -1576,10 +999,6 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "Folder Organization", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, "folderOrganizationNone": "No organization", "@folderOrganizationNone": { "description": "Folder option - flat structure" @@ -1620,27 +1039,10 @@ "@updateAvailable": { "description": "Update dialog title" }, - "updateNewVersion": "Version {version} is available", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "Download", - "@updateDownload": { - "description": "Update button - download update" - }, "updateLater": "Later", "@updateLater": { "description": "Update button - dismiss" }, - "updateChangelog": "Changelog", - "@updateChangelog": { - "description": "Link to changelog" - }, "updateStartingDownload": "Starting download...", "@updateStartingDownload": { "description": "Update status - initializing" @@ -1681,14 +1083,6 @@ "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriority": "Provider Priority", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "Drag to reorder download providers", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, "providerPriorityTitle": "Provider Priority", "@providerPriorityTitle": { "description": "Provider priority page title" @@ -1709,14 +1103,6 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriority": "Metadata Provider Priority", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Order used when fetching track metadata", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, "metadataProviderPriorityTitle": "Metadata Priority", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" @@ -1741,22 +1127,6 @@ "@logTitle": { "description": "Logs screen title" }, - "logCopy": "Copy Logs", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "Clear Logs", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "Share Logs", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "No logs yet", - "@logEmpty": { - "description": "Empty state title" - }, "logCopied": "Logs copied to clipboard", "@logCopied": { "description": "Snackbar - logs copied" @@ -1789,22 +1159,6 @@ "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "ISP BLOCKING DETECTED", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "RATE LIMITED", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "NETWORK ERROR", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "TRACK NOT FOUND", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, "logFilterBySeverity": "Filter logs by severity", "@logFilterBySeverity": { "description": "Filter dialog title" @@ -1817,60 +1171,6 @@ "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "Issue Summary", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "Your ISP may be blocking access to download services", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "Too many requests to the service", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "Wait a few minutes before trying again", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "Connection issues detected", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "Check your internet connection", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "Some tracks could not be found on download services", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "The track may not be available in lossless quality", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "Total errors: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "Affected: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, "logEntriesFiltered": "Entries ({count} filtered)", "@logEntriesFiltered": { "description": "Log count with filter active", @@ -2013,10 +1313,6 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "Choose your preferred language", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, "settingsAppearanceSubtitle": "Theme, colors, display", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" @@ -2045,10 +1341,6 @@ "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "Tracks", - "@tracksHeader": { - "description": "Section header for track list" - }, "downloadAllCount": "Download All ({count})", "@downloadAllCount": { "description": "Download all button with count", @@ -2058,15 +1350,6 @@ } } }, - "playAllCount": "Play All ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2204,15 +1487,6 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "Cannot open: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, "dateToday": "Today", "@dateToday": { "description": "Relative date - today" @@ -2248,22 +1522,6 @@ } } }, - "concurrentSequential": "Sequential", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 Parallel", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 Parallel", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "Tap to see error details", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, "storeFilterAll": "All", "@storeFilterAll": { "description": "Store filter - all extensions" @@ -2292,18 +1550,6 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "No extensions found", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "Provider Priority", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "Install Extension", - "@extensionInstallButton": { - "description": "Button to install extension" - }, "extensionDefaultProvider": "Default (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -2503,46 +1749,6 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Lossy", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Enable Lossy Option", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "Lossy quality option is available", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Lossy Format", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Choose the lossy format for conversion", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320kbps, best compatibility", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, "qualityNote": "Actual quality depends on track availability from the service", "@qualityNote": { "description": "Note about quality availability" @@ -2559,49 +1765,6 @@ "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": { - "type": "int" - }, - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateFieldLabel": "Bitrate (kbps)", - "@youtubeBitrateFieldLabel": { - "description": "Label for YouTube bitrate input field" - }, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2622,14 +1785,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -2642,14 +1797,6 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "Save Format", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "Select Service", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, "downloadSelectQuality": "Select Quality", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" @@ -2658,66 +1805,6 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "Default Quality", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "Best available", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "None", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "Save all files directly to download folder", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "Artist", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "Artist Name/filename", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "Album", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "Album Name/filename", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "Artist/Album", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "Artist Name/Album Name/filename", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, "appearanceAmoledDark": "AMOLED Dark", "@appearanceAmoledDark": { "description": "Theme option - pure black" @@ -2726,18 +1813,6 @@ "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "Choose Accent Color", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "Theme Mode", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "Download Queue", - "@queueTitle": { - "description": "Queue screen title" - }, "queueClearAll": "Clear All", "@queueClearAll": { "description": "Button - clear all queue items" @@ -2746,22 +1821,6 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "Export", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Failed downloads exported to TXT file", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "Clear Failed", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "Failed to export downloads", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, "settingsAutoExportFailed": "Auto-export failed downloads", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" @@ -2786,38 +1845,6 @@ "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, - "queueEmpty": "No downloads in queue", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "Add tracks from the home screen", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "Clear completed", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "Download Failed", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "Track:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "Artist:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "Error:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "Unknown error", - "@queueUnknownError": { - "description": "Fallback error message" - }, "albumFolderArtistAlbum": "Artist / Album", "@albumFolderArtistAlbum": { "description": "Album folder option" @@ -2871,19 +1898,6 @@ } } }, - "downloadedAlbumTracksHeader": "Tracks", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} downloaded", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, "downloadedAlbumSelectedCount": "{count} selected", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", @@ -2924,10 +1938,6 @@ } } }, - "utilityFunctions": "Utility Functions", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, "recentTypeArtist": "Artist", "@recentTypeArtist": { "description": "Recent access item type - artist" @@ -2962,32 +1972,14 @@ } } }, - "errorGeneric": "Error: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, "discographyDownload": "Download Discography", "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Play Discography", - "@discographyPlay": { - "description": "Button - play artist discography" - }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Play All", - "@discographyPlayAll": { - "description": "Option - play entire discography" - }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3069,10 +2061,6 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Play Selected", - "@discographyPlaySelected": { - "description": "Button - play selected albums" - }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3150,10 +2138,6 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Library Status", - "@libraryStatus": { - "description": "Section header for library status" - }, "libraryScanSettings": "Scan Settings", "@libraryScanSettings": { "description": "Section header for scan settings" @@ -3230,15 +2214,6 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} tracks", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", @@ -3362,26 +2337,6 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Date Added", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "Today", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "This Week", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "This Month", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "This Year", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -3394,15 +2349,6 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} filter(s) active", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -3425,106 +2371,6 @@ } } }, - "storageSwitchTitle": "Switch Storage Mode", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "Switch to SAF Storage?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "Switch to App Storage?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Existing Downloads", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "New Downloads", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "Continue", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "Select SAF Folder", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "App Storage", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "SAF Storage", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "Storage: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "Storage Statistics", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "{count} tracks in App Storage", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "{count} tracks in SAF Storage", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Your files are stored in multiple locations", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" @@ -3553,18 +2399,6 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, "tutorialDownloadTitle": "Downloading Music", "@tutorialDownloadTitle": { "description": "Tutorial download page title" @@ -3573,18 +2407,6 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, "tutorialLibraryTitle": "Your Library", "@tutorialLibraryTitle": { "description": "Tutorial library page title" @@ -3649,10 +2471,6 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "EXAMPLE", - "@tutorialExample": { - "description": "Example label in tutorial" - }, "libraryForceFullScan": "Force Full Scan", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" @@ -3879,10 +2697,6 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" @@ -4271,48 +3085,21 @@ } } }, - "setupModeSelectionTitle": "Choose Your Mode", - "@setupModeSelectionTitle": { - "description": "Title for mode selection step in setup wizard" + "downloadedAlbumDownloadedCount": "{count} downloaded", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } }, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", - "@setupModeSelectionDescription": { - "description": "Description for mode selection step" + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" }, - "setupModeDownloaderTitle": "Downloader", - "@setupModeDownloaderTitle": { - "description": "Title for downloader mode option" - }, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", - "@setupModeDownloaderFeature1": { - "description": "Downloader mode feature 1" - }, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", - "@setupModeDownloaderFeature2": { - "description": "Downloader mode feature 2" - }, - "setupModeDownloaderFeature3": "Manage your local music library", - "@setupModeDownloaderFeature3": { - "description": "Downloader mode feature 3" - }, - "setupModeStreamingTitle": "Streaming", - "@setupModeStreamingTitle": { - "description": "Title for streaming mode option" - }, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", - "@setupModeStreamingFeature1": { - "description": "Streaming mode feature 1" - }, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", - "@setupModeStreamingFeature2": { - "description": "Streaming mode feature 2" - }, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", - "@setupModeStreamingFeature3": { - "description": "Streaming mode feature 3" - }, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", - "@setupModeChangeableLater": { - "description": "Hint that mode can be changed later" + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" } } \ No newline at end of file From 585d6da98d8bea1fc1a13c58793ee0fbc33f335e Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:43 +0700 Subject: [PATCH 081/104] New translations app_en.arb (Portuguese) --- lib/l10n/arb/app_pt-PT.arb | 1241 +----------------------------------- 1 file changed, 14 insertions(+), 1227 deletions(-) diff --git a/lib/l10n/arb/app_pt-PT.arb b/lib/l10n/arb/app_pt-PT.arb index 180e8d7e..7d06cfa8 100644 --- a/lib/l10n/arb/app_pt-PT.arb +++ b/lib/l10n/arb/app_pt-PT.arb @@ -5,10 +5,6 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "appDescription": "Baixe faixas do Spotify em qualidade sem perdas de Tidal, Qobuz e Amazon Music.", - "@appDescription": { - "description": "App description shown in about page" - }, "navHome": "Início", "@navHome": { "description": "Bottom navigation - Home tab" @@ -17,10 +13,6 @@ "@navLibrary": { "description": "Bottom navigation - Library tab" }, - "navHistory": "Histórico", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, "navSettings": "Configurações", "@navSettings": { "description": "Bottom navigation - Settings tab" @@ -33,20 +25,6 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSearchHint": "Pesquise ou cole a URL do Spotify...", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "Pesquisar com {extensionName}...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, "homeSubtitle": "Cole um link do Spotify ou procure por nome", "@homeSubtitle": { "description": "Subtitle shown below search box" @@ -59,24 +37,6 @@ "@homeRecent": { "description": "Section header for recent searches" }, - "historyTitle": "Histórico", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "Baixando ({count})", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "Baixados", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, "historyFilterAll": "Tudo", "@historyFilterAll": { "description": "Filter chip - show all items" @@ -89,48 +49,6 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, one {}=1{1 faixa} other{{count} faixas}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural, one {}=1{1 álbum} other{{count} álbuns}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "Nenhum histórico de downloads", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "As faixas baixadas aparecerão aqui", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "Sem álbuns baixados", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "Baixe várias faixas de um álbum para vê-las aqui", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "Sem singles baixados", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "Os downloads de faixa individuais aparecerão aqui", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, "historySearchHint": "Pesquisar histórico...", "@historySearchHint": { "description": "Search bar placeholder in history" @@ -163,34 +81,6 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "Local dos Downloads", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "Escolha onde salvar os arquivos", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "Local padrão", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "Serviço Padrão", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "Serviço usado para downloads", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "Qualidade Predefinida", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "Perguntar qualidade antes de baixar", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, "downloadAskQualitySubtitle": "Mostrar seletor de qualidade para cada download", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" @@ -203,38 +93,10 @@ "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "Separar Singles", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "Colocar singles numa pasta separada", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "Melhor Disponível", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 kbps", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 kbps", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, "appearanceTitle": "Aparência", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "Tema", - "@appearanceTheme": { - "description": "Theme mode setting" - }, "appearanceThemeSystem": "Sistema", "@appearanceThemeSystem": { "description": "Follow system theme" @@ -255,10 +117,6 @@ "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "Cor de Destaque", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, "appearanceHistoryView": "Visualização do Histórico", "@appearanceHistoryView": { "description": "Layout style for history" @@ -275,10 +133,6 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "Origem da Pesquisa", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, "optionsPrimaryProvider": "Provedor Primário", "@optionsPrimaryProvider": { "description": "Main search provider setting" @@ -308,38 +162,6 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", - "@optionsAutoSkipUnavailableTracks": { - "description": "Toggle to skip to the next queue track when current track stream resolution fails" - }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": { - "description": "Subtitle when auto skip on resolve failure is enabled" - }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": { - "description": "Subtitle when auto skip on resolve failure is disabled" - }, - "optionsInteractionMode": "Interaction Mode", - "@optionsInteractionMode": { - "description": "Tap behavior mode for track lists" - }, - "modeDownloader": "Downloader Mode", - "@modeDownloader": { - "description": "Interaction mode where taps queue downloads" - }, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", - "@modeDownloaderSubtitle": { - "description": "Subtitle for downloader interaction mode" - }, - "modeStreaming": "Streaming Mode", - "@modeStreaming": { - "description": "Interaction mode where taps start playback" - }, - "modeStreamingSubtitle": "Tap tracks to play instantly", - "@modeStreamingSubtitle": { - "description": "Subtitle for streaming interaction mode" - }, "optionsUseExtensionProviders": "Usar Provedores de Extensão", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -470,22 +292,6 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "Extensões Instaladas", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "Nenhuma extensão instalada", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "Instalar extensões a partir da aba Loja", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "Habilitado", - "@extensionsEnabled": { - "description": "Extension status - active" - }, "extensionsDisabled": "Desabilitado", "@extensionsDisabled": { "description": "Extension status - inactive" @@ -512,10 +318,6 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "Definir como Provedor de Pesquisa", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, "storeTitle": "Loja de Extensões", "@storeTitle": { "description": "Store screen title" @@ -612,10 +414,6 @@ "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "Apoiar", - "@aboutSupport": { - "description": "Section for support/donation links" - }, "aboutApp": "Aplicativo", "@aboutApp": { "description": "Section for app info" @@ -636,14 +434,6 @@ "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "API incrível para downloads do Amazon Music. Obrigado por fazê-lo gratuitamente!", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, "aboutDabMusic": "DAB Music", "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" @@ -664,35 +454,6 @@ "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "Álbum", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural, one {}=1{1 faixa} other{{count} faixas}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "Baixar Tudo", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "Downloads Restantes", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "Playlist", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "Artista", - "@artistTitle": { - "description": "Artist screen title" - }, "artistAlbums": "Álbuns", "@artistAlbums": { "description": "Section header for artist albums" @@ -705,15 +466,6 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, one {}=1{1 lançamento} other{{count} lançamentos}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "artistPopular": "Populares", "@artistPopular": { "description": "Section header for popular/top tracks" @@ -728,34 +480,6 @@ } } }, - "trackMetadataTitle": "Informações da Faixa", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "Artista", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "Álbum", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "Duração", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "Qualidade", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "Caminho do Arquivo", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "Baixado", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, "trackMetadataService": "Serviço", "@trackMetadataService": { "description": "Metadata field - download service used" @@ -772,54 +496,10 @@ "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "Baixar Novamente", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "Abrir Pasta", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "Bem-vindo ao SpotiFLAC", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "Vamos começar", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "Permissão de Armazenamento", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "Necessária para salvar arquivos baixados", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "Permissão concedida", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "Permissão negada", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, "setupGrantPermission": "Conceder Permissão", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "Local do Download", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "Selecionar Pasta", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "Continuar", - "@setupContinue": { - "description": "Continue to next step button" - }, "setupSkip": "Ignorar por enquanto", "@setupSkip": { "description": "Skip current step button" @@ -828,10 +508,6 @@ "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "O SpotiFLAC precisa da permissão \"Acesso a todos os arquivos\" para salvar arquivos de música na sua pasta escolhida.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, "setupStorageAccessMessageAndroid11": "O Android 11+ requer a permissão \"Acesso a Todos os Arquivos\" para salvar arquivos na pasta de download escolhida.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" @@ -863,10 +539,6 @@ } } }, - "setupSelectDownloadFolder": "Escolher Pasta de Download", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, "setupUseDefaultFolder": "Usar Pasta Padrão?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" @@ -915,26 +587,6 @@ "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "Armazenamento", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "Notificação", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "Pasta", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "Permissão", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, "setupStorageGranted": "Permissão de Armazenamento Concedida!", "@setupStorageGranted": { "description": "Success message for storage permission" @@ -955,14 +607,6 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "Seja notificado quando os downloads completarem ou exigirem atenção.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "Pasta para Download Selecionada!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, "setupFolderChoose": "Escolher Pasta de Download", "@setupFolderChoose": { "description": "Button to choose folder" @@ -971,58 +615,14 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "Alterar Pasta", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, "setupSelectFolder": "Seleccionar Pasta", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "API do Spotify (opcional)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Adicione as suas credenciais da API do Spotify para obter melhores resultados de busca e acesso a conteúdo exclusivo do Spotify.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Usar API do Spotify", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "Insira as suas credenciais abaixo", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Usando o Deezer (nenhuma conta necessária)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Insira o Spotify Client ID", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Insira o Spotify Client Secret", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Receba as suas credenciais de API gratuitas na Spotify Developer Dashboard.", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, "setupEnableNotifications": "Habilitar Notificações", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "Você já pode prosseguir para o próximo passo.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "Você receberá notificações de progresso dos downloads.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, "setupNotificationBackgroundDescription": "Seja notificado sobre o progresso e conclusão do download. Isso ajuda você a acompanhar os downloads quando o app estiver em segundo plano.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" @@ -1031,10 +631,6 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "Voltar", - "@setupBack": { - "description": "Back button text" - }, "setupNext": "Próximo", "@setupNext": { "description": "Next button text" @@ -1043,26 +639,14 @@ "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "Ignorar e Iniciar", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, "setupAllowAccessToManageFiles": "Por favor, habilite \"Permitir acesso para gerenciar todos os arquivos\" na próxima tela.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "Obter credenciais do developer.spotify.com", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, "dialogCancel": "Cancelar", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "OK", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, "dialogSave": "Salvar", "@dialogSave": { "description": "Dialog button - save changes" @@ -1075,26 +659,10 @@ "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "Fechar", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "Sim", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "Não", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, "dialogClear": "Limpar", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "Confirmar", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, "dialogDone": "Concluído", "@dialogDone": { "description": "Dialog button - action completed" @@ -1123,34 +691,10 @@ "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "Download Falhou", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "Faixa:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "Artista:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "Erro:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, "dialogClearAll": "Limpar Tudo", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "Você tem certeza que deseja limpar todos os downloads?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "Remover do dispositivo?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, "dialogRemoveExtension": "Remover Extensão", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" @@ -1289,15 +833,6 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "Falha ao carregar: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, "snackbarUrlCopied": "URL do {platform} copiado para a área de transferência", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", @@ -1358,24 +893,10 @@ "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "Falha ao carregar {item}", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, "errorNoTracksFound": "Nenhuma faixa encontrada", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Seeking is not supported for this live stream", - "@errorSeekNotSupported": { - "description": "Error - seek disabled for live decrypted stream" - }, "errorMissingExtensionSource": "Não é possível carregar {item}: faltando a fonte da extensão", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1385,34 +906,6 @@ } } }, - "statusQueued": "Na Fila", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "Baixando", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "Finalizando", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "Concluído", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "Falhou", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "Ignorado", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "Pausado", - "@statusPaused": { - "description": "Download status - paused" - }, "actionPause": "Pausar", "@actionPause": { "description": "Action button - pause download" @@ -1425,14 +918,6 @@ "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "Parar", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "Selecionar", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, "actionSelectAll": "Selecionar Tudo", "@actionSelectAll": { "description": "Action button - select all items" @@ -1441,14 +926,6 @@ "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "Colar", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "Importar CSV", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, "actionRemoveCredentials": "Remover Credenciais", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" @@ -1470,19 +947,6 @@ "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "Toque nas faixas para selecionar", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "Apagar {count} {count, plural, one {}=1{faixa} other{faixas}}", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "selectionSelectToDelete": "Selecione as faixas para apagar", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" @@ -1523,51 +987,10 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "Cancelar", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "Parar", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "Tentar Novamente", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "Remover", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "Limpar", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "Colar", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, "filenameFormat": "Formato do Nome do Arquivo", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "Prévia: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "Substituições permitidas:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, "filenameShowAdvancedTags": "Show advanced tags", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -1576,10 +999,6 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "Organização de Pastas", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, "folderOrganizationNone": "Nenhuma organização", "@folderOrganizationNone": { "description": "Folder option - flat structure" @@ -1620,27 +1039,10 @@ "@updateAvailable": { "description": "Update dialog title" }, - "updateNewVersion": "A versão {version} está disponível", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "Baixar", - "@updateDownload": { - "description": "Update button - download update" - }, "updateLater": "Depois", "@updateLater": { "description": "Update button - dismiss" }, - "updateChangelog": "Lista de alterações", - "@updateChangelog": { - "description": "Link to changelog" - }, "updateStartingDownload": "Iniciando download...", "@updateStartingDownload": { "description": "Update status - initializing" @@ -1681,14 +1083,6 @@ "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriority": "Prioridade de Provedor", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "Arraste para reordenar os provedores de download", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, "providerPriorityTitle": "Prioridade de Provedor", "@providerPriorityTitle": { "description": "Provider priority page title" @@ -1709,14 +1103,6 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriority": "Prioridade de Provedor de Metadados", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Ordem usada para obter metadados de faixa", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, "metadataProviderPriorityTitle": "Prioridade de Metadados", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" @@ -1741,22 +1127,6 @@ "@logTitle": { "description": "Logs screen title" }, - "logCopy": "Copiar Registros", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "Limpar Registros", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "Compartilhar Registros", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "Ainda não há registros", - "@logEmpty": { - "description": "Empty state title" - }, "logCopied": "Registros copiados para área de transferência", "@logCopied": { "description": "Snackbar - logs copied" @@ -1789,22 +1159,6 @@ "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "BLOQUEIO DE ISP DETECTADO", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "TAXA LIMITADA (RATELIMITED)", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "ERRO DE REDE", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "FAIXA NÃO ENCONTRADA", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, "logFilterBySeverity": "Filtrar registros por gravidade", "@logFilterBySeverity": { "description": "Filter dialog title" @@ -1817,60 +1171,6 @@ "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "Resumo do Problemas", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "O seu provedor pode estar bloqueando o acesso aos serviços de download", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "Tente usar uma VPN ou altere o DNS para 1.1.1 ou 8.8.8.8", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "Muitas solicitações ao serviço", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "Aguarde alguns minutos antes de tentar novamente", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "Problemas de conexão detectados", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "Verifique sua conexão de internet", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "Algumas faixas não foram encontradas nos serviços de download", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "A faixa pode não estar disponível em qualidade sem perdas", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "Total de erros: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "Afetado(s): {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, "logEntriesFiltered": "Entradas ({count} filtradas)", "@logEntriesFiltered": { "description": "Log count with filter active", @@ -2013,10 +1313,6 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "Escolha o seu idioma preferido", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, "settingsAppearanceSubtitle": "Tema, cores, exibição", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" @@ -2045,10 +1341,6 @@ "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "Faixas", - "@tracksHeader": { - "description": "Section header for track list" - }, "downloadAllCount": "Baixar Todos ({count})", "@downloadAllCount": { "description": "Download all button with count", @@ -2058,15 +1350,6 @@ } } }, - "playAllCount": "Play All ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "tracksCount": "{count, plural, one {}=1{1 faixa} other{{count} faixas}}", "@tracksCount": { "description": "Track count display", @@ -2204,15 +1487,6 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "Não foi possível abrir: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, "dateToday": "Hoje", "@dateToday": { "description": "Relative date - today" @@ -2248,22 +1522,6 @@ } } }, - "concurrentSequential": "Sequencial", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 Paralelos", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 Paralelos", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "Toque para ver os detalhes do erro", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, "storeFilterAll": "Tudo", "@storeFilterAll": { "description": "Store filter - all extensions" @@ -2292,18 +1550,6 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "Nenhuma extensão encontrada", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "Prioridade de Provedor", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "Instalar Extensão", - "@extensionInstallButton": { - "description": "Button to install extension" - }, "extensionDefaultProvider": "Padrão (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -2503,46 +1749,6 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Lossy", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Enable Lossy Option", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "Lossy quality option is available", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Lossy Format", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Choose the lossy format for conversion", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320kbps, best compatibility", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, "qualityNote": "A qualidade real depende da faixa que estiver disponível no serviço", "@qualityNote": { "description": "Note about quality availability" @@ -2559,49 +1765,6 @@ "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": { - "type": "int" - }, - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateFieldLabel": "Bitrate (kbps)", - "@youtubeBitrateFieldLabel": { - "description": "Label for YouTube bitrate input field" - }, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, "downloadAskBeforeDownload": "Perguntar qualidade antes de baixar", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2622,14 +1785,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -2642,14 +1797,6 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "Formato para Salvar", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "Selecionar Serviço", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, "downloadSelectQuality": "Selecionar Qualidade", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" @@ -2658,66 +1805,6 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "Qualidade Padrão", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "Melhor Disponível", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "Nenhuma", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "Salvar todos os arquivos diretamente na pasta de download", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "Artista", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "Nome do Artista/arquivo", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "Álbum", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "Nome do Álbum/arquivo", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "Artista/Álbum", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "Nome do Artista/Nome do Álbum/arquivo", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, "appearanceAmoledDark": "Escuro AMOLED", "@appearanceAmoledDark": { "description": "Theme option - pure black" @@ -2726,18 +1813,6 @@ "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "Escolha a Cor de Destaque", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "Modo do Tema", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "Fila de Download", - "@queueTitle": { - "description": "Queue screen title" - }, "queueClearAll": "Limpar Tudo", "@queueClearAll": { "description": "Button - clear all queue items" @@ -2746,22 +1821,6 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "Export", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Failed downloads exported to TXT file", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "Clear Failed", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "Failed to export downloads", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, "settingsAutoExportFailed": "Auto-export failed downloads", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" @@ -2786,38 +1845,6 @@ "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, - "queueEmpty": "Nenhum download na fila", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "Adicione faixas a partir da tela inicial", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "Limpar concluídos", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "Download Falhou", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "Faixa:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "Artista:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "Erro:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "Erro desconhecido", - "@queueUnknownError": { - "description": "Fallback error message" - }, "albumFolderArtistAlbum": "Artista / Álbum", "@albumFolderArtistAlbum": { "description": "Album folder option" @@ -2871,19 +1898,6 @@ } } }, - "downloadedAlbumTracksHeader": "Faixas", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} baixado(s)", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, "downloadedAlbumSelectedCount": "{count} selecionado(s)", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", @@ -2924,10 +1938,6 @@ } } }, - "utilityFunctions": "Funções Utilitárias", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, "recentTypeArtist": "Artista", "@recentTypeArtist": { "description": "Recent access item type - artist" @@ -2962,32 +1972,14 @@ } } }, - "errorGeneric": "Erro: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, "discographyDownload": "Baixar Discografia", "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Play Discography", - "@discographyPlay": { - "description": "Button - play artist discography" - }, "discographyDownloadAll": "Baixar Tudo", "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Play All", - "@discographyPlayAll": { - "description": "Option - play entire discography" - }, "discographyDownloadAllSubtitle": "{count} faixas de {albumCount} lançamentos", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3069,10 +2061,6 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Play Selected", - "@discographyPlaySelected": { - "description": "Button - play selected albums" - }, "discographyAddedToQueue": "{count} faixas adicionadas à fila", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3150,10 +2138,6 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Library Status", - "@libraryStatus": { - "description": "Section header for library status" - }, "libraryScanSettings": "Scan Settings", "@libraryScanSettings": { "description": "Section header for scan settings" @@ -3230,15 +2214,6 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} tracks", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", @@ -3362,26 +2337,6 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Date Added", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "Today", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "This Week", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "This Month", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "This Year", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -3394,15 +2349,6 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} filter(s) active", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -3425,106 +2371,6 @@ } } }, - "storageSwitchTitle": "Switch Storage Mode", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "Switch to SAF Storage?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "Switch to App Storage?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Existing Downloads", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "New Downloads", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "Continue", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "Select SAF Folder", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "App Storage", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "SAF Storage", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "Storage: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "Storage Statistics", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "{count} tracks in App Storage", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "{count} tracks in SAF Storage", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Your files are stored in multiple locations", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" @@ -3553,18 +2399,6 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, "tutorialDownloadTitle": "Downloading Music", "@tutorialDownloadTitle": { "description": "Tutorial download page title" @@ -3573,18 +2407,6 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, "tutorialLibraryTitle": "Your Library", "@tutorialLibraryTitle": { "description": "Tutorial library page title" @@ -3649,10 +2471,6 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "EXAMPLE", - "@tutorialExample": { - "description": "Example label in tutorial" - }, "libraryForceFullScan": "Force Full Scan", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" @@ -3879,10 +2697,6 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" @@ -4271,48 +3085,21 @@ } } }, - "setupModeSelectionTitle": "Choose Your Mode", - "@setupModeSelectionTitle": { - "description": "Title for mode selection step in setup wizard" + "downloadedAlbumDownloadedCount": "{count} baixado(s)", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } }, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", - "@setupModeSelectionDescription": { - "description": "Description for mode selection step" + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" }, - "setupModeDownloaderTitle": "Downloader", - "@setupModeDownloaderTitle": { - "description": "Title for downloader mode option" - }, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", - "@setupModeDownloaderFeature1": { - "description": "Downloader mode feature 1" - }, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", - "@setupModeDownloaderFeature2": { - "description": "Downloader mode feature 2" - }, - "setupModeDownloaderFeature3": "Manage your local music library", - "@setupModeDownloaderFeature3": { - "description": "Downloader mode feature 3" - }, - "setupModeStreamingTitle": "Streaming", - "@setupModeStreamingTitle": { - "description": "Title for streaming mode option" - }, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", - "@setupModeStreamingFeature1": { - "description": "Streaming mode feature 1" - }, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", - "@setupModeStreamingFeature2": { - "description": "Streaming mode feature 2" - }, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", - "@setupModeStreamingFeature3": { - "description": "Streaming mode feature 3" - }, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", - "@setupModeChangeableLater": { - "description": "Hint that mode can be changed later" + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" } } \ No newline at end of file From e1e82ac586a0fc73b82af49445ff0ebf503861a3 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:45 +0700 Subject: [PATCH 082/104] New translations app_en.arb (Russian) --- lib/l10n/arb/app_ru.arb | 1241 +-------------------------------------- 1 file changed, 14 insertions(+), 1227 deletions(-) diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index fbdf0c06..877c925e 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -5,10 +5,6 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "appDescription": "Скачайте треки Spotify в Lossless качестве из Tidal, Qobuz и Amazon Music.", - "@appDescription": { - "description": "App description shown in about page" - }, "navHome": "Главная", "@navHome": { "description": "Bottom navigation - Home tab" @@ -17,10 +13,6 @@ "@navLibrary": { "description": "Bottom navigation - Library tab" }, - "navHistory": "История", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, "navSettings": "Настройки", "@navSettings": { "description": "Bottom navigation - Settings tab" @@ -33,20 +25,6 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSearchHint": "Вставьте URL Spotify или выполните поиск...", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "Искать с помощью {extensionName}...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, "homeSubtitle": "Вставьте ссылку Spotify или ищите по названию", "@homeSubtitle": { "description": "Subtitle shown below search box" @@ -59,24 +37,6 @@ "@homeRecent": { "description": "Section header for recent searches" }, - "historyTitle": "История", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "Скачивание ({count})", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "Скачано", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, "historyFilterAll": "Все", "@historyFilterAll": { "description": "Filter chip - show all items" @@ -89,48 +49,6 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural, one {{count} альбом} few {{count} альбома} many {{count} альбомов} =1 {1 альбом} other {{count} альбомов}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "Нет истории скачиваний", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "Скачанные треки появятся здесь", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "Нет скачанных альбомов", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "Скачайте несколько треков из альбома, чтобы увидеть их здесь", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "Нет скачанных синглов", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "Здесь будут отображаться загрузки синглов", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, "historySearchHint": "Поиск в истории...", "@historySearchHint": { "description": "Search bar placeholder in history" @@ -163,34 +81,6 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "Папка для скачивания", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "Выберите, куда сохранить файлы", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "Расположение по умолчанию", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "Сервис по умолчанию", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "Сервис, используемый для скачивания", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "Качество по умолчанию", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "Спрашивать качество перед скачиванием", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, "downloadAskQualitySubtitle": "Показывать выбор качества для каждого скачивания", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" @@ -203,38 +93,10 @@ "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "Разделять синглы", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "Помещать синглы в отдельную папку", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "Лучшее из доступных", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 кбит/с", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 кбит/с", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, "appearanceTitle": "Внешний вид", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "Тема", - "@appearanceTheme": { - "description": "Theme mode setting" - }, "appearanceThemeSystem": "Системная", "@appearanceThemeSystem": { "description": "Follow system theme" @@ -255,10 +117,6 @@ "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "Акцентный цвет", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, "appearanceHistoryView": "Отображение истории", "@appearanceHistoryView": { "description": "Layout style for history" @@ -275,10 +133,6 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "Поиск источника", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, "optionsPrimaryProvider": "Основной провайдер", "@optionsPrimaryProvider": { "description": "Main search provider setting" @@ -308,38 +162,6 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Автопропуск недоступных треков", - "@optionsAutoSkipUnavailableTracks": { - "description": "Toggle to skip to the next queue track when current track stream resolution fails" - }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Автоматически переходить к следующему треку в очереди, когда поток не может быть решен.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": { - "description": "Subtitle when auto skip on resolve failure is enabled" - }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": { - "description": "Subtitle when auto skip on resolve failure is disabled" - }, - "optionsInteractionMode": "Режим взаимодействия", - "@optionsInteractionMode": { - "description": "Tap behavior mode for track lists" - }, - "modeDownloader": "Режим загрузчика", - "@modeDownloader": { - "description": "Interaction mode where taps queue downloads" - }, - "modeDownloaderSubtitle": "Нажмите на треки, чтобы добавить их в очередь скачивания", - "@modeDownloaderSubtitle": { - "description": "Subtitle for downloader interaction mode" - }, - "modeStreaming": "Режим трансляции", - "@modeStreaming": { - "description": "Interaction mode where taps start playback" - }, - "modeStreamingSubtitle": "Коснитесь треков для мгновенного воспроизведения", - "@modeStreamingSubtitle": { - "description": "Subtitle for streaming interaction mode" - }, "optionsUseExtensionProviders": "Использовать провайдера расширений", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -470,22 +292,6 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "Установленные расширения", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "Нет установленных расширений", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "Установка расширений из вкладки Магазин", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "Включено", - "@extensionsEnabled": { - "description": "Extension status - active" - }, "extensionsDisabled": "Выключено", "@extensionsDisabled": { "description": "Extension status - inactive" @@ -512,10 +318,6 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "Установить в качестве поисковой системы", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, "storeTitle": "Магазин расширений", "@storeTitle": { "description": "Store screen title" @@ -612,10 +414,6 @@ "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "Поддержка", - "@aboutSupport": { - "description": "Section for support/donation links" - }, "aboutApp": "Приложение", "@aboutApp": { "description": "Section for app info" @@ -636,14 +434,6 @@ "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "Удивительный API для загрузок Amazon Music. Спасибо за то, что сделали это бесплатно!", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, "aboutDabMusic": "DAB Music", "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" @@ -664,35 +454,6 @@ "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "Альбом", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "Скачать всё", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "Скачать оставшиеся", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "Плейлист", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "Исполнитель", - "@artistTitle": { - "description": "Artist screen title" - }, "artistAlbums": "Альбомы", "@artistAlbums": { "description": "Section header for artist albums" @@ -705,15 +466,6 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, one {{count} релиз} few {{count} релиза} many {{count} релизов} =1 {1 релиз} other {{count} релизов}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "artistPopular": "Популярное", "@artistPopular": { "description": "Section header for popular/top tracks" @@ -728,34 +480,6 @@ } } }, - "trackMetadataTitle": "Информация о треке", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "Исполнитель", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "Альбом", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "Продолжительность", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "Качество", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "Путь к файлу", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "Скачано", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, "trackMetadataService": "Сервис", "@trackMetadataService": { "description": "Metadata field - download service used" @@ -772,54 +496,10 @@ "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "Скачать снова", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "Открыть папку", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "Добро пожаловать в SpotiFLAC", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "Давайте начнем", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "Доступ к хранилищу", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "Необходимо для сохранения загруженных файлов", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "Разрешение предоставлено", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "Разрешение не предоставлено", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, "setupGrantPermission": "Предоставить разрешение", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "Папка для скачивания", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "Выбрать папку", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "Продолжить", - "@setupContinue": { - "description": "Continue to next step button" - }, "setupSkip": "Пропустить", "@setupSkip": { "description": "Skip current step button" @@ -828,10 +508,6 @@ "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "SpotiFLAC требуется разрешение \"Доступ ко всем файлам\" для сохранения музыкальных файлов в выбранную папку.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, "setupStorageAccessMessageAndroid11": "Для Android 11+ требуется разрешение \"Доступ ко всем файлам\" для сохранения файлов в выбранную вами папку загрузки.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" @@ -863,10 +539,6 @@ } } }, - "setupSelectDownloadFolder": "Выбрать папку для скачивания", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, "setupUseDefaultFolder": "Использовать папку по умолчанию?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" @@ -915,26 +587,6 @@ "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "Хранилище", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "Уведомления", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "Папка", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "Разрешение", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, "setupStorageGranted": "Доступ к хранилищу предоставлен!", "@setupStorageGranted": { "description": "Success message for storage permission" @@ -955,14 +607,6 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "Получайте уведомления о завершении загрузки или о необходимости привлечения внимания.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "Папка для загрузки выбрана!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, "setupFolderChoose": "Выбрать папку для скачивания", "@setupFolderChoose": { "description": "Button to choose folder" @@ -971,58 +615,14 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "Сменить папку", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, "setupSelectFolder": "Выбрать папку", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "Spotify API (необязательно)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Добавьте свои учётные данные Spotify для улучшения результатов поиска и доступа к эксклюзивному контенту Spotify.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Использовать Spotify API", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "Введите ваши учётные данные ниже", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Использование Deezer (аккаунт не требуется)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Введите Client ID Spotify", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Введите Spotify Client Secret", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Получите бесплатный API учётной записи на панели разработчика Spotify.", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, "setupEnableNotifications": "Включить уведомления", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "Теперь вы можете перейти к следующему шагу.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "Вы будете получать уведомления о ходе загрузки.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, "setupNotificationBackgroundDescription": "Получайте уведомления о ходе и завершении загрузки. Это поможет вам отслеживать загрузки, когда приложение находится в фоновом режиме.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" @@ -1031,10 +631,6 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "Назад", - "@setupBack": { - "description": "Back button text" - }, "setupNext": "Далее", "@setupNext": { "description": "Next button text" @@ -1043,26 +639,14 @@ "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "Пропустить и начать", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, "setupAllowAccessToManageFiles": "Пожалуйста, включите \"Разрешить доступ для управления всеми файлами\" на следующем экране.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "Получить учётные данные с developer.spotify.com", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, "dialogCancel": "Отмена", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "ОК", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, "dialogSave": "Сохранить", "@dialogSave": { "description": "Dialog button - save changes" @@ -1075,26 +659,10 @@ "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "Закрыть", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "Да", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "Нет", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, "dialogClear": "Очистить", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "Подтвердить", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, "dialogDone": "Готово", "@dialogDone": { "description": "Dialog button - action completed" @@ -1123,34 +691,10 @@ "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "Ошибка скачивания", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "Трек:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "Исполнитель:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "Ошибка:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, "dialogClearAll": "Очистить всё", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "Вы уверены, что хотите очистить все загрузки?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "Удалить с устройства?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, "dialogRemoveExtension": "Удалить расширение", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" @@ -1289,15 +833,6 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "Ошибка загрузки: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, "snackbarUrlCopied": "{platform} ссылка скопирована в буфер обмена", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", @@ -1358,24 +893,10 @@ "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "Ошибка загрузки {item}", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, "errorNoTracksFound": "Треки не найдены", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Поиск не поддерживается для этой трансляции", - "@errorSeekNotSupported": { - "description": "Error - seek disabled for live decrypted stream" - }, "errorMissingExtensionSource": "Невозможно загрузить {item}: отсутствует источник расширения", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1385,34 +906,6 @@ } } }, - "statusQueued": "В очереди", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "Скачивание", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "Завершение", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "Завершено", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "Неудачно", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "Пропущено", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "Приостановлено", - "@statusPaused": { - "description": "Download status - paused" - }, "actionPause": "Пауза", "@actionPause": { "description": "Action button - pause download" @@ -1425,14 +918,6 @@ "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "Стоп", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "Выбрать", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, "actionSelectAll": "Выбрать все", "@actionSelectAll": { "description": "Action button - select all items" @@ -1441,14 +926,6 @@ "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "Вставить", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "Импорт CSV", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, "actionRemoveCredentials": "Удалить учётные данные", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" @@ -1470,19 +947,6 @@ "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "Нажмите на треки для выбора", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "selectionSelectToDelete": "Выберите треки для удаления", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" @@ -1523,51 +987,10 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "Отмена", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "Стоп", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "Повторить", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "Убрать", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "Очистить", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "Вставить", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, "filenameFormat": "Формат имени файла", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "Предпросмотр: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "Доступные заполнители:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, "filenameShowAdvancedTags": "Показать расширенные теги", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -1576,10 +999,6 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "Организация папок", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, "folderOrganizationNone": "Без организации", "@folderOrganizationNone": { "description": "Folder option - flat structure" @@ -1620,27 +1039,10 @@ "@updateAvailable": { "description": "Update dialog title" }, - "updateNewVersion": "Версия {version} доступна", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "Скачать", - "@updateDownload": { - "description": "Update button - download update" - }, "updateLater": "Позже", "@updateLater": { "description": "Update button - dismiss" }, - "updateChangelog": "Список изменений", - "@updateChangelog": { - "description": "Link to changelog" - }, "updateStartingDownload": "Загрузка началась...", "@updateStartingDownload": { "description": "Update status - initializing" @@ -1681,14 +1083,6 @@ "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriority": "Приоритет провайдера", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "Перетащите для изменения порядка", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, "providerPriorityTitle": "Приоритет провайдера", "@providerPriorityTitle": { "description": "Provider priority page title" @@ -1709,14 +1103,6 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriority": "Приоритет провайдера метаданных", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Порядок, используемый при получении метаданных", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, "metadataProviderPriorityTitle": "Приоритет метаданных", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" @@ -1741,22 +1127,6 @@ "@logTitle": { "description": "Logs screen title" }, - "logCopy": "Скопировать логи", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "Очистить логи", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "Поделиться логами", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "Логов нет", - "@logEmpty": { - "description": "Empty state title" - }, "logCopied": "Логи скопированы в буфер обмена", "@logCopied": { "description": "Snackbar - logs copied" @@ -1789,22 +1159,6 @@ "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "ОБНАРУЖЕНА БЛОКИРОВКА ИНТЕРНЕТ ПРОВАЙДЕРОМ", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "ОГРАНИЧЕННАЯ СКОРОСТЬ", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "ОШИБКА СЕТИ", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "ТРЕК НЕ НАЙДЕН", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, "logFilterBySeverity": "Фильтровать логи по серьезности", "@logFilterBySeverity": { "description": "Filter dialog title" @@ -1817,60 +1171,6 @@ "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "Краткое описание проблемы", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "Ваш провайдер может блокировать доступ к сервисам скачивания", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "Попробуйте использовать VPN или измените DNS на 1.1.1.1 или 8.8.8.8", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "Слишком много запросов к сервису", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "Подождите несколько минут, прежде чем повторить попытку", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "Обнаружены проблемы с подключением", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "Проверьте подключение к Интернету", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "Некоторые треки не найдены в сервисах загрузки", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "Трек может быть недоступен в lossless формате", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "Всего ошибок: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "Затронуто: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, "logEntriesFiltered": "Записи ({count} фильтровано)", "@logEntriesFiltered": { "description": "Log count with filter active", @@ -2013,10 +1313,6 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "Выберите предпочитаемый язык", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, "settingsAppearanceSubtitle": "Тема, цвета, дисплей", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" @@ -2045,10 +1341,6 @@ "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "Треки", - "@tracksHeader": { - "description": "Section header for track list" - }, "downloadAllCount": "Скачать все ({count})", "@downloadAllCount": { "description": "Download all button with count", @@ -2058,15 +1350,6 @@ } } }, - "playAllCount": "Скачать все ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "tracksCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}", "@tracksCount": { "description": "Track count display", @@ -2204,15 +1487,6 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "Невозможно открыть: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, "dateToday": "Сегодня", "@dateToday": { "description": "Relative date - today" @@ -2248,22 +1522,6 @@ } } }, - "concurrentSequential": "Последовательно", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 параллельно", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 параллельно", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "Нажмите, чтобы увидеть подробности ошибки", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, "storeFilterAll": "Все", "@storeFilterAll": { "description": "Store filter - all extensions" @@ -2292,18 +1550,6 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "Расширения не найдены", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "Приоритет провайдера", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "Установить расширение", - "@extensionInstallButton": { - "description": "Button to install extension" - }, "extensionDefaultProvider": "По умолчанию (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -2503,46 +1749,6 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Lossy", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "Opus 320 кбит/с (конвертировать из FLAC)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128 кбит/с (конвертировать из FLAC)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Включить опцию Lossy", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "Доступно качество с потерями", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Скачивать FLAC и конвертировать в MP3 320 кбит/с", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Формат с потерями", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Выберите Lossy формат для конвертации", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320 кбит/с, лучшая совместимость", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128кбит/с, лучшее качество при меньших размерах", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, "qualityNote": "Фактическое качество зависит от доступности треков в сервисе", "@qualityNote": { "description": "Note about quality availability" @@ -2559,49 +1765,6 @@ "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}кбит/с ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": { - "type": "int" - }, - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateInputHelp": "Введите битрейт ({min}-{max} кбит/с)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateFieldLabel": "Битрейт (кбит/с)", - "@youtubeBitrateFieldLabel": { - "description": "Label for YouTube bitrate input field" - }, - "youtubeBitrateValidationError": "Битрейт должен быть между {min} и {max} кбит/с", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, "downloadAskBeforeDownload": "Спрашивать перед скачиванием", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2622,14 +1785,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Для папок исполнителей используется исполнитель альбома, если он указан", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Папки исполнителя используют только трек исполнителя", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, "downloadUsePrimaryArtistOnly": "Основной исполнитель только для папок", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -2642,14 +1797,6 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "Формат сохранения", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "Выбор сервиса", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, "downloadSelectQuality": "Выбор качества", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" @@ -2658,66 +1805,6 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "Качество по умолчанию", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "Лучшее из доступных", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "Отсутствует", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "Сохранить все файлы непосредственно в папку загрузки", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "Исполнитель", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "Исполнитель/имя файла", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "Альбом", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "Альбом/имя файла", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "Исполнитель/Альбом", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "Исполнитель/ Альбом/имя файла", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, "appearanceAmoledDark": "AMOLED", "@appearanceAmoledDark": { "description": "Theme option - pure black" @@ -2726,18 +1813,6 @@ "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "Выберите акцентный цвет", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "Режим темы", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "Очередь скачиваний", - "@queueTitle": { - "description": "Queue screen title" - }, "queueClearAll": "Очистить всё", "@queueClearAll": { "description": "Button - clear all queue items" @@ -2746,22 +1821,6 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "Экспорт", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Сбой при экспорте загрузок в файл TXT", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "Не удалось очистить", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "Не удалось экспортировать загрузки", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, "settingsAutoExportFailed": "Автоэкспорт неудачных загрузок", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" @@ -2786,38 +1845,6 @@ "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, - "queueEmpty": "Нет загрузок в очереди", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "Добавить треки с главного экрана", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "Очистка завершена", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "Ошибка скачивания", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "Трек:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "Исполнитель:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "Ошибка:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "Неизвестная ошибка", - "@queueUnknownError": { - "description": "Fallback error message" - }, "albumFolderArtistAlbum": "Исполнитель / Альбом", "@albumFolderArtistAlbum": { "description": "Album folder option" @@ -2871,19 +1898,6 @@ } } }, - "downloadedAlbumTracksHeader": "Треки", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} скачано", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, "downloadedAlbumSelectedCount": "{count} выбрано", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", @@ -2924,10 +1938,6 @@ } } }, - "utilityFunctions": "Функции утилиты", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, "recentTypeArtist": "Исполнитель", "@recentTypeArtist": { "description": "Recent access item type - artist" @@ -2962,32 +1972,14 @@ } } }, - "errorGeneric": "Ошибка: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, "discographyDownload": "Скачать дискографию", "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Воспроизвести дискографию", - "@discographyPlay": { - "description": "Button - play artist discography" - }, "discographyDownloadAll": "Скачать всё", "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Воспроизвести все", - "@discographyPlayAll": { - "description": "Option - play entire discography" - }, "discographyDownloadAllSubtitle": "{count} треков из {albumCount} релизов", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3069,10 +2061,6 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Воспроизвести выбранное", - "@discographyPlaySelected": { - "description": "Button - play selected albums" - }, "discographyAddedToQueue": "Добавлено {count} треков в очередь", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3150,10 +2138,6 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Статус Библиотеки", - "@libraryStatus": { - "description": "Section header for library status" - }, "libraryScanSettings": "Настройки сканирования", "@libraryScanSettings": { "description": "Section header for scan settings" @@ -3230,15 +2214,6 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} {count, plural, one {Трек} few {Трека} many {Треков} other{Треков}}", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, "libraryTracksUnit": "{count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", @@ -3362,26 +2337,6 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Дата добавления", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "Сегодня", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "На этой неделе", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "В этом месяце", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "В этом году", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, "libraryFilterSort": "Сортировка", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -3394,15 +2349,6 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} фильтр(-ов) активно", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, "timeJustNow": "Только что", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -3425,106 +2371,6 @@ } } }, - "storageSwitchTitle": "Сменить режим хранения", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "Переключиться на SAF хранилище?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "Переключиться хранилище приложения?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Ваши скачанные файлы останутся в текущем расположении и будут доступны.\n\nНовые файлы будут сохранены в выбранной вами папке SAF.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Ваши скачанные файлы останутся в текущем выбранной вами папке SAF.\n\nНовые файлы будут сохранены в папке Music/SpotiFLAC.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Существующие загрузки", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}} в {mode} хранилище", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "Новые загрузки", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Будет сохранено в: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "Продолжить", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "Выберите папку SAF", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "Хранилище приложения", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "Хранилище SAF", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "Хранилище: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "Статистика хранилища", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}} в хранилище приложения", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}} в вашей папке в SAF", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Ваши файлы хранятся в нескольких местах", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, "tutorialWelcomeTitle": "Добро пожаловать в SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" @@ -3553,18 +2399,6 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Вставьте ссылку Spotify или Deezer прямо в поле поиска", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Или введите название песни, исполнителя или альбом для поиска", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Поддержка треков, альбомов, плейлистов и страниц исполнителей", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, "tutorialDownloadTitle": "Скачивание музыки", "@tutorialDownloadTitle": { "description": "Tutorial download page title" @@ -3573,18 +2407,6 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Нажмите кнопку скачать рядом с любым треком, чтобы начать скачивание", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Выберите предпочитаемое качество (FLAC, Hi-Res или MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Скачать все альбомы или плейлисты одним нажатием", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, "tutorialLibraryTitle": "Ваша библиотека", "@tutorialLibraryTitle": { "description": "Tutorial library page title" @@ -3649,10 +2471,6 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "ПРИМЕР", - "@tutorialExample": { - "description": "Example label in tutorial" - }, "libraryForceFullScan": "Полное сканирование", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" @@ -3879,10 +2697,6 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Повторно встраивать метаданные без повторного скачивания", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, "trackReEnrichOnlineSubtitle": "Поиск в сети метаданных и встраивание в файл", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" @@ -4271,48 +3085,21 @@ } } }, - "setupModeSelectionTitle": "Выберите свой режим", - "@setupModeSelectionTitle": { - "description": "Title for mode selection step in setup wizard" + "downloadedAlbumDownloadedCount": "{count} скачано", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } }, - "setupModeSelectionDescription": "Как вы хотите использовать SpotiFLAC? Вы всегда можете изменить это позже в настройках.", - "@setupModeSelectionDescription": { - "description": "Description for mode selection step" + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Для папок исполнителей используется исполнитель альбома, если он указан", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" }, - "setupModeDownloaderTitle": "Загрузчик", - "@setupModeDownloaderTitle": { - "description": "Title for downloader mode option" - }, - "setupModeDownloaderFeature1": "Скачать треки в FLAC формате без потерь", - "@setupModeDownloaderFeature1": { - "description": "Downloader mode feature 1" - }, - "setupModeDownloaderFeature2": "Скачать музыку на ваше устройство для оффлайн прослушивания", - "@setupModeDownloaderFeature2": { - "description": "Downloader mode feature 2" - }, - "setupModeDownloaderFeature3": "Управление вашей музыкальной библиотекой", - "@setupModeDownloaderFeature3": { - "description": "Downloader mode feature 3" - }, - "setupModeStreamingTitle": "Трансляция", - "@setupModeStreamingTitle": { - "description": "Title for streaming mode option" - }, - "setupModeStreamingFeature1": "Трансляция треков мгновенно без скачивания", - "@setupModeStreamingFeature1": { - "description": "Streaming mode feature 1" - }, - "setupModeStreamingFeature2": "Умная Очередь автоматически открывает новую музыку для вас", - "@setupModeStreamingFeature2": { - "description": "Streaming mode feature 2" - }, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", - "@setupModeStreamingFeature3": { - "description": "Streaming mode feature 3" - }, - "setupModeChangeableLater": "Вы можете переключаться между режимами в любое время в Настройках.", - "@setupModeChangeableLater": { - "description": "Hint that mode can be changed later" + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Папки исполнителя используют только трек исполнителя", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" } } \ No newline at end of file From 9c647bb31bc46f98ffda784787d33935a0485387 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:46 +0700 Subject: [PATCH 083/104] New translations app_en.arb (Chinese Simplified) --- lib/l10n/arb/app_zh_CN.arb | 1241 +----------------------------------- 1 file changed, 14 insertions(+), 1227 deletions(-) diff --git a/lib/l10n/arb/app_zh_CN.arb b/lib/l10n/arb/app_zh_CN.arb index 60bd8b49..5b4dd487 100644 --- a/lib/l10n/arb/app_zh_CN.arb +++ b/lib/l10n/arb/app_zh_CN.arb @@ -5,10 +5,6 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "appDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", - "@appDescription": { - "description": "App description shown in about page" - }, "navHome": "Home", "@navHome": { "description": "Bottom navigation - Home tab" @@ -17,10 +13,6 @@ "@navLibrary": { "description": "Bottom navigation - Library tab" }, - "navHistory": "History", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, "navSettings": "Settings", "@navSettings": { "description": "Bottom navigation - Settings tab" @@ -33,20 +25,6 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSearchHint": "Paste Spotify URL or search...", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "Search with {extensionName}...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, "homeSubtitle": "Paste a Spotify link or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" @@ -59,24 +37,6 @@ "@homeRecent": { "description": "Section header for recent searches" }, - "historyTitle": "History", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "Downloading ({count})", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "Downloaded", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, "historyFilterAll": "All", "@historyFilterAll": { "description": "Filter chip - show all items" @@ -89,48 +49,6 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural, =1{1 album} other{{count} albums}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "No download history", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "Downloaded tracks will appear here", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "No album downloads", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "Download multiple tracks from an album to see them here", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "No single downloads", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "Single track downloads will appear here", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, "historySearchHint": "Search history...", "@historySearchHint": { "description": "Search bar placeholder in history" @@ -163,34 +81,6 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "Download Location", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "Choose where to save files", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "Default location", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "Default Service", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "Service used for downloads", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "Default Quality", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "Ask Quality Before Download", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, "downloadAskQualitySubtitle": "Show quality picker for each download", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" @@ -203,38 +93,10 @@ "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "Separate Singles", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "Put single tracks in a separate folder", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "Best Available", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 kbps", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 kbps", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, "appearanceTitle": "Appearance", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "Theme", - "@appearanceTheme": { - "description": "Theme mode setting" - }, "appearanceThemeSystem": "System", "@appearanceThemeSystem": { "description": "Follow system theme" @@ -255,10 +117,6 @@ "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "Accent Color", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, "appearanceHistoryView": "History View", "@appearanceHistoryView": { "description": "Layout style for history" @@ -275,10 +133,6 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "Search Source", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, "optionsPrimaryProvider": "Primary Provider", "@optionsPrimaryProvider": { "description": "Main search provider setting" @@ -308,38 +162,6 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", - "@optionsAutoSkipUnavailableTracks": { - "description": "Toggle to skip to the next queue track when current track stream resolution fails" - }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": { - "description": "Subtitle when auto skip on resolve failure is enabled" - }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": { - "description": "Subtitle when auto skip on resolve failure is disabled" - }, - "optionsInteractionMode": "Interaction Mode", - "@optionsInteractionMode": { - "description": "Tap behavior mode for track lists" - }, - "modeDownloader": "Downloader Mode", - "@modeDownloader": { - "description": "Interaction mode where taps queue downloads" - }, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", - "@modeDownloaderSubtitle": { - "description": "Subtitle for downloader interaction mode" - }, - "modeStreaming": "Streaming Mode", - "@modeStreaming": { - "description": "Interaction mode where taps start playback" - }, - "modeStreamingSubtitle": "Tap tracks to play instantly", - "@modeStreamingSubtitle": { - "description": "Subtitle for streaming interaction mode" - }, "optionsUseExtensionProviders": "Use Extension Providers", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -470,22 +292,6 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "Installed Extensions", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "No extensions installed", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "Install extensions from the Store tab", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "Enabled", - "@extensionsEnabled": { - "description": "Extension status - active" - }, "extensionsDisabled": "Disabled", "@extensionsDisabled": { "description": "Extension status - inactive" @@ -512,10 +318,6 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "Set as Search Provider", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, "storeTitle": "Extension Store", "@storeTitle": { "description": "Store screen title" @@ -612,10 +414,6 @@ "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "Support", - "@aboutSupport": { - "description": "Section for support/donation links" - }, "aboutApp": "App", "@aboutApp": { "description": "Section for app info" @@ -636,14 +434,6 @@ "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "Amazing API for Amazon Music downloads. Thank you for making it free!", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, "aboutDabMusic": "DAB Music", "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" @@ -664,35 +454,6 @@ "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "Album", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural, =1{1 track} other{{count} tracks}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "Download All", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "Download Remaining", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "Playlist", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "Artist", - "@artistTitle": { - "description": "Artist screen title" - }, "artistAlbums": "Albums", "@artistAlbums": { "description": "Section header for artist albums" @@ -705,15 +466,6 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, =1{1 release} other{{count} releases}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "artistPopular": "Popular", "@artistPopular": { "description": "Section header for popular/top tracks" @@ -728,34 +480,6 @@ } } }, - "trackMetadataTitle": "Track Info", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "Artist", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "Album", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "Duration", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "Quality", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "File Path", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "Downloaded", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, "trackMetadataService": "Service", "@trackMetadataService": { "description": "Metadata field - download service used" @@ -772,54 +496,10 @@ "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "Re-download", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "Open Folder", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "Welcome to SpotiFLAC", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "Let's get you started", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "Storage Permission", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "Required to save downloaded files", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "Permission granted", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "Permission denied", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, "setupGrantPermission": "Grant Permission", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "Download Location", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "Choose Folder", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "Continue", - "@setupContinue": { - "description": "Continue to next step button" - }, "setupSkip": "Skip for now", "@setupSkip": { "description": "Skip current step button" @@ -828,10 +508,6 @@ "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, "setupStorageAccessMessageAndroid11": "Android 11+ requires \"All files access\" permission to save files to your chosen download folder.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" @@ -863,10 +539,6 @@ } } }, - "setupSelectDownloadFolder": "Select Download Folder", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, "setupUseDefaultFolder": "Use Default Folder?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" @@ -915,26 +587,6 @@ "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "Storage", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "Notification", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "Folder", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "Permission", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, "setupStorageGranted": "Storage Permission Granted!", "@setupStorageGranted": { "description": "Success message for storage permission" @@ -955,14 +607,6 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "Get notified when downloads complete or require attention.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "Download Folder Selected!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, "setupFolderChoose": "Choose Download Folder", "@setupFolderChoose": { "description": "Button to choose folder" @@ -971,58 +615,14 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "Change Folder", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, "setupSelectFolder": "Select Folder", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "Spotify API (Optional)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Add your Spotify API credentials for better search results and access to Spotify-exclusive content.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Use Spotify API", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "Enter your credentials below", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Using Deezer (no account needed)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Enter Spotify Client ID", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Enter Spotify Client Secret", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Get your free API credentials from the Spotify Developer Dashboard.", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, "setupEnableNotifications": "Enable Notifications", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "You can now proceed to the next step.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "You will receive download progress notifications.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, "setupNotificationBackgroundDescription": "Get notified about download progress and completion. This helps you track downloads when the app is in background.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" @@ -1031,10 +631,6 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "Back", - "@setupBack": { - "description": "Back button text" - }, "setupNext": "Next", "@setupNext": { "description": "Next button text" @@ -1043,26 +639,14 @@ "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "Skip & Start", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, "setupAllowAccessToManageFiles": "Please enable \"Allow access to manage all files\" in the next screen.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "Get credentials from developer.spotify.com", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, "dialogCancel": "Cancel", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "OK", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, "dialogSave": "Save", "@dialogSave": { "description": "Dialog button - save changes" @@ -1075,26 +659,10 @@ "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "Close", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "Yes", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "No", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, "dialogClear": "Clear", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "Confirm", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, "dialogDone": "Done", "@dialogDone": { "description": "Dialog button - action completed" @@ -1123,34 +691,10 @@ "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "Download Failed", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "Track:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "Artist:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "Error:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, "dialogClearAll": "Clear All", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "Are you sure you want to clear all downloads?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "Remove from device?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, "dialogRemoveExtension": "Remove Extension", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" @@ -1289,15 +833,6 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "Failed to load: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, "snackbarUrlCopied": "{platform} URL copied to clipboard", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", @@ -1358,24 +893,10 @@ "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "Failed to load {item}", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, "errorNoTracksFound": "No tracks found", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Seeking is not supported for this live stream", - "@errorSeekNotSupported": { - "description": "Error - seek disabled for live decrypted stream" - }, "errorMissingExtensionSource": "Cannot load {item}: missing extension source", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1385,34 +906,6 @@ } } }, - "statusQueued": "Queued", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "Downloading", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "Finalizing", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "Completed", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "Failed", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "Skipped", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "Paused", - "@statusPaused": { - "description": "Download status - paused" - }, "actionPause": "Pause", "@actionPause": { "description": "Action button - pause download" @@ -1425,14 +918,6 @@ "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "Stop", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "Select", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, "actionSelectAll": "Select All", "@actionSelectAll": { "description": "Action button - select all items" @@ -1441,14 +926,6 @@ "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "Paste", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "Import CSV", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, "actionRemoveCredentials": "Remove Credentials", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" @@ -1470,19 +947,6 @@ "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "Tap tracks to select", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "Delete {count} {count, plural, =1{track} other{tracks}}", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "selectionSelectToDelete": "Select tracks to delete", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" @@ -1523,51 +987,10 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "Cancel", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "Stop", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "Retry", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "Remove", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "Clear", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "Paste", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, "filenameFormat": "Filename Format", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "Preview: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "Available placeholders:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, "filenameShowAdvancedTags": "Show advanced tags", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -1576,10 +999,6 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "Folder Organization", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, "folderOrganizationNone": "No organization", "@folderOrganizationNone": { "description": "Folder option - flat structure" @@ -1620,27 +1039,10 @@ "@updateAvailable": { "description": "Update dialog title" }, - "updateNewVersion": "Version {version} is available", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "Download", - "@updateDownload": { - "description": "Update button - download update" - }, "updateLater": "Later", "@updateLater": { "description": "Update button - dismiss" }, - "updateChangelog": "Changelog", - "@updateChangelog": { - "description": "Link to changelog" - }, "updateStartingDownload": "Starting download...", "@updateStartingDownload": { "description": "Update status - initializing" @@ -1681,14 +1083,6 @@ "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriority": "Provider Priority", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "Drag to reorder download providers", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, "providerPriorityTitle": "Provider Priority", "@providerPriorityTitle": { "description": "Provider priority page title" @@ -1709,14 +1103,6 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriority": "Metadata Provider Priority", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Order used when fetching track metadata", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, "metadataProviderPriorityTitle": "Metadata Priority", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" @@ -1741,22 +1127,6 @@ "@logTitle": { "description": "Logs screen title" }, - "logCopy": "Copy Logs", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "Clear Logs", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "Share Logs", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "No logs yet", - "@logEmpty": { - "description": "Empty state title" - }, "logCopied": "Logs copied to clipboard", "@logCopied": { "description": "Snackbar - logs copied" @@ -1789,22 +1159,6 @@ "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "ISP BLOCKING DETECTED", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "RATE LIMITED", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "NETWORK ERROR", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "TRACK NOT FOUND", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, "logFilterBySeverity": "Filter logs by severity", "@logFilterBySeverity": { "description": "Filter dialog title" @@ -1817,60 +1171,6 @@ "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "Issue Summary", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "Your ISP may be blocking access to download services", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "Too many requests to the service", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "Wait a few minutes before trying again", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "Connection issues detected", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "Check your internet connection", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "Some tracks could not be found on download services", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "The track may not be available in lossless quality", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "Total errors: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "Affected: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, "logEntriesFiltered": "Entries ({count} filtered)", "@logEntriesFiltered": { "description": "Log count with filter active", @@ -2013,10 +1313,6 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "Choose your preferred language", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, "settingsAppearanceSubtitle": "Theme, colors, display", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" @@ -2045,10 +1341,6 @@ "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "Tracks", - "@tracksHeader": { - "description": "Section header for track list" - }, "downloadAllCount": "Download All ({count})", "@downloadAllCount": { "description": "Download all button with count", @@ -2058,15 +1350,6 @@ } } }, - "playAllCount": "Play All ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2204,15 +1487,6 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "Cannot open: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, "dateToday": "Today", "@dateToday": { "description": "Relative date - today" @@ -2248,22 +1522,6 @@ } } }, - "concurrentSequential": "Sequential", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 Parallel", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 Parallel", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "Tap to see error details", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, "storeFilterAll": "All", "@storeFilterAll": { "description": "Store filter - all extensions" @@ -2292,18 +1550,6 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "No extensions found", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "Provider Priority", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "Install Extension", - "@extensionInstallButton": { - "description": "Button to install extension" - }, "extensionDefaultProvider": "Default (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -2503,46 +1749,6 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Lossy", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Enable Lossy Option", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "Lossy quality option is available", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Lossy Format", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Choose the lossy format for conversion", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320kbps, best compatibility", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, "qualityNote": "Actual quality depends on track availability from the service", "@qualityNote": { "description": "Note about quality availability" @@ -2559,49 +1765,6 @@ "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": { - "type": "int" - }, - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateFieldLabel": "Bitrate (kbps)", - "@youtubeBitrateFieldLabel": { - "description": "Label for YouTube bitrate input field" - }, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2622,14 +1785,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -2642,14 +1797,6 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "Save Format", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "Select Service", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, "downloadSelectQuality": "Select Quality", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" @@ -2658,66 +1805,6 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "Default Quality", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "Best available", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "None", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "Save all files directly to download folder", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "Artist", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "Artist Name/filename", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "Album", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "Album Name/filename", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "Artist/Album", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "Artist Name/Album Name/filename", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, "appearanceAmoledDark": "AMOLED Dark", "@appearanceAmoledDark": { "description": "Theme option - pure black" @@ -2726,18 +1813,6 @@ "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "Choose Accent Color", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "Theme Mode", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "Download Queue", - "@queueTitle": { - "description": "Queue screen title" - }, "queueClearAll": "Clear All", "@queueClearAll": { "description": "Button - clear all queue items" @@ -2746,22 +1821,6 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "Export", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Failed downloads exported to TXT file", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "Clear Failed", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "Failed to export downloads", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, "settingsAutoExportFailed": "Auto-export failed downloads", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" @@ -2786,38 +1845,6 @@ "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, - "queueEmpty": "No downloads in queue", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "Add tracks from the home screen", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "Clear completed", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "Download Failed", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "Track:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "Artist:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "Error:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "Unknown error", - "@queueUnknownError": { - "description": "Fallback error message" - }, "albumFolderArtistAlbum": "Artist / Album", "@albumFolderArtistAlbum": { "description": "Album folder option" @@ -2871,19 +1898,6 @@ } } }, - "downloadedAlbumTracksHeader": "Tracks", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} downloaded", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, "downloadedAlbumSelectedCount": "{count} selected", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", @@ -2924,10 +1938,6 @@ } } }, - "utilityFunctions": "Utility Functions", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, "recentTypeArtist": "Artist", "@recentTypeArtist": { "description": "Recent access item type - artist" @@ -2962,32 +1972,14 @@ } } }, - "errorGeneric": "Error: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, "discographyDownload": "Download Discography", "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Play Discography", - "@discographyPlay": { - "description": "Button - play artist discography" - }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Play All", - "@discographyPlayAll": { - "description": "Option - play entire discography" - }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3069,10 +2061,6 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Play Selected", - "@discographyPlaySelected": { - "description": "Button - play selected albums" - }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3150,10 +2138,6 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Library Status", - "@libraryStatus": { - "description": "Section header for library status" - }, "libraryScanSettings": "Scan Settings", "@libraryScanSettings": { "description": "Section header for scan settings" @@ -3230,15 +2214,6 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} tracks", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", @@ -3362,26 +2337,6 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Date Added", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "Today", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "This Week", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "This Month", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "This Year", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -3394,15 +2349,6 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} filter(s) active", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -3425,106 +2371,6 @@ } } }, - "storageSwitchTitle": "Switch Storage Mode", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "Switch to SAF Storage?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "Switch to App Storage?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Existing Downloads", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "New Downloads", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "Continue", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "Select SAF Folder", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "App Storage", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "SAF Storage", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "Storage: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "Storage Statistics", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "{count} tracks in App Storage", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "{count} tracks in SAF Storage", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Your files are stored in multiple locations", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" @@ -3553,18 +2399,6 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, "tutorialDownloadTitle": "Downloading Music", "@tutorialDownloadTitle": { "description": "Tutorial download page title" @@ -3573,18 +2407,6 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, "tutorialLibraryTitle": "Your Library", "@tutorialLibraryTitle": { "description": "Tutorial library page title" @@ -3649,10 +2471,6 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "EXAMPLE", - "@tutorialExample": { - "description": "Example label in tutorial" - }, "libraryForceFullScan": "Force Full Scan", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" @@ -3879,10 +2697,6 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" @@ -4271,48 +3085,21 @@ } } }, - "setupModeSelectionTitle": "Choose Your Mode", - "@setupModeSelectionTitle": { - "description": "Title for mode selection step in setup wizard" + "downloadedAlbumDownloadedCount": "{count} downloaded", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } }, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", - "@setupModeSelectionDescription": { - "description": "Description for mode selection step" + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" }, - "setupModeDownloaderTitle": "Downloader", - "@setupModeDownloaderTitle": { - "description": "Title for downloader mode option" - }, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", - "@setupModeDownloaderFeature1": { - "description": "Downloader mode feature 1" - }, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", - "@setupModeDownloaderFeature2": { - "description": "Downloader mode feature 2" - }, - "setupModeDownloaderFeature3": "Manage your local music library", - "@setupModeDownloaderFeature3": { - "description": "Downloader mode feature 3" - }, - "setupModeStreamingTitle": "Streaming", - "@setupModeStreamingTitle": { - "description": "Title for streaming mode option" - }, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", - "@setupModeStreamingFeature1": { - "description": "Streaming mode feature 1" - }, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", - "@setupModeStreamingFeature2": { - "description": "Streaming mode feature 2" - }, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", - "@setupModeStreamingFeature3": { - "description": "Streaming mode feature 3" - }, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", - "@setupModeChangeableLater": { - "description": "Hint that mode can be changed later" + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" } } \ No newline at end of file From f0c5c5660a7caf890942876436ab1ef896e05779 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:47 +0700 Subject: [PATCH 084/104] New translations app_en.arb (Chinese Traditional) --- lib/l10n/arb/app_zh_TW.arb | 1241 +----------------------------------- 1 file changed, 14 insertions(+), 1227 deletions(-) diff --git a/lib/l10n/arb/app_zh_TW.arb b/lib/l10n/arb/app_zh_TW.arb index 0d983390..d33658af 100644 --- a/lib/l10n/arb/app_zh_TW.arb +++ b/lib/l10n/arb/app_zh_TW.arb @@ -5,10 +5,6 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "appDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", - "@appDescription": { - "description": "App description shown in about page" - }, "navHome": "Home", "@navHome": { "description": "Bottom navigation - Home tab" @@ -17,10 +13,6 @@ "@navLibrary": { "description": "Bottom navigation - Library tab" }, - "navHistory": "History", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, "navSettings": "Settings", "@navSettings": { "description": "Bottom navigation - Settings tab" @@ -33,20 +25,6 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSearchHint": "Paste Spotify URL or search...", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "Search with {extensionName}...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, "homeSubtitle": "Paste a Spotify link or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" @@ -59,24 +37,6 @@ "@homeRecent": { "description": "Section header for recent searches" }, - "historyTitle": "History", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "Downloading ({count})", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "Downloaded", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, "historyFilterAll": "All", "@historyFilterAll": { "description": "Filter chip - show all items" @@ -89,48 +49,6 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural, =1{1 album} other{{count} albums}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "No download history", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "Downloaded tracks will appear here", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "No album downloads", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "Download multiple tracks from an album to see them here", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "No single downloads", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "Single track downloads will appear here", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, "historySearchHint": "Search history...", "@historySearchHint": { "description": "Search bar placeholder in history" @@ -163,34 +81,6 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "Download Location", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "Choose where to save files", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "Default location", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "Default Service", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "Service used for downloads", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "Default Quality", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "Ask Quality Before Download", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, "downloadAskQualitySubtitle": "Show quality picker for each download", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" @@ -203,38 +93,10 @@ "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "Separate Singles", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "Put single tracks in a separate folder", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "Best Available", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 kbps", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 kbps", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, "appearanceTitle": "Appearance", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "Theme", - "@appearanceTheme": { - "description": "Theme mode setting" - }, "appearanceThemeSystem": "System", "@appearanceThemeSystem": { "description": "Follow system theme" @@ -255,10 +117,6 @@ "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "Accent Color", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, "appearanceHistoryView": "History View", "@appearanceHistoryView": { "description": "Layout style for history" @@ -275,10 +133,6 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "Search Source", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, "optionsPrimaryProvider": "Primary Provider", "@optionsPrimaryProvider": { "description": "Main search provider setting" @@ -308,38 +162,6 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", - "@optionsAutoSkipUnavailableTracks": { - "description": "Toggle to skip to the next queue track when current track stream resolution fails" - }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": { - "description": "Subtitle when auto skip on resolve failure is enabled" - }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": { - "description": "Subtitle when auto skip on resolve failure is disabled" - }, - "optionsInteractionMode": "Interaction Mode", - "@optionsInteractionMode": { - "description": "Tap behavior mode for track lists" - }, - "modeDownloader": "Downloader Mode", - "@modeDownloader": { - "description": "Interaction mode where taps queue downloads" - }, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", - "@modeDownloaderSubtitle": { - "description": "Subtitle for downloader interaction mode" - }, - "modeStreaming": "Streaming Mode", - "@modeStreaming": { - "description": "Interaction mode where taps start playback" - }, - "modeStreamingSubtitle": "Tap tracks to play instantly", - "@modeStreamingSubtitle": { - "description": "Subtitle for streaming interaction mode" - }, "optionsUseExtensionProviders": "Use Extension Providers", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -470,22 +292,6 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "Installed Extensions", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "No extensions installed", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "Install extensions from the Store tab", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "Enabled", - "@extensionsEnabled": { - "description": "Extension status - active" - }, "extensionsDisabled": "Disabled", "@extensionsDisabled": { "description": "Extension status - inactive" @@ -512,10 +318,6 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "Set as Search Provider", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, "storeTitle": "Extension Store", "@storeTitle": { "description": "Store screen title" @@ -612,10 +414,6 @@ "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "Support", - "@aboutSupport": { - "description": "Section for support/donation links" - }, "aboutApp": "App", "@aboutApp": { "description": "Section for app info" @@ -636,14 +434,6 @@ "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "Amazing API for Amazon Music downloads. Thank you for making it free!", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, "aboutDabMusic": "DAB Music", "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" @@ -664,35 +454,6 @@ "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "Album", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural, =1{1 track} other{{count} tracks}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "Download All", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "Download Remaining", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "Playlist", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "Artist", - "@artistTitle": { - "description": "Artist screen title" - }, "artistAlbums": "Albums", "@artistAlbums": { "description": "Section header for artist albums" @@ -705,15 +466,6 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, =1{1 release} other{{count} releases}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "artistPopular": "Popular", "@artistPopular": { "description": "Section header for popular/top tracks" @@ -728,34 +480,6 @@ } } }, - "trackMetadataTitle": "Track Info", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "Artist", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "Album", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "Duration", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "Quality", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "File Path", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "Downloaded", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, "trackMetadataService": "Service", "@trackMetadataService": { "description": "Metadata field - download service used" @@ -772,54 +496,10 @@ "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "Re-download", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "Open Folder", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "Welcome to SpotiFLAC", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "Let's get you started", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "Storage Permission", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "Required to save downloaded files", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "Permission granted", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "Permission denied", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, "setupGrantPermission": "Grant Permission", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "Download Location", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "Choose Folder", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "Continue", - "@setupContinue": { - "description": "Continue to next step button" - }, "setupSkip": "Skip for now", "@setupSkip": { "description": "Skip current step button" @@ -828,10 +508,6 @@ "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, "setupStorageAccessMessageAndroid11": "Android 11+ requires \"All files access\" permission to save files to your chosen download folder.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" @@ -863,10 +539,6 @@ } } }, - "setupSelectDownloadFolder": "Select Download Folder", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, "setupUseDefaultFolder": "Use Default Folder?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" @@ -915,26 +587,6 @@ "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "Storage", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "Notification", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "Folder", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "Permission", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, "setupStorageGranted": "Storage Permission Granted!", "@setupStorageGranted": { "description": "Success message for storage permission" @@ -955,14 +607,6 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "Get notified when downloads complete or require attention.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "Download Folder Selected!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, "setupFolderChoose": "Choose Download Folder", "@setupFolderChoose": { "description": "Button to choose folder" @@ -971,58 +615,14 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "Change Folder", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, "setupSelectFolder": "Select Folder", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "Spotify API (Optional)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Add your Spotify API credentials for better search results and access to Spotify-exclusive content.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Use Spotify API", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "Enter your credentials below", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Using Deezer (no account needed)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Enter Spotify Client ID", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Enter Spotify Client Secret", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Get your free API credentials from the Spotify Developer Dashboard.", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, "setupEnableNotifications": "Enable Notifications", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "You can now proceed to the next step.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "You will receive download progress notifications.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, "setupNotificationBackgroundDescription": "Get notified about download progress and completion. This helps you track downloads when the app is in background.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" @@ -1031,10 +631,6 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "Back", - "@setupBack": { - "description": "Back button text" - }, "setupNext": "Next", "@setupNext": { "description": "Next button text" @@ -1043,26 +639,14 @@ "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "Skip & Start", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, "setupAllowAccessToManageFiles": "Please enable \"Allow access to manage all files\" in the next screen.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "Get credentials from developer.spotify.com", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, "dialogCancel": "Cancel", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "OK", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, "dialogSave": "Save", "@dialogSave": { "description": "Dialog button - save changes" @@ -1075,26 +659,10 @@ "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "Close", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "Yes", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "No", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, "dialogClear": "Clear", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "Confirm", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, "dialogDone": "Done", "@dialogDone": { "description": "Dialog button - action completed" @@ -1123,34 +691,10 @@ "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "Download Failed", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "Track:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "Artist:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "Error:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, "dialogClearAll": "Clear All", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "Are you sure you want to clear all downloads?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "Remove from device?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, "dialogRemoveExtension": "Remove Extension", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" @@ -1289,15 +833,6 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "Failed to load: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, "snackbarUrlCopied": "{platform} URL copied to clipboard", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", @@ -1358,24 +893,10 @@ "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "Failed to load {item}", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, "errorNoTracksFound": "No tracks found", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Seeking is not supported for this live stream", - "@errorSeekNotSupported": { - "description": "Error - seek disabled for live decrypted stream" - }, "errorMissingExtensionSource": "Cannot load {item}: missing extension source", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1385,34 +906,6 @@ } } }, - "statusQueued": "Queued", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "Downloading", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "Finalizing", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "Completed", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "Failed", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "Skipped", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "Paused", - "@statusPaused": { - "description": "Download status - paused" - }, "actionPause": "Pause", "@actionPause": { "description": "Action button - pause download" @@ -1425,14 +918,6 @@ "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "Stop", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "Select", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, "actionSelectAll": "Select All", "@actionSelectAll": { "description": "Action button - select all items" @@ -1441,14 +926,6 @@ "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "Paste", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "Import CSV", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, "actionRemoveCredentials": "Remove Credentials", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" @@ -1470,19 +947,6 @@ "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "Tap tracks to select", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "Delete {count} {count, plural, =1{track} other{tracks}}", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "selectionSelectToDelete": "Select tracks to delete", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" @@ -1523,51 +987,10 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "Cancel", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "Stop", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "Retry", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "Remove", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "Clear", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "Paste", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, "filenameFormat": "Filename Format", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "Preview: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "Available placeholders:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, "filenameShowAdvancedTags": "Show advanced tags", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -1576,10 +999,6 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "Folder Organization", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, "folderOrganizationNone": "No organization", "@folderOrganizationNone": { "description": "Folder option - flat structure" @@ -1620,27 +1039,10 @@ "@updateAvailable": { "description": "Update dialog title" }, - "updateNewVersion": "Version {version} is available", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "Download", - "@updateDownload": { - "description": "Update button - download update" - }, "updateLater": "Later", "@updateLater": { "description": "Update button - dismiss" }, - "updateChangelog": "Changelog", - "@updateChangelog": { - "description": "Link to changelog" - }, "updateStartingDownload": "Starting download...", "@updateStartingDownload": { "description": "Update status - initializing" @@ -1681,14 +1083,6 @@ "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriority": "Provider Priority", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "Drag to reorder download providers", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, "providerPriorityTitle": "Provider Priority", "@providerPriorityTitle": { "description": "Provider priority page title" @@ -1709,14 +1103,6 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriority": "Metadata Provider Priority", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Order used when fetching track metadata", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, "metadataProviderPriorityTitle": "Metadata Priority", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" @@ -1741,22 +1127,6 @@ "@logTitle": { "description": "Logs screen title" }, - "logCopy": "Copy Logs", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "Clear Logs", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "Share Logs", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "No logs yet", - "@logEmpty": { - "description": "Empty state title" - }, "logCopied": "Logs copied to clipboard", "@logCopied": { "description": "Snackbar - logs copied" @@ -1789,22 +1159,6 @@ "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "ISP BLOCKING DETECTED", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "RATE LIMITED", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "NETWORK ERROR", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "TRACK NOT FOUND", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, "logFilterBySeverity": "Filter logs by severity", "@logFilterBySeverity": { "description": "Filter dialog title" @@ -1817,60 +1171,6 @@ "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "Issue Summary", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "Your ISP may be blocking access to download services", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "Too many requests to the service", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "Wait a few minutes before trying again", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "Connection issues detected", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "Check your internet connection", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "Some tracks could not be found on download services", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "The track may not be available in lossless quality", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "Total errors: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "Affected: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, "logEntriesFiltered": "Entries ({count} filtered)", "@logEntriesFiltered": { "description": "Log count with filter active", @@ -2013,10 +1313,6 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "Choose your preferred language", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, "settingsAppearanceSubtitle": "Theme, colors, display", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" @@ -2045,10 +1341,6 @@ "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "Tracks", - "@tracksHeader": { - "description": "Section header for track list" - }, "downloadAllCount": "Download All ({count})", "@downloadAllCount": { "description": "Download all button with count", @@ -2058,15 +1350,6 @@ } } }, - "playAllCount": "Play All ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2204,15 +1487,6 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "Cannot open: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, "dateToday": "Today", "@dateToday": { "description": "Relative date - today" @@ -2248,22 +1522,6 @@ } } }, - "concurrentSequential": "Sequential", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 Parallel", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 Parallel", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "Tap to see error details", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, "storeFilterAll": "All", "@storeFilterAll": { "description": "Store filter - all extensions" @@ -2292,18 +1550,6 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "No extensions found", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "Provider Priority", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "Install Extension", - "@extensionInstallButton": { - "description": "Button to install extension" - }, "extensionDefaultProvider": "Default (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -2503,46 +1749,6 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Lossy", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Enable Lossy Option", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "Lossy quality option is available", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Lossy Format", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Choose the lossy format for conversion", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320kbps, best compatibility", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, "qualityNote": "Actual quality depends on track availability from the service", "@qualityNote": { "description": "Note about quality availability" @@ -2559,49 +1765,6 @@ "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": { - "type": "int" - }, - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateFieldLabel": "Bitrate (kbps)", - "@youtubeBitrateFieldLabel": { - "description": "Label for YouTube bitrate input field" - }, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2622,14 +1785,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -2642,14 +1797,6 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "Save Format", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "Select Service", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, "downloadSelectQuality": "Select Quality", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" @@ -2658,66 +1805,6 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "Default Quality", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "Best available", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "None", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "Save all files directly to download folder", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "Artist", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "Artist Name/filename", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "Album", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "Album Name/filename", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "Artist/Album", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "Artist Name/Album Name/filename", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, "appearanceAmoledDark": "AMOLED Dark", "@appearanceAmoledDark": { "description": "Theme option - pure black" @@ -2726,18 +1813,6 @@ "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "Choose Accent Color", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "Theme Mode", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "Download Queue", - "@queueTitle": { - "description": "Queue screen title" - }, "queueClearAll": "Clear All", "@queueClearAll": { "description": "Button - clear all queue items" @@ -2746,22 +1821,6 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "Export", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Failed downloads exported to TXT file", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "Clear Failed", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "Failed to export downloads", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, "settingsAutoExportFailed": "Auto-export failed downloads", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" @@ -2786,38 +1845,6 @@ "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, - "queueEmpty": "No downloads in queue", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "Add tracks from the home screen", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "Clear completed", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "Download Failed", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "Track:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "Artist:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "Error:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "Unknown error", - "@queueUnknownError": { - "description": "Fallback error message" - }, "albumFolderArtistAlbum": "Artist / Album", "@albumFolderArtistAlbum": { "description": "Album folder option" @@ -2871,19 +1898,6 @@ } } }, - "downloadedAlbumTracksHeader": "Tracks", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} downloaded", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, "downloadedAlbumSelectedCount": "{count} selected", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", @@ -2924,10 +1938,6 @@ } } }, - "utilityFunctions": "Utility Functions", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, "recentTypeArtist": "Artist", "@recentTypeArtist": { "description": "Recent access item type - artist" @@ -2962,32 +1972,14 @@ } } }, - "errorGeneric": "Error: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, "discographyDownload": "Download Discography", "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Play Discography", - "@discographyPlay": { - "description": "Button - play artist discography" - }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Play All", - "@discographyPlayAll": { - "description": "Option - play entire discography" - }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3069,10 +2061,6 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Play Selected", - "@discographyPlaySelected": { - "description": "Button - play selected albums" - }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3150,10 +2138,6 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Library Status", - "@libraryStatus": { - "description": "Section header for library status" - }, "libraryScanSettings": "Scan Settings", "@libraryScanSettings": { "description": "Section header for scan settings" @@ -3230,15 +2214,6 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} tracks", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", @@ -3362,26 +2337,6 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Date Added", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "Today", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "This Week", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "This Month", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "This Year", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -3394,15 +2349,6 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} filter(s) active", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -3425,106 +2371,6 @@ } } }, - "storageSwitchTitle": "Switch Storage Mode", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "Switch to SAF Storage?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "Switch to App Storage?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Existing Downloads", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "New Downloads", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "Continue", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "Select SAF Folder", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "App Storage", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "SAF Storage", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "Storage: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "Storage Statistics", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "{count} tracks in App Storage", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "{count} tracks in SAF Storage", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Your files are stored in multiple locations", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" @@ -3553,18 +2399,6 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, "tutorialDownloadTitle": "Downloading Music", "@tutorialDownloadTitle": { "description": "Tutorial download page title" @@ -3573,18 +2407,6 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, "tutorialLibraryTitle": "Your Library", "@tutorialLibraryTitle": { "description": "Tutorial library page title" @@ -3649,10 +2471,6 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "EXAMPLE", - "@tutorialExample": { - "description": "Example label in tutorial" - }, "libraryForceFullScan": "Force Full Scan", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" @@ -3879,10 +2697,6 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" @@ -4271,48 +3085,21 @@ } } }, - "setupModeSelectionTitle": "Choose Your Mode", - "@setupModeSelectionTitle": { - "description": "Title for mode selection step in setup wizard" + "downloadedAlbumDownloadedCount": "{count} downloaded", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } }, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", - "@setupModeSelectionDescription": { - "description": "Description for mode selection step" + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" }, - "setupModeDownloaderTitle": "Downloader", - "@setupModeDownloaderTitle": { - "description": "Title for downloader mode option" - }, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", - "@setupModeDownloaderFeature1": { - "description": "Downloader mode feature 1" - }, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", - "@setupModeDownloaderFeature2": { - "description": "Downloader mode feature 2" - }, - "setupModeDownloaderFeature3": "Manage your local music library", - "@setupModeDownloaderFeature3": { - "description": "Downloader mode feature 3" - }, - "setupModeStreamingTitle": "Streaming", - "@setupModeStreamingTitle": { - "description": "Title for streaming mode option" - }, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", - "@setupModeStreamingFeature1": { - "description": "Streaming mode feature 1" - }, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", - "@setupModeStreamingFeature2": { - "description": "Streaming mode feature 2" - }, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", - "@setupModeStreamingFeature3": { - "description": "Streaming mode feature 3" - }, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", - "@setupModeChangeableLater": { - "description": "Hint that mode can be changed later" + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" } } \ No newline at end of file From c6e981b3a14b23ebbdeb2049213a06229097cf80 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:48 +0700 Subject: [PATCH 085/104] New translations app_en.arb (Indonesian) --- lib/l10n/arb/app_id.arb | 1241 +-------------------------------------- 1 file changed, 14 insertions(+), 1227 deletions(-) diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index 171cb813..dcd14626 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -5,10 +5,6 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "appDescription": "Unduh lagu Spotify dalam kualitas lossless dari Tidal, Qobuz, dan Amazon Music.", - "@appDescription": { - "description": "App description shown in about page" - }, "navHome": "Beranda", "@navHome": { "description": "Bottom navigation - Home tab" @@ -17,10 +13,6 @@ "@navLibrary": { "description": "Bottom navigation - Library tab" }, - "navHistory": "Riwayat", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, "navSettings": "Pengaturan", "@navSettings": { "description": "Bottom navigation - Settings tab" @@ -33,20 +25,6 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSearchHint": "Tempel URL Spotify atau cari...", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "Cari dengan {extensionName}...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, "homeSubtitle": "Tempel link Spotify atau cari berdasarkan nama", "@homeSubtitle": { "description": "Subtitle shown below search box" @@ -59,24 +37,6 @@ "@homeRecent": { "description": "Section header for recent searches" }, - "historyTitle": "Riwayat", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "Mengunduh ({count})", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "Terunduh", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, "historyFilterAll": "Semua", "@historyFilterAll": { "description": "Filter chip - show all items" @@ -89,48 +49,6 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, =1{1 lagu} other{{count} lagu}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural, =1{1 album} other{{count} album}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "Tidak ada riwayat unduhan", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "Lagu yang diunduh akan muncul di sini", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "Tidak ada unduhan album", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "Unduh beberapa lagu dari album untuk melihatnya di sini", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "Tidak ada unduhan single", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "Unduhan lagu satuan akan muncul di sini", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, "historySearchHint": "Cari riwayat...", "@historySearchHint": { "description": "Search bar placeholder in history" @@ -163,34 +81,6 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "Lokasi Unduhan", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "Pilih tempat menyimpan file", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "Lokasi default", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "Layanan Default", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "Layanan yang digunakan untuk unduhan", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "Kualitas Default", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "Tanya Kualitas Sebelum Unduh", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, "downloadAskQualitySubtitle": "Tampilkan pemilih kualitas untuk setiap unduhan", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" @@ -203,38 +93,10 @@ "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "Pisahkan Single", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "Letakkan lagu satuan di folder terpisah", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "Terbaik", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 kbps", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 kbps", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, "appearanceTitle": "Tampilan", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "Tema", - "@appearanceTheme": { - "description": "Theme mode setting" - }, "appearanceThemeSystem": "Sistem", "@appearanceThemeSystem": { "description": "Follow system theme" @@ -255,10 +117,6 @@ "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "Warna Aksen", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, "appearanceHistoryView": "Tampilan Riwayat", "@appearanceHistoryView": { "description": "Layout style for history" @@ -275,10 +133,6 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "Sumber Pencarian", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, "optionsPrimaryProvider": "Provider Utama", "@optionsPrimaryProvider": { "description": "Main search provider setting" @@ -308,38 +162,6 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Lewati Otomatis Trek yang Tidak Tersedia", - "@optionsAutoSkipUnavailableTracks": { - "description": "Toggle to skip to the next queue track when current track stream resolution fails" - }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Secara otomatis melompati ke trek antrean berikutnya ketika aliran data tidak dapat diselesaikan.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": { - "description": "Subtitle when auto skip on resolve failure is enabled" - }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Berhenti jika resolusi trek gagal dan tampilkan kesalahan.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": { - "description": "Subtitle when auto skip on resolve failure is disabled" - }, - "optionsInteractionMode": "Mode Interaksi", - "@optionsInteractionMode": { - "description": "Tap behavior mode for track lists" - }, - "modeDownloader": "Mode Pengunduh", - "@modeDownloader": { - "description": "Interaction mode where taps queue downloads" - }, - "modeDownloaderSubtitle": "Ketuk lagu untuk menambahkannya ke antrean unduhan", - "@modeDownloaderSubtitle": { - "description": "Subtitle for downloader interaction mode" - }, - "modeStreaming": "Mode Streaming", - "@modeStreaming": { - "description": "Interaction mode where taps start playback" - }, - "modeStreamingSubtitle": "Ketuk trek untuk memutar secara instan", - "@modeStreamingSubtitle": { - "description": "Subtitle for streaming interaction mode" - }, "optionsUseExtensionProviders": "Gunakan Provider Ekstensi", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -470,22 +292,6 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "Ekstensi Terpasang", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "Tidak ada ekstensi terpasang", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "Pasang ekstensi dari tab Toko", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "Aktif", - "@extensionsEnabled": { - "description": "Extension status - active" - }, "extensionsDisabled": "Nonaktif", "@extensionsDisabled": { "description": "Extension status - inactive" @@ -512,10 +318,6 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "Jadikan Provider Pencarian", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, "storeTitle": "Toko Ekstensi", "@storeTitle": { "description": "Store screen title" @@ -612,10 +414,6 @@ "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "Dukungan", - "@aboutSupport": { - "description": "Section for support/donation links" - }, "aboutApp": "Aplikasi", "@aboutApp": { "description": "Section for app info" @@ -636,14 +434,6 @@ "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "API luar biasa untuk unduhan Amazon Music. Terima kasih sudah membuatnya gratis!", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, "aboutDabMusic": "DAB Music", "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" @@ -664,35 +454,6 @@ "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "Album", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural, =1{1 lagu} other{{count} lagu}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "Unduh Semua", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "Unduh Sisanya", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "Daftar putar", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "Artis", - "@artistTitle": { - "description": "Artist screen title" - }, "artistAlbums": "Album", "@artistAlbums": { "description": "Section header for artist albums" @@ -705,15 +466,6 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, =1{1 rilis} other{{count} rilis}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "artistPopular": "Populer", "@artistPopular": { "description": "Section header for popular/top tracks" @@ -728,34 +480,6 @@ } } }, - "trackMetadataTitle": "Info Lagu", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "Artis", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "Album", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "Durasi", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "Kualitas", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "Lokasi File", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "Diunduh", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, "trackMetadataService": "Layanan", "@trackMetadataService": { "description": "Metadata field - download service used" @@ -772,54 +496,10 @@ "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "Unduh ulang", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "Buka Folder", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "Selamat Datang di SpotiFLAC", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "Mari mulai pengaturan", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "Izin Penyimpanan", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "Diperlukan untuk menyimpan file unduhan", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "Izin diberikan", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "Izin ditolak", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, "setupGrantPermission": "Berikan Izin", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "Lokasi Unduhan", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "Pilih Folder", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "Lanjutkan", - "@setupContinue": { - "description": "Continue to next step button" - }, "setupSkip": "Lewati untuk sekarang", "@setupSkip": { "description": "Skip current step button" @@ -828,10 +508,6 @@ "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "SpotiFLAC membutuhkan izin \"Akses semua file\" untuk menyimpan file musik ke folder pilihan Anda.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, "setupStorageAccessMessageAndroid11": "Android 11+ memerlukan izin \"Akses semua file\" untuk menyimpan file ke folder unduhan pilihan Anda.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" @@ -863,10 +539,6 @@ } } }, - "setupSelectDownloadFolder": "Pilih Folder Unduhan", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, "setupUseDefaultFolder": "Gunakan Folder Default?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" @@ -915,26 +587,6 @@ "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "Penyimpanan", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "Notifikasi", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "Folder", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "Izin", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, "setupStorageGranted": "Izin Penyimpanan Diberikan!", "@setupStorageGranted": { "description": "Success message for storage permission" @@ -955,14 +607,6 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "Dapatkan pemberitahuan saat unduhan selesai atau membutuhkan perhatian.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "Folder Unduhan Dipilih!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, "setupFolderChoose": "Pilih Folder Unduhan", "@setupFolderChoose": { "description": "Button to choose folder" @@ -971,58 +615,14 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "Ubah Folder", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, "setupSelectFolder": "Pilih Folder", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "Spotify API (Opsional)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Tambahkan kredensial Spotify API untuk hasil pencarian lebih baik dan akses ke konten eksklusif Spotify.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Gunakan Spotify API", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "Masukkan kredensial Anda di bawah", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Menggunakan Deezer (tidak perlu akun)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Masukkan Spotify Client ID", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Masukkan Spotify Client Secret", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Dapatkan kredensial API gratis dari Spotify Developer Dashboard.", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, "setupEnableNotifications": "Aktifkan Notifikasi", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "Anda dapat melanjutkan ke langkah berikutnya.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "Anda akan menerima notifikasi progres unduhan.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, "setupNotificationBackgroundDescription": "Dapatkan notifikasi tentang progres dan penyelesaian unduhan. Ini membantu Anda melacak unduhan saat aplikasi di latar belakang.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" @@ -1031,10 +631,6 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "Kembali", - "@setupBack": { - "description": "Back button text" - }, "setupNext": "Lanjut", "@setupNext": { "description": "Next button text" @@ -1043,26 +639,14 @@ "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "Lewati & Mulai", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, "setupAllowAccessToManageFiles": "Harap aktifkan \"Izinkan akses untuk mengelola semua file\" di layar berikutnya.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "Dapatkan kredensial dari developer.spotify.com", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, "dialogCancel": "Batal", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "OKE", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, "dialogSave": "Simpan", "@dialogSave": { "description": "Dialog button - save changes" @@ -1075,26 +659,10 @@ "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "Tutup", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "Ya", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "Tidak", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, "dialogClear": "Hapus", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "Konfirmasi", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, "dialogDone": "Selesai", "@dialogDone": { "description": "Dialog button - action completed" @@ -1123,34 +691,10 @@ "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "Unduhan Gagal", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "Lagu:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "Artis:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "Error:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, "dialogClearAll": "Hapus Semua", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "Apakah Anda yakin ingin menghapus semua unduhan?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "Hapus dari perangkat?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, "dialogRemoveExtension": "Hapus Ekstensi", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" @@ -1289,15 +833,6 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "Gagal memuat: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, "snackbarUrlCopied": "URL {platform} disalin ke clipboard", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", @@ -1358,24 +893,10 @@ "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "Gagal memuat {item}", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, "errorNoTracksFound": "Tidak ada lagu ditemukan", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Pencarian tidak didukung untuk siaran langsung ini", - "@errorSeekNotSupported": { - "description": "Error - seek disabled for live decrypted stream" - }, "errorMissingExtensionSource": "Tidak dapat memuat {item}: sumber ekstensi tidak ada", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1385,34 +906,6 @@ } } }, - "statusQueued": "Mengantri", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "Mengunduh", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "Menyelesaikan", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "Selesai", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "Gagal", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "Dilewati", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "Dijeda", - "@statusPaused": { - "description": "Download status - paused" - }, "actionPause": "Jeda", "@actionPause": { "description": "Action button - pause download" @@ -1425,14 +918,6 @@ "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "Hentikan", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "Pilih", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, "actionSelectAll": "Pilih Semua", "@actionSelectAll": { "description": "Action button - select all items" @@ -1441,14 +926,6 @@ "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "Tempel", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "Impor CSV", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, "actionRemoveCredentials": "Hapus Kredensial", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" @@ -1470,19 +947,6 @@ "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "Ketuk lagu untuk memilih", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "Hapus {count} {count, plural, =1{lagu} other{lagu}}", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "selectionSelectToDelete": "Pilih lagu untuk dihapus", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" @@ -1523,51 +987,10 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "Batal", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "Hentikan", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "Coba Lagi", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "Hapus", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "Hapus", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "Tempel", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, "filenameFormat": "Format Nama File", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "Pratinjau: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "Placeholder yang tersedia:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artis} - {judul}", - "@filenameHint": { - "description": "Default filename format hint" - }, "filenameShowAdvancedTags": "Show advanced tags", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -1576,10 +999,6 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "Organisasi Folder", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, "folderOrganizationNone": "Tidak ada", "@folderOrganizationNone": { "description": "Folder option - flat structure" @@ -1620,27 +1039,10 @@ "@updateAvailable": { "description": "Update dialog title" }, - "updateNewVersion": "Versi {version} tersedia", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "Unduh", - "@updateDownload": { - "description": "Update button - download update" - }, "updateLater": "Nanti", "@updateLater": { "description": "Update button - dismiss" }, - "updateChangelog": "Log Perubahan", - "@updateChangelog": { - "description": "Link to changelog" - }, "updateStartingDownload": "Memulai unduhan...", "@updateStartingDownload": { "description": "Update status - initializing" @@ -1681,14 +1083,6 @@ "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriority": "Prioritas Provider", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "Seret untuk mengatur ulang provider unduhan", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, "providerPriorityTitle": "Prioritas Provider", "@providerPriorityTitle": { "description": "Provider priority page title" @@ -1709,14 +1103,6 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriority": "Prioritas Provider Metadata", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Urutan yang digunakan saat mengambil metadata lagu", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, "metadataProviderPriorityTitle": "Prioritas Metadata", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" @@ -1741,22 +1127,6 @@ "@logTitle": { "description": "Logs screen title" }, - "logCopy": "Salin Log", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "Hapus Log", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "Bagikan Log", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "Belum ada log", - "@logEmpty": { - "description": "Empty state title" - }, "logCopied": "Log disalin ke clipboard", "@logCopied": { "description": "Snackbar - logs copied" @@ -1789,22 +1159,6 @@ "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "PEMBLOKIRAN ISP TERDETEKSI", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "DIBATASI", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "ERROR JARINGAN", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "LAGU TIDAK DITEMUKAN", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, "logFilterBySeverity": "Filter log berdasarkan tingkat keparahan", "@logFilterBySeverity": { "description": "Filter dialog title" @@ -1817,60 +1171,6 @@ "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "Ringkasan Masalah", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "ISP Anda mungkin memblokir akses ke layanan unduhan", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "Coba gunakan VPN atau ubah DNS ke 1.1.1.1 atau 8.8.8.8", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "Terlalu banyak permintaan ke layanan", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "Tunggu beberapa menit sebelum mencoba lagi", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "Masalah koneksi terdeteksi", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "Periksa koneksi internet Anda", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "Beberapa lagu tidak dapat ditemukan di layanan unduhan", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "Lagu mungkin tidak tersedia dalam kualitas lossless", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "Total error: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "Terpengaruh: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, "logEntriesFiltered": "Entri ({count} difilter)", "@logEntriesFiltered": { "description": "Log count with filter active", @@ -2013,10 +1313,6 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "Pilih bahasa yang kamu inginkan", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, "settingsAppearanceSubtitle": "Tema, warna, tampilan", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" @@ -2045,10 +1341,6 @@ "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "Lagu", - "@tracksHeader": { - "description": "Section header for track list" - }, "downloadAllCount": "Unduh Semua ({count})", "@downloadAllCount": { "description": "Download all button with count", @@ -2058,15 +1350,6 @@ } } }, - "playAllCount": "Play All ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "tracksCount": "{count, plural, =1{1 lagu} other{{count} lagu}}", "@tracksCount": { "description": "Track count display", @@ -2204,15 +1487,6 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "Tidak dapat membuka: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, "dateToday": "Hari ini", "@dateToday": { "description": "Relative date - today" @@ -2248,22 +1522,6 @@ } } }, - "concurrentSequential": "Berurutan", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 Paralel", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 Paralel", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "Ketuk untuk melihat detail error", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, "storeFilterAll": "Semua", "@storeFilterAll": { "description": "Store filter - all extensions" @@ -2292,18 +1550,6 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "Tidak ada ekstensi ditemukan", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "Prioritas Provider", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "Pasang Ekstensi", - "@extensionInstallButton": { - "description": "Button to install extension" - }, "extensionDefaultProvider": "Default (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -2503,46 +1749,6 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Lossy", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Enable Lossy Option", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "Lossy quality option is available", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Lossy Format", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Choose the lossy format for conversion", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320kbps, best compatibility", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, "qualityNote": "Kualitas sebenarnya tergantung ketersediaan lagu dari layanan", "@qualityNote": { "description": "Note about quality availability" @@ -2559,49 +1765,6 @@ "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": { - "type": "int" - }, - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateFieldLabel": "Bitrate (kbps)", - "@youtubeBitrateFieldLabel": { - "description": "Label for YouTube bitrate input field" - }, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, "downloadAskBeforeDownload": "Tanya Sebelum Unduh", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2622,14 +1785,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -2642,14 +1797,6 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "Simpan Format", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "Pilih Layanan", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, "downloadSelectQuality": "Pilih Kualitas", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" @@ -2658,66 +1805,6 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "Kualitas Default", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "Terbaik tersedia", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "Tidak ada", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "Simpan semua file langsung ke folder unduhan", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "Artis", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "Nama Artis/namafile", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "Album", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "Nama Album/namafile", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "Artis/Album", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "Nama Artis/Nama Album/namafile", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, "appearanceAmoledDark": "AMOLED Gelap", "@appearanceAmoledDark": { "description": "Theme option - pure black" @@ -2726,18 +1813,6 @@ "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "Pilih Warna Aksen", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "Mode Tema", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "Antrian Unduhan", - "@queueTitle": { - "description": "Queue screen title" - }, "queueClearAll": "Hapus Semua", "@queueClearAll": { "description": "Button - clear all queue items" @@ -2746,22 +1821,6 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "Export", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Failed downloads exported to TXT file", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "Clear Failed", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "Failed to export downloads", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, "settingsAutoExportFailed": "Auto-export failed downloads", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" @@ -2786,38 +1845,6 @@ "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, - "queueEmpty": "Tidak ada unduhan dalam antrian", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "Tambahkan lagu dari layar beranda", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "Hapus yang selesai", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "Unduhan Gagal", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "Lagu:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "Artis:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "Error:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "Error tidak diketahui", - "@queueUnknownError": { - "description": "Fallback error message" - }, "albumFolderArtistAlbum": "Artis / Album", "@albumFolderArtistAlbum": { "description": "Album folder option" @@ -2871,19 +1898,6 @@ } } }, - "downloadedAlbumTracksHeader": "Lagu", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} diunduh", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, "downloadedAlbumSelectedCount": "{count} dipilih", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", @@ -2924,10 +1938,6 @@ } } }, - "utilityFunctions": "Fungsi Utilitas", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, "recentTypeArtist": "Artis", "@recentTypeArtist": { "description": "Recent access item type - artist" @@ -2962,32 +1972,14 @@ } } }, - "errorGeneric": "Error: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, "discographyDownload": "Download Discography", "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Play Discography", - "@discographyPlay": { - "description": "Button - play artist discography" - }, "discographyDownloadAll": "Unduh Semua", "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Play All", - "@discographyPlayAll": { - "description": "Option - play entire discography" - }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3069,10 +2061,6 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Play Selected", - "@discographyPlaySelected": { - "description": "Button - play selected albums" - }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3150,10 +2138,6 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Library Status", - "@libraryStatus": { - "description": "Section header for library status" - }, "libraryScanSettings": "Scan Settings", "@libraryScanSettings": { "description": "Section header for scan settings" @@ -3230,15 +2214,6 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} tracks", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", @@ -3362,26 +2337,6 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Date Added", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "Today", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "This Week", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "This Month", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "This Year", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -3394,15 +2349,6 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} filter(s) active", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -3425,106 +2371,6 @@ } } }, - "storageSwitchTitle": "Switch Storage Mode", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "Switch to SAF Storage?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "Switch to App Storage?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Existing Downloads", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "New Downloads", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "Continue", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "Select SAF Folder", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "App Storage", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "SAF Storage", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "Storage: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "Storage Statistics", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "{count} tracks in App Storage", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "{count} tracks in SAF Storage", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Your files are stored in multiple locations", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" @@ -3553,18 +2399,6 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, "tutorialDownloadTitle": "Downloading Music", "@tutorialDownloadTitle": { "description": "Tutorial download page title" @@ -3573,18 +2407,6 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, "tutorialLibraryTitle": "Your Library", "@tutorialLibraryTitle": { "description": "Tutorial library page title" @@ -3649,10 +2471,6 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "EXAMPLE", - "@tutorialExample": { - "description": "Example label in tutorial" - }, "libraryForceFullScan": "Force Full Scan", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" @@ -3879,10 +2697,6 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" @@ -4271,48 +3085,21 @@ } } }, - "setupModeSelectionTitle": "Choose Your Mode", - "@setupModeSelectionTitle": { - "description": "Title for mode selection step in setup wizard" + "downloadedAlbumDownloadedCount": "{count} diunduh", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } }, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", - "@setupModeSelectionDescription": { - "description": "Description for mode selection step" + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" }, - "setupModeDownloaderTitle": "Downloader", - "@setupModeDownloaderTitle": { - "description": "Title for downloader mode option" - }, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", - "@setupModeDownloaderFeature1": { - "description": "Downloader mode feature 1" - }, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", - "@setupModeDownloaderFeature2": { - "description": "Downloader mode feature 2" - }, - "setupModeDownloaderFeature3": "Manage your local music library", - "@setupModeDownloaderFeature3": { - "description": "Downloader mode feature 3" - }, - "setupModeStreamingTitle": "Streaming", - "@setupModeStreamingTitle": { - "description": "Title for streaming mode option" - }, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", - "@setupModeStreamingFeature1": { - "description": "Streaming mode feature 1" - }, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", - "@setupModeStreamingFeature2": { - "description": "Streaming mode feature 2" - }, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", - "@setupModeStreamingFeature3": { - "description": "Streaming mode feature 3" - }, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", - "@setupModeChangeableLater": { - "description": "Hint that mode can be changed later" + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" } } \ No newline at end of file From 8fab3f60a7bc7cb64223dbf2f5a06a9e71d67e2b Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:49 +0700 Subject: [PATCH 086/104] New translations app_en.arb (Hindi) --- lib/l10n/arb/app_hi.arb | 1241 +-------------------------------------- 1 file changed, 14 insertions(+), 1227 deletions(-) diff --git a/lib/l10n/arb/app_hi.arb b/lib/l10n/arb/app_hi.arb index c395c127..33040826 100644 --- a/lib/l10n/arb/app_hi.arb +++ b/lib/l10n/arb/app_hi.arb @@ -5,10 +5,6 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "appDescription": "स्पॉटीफाई ट्रैक डाउनलोड करें टाइडल, क्वाबज एवं एवं अमेजन म्यूजिक से उच्चतम क्वालिटी में।", - "@appDescription": { - "description": "App description shown in about page" - }, "navHome": "होम", "@navHome": { "description": "Bottom navigation - Home tab" @@ -17,10 +13,6 @@ "@navLibrary": { "description": "Bottom navigation - Library tab" }, - "navHistory": "इतिहास", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, "navSettings": "विकल्प", "@navSettings": { "description": "Bottom navigation - Settings tab" @@ -33,20 +25,6 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSearchHint": "Paste Spotify URL or search...", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "Search with {extensionName}...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, "homeSubtitle": "Paste a Spotify link or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" @@ -59,24 +37,6 @@ "@homeRecent": { "description": "Section header for recent searches" }, - "historyTitle": "History", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "Downloading ({count})", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "Downloaded", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, "historyFilterAll": "All", "@historyFilterAll": { "description": "Filter chip - show all items" @@ -89,48 +49,6 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural, =1{1 album} other{{count} albums}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "No download history", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "Downloaded tracks will appear here", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "No album downloads", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "Download multiple tracks from an album to see them here", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "No single downloads", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "Single track downloads will appear here", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, "historySearchHint": "Search history...", "@historySearchHint": { "description": "Search bar placeholder in history" @@ -163,34 +81,6 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "Download Location", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "Choose where to save files", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "Default location", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "Default Service", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "Service used for downloads", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "Default Quality", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "Ask Quality Before Download", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, "downloadAskQualitySubtitle": "Show quality picker for each download", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" @@ -203,38 +93,10 @@ "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "Separate Singles", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "Put single tracks in a separate folder", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "Best Available", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 kbps", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 kbps", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, "appearanceTitle": "दिखावट", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "Theme", - "@appearanceTheme": { - "description": "Theme mode setting" - }, "appearanceThemeSystem": "System", "@appearanceThemeSystem": { "description": "Follow system theme" @@ -255,10 +117,6 @@ "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "Accent Color", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, "appearanceHistoryView": "History View", "@appearanceHistoryView": { "description": "Layout style for history" @@ -275,10 +133,6 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "Search Source", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, "optionsPrimaryProvider": "Primary Provider", "@optionsPrimaryProvider": { "description": "Main search provider setting" @@ -308,38 +162,6 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", - "@optionsAutoSkipUnavailableTracks": { - "description": "Toggle to skip to the next queue track when current track stream resolution fails" - }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": { - "description": "Subtitle when auto skip on resolve failure is enabled" - }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": { - "description": "Subtitle when auto skip on resolve failure is disabled" - }, - "optionsInteractionMode": "Interaction Mode", - "@optionsInteractionMode": { - "description": "Tap behavior mode for track lists" - }, - "modeDownloader": "Downloader Mode", - "@modeDownloader": { - "description": "Interaction mode where taps queue downloads" - }, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", - "@modeDownloaderSubtitle": { - "description": "Subtitle for downloader interaction mode" - }, - "modeStreaming": "Streaming Mode", - "@modeStreaming": { - "description": "Interaction mode where taps start playback" - }, - "modeStreamingSubtitle": "Tap tracks to play instantly", - "@modeStreamingSubtitle": { - "description": "Subtitle for streaming interaction mode" - }, "optionsUseExtensionProviders": "Use Extension Providers", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -470,22 +292,6 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "Installed Extensions", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "No extensions installed", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "Install extensions from the Store tab", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "Enabled", - "@extensionsEnabled": { - "description": "Extension status - active" - }, "extensionsDisabled": "Disabled", "@extensionsDisabled": { "description": "Extension status - inactive" @@ -512,10 +318,6 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "Set as Search Provider", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, "storeTitle": "Extension Store", "@storeTitle": { "description": "Store screen title" @@ -612,10 +414,6 @@ "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "Support", - "@aboutSupport": { - "description": "Section for support/donation links" - }, "aboutApp": "App", "@aboutApp": { "description": "Section for app info" @@ -636,14 +434,6 @@ "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "Amazing API for Amazon Music downloads. Thank you for making it free!", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, "aboutDabMusic": "DAB Music", "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" @@ -664,35 +454,6 @@ "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "Album", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural, =1{1 track} other{{count} tracks}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "Download All", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "Download Remaining", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "Playlist", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "Artist", - "@artistTitle": { - "description": "Artist screen title" - }, "artistAlbums": "Albums", "@artistAlbums": { "description": "Section header for artist albums" @@ -705,15 +466,6 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, =1{1 release} other{{count} releases}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "artistPopular": "Popular", "@artistPopular": { "description": "Section header for popular/top tracks" @@ -728,34 +480,6 @@ } } }, - "trackMetadataTitle": "Track Info", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "Artist", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "Album", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "Duration", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "Quality", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "File Path", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "Downloaded", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, "trackMetadataService": "Service", "@trackMetadataService": { "description": "Metadata field - download service used" @@ -772,54 +496,10 @@ "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "Re-download", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "Open Folder", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "Welcome to SpotiFLAC", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "Let's get you started", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "Storage Permission", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "Required to save downloaded files", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "Permission granted", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "Permission denied", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, "setupGrantPermission": "Grant Permission", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "Download Location", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "Choose Folder", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "Continue", - "@setupContinue": { - "description": "Continue to next step button" - }, "setupSkip": "Skip for now", "@setupSkip": { "description": "Skip current step button" @@ -828,10 +508,6 @@ "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, "setupStorageAccessMessageAndroid11": "Android 11+ requires \"All files access\" permission to save files to your chosen download folder.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" @@ -863,10 +539,6 @@ } } }, - "setupSelectDownloadFolder": "Select Download Folder", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, "setupUseDefaultFolder": "Use Default Folder?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" @@ -915,26 +587,6 @@ "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "Storage", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "Notification", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "Folder", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "Permission", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, "setupStorageGranted": "Storage Permission Granted!", "@setupStorageGranted": { "description": "Success message for storage permission" @@ -955,14 +607,6 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "Get notified when downloads complete or require attention.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "Download Folder Selected!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, "setupFolderChoose": "Choose Download Folder", "@setupFolderChoose": { "description": "Button to choose folder" @@ -971,58 +615,14 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "Change Folder", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, "setupSelectFolder": "Select Folder", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "Spotify API (Optional)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Add your Spotify API credentials for better search results and access to Spotify-exclusive content.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Use Spotify API", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "Enter your credentials below", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Using Deezer (no account needed)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Enter Spotify Client ID", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Enter Spotify Client Secret", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Get your free API credentials from the Spotify Developer Dashboard.", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, "setupEnableNotifications": "Enable Notifications", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "You can now proceed to the next step.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "You will receive download progress notifications.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, "setupNotificationBackgroundDescription": "Get notified about download progress and completion. This helps you track downloads when the app is in background.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" @@ -1031,10 +631,6 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "Back", - "@setupBack": { - "description": "Back button text" - }, "setupNext": "Next", "@setupNext": { "description": "Next button text" @@ -1043,26 +639,14 @@ "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "Skip & Start", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, "setupAllowAccessToManageFiles": "Please enable \"Allow access to manage all files\" in the next screen.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "Get credentials from developer.spotify.com", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, "dialogCancel": "Cancel", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "OK", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, "dialogSave": "Save", "@dialogSave": { "description": "Dialog button - save changes" @@ -1075,26 +659,10 @@ "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "Close", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "Yes", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "No", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, "dialogClear": "Clear", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "Confirm", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, "dialogDone": "Done", "@dialogDone": { "description": "Dialog button - action completed" @@ -1123,34 +691,10 @@ "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "Download Failed", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "Track:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "Artist:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "Error:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, "dialogClearAll": "Clear All", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "Are you sure you want to clear all downloads?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "Remove from device?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, "dialogRemoveExtension": "Remove Extension", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" @@ -1289,15 +833,6 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "Failed to load: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, "snackbarUrlCopied": "{platform} URL copied to clipboard", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", @@ -1358,24 +893,10 @@ "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "Failed to load {item}", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, "errorNoTracksFound": "No tracks found", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Seeking is not supported for this live stream", - "@errorSeekNotSupported": { - "description": "Error - seek disabled for live decrypted stream" - }, "errorMissingExtensionSource": "Cannot load {item}: missing extension source", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1385,34 +906,6 @@ } } }, - "statusQueued": "Queued", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "Downloading", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "Finalizing", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "Completed", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "Failed", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "Skipped", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "Paused", - "@statusPaused": { - "description": "Download status - paused" - }, "actionPause": "Pause", "@actionPause": { "description": "Action button - pause download" @@ -1425,14 +918,6 @@ "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "Stop", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "Select", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, "actionSelectAll": "Select All", "@actionSelectAll": { "description": "Action button - select all items" @@ -1441,14 +926,6 @@ "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "Paste", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "Import CSV", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, "actionRemoveCredentials": "Remove Credentials", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" @@ -1470,19 +947,6 @@ "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "Tap tracks to select", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "Delete {count} {count, plural, =1{track} other{tracks}}", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "selectionSelectToDelete": "Select tracks to delete", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" @@ -1523,51 +987,10 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "Cancel", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "Stop", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "Retry", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "Remove", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "Clear", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "Paste", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, "filenameFormat": "Filename Format", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "Preview: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "Available placeholders:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, "filenameShowAdvancedTags": "Show advanced tags", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -1576,10 +999,6 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "Folder Organization", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, "folderOrganizationNone": "No organization", "@folderOrganizationNone": { "description": "Folder option - flat structure" @@ -1620,27 +1039,10 @@ "@updateAvailable": { "description": "Update dialog title" }, - "updateNewVersion": "Version {version} is available", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "Download", - "@updateDownload": { - "description": "Update button - download update" - }, "updateLater": "Later", "@updateLater": { "description": "Update button - dismiss" }, - "updateChangelog": "Changelog", - "@updateChangelog": { - "description": "Link to changelog" - }, "updateStartingDownload": "Starting download...", "@updateStartingDownload": { "description": "Update status - initializing" @@ -1681,14 +1083,6 @@ "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriority": "Provider Priority", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "Drag to reorder download providers", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, "providerPriorityTitle": "Provider Priority", "@providerPriorityTitle": { "description": "Provider priority page title" @@ -1709,14 +1103,6 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriority": "Metadata Provider Priority", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Order used when fetching track metadata", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, "metadataProviderPriorityTitle": "Metadata Priority", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" @@ -1741,22 +1127,6 @@ "@logTitle": { "description": "Logs screen title" }, - "logCopy": "Copy Logs", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "Clear Logs", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "Share Logs", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "No logs yet", - "@logEmpty": { - "description": "Empty state title" - }, "logCopied": "Logs copied to clipboard", "@logCopied": { "description": "Snackbar - logs copied" @@ -1789,22 +1159,6 @@ "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "ISP BLOCKING DETECTED", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "RATE LIMITED", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "NETWORK ERROR", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "TRACK NOT FOUND", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, "logFilterBySeverity": "Filter logs by severity", "@logFilterBySeverity": { "description": "Filter dialog title" @@ -1817,60 +1171,6 @@ "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "Issue Summary", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "Your ISP may be blocking access to download services", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "Too many requests to the service", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "Wait a few minutes before trying again", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "Connection issues detected", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "Check your internet connection", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "Some tracks could not be found on download services", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "The track may not be available in lossless quality", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "Total errors: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "Affected: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, "logEntriesFiltered": "Entries ({count} filtered)", "@logEntriesFiltered": { "description": "Log count with filter active", @@ -2013,10 +1313,6 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "Choose your preferred language", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, "settingsAppearanceSubtitle": "Theme, colors, display", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" @@ -2045,10 +1341,6 @@ "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "Tracks", - "@tracksHeader": { - "description": "Section header for track list" - }, "downloadAllCount": "Download All ({count})", "@downloadAllCount": { "description": "Download all button with count", @@ -2058,15 +1350,6 @@ } } }, - "playAllCount": "Play All ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2204,15 +1487,6 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "Cannot open: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, "dateToday": "Today", "@dateToday": { "description": "Relative date - today" @@ -2248,22 +1522,6 @@ } } }, - "concurrentSequential": "Sequential", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 Parallel", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 Parallel", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "Tap to see error details", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, "storeFilterAll": "All", "@storeFilterAll": { "description": "Store filter - all extensions" @@ -2292,18 +1550,6 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "No extensions found", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "Provider Priority", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "Install Extension", - "@extensionInstallButton": { - "description": "Button to install extension" - }, "extensionDefaultProvider": "Default (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -2503,46 +1749,6 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Lossy", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Enable Lossy Option", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "Lossy quality option is available", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Lossy Format", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Choose the lossy format for conversion", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320kbps, best compatibility", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, "qualityNote": "Actual quality depends on track availability from the service", "@qualityNote": { "description": "Note about quality availability" @@ -2559,49 +1765,6 @@ "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": { - "type": "int" - }, - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateFieldLabel": "Bitrate (kbps)", - "@youtubeBitrateFieldLabel": { - "description": "Label for YouTube bitrate input field" - }, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2622,14 +1785,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -2642,14 +1797,6 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "Save Format", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "Select Service", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, "downloadSelectQuality": "Select Quality", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" @@ -2658,66 +1805,6 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "Default Quality", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "Best available", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "None", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "Save all files directly to download folder", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "Artist", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "Artist Name/filename", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "Album", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "Album Name/filename", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "Artist/Album", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "Artist Name/Album Name/filename", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, "appearanceAmoledDark": "AMOLED Dark", "@appearanceAmoledDark": { "description": "Theme option - pure black" @@ -2726,18 +1813,6 @@ "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "Choose Accent Color", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "Theme Mode", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "Download Queue", - "@queueTitle": { - "description": "Queue screen title" - }, "queueClearAll": "Clear All", "@queueClearAll": { "description": "Button - clear all queue items" @@ -2746,22 +1821,6 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "Export", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Failed downloads exported to TXT file", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "Clear Failed", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "Failed to export downloads", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, "settingsAutoExportFailed": "Auto-export failed downloads", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" @@ -2786,38 +1845,6 @@ "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, - "queueEmpty": "No downloads in queue", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "Add tracks from the home screen", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "Clear completed", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "Download Failed", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "Track:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "Artist:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "Error:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "Unknown error", - "@queueUnknownError": { - "description": "Fallback error message" - }, "albumFolderArtistAlbum": "Artist / Album", "@albumFolderArtistAlbum": { "description": "Album folder option" @@ -2871,19 +1898,6 @@ } } }, - "downloadedAlbumTracksHeader": "Tracks", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} downloaded", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, "downloadedAlbumSelectedCount": "{count} selected", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", @@ -2924,10 +1938,6 @@ } } }, - "utilityFunctions": "Utility Functions", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, "recentTypeArtist": "Artist", "@recentTypeArtist": { "description": "Recent access item type - artist" @@ -2962,32 +1972,14 @@ } } }, - "errorGeneric": "Error: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, "discographyDownload": "Download Discography", "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Play Discography", - "@discographyPlay": { - "description": "Button - play artist discography" - }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Play All", - "@discographyPlayAll": { - "description": "Option - play entire discography" - }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3069,10 +2061,6 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Play Selected", - "@discographyPlaySelected": { - "description": "Button - play selected albums" - }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3150,10 +2138,6 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Library Status", - "@libraryStatus": { - "description": "Section header for library status" - }, "libraryScanSettings": "Scan Settings", "@libraryScanSettings": { "description": "Section header for scan settings" @@ -3230,15 +2214,6 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} tracks", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", @@ -3362,26 +2337,6 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Date Added", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "Today", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "This Week", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "This Month", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "This Year", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -3394,15 +2349,6 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} filter(s) active", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -3425,106 +2371,6 @@ } } }, - "storageSwitchTitle": "Switch Storage Mode", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "Switch to SAF Storage?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "Switch to App Storage?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Existing Downloads", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "New Downloads", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "Continue", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "Select SAF Folder", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "App Storage", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "SAF Storage", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "Storage: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "Storage Statistics", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "{count} tracks in App Storage", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "{count} tracks in SAF Storage", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Your files are stored in multiple locations", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" @@ -3553,18 +2399,6 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, "tutorialDownloadTitle": "Downloading Music", "@tutorialDownloadTitle": { "description": "Tutorial download page title" @@ -3573,18 +2407,6 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, "tutorialLibraryTitle": "Your Library", "@tutorialLibraryTitle": { "description": "Tutorial library page title" @@ -3649,10 +2471,6 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "EXAMPLE", - "@tutorialExample": { - "description": "Example label in tutorial" - }, "libraryForceFullScan": "Force Full Scan", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" @@ -3879,10 +2697,6 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" @@ -4271,48 +3085,21 @@ } } }, - "setupModeSelectionTitle": "Choose Your Mode", - "@setupModeSelectionTitle": { - "description": "Title for mode selection step in setup wizard" + "downloadedAlbumDownloadedCount": "{count} downloaded", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } }, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", - "@setupModeSelectionDescription": { - "description": "Description for mode selection step" + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" }, - "setupModeDownloaderTitle": "Downloader", - "@setupModeDownloaderTitle": { - "description": "Title for downloader mode option" - }, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", - "@setupModeDownloaderFeature1": { - "description": "Downloader mode feature 1" - }, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", - "@setupModeDownloaderFeature2": { - "description": "Downloader mode feature 2" - }, - "setupModeDownloaderFeature3": "Manage your local music library", - "@setupModeDownloaderFeature3": { - "description": "Downloader mode feature 3" - }, - "setupModeStreamingTitle": "Streaming", - "@setupModeStreamingTitle": { - "description": "Title for streaming mode option" - }, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", - "@setupModeStreamingFeature1": { - "description": "Streaming mode feature 1" - }, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", - "@setupModeStreamingFeature2": { - "description": "Streaming mode feature 2" - }, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", - "@setupModeStreamingFeature3": { - "description": "Streaming mode feature 3" - }, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", - "@setupModeChangeableLater": { - "description": "Hint that mode can be changed later" + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" } } \ No newline at end of file From c582f96cf6f7dcb40b8af816e9f79550e33f5b49 Mon Sep 17 00:00:00 2001 From: Zarz Eleutherius <42882290+zarzet@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:32:51 +0700 Subject: [PATCH 087/104] New translations app_en.arb (Turkish) --- lib/l10n/arb/app_tr-TR.arb | 1241 +----------------------------------- 1 file changed, 14 insertions(+), 1227 deletions(-) diff --git a/lib/l10n/arb/app_tr-TR.arb b/lib/l10n/arb/app_tr-TR.arb index 62bdd10c..3e87884e 100644 --- a/lib/l10n/arb/app_tr-TR.arb +++ b/lib/l10n/arb/app_tr-TR.arb @@ -5,10 +5,6 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "appDescription": "Spotify şarkılarını Tidal, Qobuz ve Amazon Music'den yüksek kalitede indir.", - "@appDescription": { - "description": "App description shown in about page" - }, "navHome": "Ara", "@navHome": { "description": "Bottom navigation - Home tab" @@ -17,10 +13,6 @@ "@navLibrary": { "description": "Bottom navigation - Library tab" }, - "navHistory": "Geçmiş", - "@navHistory": { - "description": "Bottom navigation - History tab (legacy)" - }, "navSettings": "Ayarlar", "@navSettings": { "description": "Bottom navigation - Settings tab" @@ -33,20 +25,6 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSearchHint": "Spotify URL'i yapıştır veya ara...", - "@homeSearchHint": { - "description": "Placeholder text in search box" - }, - "homeSearchHintExtension": "{extensionName} ile arat...", - "@homeSearchHintExtension": { - "description": "Placeholder when extension search is active", - "placeholders": { - "extensionName": { - "type": "String", - "description": "Name of the active extension" - } - } - }, "homeSubtitle": "Spotify linki yapıştır veya isimle arat", "@homeSubtitle": { "description": "Subtitle shown below search box" @@ -59,24 +37,6 @@ "@homeRecent": { "description": "Section header for recent searches" }, - "historyTitle": "Geçmiş", - "@historyTitle": { - "description": "History screen title" - }, - "historyDownloading": "({count}) tane indiriliyor", - "@historyDownloading": { - "description": "Tab showing active downloads count", - "placeholders": { - "count": { - "type": "int", - "description": "Number of active downloads" - } - } - }, - "historyDownloaded": "İndirildi", - "@historyDownloaded": { - "description": "Tab showing completed downloads" - }, "historyFilterAll": "Tümü", "@historyFilterAll": { "description": "Filter chip - show all items" @@ -89,48 +49,6 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, one {}=1{1 şarkı} other{{count} şarkı}}", - "@historyTracksCount": { - "description": "Track count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyAlbumsCount": "{count, plural, one {}=1{1 albüm} other{{count} albüm}}", - "@historyAlbumsCount": { - "description": "Album count with plural form", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "historyNoDownloads": "İndirme geçmişi yok", - "@historyNoDownloads": { - "description": "Empty state title" - }, - "historyNoDownloadsSubtitle": "İndirilen şarkılar burada gözükecek", - "@historyNoDownloadsSubtitle": { - "description": "Empty state subtitle" - }, - "historyNoAlbums": "İndirilen albüm yok", - "@historyNoAlbums": { - "description": "Empty state when filtering albums" - }, - "historyNoAlbumsSubtitle": "Albümleri burada görmek için bir albümden birden fazla şarkı indir", - "@historyNoAlbumsSubtitle": { - "description": "Empty state subtitle for albums filter" - }, - "historyNoSingles": "Single indirilmemiş", - "@historyNoSingles": { - "description": "Empty state when filtering singles" - }, - "historyNoSinglesSubtitle": "Single şarkılar burada gözükecek", - "@historyNoSinglesSubtitle": { - "description": "Empty state subtitle for singles filter" - }, "historySearchHint": "Arama geçmişi...", "@historySearchHint": { "description": "Search bar placeholder in history" @@ -163,34 +81,6 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadLocation": "İndirme Konumu", - "@downloadLocation": { - "description": "Setting for download folder" - }, - "downloadLocationSubtitle": "Dosyaları nereye kaydedeceğinizi seçin", - "@downloadLocationSubtitle": { - "description": "Subtitle for download location" - }, - "downloadLocationDefault": "Varsayılan dizin", - "@downloadLocationDefault": { - "description": "Shown when using default folder" - }, - "downloadDefaultService": "Varsayılan Hizmet", - "@downloadDefaultService": { - "description": "Setting for preferred download service (Tidal/Qobuz/Amazon)" - }, - "downloadDefaultServiceSubtitle": "İndirmeler için kullanılan hizmet", - "@downloadDefaultServiceSubtitle": { - "description": "Subtitle for default service" - }, - "downloadDefaultQuality": "Varsayılan Kalite", - "@downloadDefaultQuality": { - "description": "Setting for audio quality" - }, - "downloadAskQuality": "İndirmeden Önce Kaliteyi Sor", - "@downloadAskQuality": { - "description": "Toggle to show quality picker" - }, "downloadAskQualitySubtitle": "Her indirmeden önce kalite seçim ekranını göster", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" @@ -203,38 +93,10 @@ "@downloadFolderOrganization": { "description": "Setting for folder structure" }, - "downloadSeparateSingles": "Single'ları Ayır", - "@downloadSeparateSingles": { - "description": "Toggle to separate single tracks" - }, - "downloadSeparateSinglesSubtitle": "Single şarkıları ayrı dosyaya koy", - "@downloadSeparateSinglesSubtitle": { - "description": "Subtitle for separate singles toggle" - }, - "qualityBest": "Mevcut en iyi", - "@qualityBest": { - "description": "Audio quality option - highest available" - }, - "qualityFlac": "FLAC", - "@qualityFlac": { - "description": "Audio quality option - FLAC lossless" - }, - "quality320": "320 kbps", - "@quality320": { - "description": "Audio quality option - 320kbps MP3" - }, - "quality128": "128 kbps", - "@quality128": { - "description": "Audio quality option - 128kbps MP3" - }, "appearanceTitle": "Görünüm", "@appearanceTitle": { "description": "Appearance settings page title" }, - "appearanceTheme": "Tema", - "@appearanceTheme": { - "description": "Theme mode setting" - }, "appearanceThemeSystem": "Sistem", "@appearanceThemeSystem": { "description": "Follow system theme" @@ -255,10 +117,6 @@ "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceAccentColor": "Vurgu Rengi", - "@appearanceAccentColor": { - "description": "Custom accent color picker" - }, "appearanceHistoryView": "Geçmiş Düzeni", "@appearanceHistoryView": { "description": "Layout style for history" @@ -275,10 +133,6 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsSearchSource": "Arama Kaynağı", - "@optionsSearchSource": { - "description": "Section for search provider settings" - }, "optionsPrimaryProvider": "Ana Kaynek", "@optionsPrimaryProvider": { "description": "Main search provider setting" @@ -308,38 +162,6 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsAutoSkipUnavailableTracks": "Auto Skip Unavailable Tracks", - "@optionsAutoSkipUnavailableTracks": { - "description": "Toggle to skip to the next queue track when current track stream resolution fails" - }, - "optionsAutoSkipUnavailableTracksSubtitleOn": "Automatically skip to the next queue track when a stream cannot be resolved.", - "@optionsAutoSkipUnavailableTracksSubtitleOn": { - "description": "Subtitle when auto skip on resolve failure is enabled" - }, - "optionsAutoSkipUnavailableTracksSubtitleOff": "Stop on failed track resolution and show an error.", - "@optionsAutoSkipUnavailableTracksSubtitleOff": { - "description": "Subtitle when auto skip on resolve failure is disabled" - }, - "optionsInteractionMode": "Interaction Mode", - "@optionsInteractionMode": { - "description": "Tap behavior mode for track lists" - }, - "modeDownloader": "Downloader Mode", - "@modeDownloader": { - "description": "Interaction mode where taps queue downloads" - }, - "modeDownloaderSubtitle": "Tap tracks to add them to download queue", - "@modeDownloaderSubtitle": { - "description": "Subtitle for downloader interaction mode" - }, - "modeStreaming": "Streaming Mode", - "@modeStreaming": { - "description": "Interaction mode where taps start playback" - }, - "modeStreamingSubtitle": "Tap tracks to play instantly", - "@modeStreamingSubtitle": { - "description": "Subtitle for streaming interaction mode" - }, "optionsUseExtensionProviders": "Eklenti sağlayıcılarını kullan", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" @@ -470,22 +292,6 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsInstalled": "Kurulu Eklentiler", - "@extensionsInstalled": { - "description": "Section header for installed extensions" - }, - "extensionsNone": "Hiçbir eklenti kurulmamış", - "@extensionsNone": { - "description": "Empty state title" - }, - "extensionsNoneSubtitle": "Dükkan sekmesinden eklenti indir", - "@extensionsNoneSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsEnabled": "Etkin", - "@extensionsEnabled": { - "description": "Extension status - active" - }, "extensionsDisabled": "Devre Dışı", "@extensionsDisabled": { "description": "Extension status - inactive" @@ -512,10 +318,6 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "extensionsSetAsSearch": "Arama Sağlayıcı olarak Ayarla", - "@extensionsSetAsSearch": { - "description": "Use extension for search" - }, "storeTitle": "Eklenti Dükkanı", "@storeTitle": { "description": "Store screen title" @@ -612,10 +414,6 @@ "@aboutSocial": { "description": "Section for social links" }, - "aboutSupport": "Destek", - "@aboutSupport": { - "description": "Section for support/donation links" - }, "aboutApp": "Uygulama", "@aboutApp": { "description": "Section for app info" @@ -636,14 +434,6 @@ "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, - "aboutDoubleDouble": "DoubleDouble", - "@aboutDoubleDouble": { - "description": "Name of Amazon API service - DO NOT TRANSLATE" - }, - "aboutDoubleDoubleDesc": "Amazom Music indirmeleri için harika bir API. Ücretsiz yaptığın için teşekkürler!", - "@aboutDoubleDoubleDesc": { - "description": "Credit for DoubleDouble API" - }, "aboutDabMusic": "DAB Music", "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" @@ -664,35 +454,6 @@ "@aboutAppDescription": { "description": "App description in header card" }, - "albumTitle": "Albüm", - "@albumTitle": { - "description": "Album screen title" - }, - "albumTracks": "{count, plural, one {}=1{1 şarkı} other{{count} şarkı}}", - "@albumTracks": { - "description": "Album track count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "albumDownloadAll": "Tümünü İndir", - "@albumDownloadAll": { - "description": "Button to download all tracks" - }, - "albumDownloadRemaining": "Kalanını İndir", - "@albumDownloadRemaining": { - "description": "Button to download remaining tracks" - }, - "playlistTitle": "Çalma Listesi", - "@playlistTitle": { - "description": "Playlist screen title" - }, - "artistTitle": "Sanatçı", - "@artistTitle": { - "description": "Artist screen title" - }, "artistAlbums": "Albümler", "@artistAlbums": { "description": "Section header for artist albums" @@ -705,15 +466,6 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, =1{1 release} other{{count} releases}}", - "@artistReleases": { - "description": "Artist release count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "artistPopular": "Popüler", "@artistPopular": { "description": "Section header for popular/top tracks" @@ -728,34 +480,6 @@ } } }, - "trackMetadataTitle": "Şarkı Bilgisi", - "@trackMetadataTitle": { - "description": "Track metadata screen title" - }, - "trackMetadataArtist": "Sanatçı", - "@trackMetadataArtist": { - "description": "Metadata field - artist name" - }, - "trackMetadataAlbum": "Albüm", - "@trackMetadataAlbum": { - "description": "Metadata field - album name" - }, - "trackMetadataDuration": "Süre", - "@trackMetadataDuration": { - "description": "Metadata field - track length" - }, - "trackMetadataQuality": "Kalite", - "@trackMetadataQuality": { - "description": "Metadata field - audio quality" - }, - "trackMetadataPath": "Dosya Yolu", - "@trackMetadataPath": { - "description": "Metadata field - file location" - }, - "trackMetadataDownloadedAt": "İndirme tarihi", - "@trackMetadataDownloadedAt": { - "description": "Metadata field - download date" - }, "trackMetadataService": "Hizmet", "@trackMetadataService": { "description": "Metadata field - download service used" @@ -772,54 +496,10 @@ "@trackMetadataDelete": { "description": "Action button - delete track" }, - "trackMetadataRedownload": "Yeniden İndir", - "@trackMetadataRedownload": { - "description": "Action button - download again" - }, - "trackMetadataOpenFolder": "Klasörü Aç", - "@trackMetadataOpenFolder": { - "description": "Action button - open containing folder" - }, - "setupTitle": "SpotiFLAC'e Hoşgeldiniz", - "@setupTitle": { - "description": "Setup wizard title" - }, - "setupSubtitle": "Hadi başlayalım", - "@setupSubtitle": { - "description": "Setup wizard subtitle" - }, - "setupStoragePermission": "Depolama İzni", - "@setupStoragePermission": { - "description": "Storage permission step title" - }, - "setupStoragePermissionSubtitle": "İndirilen dosyaları kaydetmek için gerekli", - "@setupStoragePermissionSubtitle": { - "description": "Explanation for storage permission" - }, - "setupStoragePermissionGranted": "İzin verildi", - "@setupStoragePermissionGranted": { - "description": "Status when permission granted" - }, - "setupStoragePermissionDenied": "İzin reddedildi", - "@setupStoragePermissionDenied": { - "description": "Status when permission denied" - }, "setupGrantPermission": "İzin Ver", "@setupGrantPermission": { "description": "Button to request permission" }, - "setupDownloadLocation": "İndirme Konumu", - "@setupDownloadLocation": { - "description": "Download folder step title" - }, - "setupChooseFolder": "Klasör Seç", - "@setupChooseFolder": { - "description": "Button to pick folder" - }, - "setupContinue": "Devam", - "@setupContinue": { - "description": "Continue to next step button" - }, "setupSkip": "Şimdilik atla", "@setupSkip": { "description": "Skip current step button" @@ -828,10 +508,6 @@ "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessage": "SpotiFLAC'ın şarkıları seçili klasörünüze kaydetmek için \"Bütün dosyalara eriş\" iznine ihtiyacı var.", - "@setupStorageAccessMessage": { - "description": "Explanation for storage access" - }, "setupStorageAccessMessageAndroid11": "Android 11 ve sonrasında şarkıların seçili klasörünüze kaydedilebilmesi için \"Bütün dosyalara eriş\" iznine ihtiyaç var.", "@setupStorageAccessMessageAndroid11": { "description": "Android 11+ specific explanation" @@ -863,10 +539,6 @@ } } }, - "setupSelectDownloadFolder": "İndirilecek Klasörü Seç", - "@setupSelectDownloadFolder": { - "description": "Folder selection step title" - }, "setupUseDefaultFolder": "Varsayılan Klasörü Kullan?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" @@ -915,26 +587,6 @@ "@setupDownloadInFlac": { "description": "App tagline in setup" }, - "setupStepStorage": "Depolama", - "@setupStepStorage": { - "description": "Setup step indicator - storage" - }, - "setupStepNotification": "Bildirim", - "@setupStepNotification": { - "description": "Setup step indicator - notification" - }, - "setupStepFolder": "Klasör", - "@setupStepFolder": { - "description": "Setup step indicator - folder" - }, - "setupStepSpotify": "Spotify", - "@setupStepSpotify": { - "description": "Setup step indicator - Spotify API" - }, - "setupStepPermission": "İzin", - "@setupStepPermission": { - "description": "Setup step indicator - permission" - }, "setupStorageGranted": "Depolama İzni Verildi!", "@setupStorageGranted": { "description": "Success message for storage permission" @@ -955,14 +607,6 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupNotificationDescription": "İndirmeler bittiğinde veya bakılması gereken bir şey olduğunda haberdar olun.", - "@setupNotificationDescription": { - "description": "Explanation for notifications" - }, - "setupFolderSelected": "İndirilecek Klasör Seçildi!", - "@setupFolderSelected": { - "description": "Success message for folder selection" - }, "setupFolderChoose": "İndirilecek Klasörü Seç", "@setupFolderChoose": { "description": "Button to choose folder" @@ -971,58 +615,14 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupChangeFolder": "Klasörü Değiştir", - "@setupChangeFolder": { - "description": "Button to change selected folder" - }, "setupSelectFolder": "Klasör Seç", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupSpotifyApiOptional": "Spotify API (İsteğe Bağlı)", - "@setupSpotifyApiOptional": { - "description": "Spotify API step title" - }, - "setupSpotifyApiDescription": "Daha iyi arama sonuçları ve Spotify'a özel içeriklere erişmek için Spotify API kimlik bilgilerini gir.", - "@setupSpotifyApiDescription": { - "description": "Explanation for Spotify API" - }, - "setupUseSpotifyApi": "Spotify API'ı kullan", - "@setupUseSpotifyApi": { - "description": "Toggle to enable Spotify API" - }, - "setupEnterCredentialsBelow": "Kimlik bilgilerini aşağıya gir", - "@setupEnterCredentialsBelow": { - "description": "Prompt to enter credentials" - }, - "setupUsingDeezer": "Deezer kullanılıyor (hesap gerekli değil)", - "@setupUsingDeezer": { - "description": "Status when using Deezer" - }, - "setupEnterClientId": "Spotify Client ID gir", - "@setupEnterClientId": { - "description": "Placeholder for client ID field" - }, - "setupEnterClientSecret": "Spotify Client Secret gir", - "@setupEnterClientSecret": { - "description": "Placeholder for client secret field" - }, - "setupGetFreeCredentials": "Spotify Developer Dashboard'tan API kimlik bilgilerini ücretsiz alın.", - "@setupGetFreeCredentials": { - "description": "Info about getting Spotify credentials" - }, "setupEnableNotifications": "Bildirimleri Etkinleştir", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupProceedToNextStep": "Bir sonraki adıma geçebilirsin.", - "@setupProceedToNextStep": { - "description": "Message after completing a step" - }, - "setupNotificationProgressDescription": "İndirme ilerlemelerinin bildirimlerini alacaksın.", - "@setupNotificationProgressDescription": { - "description": "Info about notification usage" - }, "setupNotificationBackgroundDescription": "İndirmelerin durumu hakkında bildirim al. Bunu açmak uygulama arka plandayken indirmelerinizi takip etmenizi sağlar.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" @@ -1031,10 +631,6 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupBack": "Geri", - "@setupBack": { - "description": "Back button text" - }, "setupNext": "Sıradaki", "@setupNext": { "description": "Next button text" @@ -1043,26 +639,14 @@ "@setupGetStarted": { "description": "Final setup button" }, - "setupSkipAndStart": "Kurulumu atla", - "@setupSkipAndStart": { - "description": "Skip setup and start app" - }, "setupAllowAccessToManageFiles": "Lütfen bir sonraki ekranda \"Bütün dosyalara eriş\" iznini sağlayın.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "setupGetCredentialsFromSpotify": "Kimlik bilgilerini developer.spotify.com'dan alın", - "@setupGetCredentialsFromSpotify": { - "description": "Link text for Spotify developer portal" - }, "dialogCancel": "İptal", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogOk": "Tamam", - "@dialogOk": { - "description": "Dialog button - confirm/acknowledge" - }, "dialogSave": "Kaydet", "@dialogSave": { "description": "Dialog button - save changes" @@ -1075,26 +659,10 @@ "@dialogRetry": { "description": "Dialog button - retry action" }, - "dialogClose": "Kapat", - "@dialogClose": { - "description": "Dialog button - close dialog" - }, - "dialogYes": "Evet", - "@dialogYes": { - "description": "Dialog button - confirm yes" - }, - "dialogNo": "Hayır", - "@dialogNo": { - "description": "Dialog button - confirm no" - }, "dialogClear": "Temizle", "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogConfirm": "Onayla", - "@dialogConfirm": { - "description": "Dialog button - confirm action" - }, "dialogDone": "Tamamlandı", "@dialogDone": { "description": "Dialog button - action completed" @@ -1123,34 +691,10 @@ "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, - "dialogDownloadFailed": "İndirme Başarısız", - "@dialogDownloadFailed": { - "description": "Dialog title - download error" - }, - "dialogTrackLabel": "Şarkı:", - "@dialogTrackLabel": { - "description": "Label for track name in error dialog" - }, - "dialogArtistLabel": "Sanatçı:", - "@dialogArtistLabel": { - "description": "Label for artist name in error dialog" - }, - "dialogErrorLabel": "Hata:", - "@dialogErrorLabel": { - "description": "Label for error message" - }, "dialogClearAll": "Tümünü Temizle", "@dialogClearAll": { "description": "Dialog title - clear all items" }, - "dialogClearAllDownloads": "Bütün indirmeleri temizlemek istediğinize emin misiniz?", - "@dialogClearAllDownloads": { - "description": "Dialog message - clear downloads confirmation" - }, - "dialogRemoveFromDevice": "Cihazdan kaldır?", - "@dialogRemoveFromDevice": { - "description": "Dialog title - delete file confirmation" - }, "dialogRemoveExtension": "Eklentiyi Kaldır", "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" @@ -1289,15 +833,6 @@ "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarFailedToLoad": "Yüklenemedi: {error}", - "@snackbarFailedToLoad": { - "description": "Snackbar - loading error", - "placeholders": { - "error": { - "type": "String" - } - } - }, "snackbarUrlCopied": "{platform} Bağlantı panoya kopyalandı", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", @@ -1358,24 +893,10 @@ "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorFailedToLoad": "{item} yüklenirken hata oluştu", - "@errorFailedToLoad": { - "description": "Error message - loading failed", - "placeholders": { - "item": { - "type": "String", - "description": "Item that failed to load (album/playlist/etc)" - } - } - }, "errorNoTracksFound": "Parça bulunamadı", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorSeekNotSupported": "Seeking is not supported for this live stream", - "@errorSeekNotSupported": { - "description": "Error - seek disabled for live decrypted stream" - }, "errorMissingExtensionSource": "{item} yüklenemedi: Eksik eklenti kaynağı", "@errorMissingExtensionSource": { "description": "Error - extension source not available", @@ -1385,34 +906,6 @@ } } }, - "statusQueued": "Sıraya alındı", - "@statusQueued": { - "description": "Download status - waiting in queue" - }, - "statusDownloading": "İndiriliyor", - "@statusDownloading": { - "description": "Download status - in progress" - }, - "statusFinalizing": "Tamamlanıyor", - "@statusFinalizing": { - "description": "Download status - writing metadata" - }, - "statusCompleted": "Tamamlandı", - "@statusCompleted": { - "description": "Download status - finished" - }, - "statusFailed": "Başarısız", - "@statusFailed": { - "description": "Download status - error occurred" - }, - "statusSkipped": "Atlandı", - "@statusSkipped": { - "description": "Download status - already exists" - }, - "statusPaused": "Durduruldu", - "@statusPaused": { - "description": "Download status - paused" - }, "actionPause": "Duraklat", "@actionPause": { "description": "Action button - pause download" @@ -1425,14 +918,6 @@ "@actionCancel": { "description": "Action button - cancel operation" }, - "actionStop": "Durdur", - "@actionStop": { - "description": "Action button - stop operation" - }, - "actionSelect": "Seç", - "@actionSelect": { - "description": "Action button - enter selection mode" - }, "actionSelectAll": "Tümünü Seç", "@actionSelectAll": { "description": "Action button - select all items" @@ -1441,14 +926,6 @@ "@actionDeselect": { "description": "Action button - deselect all" }, - "actionPaste": "Yapıştır", - "@actionPaste": { - "description": "Action button - paste from clipboard" - }, - "actionImportCsv": "CSV İçe Aktar", - "@actionImportCsv": { - "description": "Action button - import CSV file" - }, "actionRemoveCredentials": "Özellikleri kaldır", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" @@ -1470,19 +947,6 @@ "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionTapToSelect": "Seçmek için parçalara dokunun", - "@selectionTapToSelect": { - "description": "Hint - how to select items" - }, - "selectionDeleteTracks": "Delete {count} {count, plural, =1{track} other{tracks}}", - "@selectionDeleteTracks": { - "description": "Delete button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "selectionSelectToDelete": "Silinecek parçaları seçin", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" @@ -1523,51 +987,10 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "tooltipCancel": "Vazgeç", - "@tooltipCancel": { - "description": "Tooltip - cancel button" - }, - "tooltipStop": "Durdur", - "@tooltipStop": { - "description": "Tooltip - stop button" - }, - "tooltipRetry": "Yeniden dene", - "@tooltipRetry": { - "description": "Tooltip - retry button" - }, - "tooltipRemove": "Kaldır", - "@tooltipRemove": { - "description": "Tooltip - remove button" - }, - "tooltipClear": "Temizle", - "@tooltipClear": { - "description": "Tooltip - clear button" - }, - "tooltipPaste": "Yapıştır", - "@tooltipPaste": { - "description": "Tooltip - paste button" - }, "filenameFormat": "Dosya adı formatı", "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameFormatPreview": "Önizleme: {preview}", - "@filenameFormatPreview": { - "description": "Preview of filename pattern", - "placeholders": { - "preview": { - "type": "String" - } - } - }, - "filenameAvailablePlaceholders": "Kullanılabilir yer tutucular:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, "filenameShowAdvancedTags": "Show advanced tags", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" @@ -1576,10 +999,6 @@ "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganization": "Klasör Organizasyonu", - "@folderOrganization": { - "description": "Setting title - folder structure" - }, "folderOrganizationNone": "Organizasyon yok", "@folderOrganizationNone": { "description": "Folder option - flat structure" @@ -1620,27 +1039,10 @@ "@updateAvailable": { "description": "Update dialog title" }, - "updateNewVersion": "{version} sürümü mevcut", - "@updateNewVersion": { - "description": "Update available message", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "updateDownload": "İndir", - "@updateDownload": { - "description": "Update button - download update" - }, "updateLater": "Daha Sonra", "@updateLater": { "description": "Update button - dismiss" }, - "updateChangelog": "Değişiklikler", - "@updateChangelog": { - "description": "Link to changelog" - }, "updateStartingDownload": "İndirme başlıyor...", "@updateStartingDownload": { "description": "Update status - initializing" @@ -1681,14 +1083,6 @@ "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriority": "İndirme hizmetleri öncelik sırası", - "@providerPriority": { - "description": "Setting title - download provider order" - }, - "providerPrioritySubtitle": "İndirme hizmetlerini sıralamak için kaydır", - "@providerPrioritySubtitle": { - "description": "Subtitle for provider priority" - }, "providerPriorityTitle": "İndirme hizmetleri öncelik sırası", "@providerPriorityTitle": { "description": "Provider priority page title" @@ -1709,14 +1103,6 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriority": "Metadata Provider Priority", - "@metadataProviderPriority": { - "description": "Setting title - metadata provider order" - }, - "metadataProviderPrioritySubtitle": "Order used when fetching track metadata", - "@metadataProviderPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, "metadataProviderPriorityTitle": "Metadata Priority", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" @@ -1741,22 +1127,6 @@ "@logTitle": { "description": "Logs screen title" }, - "logCopy": "Copy Logs", - "@logCopy": { - "description": "Action - copy logs to clipboard" - }, - "logClear": "Kayıtları temizle", - "@logClear": { - "description": "Action - delete all logs" - }, - "logShare": "Kayıtları Paylaş", - "@logShare": { - "description": "Action - share logs file" - }, - "logEmpty": "Henüz kayıt yok", - "@logEmpty": { - "description": "Empty state title" - }, "logCopied": "Kayıtlar panoya kopyalandı", "@logCopied": { "description": "Snackbar - logs copied" @@ -1789,22 +1159,6 @@ "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logIspBlocking": "ISP BLOCKING DETECTED", - "@logIspBlocking": { - "description": "Error category - ISP blocking" - }, - "logRateLimited": "RATE LIMITED", - "@logRateLimited": { - "description": "Error category - rate limiting" - }, - "logNetworkError": "NETWORK ERROR", - "@logNetworkError": { - "description": "Error category - network issues" - }, - "logTrackNotFound": "TRACK NOT FOUND", - "@logTrackNotFound": { - "description": "Error category - missing tracks" - }, "logFilterBySeverity": "Filter logs by severity", "@logFilterBySeverity": { "description": "Filter dialog title" @@ -1817,60 +1171,6 @@ "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, - "logIssueSummary": "Issue Summary", - "@logIssueSummary": { - "description": "Section header for error summary" - }, - "logIspBlockingDescription": "Your ISP may be blocking access to download services", - "@logIspBlockingDescription": { - "description": "ISP blocking explanation" - }, - "logIspBlockingSuggestion": "Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8", - "@logIspBlockingSuggestion": { - "description": "ISP blocking fix suggestion" - }, - "logRateLimitedDescription": "Too many requests to the service", - "@logRateLimitedDescription": { - "description": "Rate limit explanation" - }, - "logRateLimitedSuggestion": "Wait a few minutes before trying again", - "@logRateLimitedSuggestion": { - "description": "Rate limit fix suggestion" - }, - "logNetworkErrorDescription": "Connection issues detected", - "@logNetworkErrorDescription": { - "description": "Network error explanation" - }, - "logNetworkErrorSuggestion": "Check your internet connection", - "@logNetworkErrorSuggestion": { - "description": "Network error fix suggestion" - }, - "logTrackNotFoundDescription": "Some tracks could not be found on download services", - "@logTrackNotFoundDescription": { - "description": "Track not found explanation" - }, - "logTrackNotFoundSuggestion": "The track may not be available in lossless quality", - "@logTrackNotFoundSuggestion": { - "description": "Track not found explanation" - }, - "logTotalErrors": "Total errors: {count}", - "@logTotalErrors": { - "description": "Error count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logAffected": "Affected: {domains}", - "@logAffected": { - "description": "Affected domains display", - "placeholders": { - "domains": { - "type": "String" - } - } - }, "logEntriesFiltered": "Entries ({count} filtered)", "@logEntriesFiltered": { "description": "Log count with filter active", @@ -2013,10 +1313,6 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "appearanceLanguageSubtitle": "Choose your preferred language", - "@appearanceLanguageSubtitle": { - "description": "Language setting subtitle" - }, "settingsAppearanceSubtitle": "Theme, colors, display", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" @@ -2045,10 +1341,6 @@ "@pressBackAgainToExit": { "description": "Exit confirmation message" }, - "tracksHeader": "Tracks", - "@tracksHeader": { - "description": "Section header for track list" - }, "downloadAllCount": "Download All ({count})", "@downloadAllCount": { "description": "Download all button with count", @@ -2058,15 +1350,6 @@ } } }, - "playAllCount": "Play All ({count})", - "@playAllCount": { - "description": "Play all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@tracksCount": { "description": "Track count display", @@ -2204,15 +1487,6 @@ "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, - "trackCannotOpen": "Cannot open: {message}", - "@trackCannotOpen": { - "description": "Error opening file", - "placeholders": { - "message": { - "type": "String" - } - } - }, "dateToday": "Today", "@dateToday": { "description": "Relative date - today" @@ -2248,22 +1522,6 @@ } } }, - "concurrentSequential": "Sequential", - "@concurrentSequential": { - "description": "Download mode - one at a time" - }, - "concurrentParallel2": "2 Parallel", - "@concurrentParallel2": { - "description": "Download mode - 2 simultaneous" - }, - "concurrentParallel3": "3 Parallel", - "@concurrentParallel3": { - "description": "Download mode - 3 simultaneous" - }, - "tapToSeeError": "Tap to see error details", - "@tapToSeeError": { - "description": "Tooltip for failed download" - }, "storeFilterAll": "All", "@storeFilterAll": { "description": "Store filter - all extensions" @@ -2292,18 +1550,6 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeNoResults": "No extensions found", - "@storeNoResults": { - "description": "Empty state when no extensions match filters" - }, - "extensionProviderPriority": "Provider Priority", - "@extensionProviderPriority": { - "description": "Extension capability - provider priority" - }, - "extensionInstallButton": "Install Extension", - "@extensionInstallButton": { - "description": "Button to install extension" - }, "extensionDefaultProvider": "Default (Deezer/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -2503,46 +1749,6 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "qualityLossy": "Lossy", - "@qualityLossy": { - "description": "Quality option - lossy format (MP3/Opus)" - }, - "qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)", - "@qualityLossyMp3Subtitle": { - "description": "Technical spec for lossy MP3" - }, - "qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)", - "@qualityLossyOpusSubtitle": { - "description": "Technical spec for lossy Opus" - }, - "enableLossyOption": "Enable Lossy Option", - "@enableLossyOption": { - "description": "Setting - enable lossy quality option" - }, - "enableLossyOptionSubtitleOn": "Lossy quality option is available", - "@enableLossyOptionSubtitleOn": { - "description": "Subtitle when lossy is enabled" - }, - "enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format", - "@enableLossyOptionSubtitleOff": { - "description": "Subtitle when lossy is disabled" - }, - "lossyFormat": "Lossy Format", - "@lossyFormat": { - "description": "Setting - choose lossy format" - }, - "lossyFormatDescription": "Choose the lossy format for conversion", - "@lossyFormatDescription": { - "description": "Description for lossy format picker" - }, - "lossyFormatMp3Subtitle": "320kbps, best compatibility", - "@lossyFormatMp3Subtitle": { - "description": "MP3 format description" - }, - "lossyFormatOpusSubtitle": "128kbps, better quality at smaller size", - "@lossyFormatOpusSubtitle": { - "description": "Opus format description" - }, "qualityNote": "Actual quality depends on track availability from the service", "@qualityNote": { "description": "Note about quality availability" @@ -2559,49 +1765,6 @@ "@youtubeMp3BitrateTitle": { "description": "Title for YouTube MP3 bitrate setting" }, - "youtubeBitrateSubtitle": "{bitrate}kbps ({min}-{max})", - "@youtubeBitrateSubtitle": { - "description": "Subtitle showing current bitrate and valid range", - "placeholders": { - "bitrate": { - "type": "int" - }, - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateInputHelp": "Enter custom bitrate ({min}-{max} kbps)", - "@youtubeBitrateInputHelp": { - "description": "Helper text for manual YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, - "youtubeBitrateFieldLabel": "Bitrate (kbps)", - "@youtubeBitrateFieldLabel": { - "description": "Label for YouTube bitrate input field" - }, - "youtubeBitrateValidationError": "Bitrate must be between {min} and {max} kbps", - "@youtubeBitrateValidationError": { - "description": "Validation error for invalid YouTube bitrate input", - "placeholders": { - "min": { - "type": "int" - }, - "max": { - "type": "int" - } - } - }, "downloadAskBeforeDownload": "Ask Before Download", "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" @@ -2622,14 +1785,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -2642,14 +1797,6 @@ "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSaveFormat": "Save Format", - "@downloadSaveFormat": { - "description": "Setting - output file format" - }, - "downloadSelectService": "Select Service", - "@downloadSelectService": { - "description": "Dialog title - choose download service" - }, "downloadSelectQuality": "Select Quality", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" @@ -2658,66 +1805,6 @@ "@downloadFrom": { "description": "Label - download source" }, - "downloadDefaultQualityLabel": "Default Quality", - "@downloadDefaultQualityLabel": { - "description": "Label - default quality setting" - }, - "downloadBestAvailable": "Best available", - "@downloadBestAvailable": { - "description": "Quality option - highest available" - }, - "folderNone": "None", - "@folderNone": { - "description": "Folder option - no organization" - }, - "folderNoneSubtitle": "Save all files directly to download folder", - "@folderNoneSubtitle": { - "description": "Subtitle for no folder organization" - }, - "folderArtist": "Artist", - "@folderArtist": { - "description": "Folder option - by artist" - }, - "folderArtistSubtitle": "Artist Name/filename", - "@folderArtistSubtitle": { - "description": "Folder structure example" - }, - "folderAlbum": "Album", - "@folderAlbum": { - "description": "Folder option - by album" - }, - "folderAlbumSubtitle": "Album Name/filename", - "@folderAlbumSubtitle": { - "description": "Folder structure example" - }, - "folderArtistAlbum": "Artist/Album", - "@folderArtistAlbum": { - "description": "Folder option - nested" - }, - "folderArtistAlbumSubtitle": "Artist Name/Album Name/filename", - "@folderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "serviceTidal": "Tidal", - "@serviceTidal": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceQobuz": "Qobuz", - "@serviceQobuz": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceAmazon": "Amazon", - "@serviceAmazon": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceDeezer": "Deezer", - "@serviceDeezer": { - "description": "Service name - DO NOT TRANSLATE" - }, - "serviceSpotify": "Spotify", - "@serviceSpotify": { - "description": "Service name - DO NOT TRANSLATE" - }, "appearanceAmoledDark": "AMOLED Dark", "@appearanceAmoledDark": { "description": "Theme option - pure black" @@ -2726,18 +1813,6 @@ "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, - "appearanceChooseAccentColor": "Choose Accent Color", - "@appearanceChooseAccentColor": { - "description": "Color picker dialog title" - }, - "appearanceChooseTheme": "Theme Mode", - "@appearanceChooseTheme": { - "description": "Theme picker dialog title" - }, - "queueTitle": "Download Queue", - "@queueTitle": { - "description": "Queue screen title" - }, "queueClearAll": "Clear All", "@queueClearAll": { "description": "Button - clear all queue items" @@ -2746,22 +1821,6 @@ "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "queueExportFailed": "Export", - "@queueExportFailed": { - "description": "Button - export failed downloads to TXT" - }, - "queueExportFailedSuccess": "Failed downloads exported to TXT file", - "@queueExportFailedSuccess": { - "description": "Success message after exporting failed downloads" - }, - "queueExportFailedClear": "Clear Failed", - "@queueExportFailedClear": { - "description": "Action to clear failed downloads after export" - }, - "queueExportFailedError": "Failed to export downloads", - "@queueExportFailedError": { - "description": "Error message when export fails" - }, "settingsAutoExportFailed": "Auto-export failed downloads", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" @@ -2786,38 +1845,6 @@ "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, - "queueEmpty": "No downloads in queue", - "@queueEmpty": { - "description": "Empty queue state title" - }, - "queueEmptySubtitle": "Add tracks from the home screen", - "@queueEmptySubtitle": { - "description": "Empty queue state subtitle" - }, - "queueClearCompleted": "Clear completed", - "@queueClearCompleted": { - "description": "Button - clear finished downloads" - }, - "queueDownloadFailed": "Download Failed", - "@queueDownloadFailed": { - "description": "Error dialog title" - }, - "queueTrackLabel": "Track:", - "@queueTrackLabel": { - "description": "Label in error dialog" - }, - "queueArtistLabel": "Artist:", - "@queueArtistLabel": { - "description": "Label in error dialog" - }, - "queueErrorLabel": "Error:", - "@queueErrorLabel": { - "description": "Label in error dialog" - }, - "queueUnknownError": "Unknown error", - "@queueUnknownError": { - "description": "Fallback error message" - }, "albumFolderArtistAlbum": "Artist / Album", "@albumFolderArtistAlbum": { "description": "Album folder option" @@ -2871,19 +1898,6 @@ } } }, - "downloadedAlbumTracksHeader": "Tracks", - "@downloadedAlbumTracksHeader": { - "description": "Section header for tracks" - }, - "downloadedAlbumDownloadedCount": "{count} downloaded", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, "downloadedAlbumSelectedCount": "{count} selected", "@downloadedAlbumSelectedCount": { "description": "Selection count indicator", @@ -2924,10 +1938,6 @@ } } }, - "utilityFunctions": "Utility Functions", - "@utilityFunctions": { - "description": "Extension capability - utility functions" - }, "recentTypeArtist": "Artist", "@recentTypeArtist": { "description": "Recent access item type - artist" @@ -2962,32 +1972,14 @@ } } }, - "errorGeneric": "Error: {message}", - "@errorGeneric": { - "description": "Generic error message format", - "placeholders": { - "message": { - "type": "String", - "description": "Error message" - } - } - }, "discographyDownload": "Download Discography", "@discographyDownload": { "description": "Button - download artist discography" }, - "discographyPlay": "Play Discography", - "@discographyPlay": { - "description": "Button - play artist discography" - }, "discographyDownloadAll": "Download All", "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyPlayAll": "Play All", - "@discographyPlayAll": { - "description": "Option - play entire discography" - }, "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", @@ -3069,10 +2061,6 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyPlaySelected": "Play Selected", - "@discographyPlaySelected": { - "description": "Button - play selected albums" - }, "discographyAddedToQueue": "Added {count} tracks to queue", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", @@ -3150,10 +2138,6 @@ "@libraryTitle": { "description": "Library settings page title" }, - "libraryStatus": "Library Status", - "@libraryStatus": { - "description": "Section header for library status" - }, "libraryScanSettings": "Scan Settings", "@libraryScanSettings": { "description": "Section header for scan settings" @@ -3230,15 +2214,6 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} tracks", - "@libraryTracksCount": { - "description": "Track count in library", - "placeholders": { - "count": { - "type": "int" - } - } - }, "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", @@ -3362,26 +2337,6 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterDate": "Date Added", - "@libraryFilterDate": { - "description": "Filter section - date range" - }, - "libraryFilterDateToday": "Today", - "@libraryFilterDateToday": { - "description": "Filter option - today only" - }, - "libraryFilterDateWeek": "This Week", - "@libraryFilterDateWeek": { - "description": "Filter option - this week" - }, - "libraryFilterDateMonth": "This Month", - "@libraryFilterDateMonth": { - "description": "Filter option - this month" - }, - "libraryFilterDateYear": "This Year", - "@libraryFilterDateYear": { - "description": "Filter option - this year" - }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -3394,15 +2349,6 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "libraryFilterActive": "{count} filter(s) active", - "@libraryFilterActive": { - "description": "Badge showing number of active filters", - "placeholders": { - "count": { - "type": "int" - } - } - }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -3425,106 +2371,6 @@ } } }, - "storageSwitchTitle": "Switch Storage Mode", - "@storageSwitchTitle": { - "description": "Dialog title when switching storage mode" - }, - "storageSwitchToSafTitle": "Switch to SAF Storage?", - "@storageSwitchToSafTitle": { - "description": "Dialog title when switching to SAF" - }, - "storageSwitchToAppTitle": "Switch to App Storage?", - "@storageSwitchToAppTitle": { - "description": "Dialog title when switching to app storage" - }, - "storageSwitchToSafMessage": "Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.", - "@storageSwitchToSafMessage": { - "description": "Explanation when switching to SAF" - }, - "storageSwitchToAppMessage": "Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.", - "@storageSwitchToAppMessage": { - "description": "Explanation when switching to app storage" - }, - "storageSwitchExistingDownloads": "Existing Downloads", - "@storageSwitchExistingDownloads": { - "description": "Section header for existing downloads info" - }, - "storageSwitchExistingDownloadsInfo": "{count} tracks in {mode} storage", - "@storageSwitchExistingDownloadsInfo": { - "description": "Info about existing downloads count", - "placeholders": { - "count": { - "type": "int" - }, - "mode": { - "type": "String" - } - } - }, - "storageSwitchNewDownloads": "New Downloads", - "@storageSwitchNewDownloads": { - "description": "Section header for new downloads info" - }, - "storageSwitchNewDownloadsLocation": "Will be saved to: {location}", - "@storageSwitchNewDownloadsLocation": { - "description": "Shows where new downloads will go", - "placeholders": { - "location": { - "type": "String" - } - } - }, - "storageSwitchContinue": "Continue", - "@storageSwitchContinue": { - "description": "Button to proceed with storage switch" - }, - "storageSwitchSelectFolder": "Select SAF Folder", - "@storageSwitchSelectFolder": { - "description": "Button to select SAF folder" - }, - "storageAppStorage": "App Storage", - "@storageAppStorage": { - "description": "Label for app storage mode" - }, - "storageSafStorage": "SAF Storage", - "@storageSafStorage": { - "description": "Label for SAF storage mode" - }, - "storageModeBadge": "Storage: {mode}", - "@storageModeBadge": { - "description": "Badge showing storage mode for a track", - "placeholders": { - "mode": { - "type": "String" - } - } - }, - "storageStatsTitle": "Storage Statistics", - "@storageStatsTitle": { - "description": "Section title for storage stats" - }, - "storageStatsAppCount": "{count} tracks in App Storage", - "@storageStatsAppCount": { - "description": "Count of tracks in app storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageStatsSafCount": "{count} tracks in SAF Storage", - "@storageStatsSafCount": { - "description": "Count of tracks in SAF storage", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storageModeInfo": "Your files are stored in multiple locations", - "@storageModeInfo": { - "description": "Info when user has files in both storage modes" - }, "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" @@ -3553,18 +2399,6 @@ "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialSearchTip1": "Paste a Spotify or Deezer URL directly in the search box", - "@tutorialSearchTip1": { - "description": "Tutorial search tip 1" - }, - "tutorialSearchTip2": "Or type the song name, artist, or album to search", - "@tutorialSearchTip2": { - "description": "Tutorial search tip 2" - }, - "tutorialSearchTip3": "Supports tracks, albums, playlists, and artist pages", - "@tutorialSearchTip3": { - "description": "Tutorial search tip 3" - }, "tutorialDownloadTitle": "Downloading Music", "@tutorialDownloadTitle": { "description": "Tutorial download page title" @@ -3573,18 +2407,6 @@ "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialDownloadTip1": "Tap the download button next to any track to start downloading", - "@tutorialDownloadTip1": { - "description": "Tutorial download tip 1" - }, - "tutorialDownloadTip2": "Choose your preferred quality (FLAC, Hi-Res, or MP3)", - "@tutorialDownloadTip2": { - "description": "Tutorial download tip 2" - }, - "tutorialDownloadTip3": "Download entire albums or playlists with one tap", - "@tutorialDownloadTip3": { - "description": "Tutorial download tip 3" - }, "tutorialLibraryTitle": "Your Library", "@tutorialLibraryTitle": { "description": "Tutorial library page title" @@ -3649,10 +2471,6 @@ "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "tutorialExample": "EXAMPLE", - "@tutorialExample": { - "description": "Example label in tutorial" - }, "libraryForceFullScan": "Force Full Scan", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" @@ -3879,10 +2697,6 @@ "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichSubtitle": "Re-embed metadata without re-downloading", - "@trackReEnrichSubtitle": { - "description": "Subtitle for re-enrich metadata action" - }, "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" @@ -4271,48 +3085,21 @@ } } }, - "setupModeSelectionTitle": "Choose Your Mode", - "@setupModeSelectionTitle": { - "description": "Title for mode selection step in setup wizard" + "downloadedAlbumDownloadedCount": "{count} downloaded", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } }, - "setupModeSelectionDescription": "How would you like to use SpotiFLAC? You can always change this later in Settings.", - "@setupModeSelectionDescription": { - "description": "Description for mode selection step" + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" }, - "setupModeDownloaderTitle": "Downloader", - "@setupModeDownloaderTitle": { - "description": "Title for downloader mode option" - }, - "setupModeDownloaderFeature1": "Download tracks in lossless FLAC quality", - "@setupModeDownloaderFeature1": { - "description": "Downloader mode feature 1" - }, - "setupModeDownloaderFeature2": "Save music to your device for offline listening", - "@setupModeDownloaderFeature2": { - "description": "Downloader mode feature 2" - }, - "setupModeDownloaderFeature3": "Manage your local music library", - "@setupModeDownloaderFeature3": { - "description": "Downloader mode feature 3" - }, - "setupModeStreamingTitle": "Streaming", - "@setupModeStreamingTitle": { - "description": "Title for streaming mode option" - }, - "setupModeStreamingFeature1": "Stream tracks instantly without downloading", - "@setupModeStreamingFeature1": { - "description": "Streaming mode feature 1" - }, - "setupModeStreamingFeature2": "Smart Queue auto-discovers new music for you", - "@setupModeStreamingFeature2": { - "description": "Streaming mode feature 2" - }, - "setupModeStreamingFeature3": "Play any track on demand with playback controls", - "@setupModeStreamingFeature3": { - "description": "Streaming mode feature 3" - }, - "setupModeChangeableLater": "You can switch between modes anytime in Settings.", - "@setupModeChangeableLater": { - "description": "Hint that mode can be changed later" + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" } } \ No newline at end of file From 7d5cb574c6db6756e558951dba2923588f217001 Mon Sep 17 00:00:00 2001 From: zarzet Date: Sat, 7 Mar 2026 00:12:07 +0700 Subject: [PATCH 088/104] feat: move Amazon Music to extension, fix Deezer download timeout --- CHANGELOG.md | 16 + README.md | 23 +- .../kotlin/com/zarz/spotiflac/MainActivity.kt | 28 +- go_backend/amazon.go | 692 ------------------ go_backend/amazon_asin_test.go | 46 -- go_backend/audio_metadata.go | 15 - go_backend/deezer_download.go | 4 +- go_backend/exports.go | 57 +- go_backend/extension_manager.go | 14 - go_backend/extension_manifest.go | 1 - go_backend/extension_providers.go | 139 ++-- go_backend/extension_runtime_auth.go | 9 - go_backend/extension_runtime_ffmpeg.go | 6 +- go_backend/extension_runtime_file.go | 3 - go_backend/extension_runtime_http.go | 3 - go_backend/extension_runtime_matching.go | 3 - go_backend/extension_runtime_polyfills.go | 14 +- go_backend/extension_runtime_storage.go | 3 - go_backend/extension_runtime_utils.go | 3 - go_backend/extension_settings.go | 1 - go_backend/extension_timeout.go | 1 - go_backend/httputil.go | 2 - go_backend/httputil_utls.go | 6 - go_backend/idhs.go | 7 - go_backend/library_scan.go | 12 - go_backend/lyrics.go | 3 - go_backend/lyrics_apple.go | 4 - go_backend/lyrics_musixmatch.go | 5 - go_backend/lyrics_netease.go | 2 - go_backend/lyrics_qqmusic.go | 2 - go_backend/mobile_deps.go | 2 - go_backend/parallel.go | 33 - go_backend/qobuz.go | 17 +- go_backend/ratelimit.go | 1 - go_backend/romaji.go | 5 - go_backend/songlink.go | 14 +- go_backend/tidal.go | 20 +- go_backend/youtube.go | 5 - ios/Runner/AppDelegate.swift | 7 - lib/app.dart | 1 - lib/constants/app_info.dart | 4 +- lib/l10n/app_localizations.dart | 6 +- lib/l10n/app_localizations_de.dart | 4 +- lib/l10n/app_localizations_en.dart | 4 +- lib/l10n/app_localizations_es.dart | 8 +- lib/l10n/app_localizations_fr.dart | 4 +- lib/l10n/app_localizations_hi.dart | 4 +- lib/l10n/app_localizations_id.dart | 4 +- lib/l10n/app_localizations_ja.dart | 5 +- lib/l10n/app_localizations_ko.dart | 5 +- lib/l10n/app_localizations_nl.dart | 4 +- lib/l10n/app_localizations_pt.dart | 8 +- lib/l10n/app_localizations_ru.dart | 5 +- lib/l10n/app_localizations_tr.dart | 4 +- lib/l10n/app_localizations_zh.dart | 14 +- lib/l10n/arb/app_de.arb | 6 +- lib/l10n/arb/app_en.arb | 6 +- lib/l10n/arb/app_es.arb | 4 +- lib/l10n/arb/app_es_ES.arb | 6 +- lib/l10n/arb/app_fr.arb | 6 +- lib/l10n/arb/app_hi.arb | 6 +- lib/l10n/arb/app_id.arb | 6 +- lib/l10n/arb/app_ja.arb | 6 +- lib/l10n/arb/app_ko.arb | 6 +- lib/l10n/arb/app_nl.arb | 6 +- lib/l10n/arb/app_pt.arb | 4 +- lib/l10n/arb/app_pt_PT.arb | 6 +- lib/l10n/arb/app_ru.arb | 6 +- lib/l10n/arb/app_tr.arb | 6 +- lib/l10n/arb/app_zh.arb | 4 +- lib/l10n/arb/app_zh_CN.arb | 6 +- lib/l10n/arb/app_zh_TW.arb | 6 +- lib/models/settings.dart | 16 - lib/providers/download_queue_provider.dart | 32 +- lib/providers/extension_provider.dart | 14 +- lib/providers/track_provider.dart | 5 - lib/screens/album_screen.dart | 3 - lib/screens/artist_screen.dart | 12 +- lib/screens/downloaded_album_screen.dart | 11 - lib/screens/home_tab.dart | 11 +- lib/screens/library_playlists_screen.dart | 4 - lib/screens/library_tracks_folder_screen.dart | 14 - lib/screens/local_album_screen.dart | 15 - lib/screens/playlist_screen.dart | 5 - lib/screens/queue_tab.dart | 14 +- lib/screens/settings/about_page.dart | 5 +- .../settings/appearance_settings_page.dart | 6 +- lib/screens/settings/donate_page.dart | 3 - .../settings/download_settings_page.dart | 21 +- .../settings/extension_detail_page.dart | 1 - .../settings/library_settings_page.dart | 4 - .../lyrics_provider_priority_page.dart | 26 - .../settings/options_settings_page.dart | 5 +- .../settings/provider_priority_page.dart | 6 - lib/screens/setup_screen.dart | 7 +- lib/screens/track_metadata_screen.dart | 15 - lib/screens/tutorial_screen.dart | 9 - lib/services/ffmpeg_service.dart | 12 +- lib/services/history_database.dart | 4 - lib/services/platform_bridge.dart | 5 - lib/widgets/donate_icons.dart | 2 - lib/widgets/download_service_picker.dart | 12 - pubspec.yaml | 4 +- site/docs.html | 2 +- site/index.html | 12 +- 105 files changed, 285 insertions(+), 1440 deletions(-) delete mode 100644 go_backend/amazon.go delete mode 100644 go_backend/amazon_asin_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 25d41bf0..49976882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [3.7.2] - 2026-03-07 + +### Changed + +- **Amazon Music is now an Extension**: Amazon Music has been moved from a built-in service to a separate installable extension. Install the "Amazon Music" extension from the Store to continue using it. + +### Fixed + +- **Deezer Downloads Timing Out**: Deezer downloads were failing with "context deadline exceeded" on larger files. Now uses the proper download timeout, matching Tidal and Qobuz. + +### Added + +- **Amazon Music Extension**: Available in `extension/Amazon-SpotiFLAC/` — same functionality as before, now as an installable extension. + +--- + ## [3.7.1] - 2026-03-06 ### Added diff --git a/README.md b/README.md index 83669b33..ae6db959 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ -Download music in true lossless FLAC from Tidal, Qobuz & Amazon Music — no account required. +Download music in true lossless FLAC from Tidal, Qobuz & Deezer — no account required. ![Android](https://img.shields.io/badge/Android-7.0%2B-3DDC84?style=for-the-badge&logo=android&logoColor=white) ![iOS](https://img.shields.io/badge/iOS-14.0%2B-000000?style=for-the-badge&logo=apple&logoColor=white) @@ -51,10 +51,10 @@ Download music in true lossless FLAC from Tidal, Qobuz & Amazon Music for Window ## FAQ **Q: Why is my download failing with "Song not found"?** -A: The track may not be available on Tidal, Qobuz, or Amazon Music. Try enabling more download services in Settings > Download > Provider Priority, or install additional extensions from the Store. +A: The track may not be available on the streaming services. Try enabling more download services in Settings > Download > Provider Priority, or install additional extensions like Amazon Music from the Store. **Q: Why are some tracks downloading in lower quality?** -A: Quality depends on what's available from the streaming service. Tidal offers up to 24-bit/192kHz, Qobuz up to 24-bit/192kHz, and Amazon up to 24-bit/48kHz. The app automatically selects the best available quality. +A: Quality depends on what's available from the streaming service and extensions. Built-in providers: Tidal offers up to 24-bit/192kHz, Qobuz up to 24-bit/192kHz, and Deezer up to 16-bit/44.1kHz. **Q: Can I download playlists?** A: Yes! Just paste the playlist URL in the search bar. The app will fetch all tracks and queue them for download. @@ -75,23 +75,6 @@ _If this software is useful and brings you value, consider supporting the projec [![Ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/zarzet) - -## Disclaimer - -This project is for **educational and private use only**. The developer does not condone or encourage copyright infringement. - -**SpotiFLAC** is a third-party tool and is not affiliated with, endorsed by, or connected to Tidal, Qobuz, Amazon Music, Deezer, or any other streaming service. - -The application is purely a user interface that facilitates communication between your device and existing third-party services. - -You are solely responsible for: -1. Ensuring your use of this software complies with your local laws. -2. Reading and adhering to the Terms of Service of the respective platforms. -3. Any legal consequences resulting from the misuse of this tool. - -The software is provided "as is", without warranty of any kind. The author assumes no liability for any bans, damages, or legal issues arising from its use. - - ## API Credits [hifi-api](https://github.com/binimum/hifi-api) · [music.binimum.org](https://music.binimum.org) · [qqdl.site](https://qqdl.site) · [squid.wtf](https://squid.wtf) · [spotisaver.net](https://spotisaver.net) · [dabmusic.xyz](https://dabmusic.xyz) · [AfkarXYZ](https://github.com/afkarxyz) · [LRCLib](https://lrclib.net) · [Paxsenix](https://lyrics.paxsenix.org) · [Cobalt](https://cobalt.tools) · [qwkuns.me](https://qwkuns.me) · [SpotubeDL](https://spotubedl.com) · [Song.link](https://song.link) · [IDHS](https://github.com/sjdonado/idonthavespotify) diff --git a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt index c30a1775..da56220d 100644 --- a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt +++ b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt @@ -766,6 +766,27 @@ class MainActivity: FlutterFragmentActivity() { val response = downloader(req.toString()) val respObj = JSONObject(response) if (respObj.optBoolean("success", false)) { + // Extension providers write to a local temp path instead of the SAF FD. + // Copy the local file into the SAF document so it is not empty. + val goFilePath = respObj.optString("file_path", "") + if (goFilePath.isNotEmpty() && + !goFilePath.startsWith("content://") && + !goFilePath.startsWith("/proc/self/fd/") + ) { + try { + val srcFile = java.io.File(goFilePath) + if (srcFile.exists() && srcFile.length() > 0) { + contentResolver.openOutputStream(document.uri, "wt")?.use { output -> + srcFile.inputStream().use { input -> + input.copyTo(output) + } + } + srcFile.delete() + } + } catch (e: Exception) { + android.util.Log.w("SpotiFLAC", "Failed to copy extension output to SAF: ${e.message}") + } + } respObj.put("file_path", document.uri.toString()) respObj.put("file_name", document.name ?: fileName) } else { @@ -2239,13 +2260,6 @@ class MainActivity: FlutterFragmentActivity() { } result.success(response) } - "getAmazonURLFromDeezerTrack" -> { - val deezerTrackId = call.argument("deezer_track_id") ?: "" - val response = withContext(Dispatchers.IO) { - Gobackend.getAmazonURLFromDeezerTrack(deezerTrackId) - } - result.success(response) - } // Log methods "getLogs" -> { val response = withContext(Dispatchers.IO) { diff --git a/go_backend/amazon.go b/go_backend/amazon.go deleted file mode 100644 index b30d669d..00000000 --- a/go_backend/amazon.go +++ /dev/null @@ -1,692 +0,0 @@ -package gobackend - -import ( - "bufio" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "os" - "path/filepath" - "regexp" - "strings" - "sync" - "time" -) - -// Amazon API timeout and retry configuration for mobile networks -const ( - amazonAPITimeoutMobile = 30 * time.Second // Longer timeout for unstable mobile networks - amazonMaxRetries = 2 // Number of retry attempts - amazonRetryDelay = 500 * time.Millisecond -) - -type AmazonDownloader struct { - client *http.Client -} - -var ( - globalAmazonDownloader *AmazonDownloader - amazonDownloaderOnce sync.Once - amazonASINRegex = regexp.MustCompile(`(?i)^B[0-9A-Z]{9}$`) - amazonASINFindRegex = regexp.MustCompile(`(?i)B[0-9A-Z]{9}`) -) - -// AfkarXYZResponse is the response from AfkarXYZ API -type AfkarXYZResponse struct { - Success bool `json:"success"` - Data struct { - DirectLink string `json:"direct_link"` - FileName string `json:"file_name"` - FileSize int64 `json:"file_size"` - } `json:"data"` -} - -// AmazonStreamResponse is the new response format from amzn.afkarxyz.fun/api/track/{asin} -type AmazonStreamResponse struct { - StreamURL string `json:"streamUrl"` - DecryptionKey string `json:"decryptionKey"` -} - -func NewAmazonDownloader() *AmazonDownloader { - amazonDownloaderOnce.Do(func() { - globalAmazonDownloader = &AmazonDownloader{ - client: NewHTTPClientWithTimeout(120 * time.Second), - } - }) - return globalAmazonDownloader -} - -// fetchAmazonURLWithRetry fetches from AfkarXYZ API with retry logic for mobile networks. -// Returns downloadURL, suggested fileName, optional decryptionKey. -func (a *AmazonDownloader) fetchAmazonURLWithRetry(amazonURL string) (string, string, string, error) { - var lastErr error - for attempt := 0; attempt <= amazonMaxRetries; attempt++ { - if attempt > 0 { - delay := amazonRetryDelay * time.Duration(1<<(attempt-1)) // Exponential backoff - GoLog("[Amazon] Retry %d/%d after %v...\n", attempt, amazonMaxRetries, delay) - time.Sleep(delay) - } - - downloadURL, fileName, decryptionKey, err := a.doAfkarXYZRequest(amazonURL) - if err == nil { - return downloadURL, fileName, decryptionKey, nil - } - - lastErr = err - errStr := strings.ToLower(err.Error()) - - // Check if error is retryable - isRetryable := strings.Contains(errStr, "timeout") || - strings.Contains(errStr, "connection reset") || - strings.Contains(errStr, "connection refused") || - strings.Contains(errStr, "eof") || - strings.Contains(errStr, "status 5") || - strings.Contains(errStr, "status 429") || - strings.Contains(errStr, "http 429") - - if !isRetryable { - return "", "", "", err - } - - GoLog("[Amazon] Attempt %d failed (retryable): %v\n", attempt+1, err) - } - - return "", "", "", fmt.Errorf("all %d attempts failed: %w", amazonMaxRetries+1, lastErr) -} - -func normalizeAmazonASIN(candidate string) string { - trimmed := strings.TrimSpace(candidate) - if trimmed == "" { - return "" - } - - if decoded, err := url.QueryUnescape(trimmed); err == nil { - trimmed = decoded - } - - trimmed = strings.ToUpper(trimmed) - if idx := strings.IndexAny(trimmed, "?#&/"); idx >= 0 { - trimmed = trimmed[:idx] - } - - if amazonASINRegex.MatchString(trimmed) { - return trimmed - } - - return "" -} - -func extractAmazonASIN(amazonURL string) string { - raw := strings.TrimSpace(amazonURL) - if raw == "" { - return "" - } - - parsed, err := url.Parse(raw) - if err == nil { - query := parsed.Query() - - // Prefer track-level ASIN when URL also contains albumAsin. - for _, key := range []string{"trackAsin", "trackasin", "trackASIN", "asin", "ASIN", "i"} { - if asin := normalizeAmazonASIN(query.Get(key)); asin != "" { - return asin - } - } - - path := strings.Trim(parsed.Path, "/") - if path != "" { - segments := strings.Split(path, "/") - - for i := 0; i < len(segments)-1; i++ { - segment := strings.ToLower(strings.TrimSpace(segments[i])) - if segment == "track" || segment == "tracks" { - if asin := normalizeAmazonASIN(segments[i+1]); asin != "" { - return asin - } - } - } - - if asin := normalizeAmazonASIN(segments[len(segments)-1]); asin != "" { - return asin - } - } - } - - match := amazonASINFindRegex.FindString(strings.ToUpper(raw)) - return normalizeAmazonASIN(match) -} - -// doAfkarXYZRequest performs a single request to Amazon API. -// It tries new endpoint first, then falls back to legacy /convert endpoint. -func (a *AmazonDownloader) doAfkarXYZRequest(amazonURL string) (string, string, string, error) { - asin := extractAmazonASIN(amazonURL) - if asin != "" { - GoLog("[Amazon] Using ASIN: %s\n", asin) - downloadURL, fileName, decryptKey, err := a.doAfkarXYZRequestNew(asin) - if err == nil { - return downloadURL, fileName, decryptKey, nil - } - GoLog("[Amazon] New API failed for ASIN %s, trying legacy endpoint: %v\n", asin, err) - } - return a.doAfkarXYZRequestLegacy(amazonURL) -} - -func (a *AmazonDownloader) doAfkarXYZRequestNew(asin string) (string, string, string, error) { - ctx, cancel := context.WithTimeout(context.Background(), amazonAPITimeoutMobile) - defer cancel() - - apiURL := fmt.Sprintf("https://amzn.afkarxyz.fun/api/track/%s", asin) - req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) - if err != nil { - return "", "", "", fmt.Errorf("failed to create request: %w", err) - } - - req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36") - - resp, err := a.client.Do(req) - if err != nil { - return "", "", "", fmt.Errorf("failed to call Amazon API: %w", err) - } - defer resp.Body.Close() - - body, readErr := io.ReadAll(resp.Body) - if readErr != nil { - return "", "", "", fmt.Errorf("failed to read response: %w", readErr) - } - - if resp.StatusCode != 200 { - return "", "", "", fmt.Errorf("Amazon API returned status %d", resp.StatusCode) - } - - var apiResp AmazonStreamResponse - if err := json.Unmarshal(body, &apiResp); err != nil { - return "", "", "", fmt.Errorf("failed to decode response: %w", err) - } - - if strings.TrimSpace(apiResp.StreamURL) == "" { - return "", "", "", fmt.Errorf("Amazon API returned empty stream URL") - } - - fileName := asin + ".m4a" - return apiResp.StreamURL, fileName, strings.TrimSpace(apiResp.DecryptionKey), nil -} - -func (a *AmazonDownloader) doAfkarXYZRequestLegacy(amazonURL string) (string, string, string, error) { - ctx, cancel := context.WithTimeout(context.Background(), amazonAPITimeoutMobile) - defer cancel() - - apiURL := "https://amzn.afkarxyz.fun/convert?url=" + url.QueryEscape(amazonURL) - req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) - if err != nil { - return "", "", "", fmt.Errorf("failed to create legacy request: %w", err) - } - - req.Header.Set("User-Agent", getRandomUserAgent()) - - resp, err := a.client.Do(req) - if err != nil { - return "", "", "", fmt.Errorf("failed to call legacy AfkarXYZ API: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return "", "", "", fmt.Errorf("legacy AfkarXYZ API returned status %d", resp.StatusCode) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return "", "", "", fmt.Errorf("failed to read legacy response: %w", err) - } - - var apiResp AfkarXYZResponse - if err := json.Unmarshal(body, &apiResp); err != nil { - return "", "", "", fmt.Errorf("failed to decode legacy response: %w", err) - } - - if !apiResp.Success || strings.TrimSpace(apiResp.Data.DirectLink) == "" { - return "", "", "", fmt.Errorf("legacy AfkarXYZ API failed or no download link found") - } - - fileName := apiResp.Data.FileName - if fileName == "" { - fileName = "track.flac" - } - - reg := regexp.MustCompile(`[<>:"/\\|?*]`) - fileName = reg.ReplaceAllString(fileName, "") - - return apiResp.Data.DirectLink, fileName, "", nil -} - -func (a *AmazonDownloader) downloadFromAfkarXYZ(amazonURL string) (string, string, string, error) { - GoLog("[Amazon] Fetching from AfkarXYZ API...\n") - - downloadURL, fileName, decryptionKey, err := a.fetchAmazonURLWithRetry(amazonURL) - if err != nil { - return "", "", "", err - } - - if decryptionKey != "" { - GoLog("[Amazon] AfkarXYZ returned encrypted stream (decryption key available)\n") - } - GoLog("[Amazon] AfkarXYZ returned: %s\n", fileName) - return downloadURL, fileName, decryptionKey, nil -} - -func (a *AmazonDownloader) DownloadFile(downloadURL, outputPath string, outputFD int, itemID string) error { - ctx := context.Background() - - if itemID != "" { - StartItemProgress(itemID) - defer CompleteItemProgress(itemID) - ctx = initDownloadCancel(itemID) - defer clearDownloadCancel(itemID) - } - - if isDownloadCancelled(itemID) { - return ErrDownloadCancelled - } - - req, err := http.NewRequestWithContext(ctx, "GET", downloadURL, nil) - if err != nil { - return fmt.Errorf("failed to create request: %w", err) - } - - req.Header.Set("User-Agent", getRandomUserAgent()) - - resp, err := a.client.Do(req) - if err != nil { - if isDownloadCancelled(itemID) { - return ErrDownloadCancelled - } - return err - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return fmt.Errorf("download failed: HTTP %d", resp.StatusCode) - } - - expectedSize := resp.ContentLength - if expectedSize > 0 && itemID != "" { - SetItemBytesTotal(itemID, expectedSize) - } - - out, err := openOutputForWrite(outputPath, outputFD) - if err != nil { - return err - } - - bufWriter := bufio.NewWriterSize(out, 256*1024) - - var written int64 - if itemID != "" { - pw := NewItemProgressWriter(bufWriter, itemID) - written, err = io.Copy(pw, resp.Body) - } else { - written, err = io.Copy(bufWriter, resp.Body) - } - - flushErr := bufWriter.Flush() - closeErr := out.Close() - - if err != nil { - cleanupOutputOnError(outputPath, outputFD) - if isDownloadCancelled(itemID) { - return ErrDownloadCancelled - } - return fmt.Errorf("download interrupted: %w", err) - } - if flushErr != nil { - cleanupOutputOnError(outputPath, outputFD) - return fmt.Errorf("failed to flush buffer: %w", flushErr) - } - if closeErr != nil { - cleanupOutputOnError(outputPath, outputFD) - return fmt.Errorf("failed to close file: %w", closeErr) - } - - if expectedSize > 0 && written != expectedSize { - cleanupOutputOnError(outputPath, outputFD) - return fmt.Errorf("incomplete download: expected %d bytes, got %d bytes", expectedSize, written) - } - - GoLog("[Amazon] Downloaded: %.2f MB (Complete)\n", float64(written)/(1024*1024)) - return nil -} - -// AmazonDownloadResult contains download result with quality info -type AmazonDownloadResult struct { - FilePath string - BitDepth int - SampleRate int - Title string - Artist string - Album string - ReleaseDate string - TrackNumber int - DiscNumber int - ISRC string - LyricsLRC string - DecryptionKey string -} - -func resolveAmazonURLForRequest(req DownloadRequest, logPrefix string) (string, error) { - if strings.TrimSpace(logPrefix) == "" { - logPrefix = "Amazon" - } - - amazonURL := "" - if req.ISRC != "" { - if cached := GetTrackIDCache().Get(req.ISRC); cached != nil && cached.AmazonURL != "" { - amazonURL = cached.AmazonURL - GoLog("[%s] Cache hit! Using cached Amazon URL for ISRC %s\n", logPrefix, req.ISRC) - } - } - - if amazonURL != "" { - return amazonURL, nil - } - - songlink := NewSongLinkClient() - var availability *TrackAvailability - var err error - - deezerID := strings.TrimSpace(req.DeezerID) - if prefixedDeezerID, found := strings.CutPrefix(req.SpotifyID, "deezer:"); found && strings.TrimSpace(prefixedDeezerID) != "" { - deezerID = strings.TrimSpace(prefixedDeezerID) - } - - if deezerID != "" { - GoLog("[%s] Using Deezer ID for SongLink lookup: %s\n", logPrefix, deezerID) - availability, err = songlink.CheckAvailabilityFromDeezer(deezerID) - } else if req.SpotifyID != "" { - availability, err = songlink.CheckTrackAvailability(req.SpotifyID, req.ISRC) - } else { - return "", fmt.Errorf("no valid Spotify or Deezer ID provided for Amazon lookup") - } - - if err != nil { - return "", fmt.Errorf("failed to check Amazon availability via SongLink: %w", err) - } - - if availability == nil || !availability.Amazon || availability.AmazonURL == "" { - return "", fmt.Errorf("track not available on Amazon Music (SongLink returned no Amazon URL)") - } - - amazonURL = availability.AmazonURL - if req.ISRC != "" { - GetTrackIDCache().SetAmazonURL(req.ISRC, amazonURL) - } - - return amazonURL, nil -} - -func downloadFromAmazon(req DownloadRequest) (AmazonDownloadResult, error) { - downloader := NewAmazonDownloader() - - isSafOutput := isFDOutput(req.OutputFD) || strings.TrimSpace(req.OutputPath) != "" - if !isSafOutput { - if existingFile, exists := checkISRCExistsInternal(req.OutputDir, req.ISRC); exists { - return AmazonDownloadResult{FilePath: "EXISTS:" + existingFile}, nil - } - } - - amazonURL, err := resolveAmazonURLForRequest(req, "Amazon") - if err != nil { - return AmazonDownloadResult{}, err - } - - if !isSafOutput && req.OutputDir != "." { - if err := os.MkdirAll(req.OutputDir, 0755); err != nil { - return AmazonDownloadResult{}, fmt.Errorf("failed to create output directory: %w", err) - } - } - - // Download using AfkarXYZ API - downloadURL, afkarFileName, decryptionKey, err := downloader.downloadFromAfkarXYZ(amazonURL) - if err != nil { - return AmazonDownloadResult{}, fmt.Errorf("failed to get download URL from AfkarXYZ: %w", err) - } - - GoLog("[Amazon] Match found: '%s' by '%s'\n", req.TrackName, req.ArtistName) - - filename := buildFilenameFromTemplate(req.FilenameFormat, map[string]any{ - "title": req.TrackName, - "artist": req.ArtistName, - "album": req.AlbumName, - "track": req.TrackNumber, - "year": extractYear(req.ReleaseDate), - "date": req.ReleaseDate, - "disc": req.DiscNumber, - }) - var outputPath string - if isSafOutput { - outputPath = strings.TrimSpace(req.OutputPath) - if outputPath == "" && isFDOutput(req.OutputFD) { - outputPath = fmt.Sprintf("/proc/self/fd/%d", req.OutputFD) - } - } else { - outputExt := strings.ToLower(filepath.Ext(afkarFileName)) - if outputExt == "" { - outputExt = ".flac" - } - filename = sanitizeFilename(filename) + outputExt - outputPath = filepath.Join(req.OutputDir, filename) - if fileInfo, statErr := os.Stat(outputPath); statErr == nil && fileInfo.Size() > 0 { - return AmazonDownloadResult{FilePath: "EXISTS:" + outputPath}, nil - } - } - - // START PARALLEL: Fetch cover and lyrics while downloading audio - var parallelResult *ParallelDownloadResult - parallelDone := make(chan struct{}) - go func() { - defer close(parallelDone) - coverURL := req.CoverURL - embedLyrics := req.EmbedLyrics - if !req.EmbedMetadata { - coverURL = "" - embedLyrics = false - } - parallelResult = FetchCoverAndLyricsParallel( - coverURL, - req.EmbedMaxQualityCover, - req.SpotifyID, - req.TrackName, - req.ArtistName, - embedLyrics, - int64(req.DurationMS), - ) - }() - - // Download audio file with item ID for progress tracking - if err := downloader.DownloadFile(downloadURL, outputPath, req.OutputFD, req.ItemID); err != nil { - if errors.Is(err, ErrDownloadCancelled) { - return AmazonDownloadResult{}, ErrDownloadCancelled - } - return AmazonDownloadResult{}, fmt.Errorf("download failed: %w", err) - } - - actualOutputPath := outputPath - needsDecryption := strings.TrimSpace(decryptionKey) != "" - if needsDecryption { - GoLog("[Amazon] Download requires decryption; deferring decrypt to Flutter FFmpeg path\n") - } - - // Wait for parallel operations to complete - <-parallelDone - - if req.ItemID != "" { - SetItemProgress(req.ItemID, 1.0, 0, 0) - SetItemFinalizing(req.ItemID) - } - - actualTrackNum := req.TrackNumber - actualDiscNum := req.DiscNumber - actualDate := req.ReleaseDate - actualAlbum := req.AlbumName - actualTitle := req.TrackName - actualArtist := req.ArtistName - - if !needsDecryption { - existingMeta, metaErr := ReadMetadata(actualOutputPath) - if metaErr == nil && existingMeta != nil { - if existingMeta.TrackNumber > 0 && (req.TrackNumber == 0 || req.TrackNumber == 1) { - actualTrackNum = existingMeta.TrackNumber - GoLog("[Amazon] Using track number from file: %d (request had: %d)\n", actualTrackNum, req.TrackNumber) - } - if existingMeta.DiscNumber > 0 && (req.DiscNumber == 0 || req.DiscNumber == 1) { - actualDiscNum = existingMeta.DiscNumber - GoLog("[Amazon] Using disc number from file: %d (request had: %d)\n", actualDiscNum, req.DiscNumber) - } - if existingMeta.Date != "" && req.ReleaseDate == "" { - actualDate = existingMeta.Date - GoLog("[Amazon] Using release date from file: %s\n", actualDate) - } - if existingMeta.Album != "" && req.AlbumName == "" { - actualAlbum = existingMeta.Album - GoLog("[Amazon] Using album from file: %s\n", actualAlbum) - } - GoLog("[Amazon] Existing metadata - Title: %s, Artist: %s, Album: %s, Date: %s\n", - existingMeta.Title, existingMeta.Artist, existingMeta.Album, existingMeta.Date) - } - } - - metadata := Metadata{ - Title: actualTitle, - Artist: actualArtist, - Album: actualAlbum, - AlbumArtist: req.AlbumArtist, - Date: actualDate, - TrackNumber: actualTrackNum, - TotalTracks: req.TotalTracks, - DiscNumber: actualDiscNum, - ISRC: req.ISRC, - Genre: req.Genre, - Label: req.Label, - Copyright: req.Copyright, - } - - var coverData []byte - if parallelResult != nil && parallelResult.CoverData != nil && len(parallelResult.CoverData) > 0 { - coverData = parallelResult.CoverData - GoLog("[Amazon] Using parallel-fetched cover (%d bytes)\n", len(coverData)) - } else { - existingCover, coverErr := ExtractCoverArt(actualOutputPath) - if coverErr == nil && len(existingCover) > 0 { - coverData = existingCover - GoLog("[Amazon] Using existing cover from Amazon file (%d bytes)\n", len(coverData)) - } else { - GoLog("[Amazon] No cover available (parallel fetch failed and no existing cover)\n") - } - } - - if isSafOutput || needsDecryption || !req.EmbedMetadata { - if !req.EmbedMetadata { - GoLog("[Amazon] Metadata embedding disabled by settings, skipping in-backend metadata/lyrics embedding\n") - } else { - GoLog("[Amazon] SAF output detected - skipping in-backend metadata/lyrics embedding (handled in Flutter)\n") - } - } else { - isFlacOutput := strings.HasSuffix(strings.ToLower(actualOutputPath), ".flac") - if isFlacOutput { - if err := EmbedMetadataWithCoverData(actualOutputPath, metadata, coverData); err != nil { - GoLog("[Amazon] Warning: failed to embed metadata: %v\n", err) - } - } else { - GoLog("[Amazon] Non-FLAC output detected (%s), skipping native metadata embedding\n", filepath.Ext(actualOutputPath)) - } - - if req.EmbedLyrics && parallelResult != nil && parallelResult.LyricsLRC != "" { - lyricsMode := req.LyricsMode - if lyricsMode == "" { - lyricsMode = "embed" - } - - if lyricsMode == "external" || lyricsMode == "both" { - GoLog("[Amazon] Saving external LRC file...\n") - if lrcPath, lrcErr := SaveLRCFile(actualOutputPath, parallelResult.LyricsLRC); lrcErr != nil { - GoLog("[Amazon] Warning: failed to save LRC file: %v\n", lrcErr) - } else { - GoLog("[Amazon] LRC file saved: %s\n", lrcPath) - } - } - - if (lyricsMode == "embed" || lyricsMode == "both") && isFlacOutput { - GoLog("[Amazon] Embedding parallel-fetched lyrics (%d lines)...\n", len(parallelResult.LyricsData.Lines)) - if embedErr := EmbedLyrics(actualOutputPath, parallelResult.LyricsLRC); embedErr != nil { - GoLog("[Amazon] Warning: failed to embed lyrics: %v\n", embedErr) - } else { - GoLog("[Amazon] Lyrics embedded successfully\n") - } - } else if (lyricsMode == "embed" || lyricsMode == "both") && !isFlacOutput { - GoLog("[Amazon] Skipping embedded lyrics for non-FLAC output\n") - } - } else if req.EmbedLyrics { - GoLog("[Amazon] No lyrics available from parallel fetch\n") - } - } - - GoLog("[Amazon] Downloaded successfully from Amazon Music\n") - - quality := AudioQuality{} - if isSafOutput || needsDecryption { - GoLog("[Amazon] SAF output detected - skipping post-write file inspection in backend\n") - } else { - quality, err = GetAudioQuality(actualOutputPath) - if err != nil { - GoLog("[Amazon] Warning: couldn't read quality from file: %v\n", err) - } else { - GoLog("[Amazon] Actual quality: %d-bit/%dHz\n", quality.BitDepth, quality.SampleRate) - } - - finalMeta, metaReadErr := ReadMetadata(actualOutputPath) - if metaReadErr == nil && finalMeta != nil { - GoLog("[Amazon] Final metadata from file - Track: %d, Disc: %d, Date: %s\n", - finalMeta.TrackNumber, finalMeta.DiscNumber, finalMeta.Date) - actualTrackNum = finalMeta.TrackNumber - actualDiscNum = finalMeta.DiscNumber - if finalMeta.Date != "" { - req.ReleaseDate = finalMeta.Date - } - } - } - - // Add to ISRC index for fast duplicate checking. - // When decryption is pending in Flutter, postpone indexing until final file is settled. - if !isSafOutput && !needsDecryption { - AddToISRCIndex(req.OutputDir, req.ISRC, actualOutputPath) - } - - bitDepth := 0 - sampleRate := 0 - if err == nil { - bitDepth = quality.BitDepth - sampleRate = quality.SampleRate - } - - lyricsLRC := "" - if req.EmbedMetadata && req.EmbedLyrics && parallelResult != nil && parallelResult.LyricsLRC != "" { - lyricsLRC = parallelResult.LyricsLRC - } - - return AmazonDownloadResult{ - FilePath: outputPath, - BitDepth: bitDepth, - SampleRate: sampleRate, - Title: req.TrackName, - Artist: req.ArtistName, - Album: req.AlbumName, - ReleaseDate: req.ReleaseDate, - TrackNumber: actualTrackNum, - DiscNumber: actualDiscNum, - ISRC: req.ISRC, - LyricsLRC: lyricsLRC, - DecryptionKey: decryptionKey, - }, nil -} diff --git a/go_backend/amazon_asin_test.go b/go_backend/amazon_asin_test.go deleted file mode 100644 index 705e9c01..00000000 --- a/go_backend/amazon_asin_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package gobackend - -import "testing" - -func TestExtractAmazonASIN(t *testing.T) { - tests := []struct { - name string - url string - want string - }{ - { - name: "prefers trackAsin over albumAsin", - url: "https://music.amazon.com/albums/B0ALBUM123?trackAsin=B0TRACK456&musicTerritory=US", - want: "B0TRACK456", - }, - { - name: "extract from tracks path", - url: "https://music.amazon.com/tracks/B0CYQHGWZJ?musicTerritory=US", - want: "B0CYQHGWZJ", - }, - { - name: "extract from plain query asin", - url: "https://example.com/?asin=B0CYQHGWZJ", - want: "B0CYQHGWZJ", - }, - { - name: "fallback regex", - url: "https://example.com/path/B0CYQHGWZJ", - want: "B0CYQHGWZJ", - }, - { - name: "invalid url", - url: "https://music.amazon.com/tracks/not-valid", - want: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := extractAmazonASIN(tt.url) - if got != tt.want { - t.Fatalf("extractAmazonASIN() = %q, want %q", got, tt.want) - } - }) - } -} diff --git a/go_backend/audio_metadata.go b/go_backend/audio_metadata.go index 4ccfc627..faf4ccf2 100644 --- a/go_backend/audio_metadata.go +++ b/go_backend/audio_metadata.go @@ -12,7 +12,6 @@ import ( "strings" ) -// AudioMetadata represents common audio file metadata type AudioMetadata struct { Title string Artist string @@ -31,7 +30,6 @@ type AudioMetadata struct { Comment string } -// MP3Quality represents MP3 specific quality info type MP3Quality struct { SampleRate int BitDepth int @@ -39,7 +37,6 @@ type MP3Quality struct { Bitrate int } -// OggQuality represents Ogg/Opus specific quality info type OggQuality struct { SampleRate int BitDepth int @@ -47,10 +44,6 @@ type OggQuality struct { Bitrate int // estimated bitrate in bps } -// ============================================================================= -// ID3 Tag Reading (MP3) -// ============================================================================= - func ReadID3Tags(filePath string) (*AudioMetadata, error) { file, err := os.Open(filePath) if err != nil { @@ -1210,10 +1203,6 @@ func readLastOggGranulePosition(file *os.File, fileSize int64) int64 { return 0 } -// ============================================================================= -// ID3v1 Genre List -// ============================================================================= - var id3v1Genres = []string{ "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", @@ -1244,10 +1233,6 @@ var id3v1Genres = []string{ "Thrash Metal", "Anime", "J-Pop", "Synthpop", } -// ============================================================================= -// Cover Art Extraction -// ============================================================================= - func extractMP3CoverArt(filePath string) ([]byte, string, error) { file, err := os.Open(filePath) if err != nil { diff --git a/go_backend/deezer_download.go b/go_backend/deezer_download.go index 0c64b658..01e5c654 100644 --- a/go_backend/deezer_download.go +++ b/go_backend/deezer_download.go @@ -120,7 +120,7 @@ func (c *DeezerClient) DownloadFromYoinkify(spotifyURL, outputPath string, outpu req.Header.Set("Accept", "*/*") req.Header.Set("User-Agent", getRandomUserAgent()) - resp, err := c.httpClient.Do(req) + resp, err := GetDownloadClient().Do(req) if err != nil { if isDownloadCancelled(itemID) { return ErrDownloadCancelled @@ -324,7 +324,7 @@ func (c *DeezerClient) DownloadFromMusicDL(deezerTrackURL, outputPath string, ou } req.Header.Set("User-Agent", getRandomUserAgent()) - resp, err := c.httpClient.Do(req) + resp, err := GetDownloadClient().Do(req) if err != nil { if isDownloadCancelled(itemID) { return ErrDownloadCancelled diff --git a/go_backend/exports.go b/go_backend/exports.go index 3c05a0fd..72edd394 100644 --- a/go_backend/exports.go +++ b/go_backend/exports.go @@ -478,25 +478,6 @@ func DownloadTrack(requestJSON string) (string, error) { } } err = qobuzErr - case "amazon": - amazonResult, amazonErr := downloadFromAmazon(req) - if amazonErr == nil { - result = DownloadResult{ - FilePath: amazonResult.FilePath, - BitDepth: amazonResult.BitDepth, - SampleRate: amazonResult.SampleRate, - Title: amazonResult.Title, - Artist: amazonResult.Artist, - Album: amazonResult.Album, - ReleaseDate: amazonResult.ReleaseDate, - TrackNumber: amazonResult.TrackNumber, - DiscNumber: amazonResult.DiscNumber, - ISRC: amazonResult.ISRC, - LyricsLRC: amazonResult.LyricsLRC, - DecryptionKey: amazonResult.DecryptionKey, - } - } - err = amazonErr case "deezer": deezerResult, deezerErr := downloadFromDeezer(req) if deezerErr == nil { @@ -640,7 +621,7 @@ func DownloadWithFallback(requestJSON string) (string, error) { enrichRequestExtendedMetadata(&req) - allServices := []string{"tidal", "qobuz", "amazon", "deezer"} + allServices := []string{"tidal", "qobuz", "deezer"} preferredService := req.Service if preferredService == "" { preferredService = "tidal" @@ -707,27 +688,6 @@ func DownloadWithFallback(requestJSON string) (string, error) { GoLog("[DownloadWithFallback] Qobuz error: %v\n", qobuzErr) } err = qobuzErr - case "amazon": - amazonResult, amazonErr := downloadFromAmazon(req) - if amazonErr == nil { - result = DownloadResult{ - FilePath: amazonResult.FilePath, - BitDepth: amazonResult.BitDepth, - SampleRate: amazonResult.SampleRate, - Title: amazonResult.Title, - Artist: amazonResult.Artist, - Album: amazonResult.Album, - ReleaseDate: amazonResult.ReleaseDate, - TrackNumber: amazonResult.TrackNumber, - DiscNumber: amazonResult.DiscNumber, - ISRC: amazonResult.ISRC, - LyricsLRC: amazonResult.LyricsLRC, - DecryptionKey: amazonResult.DecryptionKey, - } - } else if !errors.Is(amazonErr, ErrDownloadCancelled) { - GoLog("[DownloadWithFallback] Amazon error: %v\n", amazonErr) - } - err = amazonErr case "deezer": deezerResult, deezerErr := downloadFromDeezer(req) if deezerErr == nil { @@ -1579,11 +1539,6 @@ func GetTidalURLFromDeezerTrack(deezerTrackID string) (string, error) { return client.GetTidalURLFromDeezer(deezerTrackID) } -func GetAmazonURLFromDeezerTrack(deezerTrackID string) (string, error) { - client := NewSongLinkClient() - return client.GetAmazonURLFromDeezer(deezerTrackID) -} - func errorResponse(msg string) (string, error) { errorType := "unknown" lowerMsg := strings.ToLower(msg) @@ -2146,8 +2101,6 @@ func ReEnrichFile(requestJSON string) (string, error) { return string(jsonBytes), nil } -// ==================== EXTENSION SYSTEM ==================== - func InitExtensionSystem(extensionsDir, dataDir string) error { manager := GetExtensionManager() if err := manager.SetDirectories(extensionsDir, dataDir); err != nil { @@ -2519,8 +2472,6 @@ func GetAllPendingFFmpegCommandsJSON() (string, error) { return string(jsonBytes), nil } -// ==================== EXTENSION CUSTOM SEARCH ==================== - func EnrichTrackWithExtensionJSON(extensionID, trackJSON string) (string, error) { manager := GetExtensionManager() ext, err := manager.GetExtension(extensionID) @@ -3273,9 +3224,6 @@ func GetExtensionBrowseCategoriesJSON(extensionID string) (string, error) { return callExtensionFunctionJSON(extensionID, "getBrowseCategories", 30*time.Second) } -// ==================== LOCAL LIBRARY SCANNING ==================== - -// SetLibraryCoverCacheDirJSON sets the directory for caching extracted cover art func SetLibraryCoverCacheDirJSON(cacheDir string) { SetLibraryCoverCacheDir(cacheDir) } @@ -3284,9 +3232,6 @@ func ScanLibraryFolderJSON(folderPath string) (string, error) { return ScanLibraryFolder(folderPath) } -// ScanLibraryFolderIncrementalJSON performs an incremental library scan -// existingFilesJSON: JSON object mapping filePath -> modTime (unix millis) -// Returns IncrementalScanResult as JSON func ScanLibraryFolderIncrementalJSON(folderPath, existingFilesJSON string) (string, error) { return ScanLibraryFolderIncremental(folderPath, existingFilesJSON) } diff --git a/go_backend/extension_manager.go b/go_backend/extension_manager.go index b9b460c2..5c73e251 100644 --- a/go_backend/extension_manager.go +++ b/go_backend/extension_manager.go @@ -401,7 +401,6 @@ func (m *ExtensionManager) loadExtensionFromDirectory(dirPath string) (*LoadedEx return nil, fmt.Errorf("failed to read manifest.json: %w", err) } - // Parse and validate manifest manifest, err := ParseManifest(manifestData) if err != nil { return nil, fmt.Errorf("Invalid extension manifest: %w", err) @@ -467,17 +466,11 @@ func (m *ExtensionManager) RemoveExtension(extensionID string) error { } } - // Optionally remove data directory (keep for now to preserve settings) - // if ext.DataDir != "" { - // os.RemoveAll(ext.DataDir) - // } - return nil } // Only allows upgrades (new version > current version), not downgrades func (m *ExtensionManager) UpgradeExtension(filePath string) (*LoadedExtension, error) { - // Validate file extension if !strings.HasSuffix(strings.ToLower(filePath), ".spotiflac-ext") { return nil, fmt.Errorf("Invalid file format. Please select a .spotiflac-ext file") } @@ -529,7 +522,6 @@ func (m *ExtensionManager) UpgradeExtension(filePath string) (*LoadedExtension, return nil, fmt.Errorf("Extension '%s' is not installed. Use install instead of upgrade.", newManifest.DisplayName) } - // Compare versions - only allow upgrade, not downgrade versionCompare := compareVersions(newManifest.Version, existing.Manifest.Version) if versionCompare < 0 { return nil, fmt.Errorf("Cannot downgrade extension. Current version: %s, New version: %s", existing.Manifest.Version, newManifest.Version) @@ -540,7 +532,6 @@ func (m *ExtensionManager) UpgradeExtension(filePath string) (*LoadedExtension, GoLog("[Extension] Upgrading %s from v%s to v%s\n", newManifest.DisplayName, existing.Manifest.Version, newManifest.Version) - // Save data directory path and enabled state (we want to preserve them) extDataDir := existing.DataDir extDir := existing.SourceDir wasEnabled := existing.Enabled @@ -601,7 +592,6 @@ func (m *ExtensionManager) UpgradeExtension(filePath string) (*LoadedExtension, SourceDir: extDir, } - // Initialize Goja VM if err := m.initializeVM(ext); err != nil { ext.Error = err.Error() ext.Enabled = false @@ -626,7 +616,6 @@ type ExtensionUpgradeInfo struct { } func (m *ExtensionManager) checkExtensionUpgradeInternal(filePath string) (*ExtensionUpgradeInfo, error) { - // Validate file extension if !strings.HasSuffix(strings.ToLower(filePath), ".spotiflac-ext") { return nil, fmt.Errorf("Invalid file format. Please select a .spotiflac-ext file") } @@ -675,7 +664,6 @@ func (m *ExtensionManager) checkExtensionUpgradeInternal(filePath string) (*Exte } if !exists { - // Not installed - this is a new install, not upgrade info.CurrentVersion = "" info.CanUpgrade = false } else { @@ -739,7 +727,6 @@ func (m *ExtensionManager) GetInstalledExtensionsJSON() (string, error) { permissions = append(permissions, "storage:enabled") } - // Determine status status := "loaded" if ext.Error != "" { status = "error" @@ -940,7 +927,6 @@ func (m *ExtensionManager) InvokeAction(extensionID string, actionName string) ( return nil, fmt.Errorf("extension is disabled") } - // Call the action function on the extension object script := fmt.Sprintf(` (function() { if (typeof extension !== 'undefined' && typeof extension.%s === 'function') { diff --git a/go_backend/extension_manifest.go b/go_backend/extension_manifest.go index f4164b4f..6166a667 100644 --- a/go_backend/extension_manifest.go +++ b/go_backend/extension_manifest.go @@ -1,4 +1,3 @@ -// Package gobackend provides extension manifest parsing and validation package gobackend import ( diff --git a/go_backend/extension_providers.go b/go_backend/extension_providers.go index ceac1af4..a1a419b5 100644 --- a/go_backend/extension_providers.go +++ b/go_backend/extension_providers.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "os" "path/filepath" "sort" "strings" @@ -99,15 +100,16 @@ type ExtDownloadResult struct { ErrorMessage string `json:"error_message,omitempty"` ErrorType string `json:"error_type,omitempty"` - Title string `json:"title,omitempty"` - Artist string `json:"artist,omitempty"` - Album string `json:"album,omitempty"` - AlbumArtist string `json:"album_artist,omitempty"` - TrackNumber int `json:"track_number,omitempty"` - DiscNumber int `json:"disc_number,omitempty"` - ReleaseDate string `json:"release_date,omitempty"` - CoverURL string `json:"cover_url,omitempty"` - ISRC string `json:"isrc,omitempty"` + Title string `json:"title,omitempty"` + Artist string `json:"artist,omitempty"` + Album string `json:"album,omitempty"` + AlbumArtist string `json:"album_artist,omitempty"` + TrackNumber int `json:"track_number,omitempty"` + DiscNumber int `json:"disc_number,omitempty"` + ReleaseDate string `json:"release_date,omitempty"` + CoverURL string `json:"cover_url,omitempty"` + ISRC string `json:"isrc,omitempty"` + DecryptionKey string `json:"decryption_key,omitempty"` } type ExtensionProviderWrapper struct { @@ -388,7 +390,7 @@ func (p *ExtensionProviderWrapper) EnrichTrack(track *ExtTrackMetadata) (*ExtTra return &enrichedTrack, nil } -func (p *ExtensionProviderWrapper) CheckAvailability(isrc, trackName, artistName string) (*ExtAvailabilityResult, error) { +func (p *ExtensionProviderWrapper) CheckAvailability(isrc, trackName, artistName, spotifyID, deezerID string) (*ExtAvailabilityResult, error) { if !p.extension.Manifest.IsDownloadProvider() { return nil, fmt.Errorf("extension '%s' is not a download provider", p.extension.ID) } @@ -403,11 +405,11 @@ func (p *ExtensionProviderWrapper) CheckAvailability(isrc, trackName, artistName script := fmt.Sprintf(` (function() { if (typeof extension !== 'undefined' && typeof extension.checkAvailability === 'function') { - return extension.checkAvailability(%q, %q, %q); + return extension.checkAvailability(%q, %q, %q, {spotify_id: %q, deezer_id: %q}); } return null; })() - `, isrc, trackName, artistName) + `, isrc, trackName, artistName, spotifyID, deezerID) result, err := RunWithTimeoutAndRecover(p.vm, script, DefaultJSTimeout) if err != nil { @@ -631,7 +633,7 @@ func GetProviderPriority() []string { defer providerPriorityMu.RUnlock() if len(providerPriority) == 0 { - return []string{"tidal", "qobuz", "amazon", "deezer"} + return []string{"tidal", "qobuz", "deezer"} } result := make([]string, len(providerPriority)) @@ -661,7 +663,7 @@ func GetMetadataProviderPriority() []string { func isBuiltInProvider(providerID string) bool { switch providerID { - case "tidal", "qobuz", "amazon", "deezer": + case "tidal", "qobuz", "deezer": return true default: return false @@ -694,6 +696,27 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro } priority = newPriority GoLog("[DownloadWithExtensionFallback] New priority order: %v\n", priority) + } else if !strictMode && req.Service != "" && !isBuiltInProvider(strings.ToLower(req.Service)) { + found := false + for _, p := range priority { + if strings.EqualFold(p, req.Service) { + found = true + break + } + } + newPriority := []string{req.Service} + for _, p := range priority { + if !strings.EqualFold(p, req.Service) { + newPriority = append(newPriority, p) + } + } + priority = newPriority + if !found { + GoLog("[DownloadWithExtensionFallback] Extension service '%s' added to priority front\n", req.Service) + } else { + GoLog("[DownloadWithExtensionFallback] Extension service '%s' moved to priority front\n", req.Service) + } + GoLog("[DownloadWithExtensionFallback] New priority order: %v\n", priority) } var lastErr error @@ -777,7 +800,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro GoLog("[DownloadWithExtensionFallback] Downloading from source extension with trackID: %s (skipBuiltInFallback: %v)\n", trackID, skipBuiltIn) - outputPath := buildOutputPath(req) + outputPath := buildOutputPathForExtension(req, ext) if req.ItemID != "" { StartItemProgress(req.ItemID) } @@ -813,6 +836,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro Genre: req.Genre, Label: req.Label, Copyright: req.Copyright, + DecryptionKey: result.DecryptionKey, } if req.EmbedMetadata && (req.Genre != "" || req.Label != "") { @@ -966,7 +990,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro provider := NewExtensionProviderWrapper(ext) - availability, err := provider.CheckAvailability(req.ISRC, req.TrackName, req.ArtistName) + availability, err := provider.CheckAvailability(req.ISRC, req.TrackName, req.ArtistName, req.SpotifyID, req.DeezerID) if err != nil || !availability.Available { GoLog("[DownloadWithExtensionFallback] %s: not available\n", providerID) if err != nil { @@ -975,7 +999,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro continue } - outputPath := buildOutputPath(req) + outputPath := buildOutputPathForExtension(req, ext) if req.ItemID != "" { StartItemProgress(req.ItemID) } @@ -1011,6 +1035,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro Genre: req.Genre, Label: req.Label, Copyright: req.Copyright, + DecryptionKey: result.DecryptionKey, } if req.EmbedMetadata && (req.Genre != "" || req.Label != "") { @@ -1128,25 +1153,6 @@ func tryBuiltInProvider(providerID string, req DownloadRequest) (*DownloadRespon } } err = qobuzErr - case "amazon": - amazonResult, amazonErr := downloadFromAmazon(req) - if amazonErr == nil { - result = DownloadResult{ - FilePath: amazonResult.FilePath, - BitDepth: amazonResult.BitDepth, - SampleRate: amazonResult.SampleRate, - Title: amazonResult.Title, - Artist: amazonResult.Artist, - Album: amazonResult.Album, - ReleaseDate: amazonResult.ReleaseDate, - TrackNumber: amazonResult.TrackNumber, - DiscNumber: amazonResult.DiscNumber, - ISRC: amazonResult.ISRC, - LyricsLRC: amazonResult.LyricsLRC, - DecryptionKey: amazonResult.DecryptionKey, - } - } - err = amazonErr case "deezer": deezerResult, deezerErr := downloadFromDeezer(req) if deezerErr == nil { @@ -1226,7 +1232,58 @@ func buildOutputPath(req DownloadRequest) string { ext = "." + ext } - return fmt.Sprintf("%s/%s%s", req.OutputDir, filename, ext) + outputDir := req.OutputDir + if strings.TrimSpace(outputDir) == "" { + outputDir = filepath.Join(os.TempDir(), "spotiflac-downloads") + os.MkdirAll(outputDir, 0755) + AddAllowedDownloadDir(outputDir) + } + + return filepath.Join(outputDir, filename+ext) +} + +func buildOutputPathForExtension(req DownloadRequest, ext *LoadedExtension) string { + if strings.TrimSpace(req.OutputPath) != "" { + return strings.TrimSpace(req.OutputPath) + } + + if strings.TrimSpace(req.OutputDir) != "" { + return buildOutputPath(req) + } + + // SAF mode: use extension's data dir as writable temp location + tempDir := filepath.Join(ext.DataDir, "downloads") + os.MkdirAll(tempDir, 0755) + AddAllowedDownloadDir(tempDir) + + metadata := map[string]interface{}{ + "title": req.TrackName, + "artist": req.ArtistName, + "album": req.AlbumName, + "album_artist": req.AlbumArtist, + "track": req.TrackNumber, + "track_number": req.TrackNumber, + "disc": req.DiscNumber, + "disc_number": req.DiscNumber, + "year": extractYear(req.ReleaseDate), + "date": req.ReleaseDate, + "release_date": req.ReleaseDate, + "isrc": req.ISRC, + } + + filename := buildFilenameFromTemplate(req.FilenameFormat, metadata) + if filename == "" { + filename = sanitizeFilename(fmt.Sprintf("%s - %s", req.ArtistName, req.TrackName)) + } + + outputExt := strings.TrimSpace(req.OutputExt) + if outputExt == "" { + outputExt = ".flac" + } else if !strings.HasPrefix(outputExt, ".") { + outputExt = "." + outputExt + } + + return filepath.Join(tempDir, filename+outputExt) } func (p *ExtensionProviderWrapper) CustomSearch(query string, options map[string]interface{}) ([]ExtTrackMetadata, error) { @@ -1653,7 +1710,6 @@ func (m *ExtensionManager) HandleURLWithExtension(url string) (*ExtURLHandleResu }, nil } -// GetPostProcessingProviders returns all extensions that provide post-processing func (m *ExtensionManager) GetPostProcessingProviders() []*ExtensionProviderWrapper { m.mu.RLock() defer m.mu.RUnlock() @@ -1667,7 +1723,6 @@ func (m *ExtensionManager) GetPostProcessingProviders() []*ExtensionProviderWrap return providers } -// RunPostProcessing runs all enabled post-processing hooks on a file func (m *ExtensionManager) RunPostProcessing(filePath string, metadata map[string]interface{}) (*PostProcessResult, error) { providers := m.GetPostProcessingProviders() if len(providers) == 0 { @@ -1713,7 +1768,6 @@ func (m *ExtensionManager) RunPostProcessing(filePath string, metadata map[strin return &PostProcessResult{Success: true, NewFilePath: currentPath}, nil } -// RunPostProcessingV2 runs all enabled post-processing hooks on a file input. func (m *ExtensionManager) RunPostProcessingV2(input PostProcessInput, metadata map[string]interface{}) (*PostProcessResult, error) { providers := m.GetPostProcessingProviders() if len(providers) == 0 { @@ -1768,9 +1822,6 @@ func (m *ExtensionManager) RunPostProcessingV2(input PostProcessInput, metadata return &PostProcessResult{Success: true, NewFilePath: currentInput.Path, NewFileURI: currentInput.URI}, nil } -// ==================== Lyrics Provider ==================== - -// ExtLyricsResult represents lyrics data returned from an extension type ExtLyricsResult struct { Lines []ExtLyricsLine `json:"lines"` SyncType string `json:"syncType"` @@ -1785,7 +1836,6 @@ type ExtLyricsLine struct { EndTimeMs int64 `json:"endTimeMs"` } -// FetchLyrics calls the extension's fetchLyrics function func (p *ExtensionProviderWrapper) FetchLyrics(trackName, artistName, albumName string, durationSec float64) (*LyricsResponse, error) { if !p.extension.Manifest.IsLyricsProvider() { return nil, fmt.Errorf("extension '%s' is not a lyrics provider", p.extension.ID) @@ -1885,7 +1935,6 @@ func (p *ExtensionProviderWrapper) FetchLyrics(trackName, artistName, albumName return response, nil } -// GetLyricsProviders returns all enabled extensions that provide lyrics func (m *ExtensionManager) GetLyricsProviders() []*ExtensionProviderWrapper { m.mu.RLock() defer m.mu.RUnlock() diff --git a/go_backend/extension_runtime_auth.go b/go_backend/extension_runtime_auth.go index de4ed06c..e17e0d4d 100644 --- a/go_backend/extension_runtime_auth.go +++ b/go_backend/extension_runtime_auth.go @@ -1,4 +1,3 @@ -// Package gobackend provides Auth API and PKCE support for extension runtime package gobackend import ( @@ -16,8 +15,6 @@ import ( "github.com/dop251/goja" ) -// ==================== Auth API (OAuth Support) ==================== - func validateExtensionAuthURL(urlStr string) error { parsed, err := url.Parse(urlStr) if err != nil { @@ -204,9 +201,6 @@ func (r *ExtensionRuntime) authGetTokens(call goja.FunctionCall) goja.Value { return r.vm.ToValue(result) } -// ==================== PKCE Support ==================== - -// generatePKCEVerifier generates a cryptographically random code verifier // Length should be between 43-128 characters (RFC 7636) func generatePKCEVerifier(length int) (string, error) { if length < 43 { @@ -394,9 +388,7 @@ func (r *ExtensionRuntime) authStartOAuthWithPKCE(call goja.FunctionCall) goja.V }) } -// authExchangeCodeWithPKCE exchanges auth code for tokens using PKCE // config: { tokenUrl, clientId, redirectUri, code, extraParams } -// Uses the stored PKCE verifier automatically func (r *ExtensionRuntime) authExchangeCodeWithPKCE(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(map[string]interface{}{ @@ -414,7 +406,6 @@ func (r *ExtensionRuntime) authExchangeCodeWithPKCE(call goja.FunctionCall) goja }) } - // Required fields tokenURL, _ := config["tokenUrl"].(string) clientID, _ := config["clientId"].(string) redirectURI, _ := config["redirectUri"].(string) diff --git a/go_backend/extension_runtime_ffmpeg.go b/go_backend/extension_runtime_ffmpeg.go index 19e1b67c..7c1e8d69 100644 --- a/go_backend/extension_runtime_ffmpeg.go +++ b/go_backend/extension_runtime_ffmpeg.go @@ -1,4 +1,3 @@ -// Package gobackend provides FFmpeg API for extension runtime package gobackend import ( @@ -10,9 +9,7 @@ import ( "github.com/dop251/goja" ) -// ==================== FFmpeg API (Post-Processing) ==================== - -// FFmpegCommand holds a pending FFmpeg command for Flutter to execute +// FFmpegCommand holds a pending FFmpeg command for Flutter to execute. type FFmpegCommand struct { ExtensionID string Command string @@ -24,7 +21,6 @@ type FFmpegCommand struct { Output string } -// Global FFmpeg command queue var ( ffmpegCommands = make(map[string]*FFmpegCommand) ffmpegCommandsMu sync.RWMutex diff --git a/go_backend/extension_runtime_file.go b/go_backend/extension_runtime_file.go index 9bb1191e..414f174c 100644 --- a/go_backend/extension_runtime_file.go +++ b/go_backend/extension_runtime_file.go @@ -1,4 +1,3 @@ -// Package gobackend provides File API for extension runtime package gobackend import ( @@ -13,8 +12,6 @@ import ( "github.com/dop251/goja" ) -// ==================== File API (Sandboxed) ==================== - var ( allowedDownloadDirs []string allowedDownloadDirsMu sync.RWMutex diff --git a/go_backend/extension_runtime_http.go b/go_backend/extension_runtime_http.go index dcdd32f4..65775832 100644 --- a/go_backend/extension_runtime_http.go +++ b/go_backend/extension_runtime_http.go @@ -1,4 +1,3 @@ -// Package gobackend provides HTTP API for extension runtime package gobackend import ( @@ -12,8 +11,6 @@ import ( "github.com/dop251/goja" ) -// ==================== HTTP API (Sandboxed) ==================== - type HTTPResponse struct { StatusCode int `json:"statusCode"` Body string `json:"body"` diff --git a/go_backend/extension_runtime_matching.go b/go_backend/extension_runtime_matching.go index 4ad4b0e3..30b61e7b 100644 --- a/go_backend/extension_runtime_matching.go +++ b/go_backend/extension_runtime_matching.go @@ -1,4 +1,3 @@ -// Package gobackend provides Track Matching API for extension runtime package gobackend import ( @@ -7,8 +6,6 @@ import ( "github.com/dop251/goja" ) -// ==================== Track Matching API ==================== - func (r *ExtensionRuntime) matchingCompareStrings(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { return r.vm.ToValue(0.0) diff --git a/go_backend/extension_runtime_polyfills.go b/go_backend/extension_runtime_polyfills.go index 62892c70..a5334e06 100644 --- a/go_backend/extension_runtime_polyfills.go +++ b/go_backend/extension_runtime_polyfills.go @@ -1,4 +1,3 @@ -// Package gobackend provides Browser-like Polyfills for extension runtime package gobackend import ( @@ -13,12 +12,10 @@ import ( "github.com/dop251/goja" ) -// ==================== Browser-like Polyfills ==================== // These polyfills make porting browser/Node.js libraries easier -// without compromising sandbox security +// without compromising sandbox security. -// fetchPolyfill implements browser-compatible fetch() API -// Returns a Promise-like object with json(), text() methods +// Returns a Promise-like object with json(), text() methods. func (r *ExtensionRuntime) fetchPolyfill(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.createFetchError("URL is required") @@ -141,7 +138,6 @@ func (r *ExtensionRuntime) fetchPolyfill(call goja.FunctionCall) goja.Value { return responseObj } -// createFetchError creates a fetch error response func (r *ExtensionRuntime) createFetchError(message string) goja.Value { errorObj := r.vm.NewObject() errorObj.Set("ok", false) @@ -157,7 +153,6 @@ func (r *ExtensionRuntime) createFetchError(message string) goja.Value { return errorObj } -// atobPolyfill implements browser atob() - decode base64 to string func (r *ExtensionRuntime) atobPolyfill(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue("") @@ -174,7 +169,6 @@ func (r *ExtensionRuntime) atobPolyfill(call goja.FunctionCall) goja.Value { return r.vm.ToValue(string(decoded)) } -// btoaPolyfill implements browser btoa() - encode string to base64 func (r *ExtensionRuntime) btoaPolyfill(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue("") @@ -183,7 +177,6 @@ func (r *ExtensionRuntime) btoaPolyfill(call goja.FunctionCall) goja.Value { return r.vm.ToValue(base64.StdEncoding.EncodeToString([]byte(input))) } -// registerTextEncoderDecoder registers TextEncoder and TextDecoder classes func (r *ExtensionRuntime) registerTextEncoderDecoder(vm *goja.Runtime) { vm.Set("TextEncoder", func(call goja.ConstructorCall) *goja.Object { encoder := call.This @@ -429,9 +422,8 @@ func (r *ExtensionRuntime) registerURLClass(vm *goja.Runtime) { }) } -// registerJSONGlobal ensures JSON global is properly set up +// JSON is already built-in to Goja; this ensures a fallback exists. func (r *ExtensionRuntime) registerJSONGlobal(vm *goja.Runtime) { - // JSON is already built-in to Goja, but we can enhance it jsonScript := ` if (typeof JSON === 'undefined') { var JSON = { diff --git a/go_backend/extension_runtime_storage.go b/go_backend/extension_runtime_storage.go index 06cbdd33..50815b40 100644 --- a/go_backend/extension_runtime_storage.go +++ b/go_backend/extension_runtime_storage.go @@ -1,4 +1,3 @@ -// Package gobackend provides Storage and Credentials API for extension runtime package gobackend import ( @@ -17,8 +16,6 @@ import ( "github.com/dop251/goja" ) -// ==================== Storage API ==================== - const ( defaultStorageFlushDelay = 400 * time.Millisecond storageFlushRetryDelay = 2 * time.Second diff --git a/go_backend/extension_runtime_utils.go b/go_backend/extension_runtime_utils.go index c3a7675e..f91918ff 100644 --- a/go_backend/extension_runtime_utils.go +++ b/go_backend/extension_runtime_utils.go @@ -1,4 +1,3 @@ -// Package gobackend provides Utility functions for extension runtime package gobackend import ( @@ -17,8 +16,6 @@ import ( "github.com/dop251/goja" ) -// ==================== Utility Functions ==================== - func (r *ExtensionRuntime) base64Encode(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue("") diff --git a/go_backend/extension_settings.go b/go_backend/extension_settings.go index 9ad0c0c1..e241f6e2 100644 --- a/go_backend/extension_settings.go +++ b/go_backend/extension_settings.go @@ -1,4 +1,3 @@ -// Package gobackend provides extension settings storage package gobackend import ( diff --git a/go_backend/extension_timeout.go b/go_backend/extension_timeout.go index e9a5605f..76e51cfa 100644 --- a/go_backend/extension_timeout.go +++ b/go_backend/extension_timeout.go @@ -1,4 +1,3 @@ -// Package gobackend provides timeout execution for extension JS code package gobackend import ( diff --git a/go_backend/httputil.go b/go_backend/httputil.go index 08137fa8..b54ec489 100644 --- a/go_backend/httputil.go +++ b/go_backend/httputil.go @@ -489,7 +489,6 @@ func IsISPBlocking(err error, requestURL string) *ISPBlockingError { } } - // Check error message patterns for common ISP blocking indicators blockingPatterns := []struct { pattern string reason string @@ -532,7 +531,6 @@ func CheckAndLogISPBlocking(err error, requestURL string, tag string) bool { return false } -// extractDomain extracts the domain from a URL string func extractDomain(rawURL string) string { if rawURL == "" { return "unknown" diff --git a/go_backend/httputil_utls.go b/go_backend/httputil_utls.go index bf5ec9ed..4b09deb7 100644 --- a/go_backend/httputil_utls.go +++ b/go_backend/httputil_utls.go @@ -91,7 +91,6 @@ func (t *utlsTransport) getPort(u *url.URL) string { return "80" } -// Cloudflare bypass client using uTLS Chrome fingerprint var cloudflareBypassTransport = newUTLSTransport() var cloudflareBypassClient = &http.Client{ @@ -111,7 +110,6 @@ func GetCloudflareBypassClient() *http.Client { func DoRequestWithCloudflareBypass(req *http.Request) (*http.Response, error) { req.Header.Set("User-Agent", getRandomUserAgent()) - // Try with standard client first resp, err := sharedClient.Do(req) if err == nil { // Check for Cloudflare challenge page (403 with specific markers) @@ -138,11 +136,9 @@ func DoRequestWithCloudflareBypass(req *http.Request) (*http.Response, error) { if isCloudflare { LogDebug("HTTP", "Cloudflare detected, retrying with Chrome TLS fingerprint...") - // Clone request for retry reqCopy := req.Clone(req.Context()) reqCopy.Header.Set("User-Agent", getRandomUserAgent()) - // Retry with uTLS Chrome fingerprint return cloudflareBypassClient.Do(reqCopy) } } @@ -168,11 +164,9 @@ func DoRequestWithCloudflareBypass(req *http.Request) (*http.Response, error) { if tlsRelated { LogDebug("HTTP", "TLS error detected, retrying with Chrome TLS fingerprint: %v", err) - // Clone request for retry reqCopy := req.Clone(req.Context()) reqCopy.Header.Set("User-Agent", getRandomUserAgent()) - // Retry with uTLS Chrome fingerprint return cloudflareBypassClient.Do(reqCopy) } diff --git a/go_backend/idhs.go b/go_backend/idhs.go index 72c8cff3..3b339ed0 100644 --- a/go_backend/idhs.go +++ b/go_backend/idhs.go @@ -22,13 +22,11 @@ var ( idhsRateLimiter = NewRateLimiter(8, time.Minute) // 8 req/min (below 10 limit) ) -// IDHSSearchRequest represents the request body for IDHS API type IDHSSearchRequest struct { Link string `json:"link"` Adapters []string `json:"adapters,omitempty"` } -// IDHSSearchResponse represents the response from IDHS API type IDHSSearchResponse struct { ID string `json:"id"` Type string `json:"type"` // song, album, artist, podcast, show @@ -41,7 +39,6 @@ type IDHSSearchResponse struct { Links []IDHSLink `json:"links"` } -// IDHSLink represents a link to a streaming platform type IDHSLink struct { Type string `json:"type"` // spotify, youTube, appleMusic, deezer, soundCloud, tidal URL string `json:"url"` @@ -49,7 +46,6 @@ type IDHSLink struct { NotAvailable bool `json:"notAvailable,omitempty"` } -// NewIDHSClient creates a new IDHS client func NewIDHSClient() *IDHSClient { idhsClientOnce.Do(func() { globalIDHSClient = &IDHSClient{ @@ -117,7 +113,6 @@ func (c *IDHSClient) Search(link string, adapters []string) (*IDHSSearchResponse func (c *IDHSClient) GetAvailabilityFromSpotify(spotifyTrackID string) (*TrackAvailability, error) { spotifyURL := fmt.Sprintf("https://open.spotify.com/track/%s", spotifyTrackID) - // Request only the platforms we need adapters := []string{"tidal", "deezer"} result, err := c.Search(spotifyURL, adapters) @@ -151,11 +146,9 @@ func (c *IDHSClient) GetAvailabilityFromSpotify(spotifyTrackID string) (*TrackAv return availability, nil } -// GetAvailabilityFromDeezer checks track availability using IDHS func (c *IDHSClient) GetAvailabilityFromDeezer(deezerTrackID string) (*TrackAvailability, error) { deezerURL := fmt.Sprintf("https://www.deezer.com/track/%s", deezerTrackID) - // Request only the platforms we need adapters := []string{"spotify", "tidal"} result, err := c.Search(deezerURL, adapters) diff --git a/go_backend/library_scan.go b/go_backend/library_scan.go index 52e91fb8..d9e53663 100644 --- a/go_backend/library_scan.go +++ b/go_backend/library_scan.go @@ -10,7 +10,6 @@ import ( "time" ) -// LibraryScanResult represents metadata from a scanned audio file type LibraryScanResult struct { ID string `json:"id"` TrackName string `json:"trackName"` @@ -42,7 +41,6 @@ type LibraryScanProgress struct { IsComplete bool `json:"is_complete"` } -// IncrementalScanResult contains results of an incremental library scan type IncrementalScanResult struct { Scanned []LibraryScanResult `json:"scanned"` // New or updated files DeletedPaths []string `json:"deletedPaths"` // Files that no longer exist @@ -216,7 +214,6 @@ func scanAudioFile(filePath, scanTime string) (*LibraryScanResult, error) { Format: strings.TrimPrefix(ext, "."), } - // Get file modification time if info, err := os.Stat(filePath); err == nil { result.FileModTime = info.ModTime().UnixMilli() } @@ -466,7 +463,6 @@ func ScanLibraryFolderIncremental(folderPath, existingFilesJSON string) (string, return "{}", fmt.Errorf("path is not a folder: %s", folderPath) } - // Parse existing files map existingFiles := make(map[string]int64) if existingFilesJSON != "" && existingFilesJSON != "{}" { if err := json.Unmarshal([]byte(existingFilesJSON), &existingFiles); err != nil { @@ -476,12 +472,10 @@ func ScanLibraryFolderIncremental(folderPath, existingFilesJSON string) (string, GoLog("[LibraryScan] Incremental scan starting, %d existing files in database\n", len(existingFiles)) - // Reset progress libraryScanProgressMu.Lock() libraryScanProgress = LibraryScanProgress{} libraryScanProgressMu.Unlock() - // Setup cancellation libraryScanCancelMu.Lock() if libraryScanCancel != nil { close(libraryScanCancel) @@ -490,7 +484,6 @@ func ScanLibraryFolderIncremental(folderPath, existingFilesJSON string) (string, cancelCh := libraryScanCancel libraryScanCancelMu.Unlock() - // Collect all audio files with their mod times currentFiles, err := collectLibraryAudioFiles(folderPath, cancelCh) if err != nil { return "{}", err @@ -512,18 +505,14 @@ func ScanLibraryFolderIncremental(folderPath, existingFilesJSON string) (string, for _, f := range currentFiles { existingModTime, exists := existingFiles[f.path] if !exists { - // New file filesToScan = append(filesToScan, f) } else if f.modTime != existingModTime { - // Modified file filesToScan = append(filesToScan, f) } else { - // Unchanged file - skip skippedCount++ } } - // Find deleted files var deletedPaths []string for existingPath := range existingFiles { if !currentPathSet[existingPath] { @@ -551,7 +540,6 @@ func ScanLibraryFolderIncremental(folderPath, existingFilesJSON string) (string, return string(jsonBytes), nil } - // Scan the files that need scanning results := make([]LibraryScanResult, 0, len(filesToScan)) scanTime := time.Now().UTC().Format(time.RFC3339) errorCount := 0 diff --git a/go_backend/lyrics.go b/go_backend/lyrics.go index 3ec16555..1a6c4771 100644 --- a/go_backend/lyrics.go +++ b/go_backend/lyrics.go @@ -41,7 +41,6 @@ var DefaultLyricsProviders = []string{ LyricsProviderQQMusic, } -// Global lyrics provider configuration var ( lyricsProvidersMu sync.RWMutex lyricsProviders []string // ordered list of enabled providers @@ -598,7 +597,6 @@ func (c *LyricsClient) FetchLyricsAllSources(spotifyID, trackName, artistName st return lyricsHasUsableText(l) } - // Try extension lyrics providers first if len(extensionProviders) > 0 { for _, provider := range extensionProviders { GoLog("[Lyrics] Trying extension lyrics provider: %s\n", provider.extension.ID) @@ -621,7 +619,6 @@ func (c *LyricsClient) FetchLyricsAllSources(spotifyID, trackName, artistName st return &cachedCopy, nil } - // Get configured provider order providerOrder := GetLyricsProviderOrder() simplifiedTrack := simplifyTrackName(trackName) diff --git a/go_backend/lyrics_apple.go b/go_backend/lyrics_apple.go index 957db6fc..538b0b89 100644 --- a/go_backend/lyrics_apple.go +++ b/go_backend/lyrics_apple.go @@ -97,7 +97,6 @@ func (m *appleTokenManager) clearToken() { m.token = "" } -// Apple Music API response models type appleMusicSearchResponse struct { Results struct { Songs *struct { @@ -239,15 +238,12 @@ func (c *AppleMusicClient) FetchLyricsByID(songID string) (string, error) { return bodyStr, nil } -// formatPaxLyricsToLRC converts a pax proxy response to standard LRC format. func formatPaxLyricsToLRC(rawJSON string, multiPersonWordByWord bool) (string, error) { - // Try to parse as PaxResponse first var paxResp paxResponse if err := json.Unmarshal([]byte(rawJSON), &paxResp); err == nil && paxResp.Content != nil { return formatPaxContent(paxResp.Type, paxResp.Content, multiPersonWordByWord), nil } - // Try to parse as a direct list of PaxLyrics var directLyrics []paxLyrics if err := json.Unmarshal([]byte(rawJSON), &directLyrics); err == nil && len(directLyrics) > 0 { return formatPaxContent("Syllable", directLyrics, multiPersonWordByWord), nil diff --git a/go_backend/lyrics_musixmatch.go b/go_backend/lyrics_musixmatch.go index 71d4544e..f962a110 100644 --- a/go_backend/lyrics_musixmatch.go +++ b/go_backend/lyrics_musixmatch.go @@ -16,7 +16,6 @@ type MusixmatchClient struct { baseURL string } -// Musixmatch proxy response models type musixmatchSearchResponse struct { ID int64 `json:"id"` SongName string `json:"songName"` @@ -116,7 +115,6 @@ func (c *MusixmatchClient) FetchLyricsInLanguage(songID int64, language string) return nil, fmt.Errorf("failed to decode musixmatch language response: %w", err) } - // Prefer synced lyrics for selected language if result.SyncedLyrics != nil && strings.TrimSpace(result.SyncedLyrics.Lyrics) != "" { lines := parseSyncedLyrics(result.SyncedLyrics.Lyrics) if len(lines) > 0 { @@ -129,7 +127,6 @@ func (c *MusixmatchClient) FetchLyricsInLanguage(songID int64, language string) } } - // Fall back to unsynced lyrics for selected language if result.UnsyncedLyrics != nil && strings.TrimSpace(result.UnsyncedLyrics.Lyrics) != "" { lines := plainTextLyricsLines(result.UnsyncedLyrics.Lyrics) @@ -162,7 +159,6 @@ func (c *MusixmatchClient) FetchLyrics(trackName, artistName string, durationSec GoLog("[Musixmatch] Language override '%s' failed: %v\n", preferred, localizedErr) } - // Prefer synced lyrics if result.SyncedLyrics != nil && strings.TrimSpace(result.SyncedLyrics.Lyrics) != "" { lines := parseSyncedLyrics(result.SyncedLyrics.Lyrics) if len(lines) > 0 { @@ -175,7 +171,6 @@ func (c *MusixmatchClient) FetchLyrics(trackName, artistName string, durationSec } } - // Fall back to unsynced lyrics if result.UnsyncedLyrics != nil && strings.TrimSpace(result.UnsyncedLyrics.Lyrics) != "" { lines := plainTextLyricsLines(result.UnsyncedLyrics.Lyrics) diff --git a/go_backend/lyrics_netease.go b/go_backend/lyrics_netease.go index e9fbf1e6..f6ce6b6c 100644 --- a/go_backend/lyrics_netease.go +++ b/go_backend/lyrics_netease.go @@ -15,7 +15,6 @@ type NeteaseClient struct { httpClient *http.Client } -// Netease API response models type neteaseSearchResponse struct { Result struct { Songs []struct { @@ -172,7 +171,6 @@ func (c *NeteaseClient) FetchLyrics( return nil, err } - // Parse the LRC text into LyricsResponse lines := parseSyncedLyrics(lrcText) if len(lines) == 0 { // May be plain text lyrics without timestamps diff --git a/go_backend/lyrics_qqmusic.go b/go_backend/lyrics_qqmusic.go index 0b76a49f..dde3631d 100644 --- a/go_backend/lyrics_qqmusic.go +++ b/go_backend/lyrics_qqmusic.go @@ -17,7 +17,6 @@ type QQMusicClient struct { httpClient *http.Client } -// QQ Music search response models type qqMusicSearchResponse struct { Data struct { Song struct { @@ -184,7 +183,6 @@ func (c *QQMusicClient) FetchLyrics( }, nil } - // Fall back to plain text resultLines := plainTextLyricsLines(lrcText) if len(resultLines) > 0 { diff --git a/go_backend/mobile_deps.go b/go_backend/mobile_deps.go index bbdb1890..57aaaeec 100644 --- a/go_backend/mobile_deps.go +++ b/go_backend/mobile_deps.go @@ -1,10 +1,8 @@ -// mobile_deps.go // This file ensures gomobile dependencies are not removed by go mod tidy. // These packages are required by gomobile bind but not directly imported in code. package gobackend import ( - // Required for gomobile bind to work _ "golang.org/x/mobile/bind" ) diff --git a/go_backend/parallel.go b/go_backend/parallel.go index b275ade9..2526e139 100644 --- a/go_backend/parallel.go +++ b/go_backend/parallel.go @@ -10,7 +10,6 @@ import ( type TrackIDCacheEntry struct { TidalTrackID int64 QobuzTrackID int64 - AmazonURL string ExpiresAt time.Time } @@ -107,25 +106,6 @@ func (c *TrackIDCache) SetQobuz(isrc string, trackID int64) { } } -func (c *TrackIDCache) SetAmazonURL(isrc string, amazonURL string) { - c.mu.Lock() - defer c.mu.Unlock() - - entry, exists := c.cache[isrc] - if !exists { - entry = &TrackIDCacheEntry{} - c.cache[isrc] = entry - } - entry.AmazonURL = amazonURL - now := time.Now() - entry.ExpiresAt = now.Add(c.ttl) - - if c.cleanupInterval > 0 && (c.lastCleanup.IsZero() || now.Sub(c.lastCleanup) >= c.cleanupInterval) { - c.pruneExpiredLocked(now) - c.lastCleanup = now - } -} - func (c *TrackIDCache) Clear() { c.mu.Lock() defer c.mu.Unlock() @@ -235,8 +215,6 @@ func PreWarmTrackCache(requests []PreWarmCacheRequest) { preWarmTidalCache(r.ISRC, r.TrackName, r.ArtistName) case "qobuz": preWarmQobuzCache(r.ISRC, r.SpotifyID) - case "amazon": - preWarmAmazonCache(r.ISRC, r.SpotifyID) } }(req) } @@ -256,12 +234,10 @@ func preWarmTidalCache(isrc, _, _ string) { // 1. From SongLink (fast, no Qobuz API call needed) // 2. Direct ISRC search on Qobuz API (slower, may fail if ISRC not in Qobuz database) func preWarmQobuzCache(isrc, spotifyID string) { - // First, try to get QobuzID from SongLink - this is faster and more reliable if spotifyID != "" { client := NewSongLinkClient() availability, err := client.CheckTrackAvailability(spotifyID, isrc) if err == nil && availability != nil && availability.QobuzID != "" { - // Parse QobuzID to int64 var trackID int64 if _, parseErr := fmt.Sscanf(availability.QobuzID, "%d", &trackID); parseErr == nil && trackID > 0 { GoLog("[Qobuz] Pre-warm cache: Got Qobuz ID %d from SongLink for ISRC %s\n", trackID, isrc) @@ -271,7 +247,6 @@ func preWarmQobuzCache(isrc, spotifyID string) { } } - // Fallback: Direct ISRC search on Qobuz API downloader := NewQobuzDownloader() track, err := downloader.SearchTrackByISRC(isrc) if err == nil && track != nil { @@ -280,14 +255,6 @@ func preWarmQobuzCache(isrc, spotifyID string) { } } -func preWarmAmazonCache(isrc, spotifyID string) { - client := NewSongLinkClient() - availability, err := client.CheckTrackAvailability(spotifyID, isrc) - if err == nil && availability != nil && availability.AmazonURL != "" { - GetTrackIDCache().SetAmazonURL(isrc, availability.AmazonURL) - } -} - func PreWarmCache(tracksJSON string) error { var tracks []struct { ISRC string `json:"isrc"` diff --git a/go_backend/qobuz.go b/go_backend/qobuz.go index 015dca8d..092c0e96 100644 --- a/go_backend/qobuz.go +++ b/go_backend/qobuz.go @@ -923,18 +923,14 @@ type qobuzAPIResult struct { duration time.Duration } -// Qobuz API timeout configuration // Mobile networks are more unstable, so we use longer timeouts const ( qobuzAPITimeoutMobile = 25 * time.Second - qobuzMaxRetries = 2 // Number of retries per API + qobuzMaxRetries = 2 qobuzRetryDelay = 500 * time.Millisecond ) -// getQobuzAPITimeout returns appropriate timeout based on platform -// For mobile (gomobile builds), we use longer timeouts func getQobuzAPITimeout() time.Duration { - // Since this runs in gomobile context, we always use mobile timeout // The Go backend is only used on mobile (Android/iOS) return qobuzAPITimeoutMobile } @@ -944,7 +940,6 @@ func fetchQobuzURLWithRetry(provider qobuzAPIProvider, trackID int64, quality st return fetchQobuzURLSingleAttempt(provider, trackID, quality, timeout, "") } -// fetchQobuzURLSingleAttempt fetches download URL with retry logic for a single API+country combination func fetchQobuzURLSingleAttempt(provider qobuzAPIProvider, trackID int64, quality string, timeout time.Duration, country string) (qobuzDownloadInfo, error) { var lastErr error retryDelay := qobuzRetryDelay @@ -967,7 +962,7 @@ func fetchQobuzURLSingleAttempt(provider qobuzAPIProvider, trackID int64, qualit if attempt > 0 { GoLog("[Qobuz] Retry %d/%d for %s after %v\n", attempt, qobuzMaxRetries, provider.Name, retryDelay) time.Sleep(retryDelay) - retryDelay *= 2 // Exponential backoff + retryDelay *= 2 } client := NewHTTPClientWithTimeout(timeout) @@ -1014,11 +1009,10 @@ func fetchQobuzURLSingleAttempt(provider qobuzAPIProvider, trackID int64, qualit strings.Contains(errStr, "reset") || strings.Contains(errStr, "connection refused") || strings.Contains(errStr, "eof") { - continue // Retry + continue } - break // Non-retryable error + break } - // Server errors are retryable if resp.StatusCode >= 500 { io.Copy(io.Discard, resp.Body) resp.Body.Close() @@ -1031,7 +1025,7 @@ func fetchQobuzURLSingleAttempt(provider qobuzAPIProvider, trackID int64, qualit io.Copy(io.Discard, resp.Body) resp.Body.Close() lastErr = fmt.Errorf("rate limited") - retryDelay = 2 * time.Second // Wait longer for rate limit + retryDelay = 2 * time.Second continue } @@ -1308,7 +1302,6 @@ func resolveQobuzTrackForRequest(req DownloadRequest, downloader *QobuzDownloade track = nil } else if track != nil { GoLog("[%s] Successfully found track via SongLink ID: '%s' by '%s'\n", logPrefix, track.Title, track.Performer.Name) - // Cache for future use if req.ISRC != "" { GetTrackIDCache().SetQobuz(req.ISRC, track.ID) } diff --git a/go_backend/ratelimit.go b/go_backend/ratelimit.go index 1f2ac1f6..662d86e4 100644 --- a/go_backend/ratelimit.go +++ b/go_backend/ratelimit.go @@ -48,7 +48,6 @@ func (r *RateLimiter) WaitForSlot() { r.timestamps = append(r.timestamps, time.Now()) } -// cleanOldTimestamps removes timestamps that are outside the current window func (r *RateLimiter) cleanOldTimestamps(now time.Time) { cutoff := now.Add(-r.window) validStart := 0 diff --git a/go_backend/romaji.go b/go_backend/romaji.go index d5a73963..3c45d2d9 100644 --- a/go_backend/romaji.go +++ b/go_backend/romaji.go @@ -170,11 +170,9 @@ func JapaneseToRomaji(text string) string { } func BuildSearchQuery(trackName, artistName string) string { - // Convert Japanese to romaji trackRomaji := JapaneseToRomaji(trackName) artistRomaji := JapaneseToRomaji(artistName) - // Clean up the query - remove special characters that might interfere with search trackClean := cleanSearchQuery(trackRomaji) artistClean := cleanSearchQuery(artistRomaji) @@ -196,16 +194,13 @@ func cleanSearchQuery(s string) string { func CleanToASCII(s string) string { var result strings.Builder for _, r := range s { - // Keep only ASCII letters, numbers, spaces, and basic punctuation if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == ' ' || r == '-' || r == '\'' { result.WriteRune(r) } else if r == ',' || r == '.' { - // Convert punctuation to space result.WriteRune(' ') } } - // Clean up multiple spaces cleaned := strings.Join(strings.Fields(result.String()), " ") return strings.TrimSpace(cleaned) } diff --git a/go_backend/songlink.go b/go_backend/songlink.go index b5dfd58d..f38a8edb 100644 --- a/go_backend/songlink.go +++ b/go_backend/songlink.go @@ -291,7 +291,7 @@ func extractDeezerIDFromURL(deezerURL string) string { return "" } -// extractQobuzIDFromURL extracts Qobuz track ID from URL +// extractQobuzIDFromURL extracts Qobuz track ID from URL. // URL formats: // - https://www.qobuz.com/us-en/album/.../12345678 (album page with track highlight) // - https://open.qobuz.com/track/12345678 @@ -302,29 +302,24 @@ func extractQobuzIDFromURL(qobuzURL string) string { return "" } - // Try to find /track/ID pattern first if strings.Contains(qobuzURL, "/track/") { parts := strings.Split(qobuzURL, "/track/") if len(parts) > 1 { idPart := parts[1] - // Remove query parameters if idx := strings.Index(idPart, "?"); idx > 0 { idPart = idPart[:idx] } - // Remove trailing slash or path if idx := strings.Index(idPart, "/"); idx > 0 { idPart = idPart[:idx] } idPart = strings.TrimSpace(idPart) - // Validate it's a number if idPart != "" && isNumeric(idPart) { return idPart } } } - // Try to extract from album URL with track highlight - // Format: /album/albumname/trackid or ?trackId=12345678 + // Try to extract from album URL with track highlight (e.g. ?trackId=12345678) if strings.Contains(qobuzURL, "trackId=") { parts := strings.Split(qobuzURL, "trackId=") if len(parts) > 1 { @@ -343,7 +338,6 @@ func extractQobuzIDFromURL(qobuzURL string) string { parts := strings.Split(qobuzURL, "/") for i := len(parts) - 1; i >= 0; i-- { part := parts[i] - // Remove query parameters if idx := strings.Index(part, "?"); idx > 0 { part = part[:idx] } @@ -386,7 +380,6 @@ func extractYouTubeIDFromURL(youtubeURL string) string { return "" } - // Handle youtu.be short URLs if strings.Contains(youtubeURL, "youtu.be/") { parts := strings.Split(youtubeURL, "youtu.be/") if len(parts) >= 2 { @@ -401,7 +394,6 @@ func extractYouTubeIDFromURL(youtubeURL string) string { } } - // Handle youtube.com URLs with ?v= parameter parsed, err := url.Parse(youtubeURL) if err != nil { return "" @@ -411,7 +403,6 @@ func extractYouTubeIDFromURL(youtubeURL string) string { return v } - // Handle /embed/ format if strings.Contains(parsed.Path, "/embed/") { parts := strings.Split(parsed.Path, "/embed/") if len(parts) >= 2 { @@ -540,7 +531,6 @@ func (s *SongLinkClient) CheckAvailabilityFromDeezer(deezerTrackID string) (*Tra return availability, nil } -// checkAvailabilityFromDeezerSongLink is the original SongLink implementation for Deezer func (s *SongLinkClient) checkAvailabilityFromDeezerSongLink(deezerTrackID string) (*TrackAvailability, error) { songLinkRateLimiter.WaitForSlot() diff --git a/go_backend/tidal.go b/go_backend/tidal.go index 22fd2377..ea9a8886 100644 --- a/go_backend/tidal.go +++ b/go_backend/tidal.go @@ -103,7 +103,7 @@ type MPD struct { func NewTidalDownloader() *TidalDownloader { tidalDownloaderOnce.Do(func() { globalTidalDownloader = &TidalDownloader{ - client: NewHTTPClientWithTimeout(DefaultTimeout), // 60s timeout + client: NewHTTPClientWithTimeout(DefaultTimeout), } apis := globalTidalDownloader.GetAvailableAPIs() @@ -116,7 +116,7 @@ func NewTidalDownloader() *TidalDownloader { func (t *TidalDownloader) GetAvailableAPIs() []string { return []string{ - "https://tidal-api.binimum.org", // priority + "https://tidal-api.binimum.org", "https://tidal.kinoplus.online", "https://triton.squid.wtf", "https://vogel.qqdl.site", @@ -195,7 +195,6 @@ func (t *TidalDownloader) SearchTrackByISRC(isrc string) (*TidalTrack, error) { return nil, fmt.Errorf("tidal ISRC search API disabled: no client credentials mode") } -// Now includes romaji conversion for Japanese text (4 search strategies like PC) func (t *TidalDownloader) SearchTrackByMetadataWithISRC(trackName, artistName, albumName, spotifyISRC string, expectedDuration int) (*TidalTrack, error) { return nil, fmt.Errorf("tidal metadata search API disabled: no client credentials mode") } @@ -204,7 +203,6 @@ func (t *TidalDownloader) SearchTrackByMetadata(trackName, artistName string) (* return nil, fmt.Errorf("tidal metadata search API disabled: no client credentials mode") } -// TidalDownloadInfo contains download URL and quality info type TidalDownloadInfo struct { URL string BitDepth int @@ -218,15 +216,13 @@ type tidalAPIResult struct { duration time.Duration } -// Tidal API timeout configuration // Mobile networks are more unstable, so we use longer timeouts const ( tidalAPITimeoutMobile = 25 * time.Second - tidalMaxRetries = 2 // Number of retries per API + tidalMaxRetries = 2 tidalRetryDelay = 500 * time.Millisecond ) -// fetchTidalURLWithRetry fetches download URL from a single Tidal API with retry logic func fetchTidalURLWithRetry(api string, trackID int64, quality string, timeout time.Duration) (TidalDownloadInfo, error) { var lastErr error retryDelay := tidalRetryDelay @@ -235,7 +231,7 @@ func fetchTidalURLWithRetry(api string, trackID int64, quality string, timeout t if attempt > 0 { GoLog("[Tidal] Retry %d/%d for %s after %v\n", attempt, tidalMaxRetries, api, retryDelay) time.Sleep(retryDelay) - retryDelay *= 2 // Exponential backoff + retryDelay *= 2 } client := NewHTTPClientWithTimeout(timeout) @@ -250,17 +246,15 @@ func fetchTidalURLWithRetry(api string, trackID int64, quality string, timeout t resp, err := client.Do(req) if err != nil { lastErr = err - // Check for retryable errors (timeout, connection reset) errStr := strings.ToLower(err.Error()) if strings.Contains(errStr, "timeout") || strings.Contains(errStr, "reset") || strings.Contains(errStr, "connection refused") || strings.Contains(errStr, "eof") { - continue // Retry + continue } - break // Non-retryable error + break } - // Server errors are retryable if resp.StatusCode >= 500 { io.Copy(io.Discard, resp.Body) resp.Body.Close() @@ -273,7 +267,7 @@ func fetchTidalURLWithRetry(api string, trackID int64, quality string, timeout t io.Copy(io.Discard, resp.Body) resp.Body.Close() lastErr = fmt.Errorf("rate limited") - retryDelay = 2 * time.Second // Wait longer for rate limit + retryDelay = 2 * time.Second continue } diff --git a/go_backend/youtube.go b/go_backend/youtube.go index bfbedcbb..e43d0e39 100644 --- a/go_backend/youtube.go +++ b/go_backend/youtube.go @@ -1,4 +1,3 @@ -// Package gobackend - YouTube download via Cobalt API (lossy-only provider) package gobackend import ( @@ -161,7 +160,6 @@ func parseYouTubeQualityInput(raw string) (format string, bitrate int, normalize } } -// SearchYouTube returns a YouTube Music search URL for the given track func (y *YouTubeDownloader) SearchYouTube(trackName, artistName string) (string, error) { query := fmt.Sprintf("%s %s", artistName, trackName) searchQuery := url.QueryEscape(query) @@ -213,7 +211,6 @@ func (y *YouTubeDownloader) GetDownloadURL(youtubeURL string, quality YouTubeQua return resp, nil } -// requestCobaltDirect sends a download request to the primary Cobalt API. func (y *YouTubeDownloader) requestCobaltDirect(videoURL, audioFormat, audioBitrate string) (*CobaltResponse, error) { reqBody := CobaltRequest{ URL: videoURL, @@ -470,7 +467,6 @@ func BuildYouTubeWatchURL(videoID string) string { return fmt.Sprintf("https://music.youtube.com/watch?v=%s", videoID) } -// isYouTubeVideoID checks if s is an 11-char YouTube video ID func isYouTubeVideoID(s string) bool { if len(s) != 11 { return false @@ -707,7 +703,6 @@ func downloadFromYouTube(req DownloadRequest) (YouTubeDownloadResult, error) { GoLog("[YouTube] Downloading to: %s\n", outputPath) - // Parallel fetch cover art + lyrics var parallelResult *ParallelDownloadResult if req.EmbedLyrics || req.CoverURL != "" { GoLog("[YouTube] Starting parallel fetch for cover and lyrics...\n") diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 8dfaa378..f3c93676 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -492,13 +492,6 @@ import Gobackend // Import Go framework if let error = error { throw error } return response - case "getAmazonURLFromDeezerTrack": - let args = call.arguments as! [String: Any] - let deezerTrackId = args["deezer_track_id"] as! String - let response = GobackendGetAmazonURLFromDeezerTrack(deezerTrackId, &error) - if let error = error { throw error } - return response - case "preWarmTrackCache": let args = call.arguments as! [String: Any] let tracksJson = args["tracks"] as! String diff --git a/lib/app.dart b/lib/app.dart index 981c3bae..cf0ec25b 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -17,7 +17,6 @@ final _routerProvider = Provider((ref) { settingsProvider.select((s) => s.hasCompletedTutorial), ); - // Determine initial location based on app state String initialLocation; if (isFirstLaunch) { initialLocation = '/setup'; diff --git a/lib/constants/app_info.dart b/lib/constants/app_info.dart index 48a1c1e2..d423fac6 100644 --- a/lib/constants/app_info.dart +++ b/lib/constants/app_info.dart @@ -1,8 +1,8 @@ /// App version and info constants /// Update version here only - all other files will reference this class AppInfo { - static const String version = '3.7.1'; - static const String buildNumber = '104'; + static const String version = '3.7.2'; + static const String buildNumber = '105'; static const String fullVersion = '$version+$buildNumber'; diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index b321656f..822982eb 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -763,7 +763,7 @@ abstract class AppLocalizations { /// App description in header card /// /// In en, this message translates to: - /// **'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'** + /// **'Download Spotify tracks in lossless quality from Tidal and Qobuz.'** String get aboutAppDescription; /// Section header for artist albums @@ -1576,7 +1576,7 @@ abstract class AppLocalizations { /// **'If a track is not available on the first provider, the app will automatically try the next one.'** String get providerPriorityInfo; - /// Label for built-in providers (Tidal/Qobuz/Amazon) + /// Label for built-in providers (Tidal/Qobuz) /// /// In en, this message translates to: /// **'Built-in'** @@ -3271,7 +3271,7 @@ abstract class AppLocalizations { /// Tutorial welcome tip 2 /// /// In en, this message translates to: - /// **'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'** + /// **'Get FLAC quality audio from Tidal, Qobuz, or Deezer'** String get tutorialWelcomeTip2; /// Tutorial welcome tip 3 diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 2b846be4..af8f7b73 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -365,7 +365,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get aboutAppDescription => - 'Lade Spotify-Titel in verlustfreier Qualität von Tidal, Qobuz und Amazon Music herunter.'; + 'Lade Spotify-Titel in verlustfreier Qualität von Tidal und Qobuz herunter.'; @override String get artistAlbums => 'Alben'; @@ -1826,7 +1826,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + 'FLAC-Qualität von Tidal, Qobuz oder Deezer'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 8f244a7f..c98cf0ee 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -356,7 +356,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; @override String get artistAlbums => 'Albums'; @@ -1809,7 +1809,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + 'Get FLAC quality audio from Tidal, Qobuz, or Deezer'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 58ae25af..efc7a016 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -356,7 +356,7 @@ class AppLocalizationsEs extends AppLocalizations { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; @override String get artistAlbums => 'Albums'; @@ -1809,7 +1809,7 @@ class AppLocalizationsEs extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + 'Get FLAC quality audio from Tidal, Qobuz, or Deezer'; @override String get tutorialWelcomeTip3 => @@ -2705,7 +2705,7 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get aboutAppDescription => - 'Descarga pistas de Spotify con calidad sin pérdida de Tidal, Qobuz y Amazon Music.'; + 'Descarga pistas de Spotify con calidad sin pérdida de Tidal y Qobuz.'; @override String get artistAlbums => 'Álbumes'; @@ -4150,7 +4150,7 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + 'Obtén audio en calidad FLAC de Tidal, Qobuz o Deezer'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index adf0d48d..89d94355 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -358,7 +358,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; @override String get artistAlbums => 'Albums'; @@ -1811,7 +1811,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + 'Audio en qualité FLAC depuis Tidal, Qobuz ou Deezer'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index 50ca83db..5b6ff05f 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -356,7 +356,7 @@ class AppLocalizationsHi extends AppLocalizations { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; @override String get artistAlbums => 'Albums'; @@ -1809,7 +1809,7 @@ class AppLocalizationsHi extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + 'Tidal, Qobuz, या Deezer से FLAC गुणवत्ता ऑडियो प्राप्त करें'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index ff3af0fa..4312a41a 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -359,7 +359,7 @@ class AppLocalizationsId extends AppLocalizations { @override String get aboutAppDescription => - 'Unduh lagu Spotify dalam kualitas lossless dari Tidal, Qobuz, dan Amazon Music.'; + 'Unduh lagu Spotify dalam kualitas lossless dari Tidal dan Qobuz.'; @override String get artistAlbums => 'Album'; @@ -1816,7 +1816,7 @@ class AppLocalizationsId extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + 'Dapatkan audio kualitas FLAC dari Tidal, Qobuz, atau Deezer'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index b473562c..9aa5a2fb 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -352,7 +352,7 @@ class AppLocalizationsJa extends AppLocalizations { @override String get aboutAppDescription => - 'Tidal、Qobuz、Amazon Music から Spotify のトラックをロスレス品質でダウンロードします。'; + 'Tidal、Qobuz から Spotify のトラックをロスレス品質でダウンロードします。'; @override String get artistAlbums => 'アルバム'; @@ -1795,8 +1795,7 @@ class AppLocalizationsJa extends AppLocalizations { 'Download music from Spotify, Deezer, or paste any supported URL'; @override - String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + String get tutorialWelcomeTip2 => 'Tidal、Qobuz、Deezer から FLAC 品質のオーディオを取得'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index f94d1f3f..3796e499 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -355,7 +355,7 @@ class AppLocalizationsKo extends AppLocalizations { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; @override String get artistAlbums => 'Albums'; @@ -1807,8 +1807,7 @@ class AppLocalizationsKo extends AppLocalizations { 'Download music from Spotify, Deezer, or paste any supported URL'; @override - String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + String get tutorialWelcomeTip2 => 'Tidal, Qobuz 또는 Deezer에서 FLAC 품질 오디오 받기'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index be1be644..81ecfd12 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -356,7 +356,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; @override String get artistAlbums => 'Albums'; @@ -1809,7 +1809,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + 'Krijg FLAC-kwaliteit audio van Tidal, Qobuz of Deezer'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 499adcbb..24b813f3 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -356,7 +356,7 @@ class AppLocalizationsPt extends AppLocalizations { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; @override String get artistAlbums => 'Albums'; @@ -1809,7 +1809,7 @@ class AppLocalizationsPt extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + 'Get FLAC quality audio from Tidal, Qobuz, or Deezer'; @override String get tutorialWelcomeTip3 => @@ -2705,7 +2705,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get aboutAppDescription => - 'Baixe faixas do Spotify em qualidade sem perdas do Tidal, Qobuz e Amazon Music.'; + 'Baixe faixas do Spotify em qualidade sem perdas do Tidal e Qobuz.'; @override String get artistAlbums => 'Álbuns'; @@ -4147,7 +4147,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + 'Obtenha áudio em qualidade FLAC do Tidal, Qobuz ou Deezer'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 85af897d..77913294 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -363,7 +363,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get aboutAppDescription => - 'Скачайте треки Spotify в Lossless качестве из Tidal, Qobuz и Amazon Music.'; + 'Скачайте треки Spotify в Lossless качестве из Tidal и Qobuz.'; @override String get artistAlbums => 'Альбомы'; @@ -1855,8 +1855,7 @@ class AppLocalizationsRu extends AppLocalizations { 'Скачивайте музыку из Spotify, Deezer, или вставьте любой поддерживаемый URL'; @override - String get tutorialWelcomeTip2 => - 'Скачайте FLAC с Tidal, Qobuz или Amazon Music'; + String get tutorialWelcomeTip2 => 'Скачайте FLAC с Tidal, Qobuz или Deezer'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index bcc77961..53034492 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -361,7 +361,7 @@ class AppLocalizationsTr extends AppLocalizations { @override String get aboutAppDescription => - 'Spotify şarkılarını Tidal, Qobuz ve Amazon Music\'den yüksek kalitede indir.'; + 'Spotify şarkılarını Tidal ve Qobuz\'den yüksek kalitede indir.'; @override String get artistAlbums => 'Albümler'; @@ -1821,7 +1821,7 @@ class AppLocalizationsTr extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + 'Tidal, Qobuz veya Deezer\'den FLAC kalitesinde ses alın'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 433e771b..a4fc7e77 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -356,7 +356,7 @@ class AppLocalizationsZh extends AppLocalizations { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; @override String get artistAlbums => 'Albums'; @@ -1809,7 +1809,7 @@ class AppLocalizationsZh extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + 'Get FLAC quality audio from Tidal, Qobuz, or Deezer'; @override String get tutorialWelcomeTip3 => @@ -2696,7 +2696,7 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; @override String get artistAlbums => 'Albums'; @@ -4124,8 +4124,7 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { 'Download music from Spotify, Deezer, or paste any supported URL'; @override - String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + String get tutorialWelcomeTip2 => '从 Tidal、Qobuz 或 Deezer 获取 FLAC 品质音频'; @override String get tutorialWelcomeTip3 => @@ -4808,7 +4807,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; @override String get artistAlbums => 'Albums'; @@ -6236,8 +6235,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { 'Download music from Spotify, Deezer, or paste any supported URL'; @override - String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + String get tutorialWelcomeTip2 => '從 Tidal、Qobuz 或 Deezer 取得 FLAC 品質音訊'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index b40c1f71..5e37729d 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Lade Spotify-Titel in verlustfreier Qualität von Tidal, Qobuz und Amazon Music herunter.", + "aboutAppDescription": "Lade Spotify-Titel in verlustfreier Qualität von Tidal und Qobuz herunter.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1089,7 +1089,7 @@ }, "providerBuiltIn": "Integriert", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Erweiterung", "@providerExtension": { @@ -2358,7 +2358,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "FLAC-Qualität von Tidal, Qobuz oder Deezer", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index ca416fa0..7eee4649 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", + "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1097,7 +1097,7 @@ }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Extension", "@providerExtension": { @@ -2383,7 +2383,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Deezer", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/l10n/arb/app_es.arb b/lib/l10n/arb/app_es.arb index 497c11bc..45733fc8 100644 --- a/lib/l10n/arb/app_es.arb +++ b/lib/l10n/arb/app_es.arb @@ -402,7 +402,7 @@ "@aboutDabMusicDesc": { "description": "Credit for DAB Music API" }, - "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", + "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1005,7 +1005,7 @@ }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Extension", "@providerExtension": { diff --git a/lib/l10n/arb/app_es_ES.arb b/lib/l10n/arb/app_es_ES.arb index 304b9cd8..f7d481c3 100644 --- a/lib/l10n/arb/app_es_ES.arb +++ b/lib/l10n/arb/app_es_ES.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Descarga pistas de Spotify con calidad sin pérdida de Tidal, Qobuz y Amazon Music.", + "aboutAppDescription": "Descarga pistas de Spotify con calidad sin pérdida de Tidal y Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1089,7 +1089,7 @@ }, "providerBuiltIn": "Integrado", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Extensión", "@providerExtension": { @@ -2358,7 +2358,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "Obtén audio en calidad FLAC de Tidal, Qobuz o Deezer", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index f7e9c2d1..83bd7f3f 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", + "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1089,7 +1089,7 @@ }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Extension", "@providerExtension": { @@ -2358,7 +2358,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "Audio en qualité FLAC depuis Tidal, Qobuz ou Deezer", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/l10n/arb/app_hi.arb b/lib/l10n/arb/app_hi.arb index 064202f2..b7d2f1e5 100644 --- a/lib/l10n/arb/app_hi.arb +++ b/lib/l10n/arb/app_hi.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", + "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1089,7 +1089,7 @@ }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Extension", "@providerExtension": { @@ -2358,7 +2358,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "Tidal, Qobuz, या Deezer से FLAC गुणवत्ता ऑडियो प्राप्त करें", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index 0d7caf67..c6f8a7ac 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Unduh lagu Spotify dalam kualitas lossless dari Tidal, Qobuz, dan Amazon Music.", + "aboutAppDescription": "Unduh lagu Spotify dalam kualitas lossless dari Tidal dan Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1097,7 +1097,7 @@ }, "providerBuiltIn": "Bawaan", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Ekstensi", "@providerExtension": { @@ -2383,7 +2383,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "Dapatkan audio kualitas FLAC dari Tidal, Qobuz, atau Deezer", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/l10n/arb/app_ja.arb b/lib/l10n/arb/app_ja.arb index b23b0de3..e3498509 100644 --- a/lib/l10n/arb/app_ja.arb +++ b/lib/l10n/arb/app_ja.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Tidal、Qobuz、Amazon Music から Spotify のトラックをロスレス品質でダウンロードします。", + "aboutAppDescription": "Tidal、Qobuz から Spotify のトラックをロスレス品質でダウンロードします。", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1089,7 +1089,7 @@ }, "providerBuiltIn": "内蔵", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "拡張", "@providerExtension": { @@ -2358,7 +2358,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "Tidal、Qobuz、Deezer から FLAC 品質のオーディオを取得", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/l10n/arb/app_ko.arb b/lib/l10n/arb/app_ko.arb index 22b488a3..0c2e172c 100644 --- a/lib/l10n/arb/app_ko.arb +++ b/lib/l10n/arb/app_ko.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", + "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1089,7 +1089,7 @@ }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Extension", "@providerExtension": { @@ -2358,7 +2358,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "Tidal, Qobuz 또는 Deezer에서 FLAC 품질 오디오 받기", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/l10n/arb/app_nl.arb b/lib/l10n/arb/app_nl.arb index 1fd0bec6..7699c63c 100644 --- a/lib/l10n/arb/app_nl.arb +++ b/lib/l10n/arb/app_nl.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", + "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1089,7 +1089,7 @@ }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Extension", "@providerExtension": { @@ -2358,7 +2358,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "Krijg FLAC-kwaliteit audio van Tidal, Qobuz of Deezer", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/l10n/arb/app_pt.arb b/lib/l10n/arb/app_pt.arb index 503247dd..27a0b5e2 100644 --- a/lib/l10n/arb/app_pt.arb +++ b/lib/l10n/arb/app_pt.arb @@ -402,7 +402,7 @@ "@aboutDabMusicDesc": { "description": "Credit for DAB Music API" }, - "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", + "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1005,7 +1005,7 @@ }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Extension", "@providerExtension": { diff --git a/lib/l10n/arb/app_pt_PT.arb b/lib/l10n/arb/app_pt_PT.arb index 8d844396..73190e72 100644 --- a/lib/l10n/arb/app_pt_PT.arb +++ b/lib/l10n/arb/app_pt_PT.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Baixe faixas do Spotify em qualidade sem perdas do Tidal, Qobuz e Amazon Music.", + "aboutAppDescription": "Baixe faixas do Spotify em qualidade sem perdas do Tidal e Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1089,7 +1089,7 @@ }, "providerBuiltIn": "Embutido", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Extensão", "@providerExtension": { @@ -2358,7 +2358,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "Obtenha áudio em qualidade FLAC do Tidal, Qobuz ou Deezer", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 4ea4e618..c68667ff 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Скачайте треки Spotify в Lossless качестве из Tidal, Qobuz и Amazon Music.", + "aboutAppDescription": "Скачайте треки Spotify в Lossless качестве из Tidal и Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1089,7 +1089,7 @@ }, "providerBuiltIn": "Встроенные", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Расширение", "@providerExtension": { @@ -2358,7 +2358,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Скачайте FLAC с Tidal, Qobuz или Amazon Music", + "tutorialWelcomeTip2": "Скачайте FLAC с Tidal, Qobuz или Deezer", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/l10n/arb/app_tr.arb b/lib/l10n/arb/app_tr.arb index b7b16c7f..7b808ec4 100644 --- a/lib/l10n/arb/app_tr.arb +++ b/lib/l10n/arb/app_tr.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Spotify şarkılarını Tidal, Qobuz ve Amazon Music'den yüksek kalitede indir.", + "aboutAppDescription": "Spotify şarkılarını Tidal ve Qobuz'den yüksek kalitede indir.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1089,7 +1089,7 @@ }, "providerBuiltIn": "Dahili", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Eklenti", "@providerExtension": { @@ -2358,7 +2358,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "Tidal, Qobuz veya Deezer'den FLAC kalitesinde ses alın", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/l10n/arb/app_zh.arb b/lib/l10n/arb/app_zh.arb index f6f4895f..453c9f5c 100644 --- a/lib/l10n/arb/app_zh.arb +++ b/lib/l10n/arb/app_zh.arb @@ -402,7 +402,7 @@ "@aboutDabMusicDesc": { "description": "Credit for DAB Music API" }, - "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", + "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1005,7 +1005,7 @@ }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Extension", "@providerExtension": { diff --git a/lib/l10n/arb/app_zh_CN.arb b/lib/l10n/arb/app_zh_CN.arb index 67d8b58c..e649da18 100644 --- a/lib/l10n/arb/app_zh_CN.arb +++ b/lib/l10n/arb/app_zh_CN.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", + "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1089,7 +1089,7 @@ }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Extension", "@providerExtension": { @@ -2358,7 +2358,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "从 Tidal、Qobuz 或 Deezer 获取 FLAC 品质音频", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/l10n/arb/app_zh_TW.arb b/lib/l10n/arb/app_zh_TW.arb index d230aa50..b9349870 100644 --- a/lib/l10n/arb/app_zh_TW.arb +++ b/lib/l10n/arb/app_zh_TW.arb @@ -450,7 +450,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.", + "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -1089,7 +1089,7 @@ }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" + "description": "Label for built-in providers (Tidal/Qobuz)" }, "providerExtension": "Extension", "@providerExtension": { @@ -2358,7 +2358,7 @@ "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", + "tutorialWelcomeTip2": "從 Tidal、Qobuz 或 Deezer 取得 FLAC 品質音訊", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, diff --git a/lib/models/settings.dart b/lib/models/settings.dart index 545e7440..d42b9fa6 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -55,17 +55,14 @@ class AppSettings { final String songLinkRegion; // SongLink userCountry region code used for platform lookup - // Local Library Settings final bool localLibraryEnabled; // Enable local library scanning final String localLibraryPath; // Path to scan for audio files final bool localLibraryShowDuplicates; // Show indicator when searching for existing tracks - // Tutorial/Onboarding final bool hasCompletedTutorial; // Track if user has completed the app tutorial - // Lyrics Provider Settings final List lyricsProviders; // Ordered list of enabled lyrics provider IDs final bool @@ -77,7 +74,6 @@ class AppSettings { final String musixmatchLanguage; // Optional ISO language code for Musixmatch localized lyrics - // Version upgrade tracking final String lastSeenVersion; // Last app version the user has acknowledged (e.g. '3.7.0') @@ -124,13 +120,10 @@ class AppSettings { this.downloadNetworkMode = 'any', this.networkCompatibilityMode = false, this.songLinkRegion = 'US', - // Local Library defaults this.localLibraryEnabled = false, this.localLibraryPath = '', this.localLibraryShowDuplicates = true, - // Tutorial default this.hasCompletedTutorial = false, - // Lyrics providers default order this.lyricsProviders = const [ 'lrclib', 'spotify_api', @@ -143,7 +136,6 @@ class AppSettings { this.lyricsIncludeRomanizationNetease = false, this.lyricsMultiPersonWordByWord = false, this.musixmatchLanguage = '', - // Version upgrade tracking this.lastSeenVersion = '', }); @@ -191,19 +183,15 @@ class AppSettings { String? downloadNetworkMode, bool? networkCompatibilityMode, String? songLinkRegion, - // Local Library bool? localLibraryEnabled, String? localLibraryPath, bool? localLibraryShowDuplicates, - // Tutorial bool? hasCompletedTutorial, - // Lyrics providers List? lyricsProviders, bool? lyricsIncludeTranslationNetease, bool? lyricsIncludeRomanizationNetease, bool? lyricsMultiPersonWordByWord, String? musixmatchLanguage, - // Version upgrade tracking String? lastSeenVersion, }) { return AppSettings( @@ -259,14 +247,11 @@ class AppSettings { networkCompatibilityMode: networkCompatibilityMode ?? this.networkCompatibilityMode, songLinkRegion: songLinkRegion ?? this.songLinkRegion, - // Local Library localLibraryEnabled: localLibraryEnabled ?? this.localLibraryEnabled, localLibraryPath: localLibraryPath ?? this.localLibraryPath, localLibraryShowDuplicates: localLibraryShowDuplicates ?? this.localLibraryShowDuplicates, - // Tutorial hasCompletedTutorial: hasCompletedTutorial ?? this.hasCompletedTutorial, - // Lyrics providers lyricsProviders: lyricsProviders ?? this.lyricsProviders, lyricsIncludeTranslationNetease: lyricsIncludeTranslationNetease ?? @@ -277,7 +262,6 @@ class AppSettings { lyricsMultiPersonWordByWord: lyricsMultiPersonWordByWord ?? this.lyricsMultiPersonWordByWord, musixmatchLanguage: musixmatchLanguage ?? this.musixmatchLanguage, - // Version upgrade tracking lastSeenVersion: lastSeenVersion ?? this.lastSeenVersion, ); } diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index 4f2059db..999a65a0 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -592,10 +592,8 @@ class DownloadHistoryNotifier extends Notifier { return 0; } - // Delete from database final deletedCount = await _db.deleteByIds(orphanedIds); - // Update in-memory state final orphanedSet = orphanedIds.toSet(); state = state.copyWith( items: state.items @@ -1596,18 +1594,12 @@ class DownloadQueueNotifier extends Notifier { } String _determineOutputExt(String quality, String service) { - // YouTube provider - lossy only (Opus or MP3) if (service.toLowerCase() == 'youtube') { if (quality.toLowerCase().contains('mp3')) { return '.mp3'; } return '.opus'; } - // Amazon stream is delivered as MP4/M4A container (may contain FLAC audio), - // so SAF should keep .m4a before decrypt/convert pipeline. - if (service.toLowerCase() == 'amazon') { - return '.m4a'; - } if (service.toLowerCase() == 'tidal' && quality == 'HIGH') { return '.m4a'; } @@ -2903,7 +2895,6 @@ class DownloadQueueNotifier extends Notifier { failedCount: _failedInSession, ); - // Auto-export failed downloads if enabled final settings = ref.read(settingsProvider); if (settings.autoExportFailedDownloads && _failedInSession > 0) { final exportPath = await exportFailedDownloads(); @@ -3206,7 +3197,6 @@ class DownloadQueueNotifier extends Notifier { !trackToDownload.id.startsWith('deezer:') && !trackToDownload.id.startsWith('extension:')) { try { - // Extract clean Spotify ID (remove spotify: prefix if present) String spotifyId = trackToDownload.id; if (spotifyId.startsWith('spotify:track:')) { spotifyId = spotifyId.split(':').last; @@ -3508,9 +3498,8 @@ class DownloadQueueNotifier extends Notifier { if (!wasExisting && decryptionKey.isNotEmpty && - filePath != null && - actualService == 'amazon') { - _log.i('Amazon encrypted stream detected, decrypting via FFmpeg...'); + filePath != null) { + _log.i('Encrypted stream detected, decrypting via FFmpeg...'); updateItemStatus(item.id, DownloadStatus.downloading, progress: 0.9); if (effectiveSafMode && isContentUri(filePath)) { @@ -3539,7 +3528,7 @@ class DownloadQueueNotifier extends Notifier { updateItemStatus( item.id, DownloadStatus.failed, - error: 'Failed to decrypt Amazon stream', + error: 'Failed to decrypt encrypted stream', errorType: DownloadErrorType.unknown, ); return; @@ -3564,7 +3553,7 @@ class DownloadQueueNotifier extends Notifier { ); if (newUri == null) { - _log.e('Failed to write decrypted Amazon stream back to SAF'); + _log.e('Failed to write decrypted stream back to SAF'); updateItemStatus( item.id, DownloadStatus.failed, @@ -3579,7 +3568,7 @@ class DownloadQueueNotifier extends Notifier { } filePath = newUri; finalSafFileName = newFileName; - _log.i('Amazon SAF decryption completed'); + _log.i('SAF decryption completed'); } finally { try { await File(tempPath).delete(); @@ -3601,7 +3590,7 @@ class DownloadQueueNotifier extends Notifier { updateItemStatus( item.id, DownloadStatus.failed, - error: 'Failed to decrypt Amazon stream', + error: 'Failed to decrypt encrypted stream', errorType: DownloadErrorType.unknown, ); try { @@ -3610,7 +3599,7 @@ class DownloadQueueNotifier extends Notifier { return; } filePath = decryptedPath; - _log.i('Amazon local decryption completed'); + _log.i('Local decryption completed'); } } @@ -3832,7 +3821,6 @@ class DownloadQueueNotifier extends Notifier { } } } else { - // Local file path flow (original) if (quality == 'HIGH') { final tidalHighFormat = settings.tidalHighFormat; _log.i( @@ -4049,10 +4037,9 @@ class DownloadQueueNotifier extends Notifier { !effectiveSafMode && isFlacFile && !wasExisting && - actualService == 'amazon' && decryptionKey.isNotEmpty) { _log.d( - 'Local FLAC after Amazon decrypt detected, embedding metadata and cover...', + 'Local FLAC after decrypt detected, embedding metadata and cover...', ); try { updateItemStatus( @@ -4112,7 +4099,6 @@ class DownloadQueueNotifier extends Notifier { final isContentUriPath = isContentUri(filePath); if (isContentUriPath && effectiveSafMode) { - // SAF mode: copy to temp, embed, write back final tempPath = await _copySafToTemp(filePath); if (tempPath != null) { try { @@ -4133,7 +4119,6 @@ class DownloadQueueNotifier extends Notifier { copyright: backendCopyright, ); } - // Write back to SAF final ext = isMp3File ? '.mp3' : '.opus'; final newFileName = '${safBaseName ?? 'track'}$ext'; final newUri = await _writeTempToSaf( @@ -4162,7 +4147,6 @@ class DownloadQueueNotifier extends Notifier { } } } else { - // Non-SAF mode: embed directly try { if (isMp3File) { await _embedMetadataToMp3( diff --git a/lib/providers/extension_provider.dart b/lib/providers/extension_provider.dart index 1b55f744..f53aca0c 100644 --- a/lib/providers/extension_provider.dart +++ b/lib/providers/extension_provider.dart @@ -757,7 +757,6 @@ class ExtensionNotifier extends Notifier { Future loadProviderPriority() async { try { - // Load from SharedPreferences first (persisted) final prefs = await SharedPreferences.getInstance(); final savedJson = prefs.getString(_providerPriorityKey); @@ -768,10 +767,8 @@ class ExtensionNotifier extends Notifier { priority = _sanitizeDownloadProviderPriority(priority); _log.d('Loaded provider priority from prefs: $priority'); await prefs.setString(_providerPriorityKey, jsonEncode(priority)); - // Sync to Go backend await PlatformBridge.setProviderPriority(priority); } else { - // Fallback to Go backend default priority = await PlatformBridge.getProviderPriority(); priority = _sanitizeDownloadProviderPriority(priority); await PlatformBridge.setProviderPriority(priority); @@ -787,11 +784,9 @@ class ExtensionNotifier extends Notifier { Future setProviderPriority(List priority) async { try { final sanitized = _sanitizeDownloadProviderPriority(priority); - // Save to SharedPreferences for persistence final prefs = await SharedPreferences.getInstance(); await prefs.setString(_providerPriorityKey, jsonEncode(sanitized)); - // Sync to Go backend await PlatformBridge.setProviderPriority(sanitized); state = state.copyWith(providerPriority: sanitized); _log.d('Saved provider priority: $sanitized'); @@ -811,7 +806,7 @@ class ExtensionNotifier extends Notifier { } } - for (final provider in const ['tidal', 'qobuz', 'amazon', 'deezer']) { + for (final provider in const ['tidal', 'qobuz', 'deezer']) { if (!result.contains(provider)) { result.add(provider); } @@ -822,7 +817,6 @@ class ExtensionNotifier extends Notifier { Future loadMetadataProviderPriority() async { try { - // Load from SharedPreferences first (persisted) final prefs = await SharedPreferences.getInstance(); final savedJson = prefs.getString(_metadataProviderPriorityKey); @@ -831,10 +825,8 @@ class ExtensionNotifier extends Notifier { final saved = jsonDecode(savedJson) as List; priority = saved.map((e) => e as String).toList(); _log.d('Loaded metadata provider priority from prefs: $priority'); - // Sync to Go backend await PlatformBridge.setMetadataProviderPriority(priority); } else { - // Fallback to Go backend default priority = await PlatformBridge.getMetadataProviderPriority(); _log.d('Using default metadata provider priority: $priority'); } @@ -847,11 +839,9 @@ class ExtensionNotifier extends Notifier { Future setMetadataProviderPriority(List priority) async { try { - // Save to SharedPreferences for persistence final prefs = await SharedPreferences.getInstance(); await prefs.setString(_metadataProviderPriorityKey, jsonEncode(priority)); - // Sync to Go backend await PlatformBridge.setMetadataProviderPriority(priority); state = state.copyWith(metadataProviderPriority: priority); _log.d('Saved metadata provider priority: $priority'); @@ -880,7 +870,7 @@ class ExtensionNotifier extends Notifier { } List getAllDownloadProviders() { - final providers = ['tidal', 'qobuz', 'amazon', 'deezer']; + final providers = ['tidal', 'qobuz', 'deezer']; for (final ext in state.extensions) { if (ext.enabled && ext.hasDownloadProvider) { providers.add(ext.id); diff --git a/lib/providers/track_provider.dart b/lib/providers/track_provider.dart index 32022b81..271f22f7 100644 --- a/lib/providers/track_provider.dart +++ b/lib/providers/track_provider.dart @@ -204,7 +204,6 @@ class TrackNotifier extends Notifier { state = TrackState(isLoading: true, hasSearchText: state.hasSearchText); try { - // Step 1: Check for extension URL handlers first (handles YT Music, etc.) final extensionHandler = await PlatformBridge.findURLHandler(url); if (extensionHandler != null) { _log.i('Found extension URL handler: $extensionHandler for URL: $url'); @@ -215,7 +214,6 @@ class TrackNotifier extends Notifier { result = await PlatformBridge.handleURLWithExtension(url); if (!_isRequestValid(requestId)) return; - // Check if we got valid data if (result != null && result['type'] == 'track' && result['track'] != null) { @@ -321,7 +319,6 @@ class TrackNotifier extends Notifier { } } - // Step 2: Try Deezer URL parsing if (url.contains('deezer.com') || url.contains('deezer.page.link')) { _log.i('Detected Deezer URL, parsing...'); final parsed = await PlatformBridge.parseDeezerUrl(url); @@ -387,7 +384,6 @@ class TrackNotifier extends Notifier { return; } - // Step 3: Try Tidal URL parsing if (url.contains('tidal.com')) { _log.i('Detected Tidal URL, parsing...'); final parsed = await PlatformBridge.parseTidalUrl(url); @@ -461,7 +457,6 @@ class TrackNotifier extends Notifier { return; } - // Step 4: Fall back to Spotify parsing final parsed = await PlatformBridge.parseSpotifyUrl(url); if (!_isRequestValid(requestId)) return; diff --git a/lib/screens/album_screen.dart b/lib/screens/album_screen.dart index a5411ae3..f226a80f 100644 --- a/lib/screens/album_screen.dart +++ b/lib/screens/album_screen.dart @@ -305,7 +305,6 @@ class _AlbumScreenState extends ConsumerState { background: Stack( fit: StackFit.expand, children: [ - // Full-screen cover background (no blur, full resolution) if (widget.coverUrl != null) CachedNetworkImage( imageUrl: @@ -326,7 +325,6 @@ class _AlbumScreenState extends ConsumerState { color: colorScheme.onSurfaceVariant, ), ), - // Bottom gradient for readability Positioned( left: 0, right: 0, @@ -345,7 +343,6 @@ class _AlbumScreenState extends ConsumerState { ), ), ), - // Album info overlay at bottom Positioned( left: 20, right: 20, diff --git a/lib/screens/artist_screen.dart b/lib/screens/artist_screen.dart index 43cfa2eb..cdf58522 100644 --- a/lib/screens/artist_screen.dart +++ b/lib/screens/artist_screen.dart @@ -21,7 +21,6 @@ import 'package:spotiflac_android/widgets/download_service_picker.dart'; import 'package:spotiflac_android/widgets/track_collection_quick_actions.dart'; import 'package:spotiflac_android/utils/clickable_metadata.dart'; -/// Simple in-memory cache for artist data class _ArtistCache { static final Map _cache = {}; static const Duration _ttl = Duration(minutes: 10); @@ -69,7 +68,6 @@ class _CacheEntry { }); } -/// Artist screen with Spotify-like design class ArtistScreen extends ConsumerStatefulWidget { final String artistId; final String artistName; @@ -717,7 +715,6 @@ class _ArtistScreenState extends ConsumerState { ), ), const Divider(height: 1), - // Options if (albums.isNotEmpty) _DiscographyOptionTile( icon: Icons.library_music, @@ -830,7 +827,7 @@ class _ArtistScreenState extends ConsumerState { int failedCount = 0; for (final album in albums) { - if (!_isFetchingDiscography) break; // Cancelled + if (!_isFetchingDiscography) break; try { final tracks = await _fetchAlbumTracks(album); @@ -1066,7 +1063,7 @@ class _ArtistScreenState extends ConsumerState { CachedNetworkImage( imageUrl: imageUrl, fit: BoxFit.cover, - alignment: Alignment.topCenter, // Show top of image (faces) + alignment: Alignment.topCenter, memCacheWidth: 800, cacheManager: CoverCacheManager.instance, placeholder: (context, url) => @@ -1155,7 +1152,6 @@ class _ArtistScreenState extends ConsumerState { ], ), ), - // Download Discography button (icon only, right-aligned) if (hasDiscography && !_isSelectionMode) ...[ const SizedBox(width: 12), Container( @@ -1201,7 +1197,6 @@ class _ArtistScreenState extends ConsumerState { ); } - /// Build Popular tracks section like Spotify Widget _buildPopularSection(ColorScheme colorScheme) { if (_topTracks == null || _topTracks!.isEmpty) { return const SizedBox.shrink(); @@ -1416,7 +1411,6 @@ class _ArtistScreenState extends ConsumerState { ); } - /// Handle tap on popular track item void _handlePopularTrackTap(Track track, {required bool isQueued}) async { if (isQueued) return; @@ -1636,7 +1630,6 @@ class _ArtistScreenState extends ConsumerState { ), ), ), - // Selection overlay if (_isSelectionMode) Positioned.fill( child: AnimatedContainer( @@ -1652,7 +1645,6 @@ class _ArtistScreenState extends ConsumerState { ), ), ), - // Checkbox if (_isSelectionMode) Positioned( top: 8, diff --git a/lib/screens/downloaded_album_screen.dart b/lib/screens/downloaded_album_screen.dart index 0db150eb..425dfd4b 100644 --- a/lib/screens/downloaded_album_screen.dart +++ b/lib/screens/downloaded_album_screen.dart @@ -18,7 +18,6 @@ import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/screens/track_metadata_screen.dart'; import 'package:spotiflac_android/services/downloaded_embedded_cover_resolver.dart'; -/// Screen to display downloaded tracks from a specific album class DownloadedAlbumScreen extends ConsumerStatefulWidget { final String albumName; final String artistName; @@ -361,7 +360,6 @@ class _DownloadedAlbumScreenState extends ConsumerState { ); final tracks = _getAlbumTracks(allHistoryItems); - // Show empty state if no tracks found if (tracks.isEmpty) { return Scaffold( appBar: AppBar(title: Text(widget.albumName)), @@ -480,7 +478,6 @@ class _DownloadedAlbumScreenState extends ConsumerState { background: Stack( fit: StackFit.expand, children: [ - // Full-screen cover background if (embeddedCoverPath != null) Image.file( File(embeddedCoverPath), @@ -508,7 +505,6 @@ class _DownloadedAlbumScreenState extends ConsumerState { color: colorScheme.onSurfaceVariant, ), ), - // Bottom gradient for readability Positioned( left: 0, right: 0, @@ -527,7 +523,6 @@ class _DownloadedAlbumScreenState extends ConsumerState { ), ), ), - // Album info overlay at bottom Positioned( left: 20, right: 20, @@ -711,10 +706,8 @@ class _DownloadedAlbumScreenState extends ConsumerState { final discTracks = discMap[discNumber]; if (discTracks == null || discTracks.isEmpty) continue; - // Add disc separator children.add(_buildDiscSeparator(context, colorScheme, discNumber)); - // Add tracks for this disc for (final track in discTracks) { children.add( KeyedSubtree( @@ -897,7 +890,6 @@ class _DownloadedAlbumScreenState extends ConsumerState { return; } - // Share SAF content URIs via native intent if (safUris.isNotEmpty) { try { if (safUris.length == 1) { @@ -908,13 +900,11 @@ class _DownloadedAlbumScreenState extends ConsumerState { } catch (_) {} } - // Share regular files via SharePlus if (filesToShare.isNotEmpty) { await SharePlus.instance.share(ShareParams(files: filesToShare)); } } - /// Show batch convert bottom sheet void _showBatchConvertSheet( BuildContext context, List allTracks, @@ -1388,7 +1378,6 @@ class _DownloadedAlbumScreenState extends ConsumerState { ), const SizedBox(height: 12), - // Action buttons row: Share, Convert Row( children: [ Expanded( diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index 6462d952..b20d7b90 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -520,7 +520,6 @@ class _HomeTabState extends ConsumerState final settings = ref.read(settingsProvider); final extState = ref.read(extensionProvider); final searchProvider = settings.searchProvider; - // Use filterOverride if provided, otherwise read from state final selectedFilter = filterOverride ?? ref.read(trackProvider).selectedSearchFilter; @@ -535,7 +534,6 @@ class _HomeTabState extends ConsumerState extState.extensions.any((e) => e.id == searchProvider && e.enabled); if (isExtensionEnabled) { - // Build options with filter if selected Map? options; if (selectedFilter != null) { options = {'filter': selectedFilter}; @@ -1116,7 +1114,6 @@ class _HomeTabState extends ConsumerState ), ), - // Search filter bar (only shown when has search results) if (hasActualResults && !showRecentAccess) Consumer( builder: (context, ref, _) { @@ -1286,8 +1283,8 @@ class _HomeTabState extends ConsumerState ), ], ), - ), // Close RefreshIndicator - ), // Close GestureDetector + ), + ), ); } @@ -1434,7 +1431,6 @@ class _HomeTabState extends ConsumerState return _buildExploreSection(sections[sectionIndex], colorScheme); } - // Bottom padding return const SizedBox(height: 16); }, childCount: totalCount), ), @@ -2705,7 +2701,6 @@ class _HomeTabState extends ConsumerState scrollDirection: Axis.horizontal, child: Row( children: [ - // "All" chip (no filter) Padding( padding: const EdgeInsets.only(right: 8), child: FilterChip( @@ -2728,7 +2723,6 @@ class _HomeTabState extends ConsumerState ), ), ), - // Filter chips from extension ...filters.map((filter) { final isSelected = selectedFilter == filter.id; return Padding( @@ -2830,7 +2824,6 @@ class _HomeTabState extends ConsumerState prefixIcon: _SearchProviderDropdown( onProviderChanged: () { _lastSearchQuery = null; - // Reset filter when provider changes ref.read(trackProvider.notifier).setSearchFilter(null); setState(() {}); final text = _urlController.text.trim(); diff --git a/lib/screens/library_playlists_screen.dart b/lib/screens/library_playlists_screen.dart index 2f07b435..df68b16f 100644 --- a/lib/screens/library_playlists_screen.dart +++ b/lib/screens/library_playlists_screen.dart @@ -158,7 +158,6 @@ class LibraryPlaylistsScreen extends ConsumerWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Header: drag handle + thumbnail + playlist info Column( children: [ const SizedBox(height: 8), @@ -210,7 +209,6 @@ class LibraryPlaylistsScreen extends ConsumerWidget { color: colorScheme.outlineVariant.withValues(alpha: 0.5), ), - // Rename _PlaylistOptionTile( icon: Icons.edit_outlined, title: context.l10n.collectionRenamePlaylist, @@ -225,7 +223,6 @@ class LibraryPlaylistsScreen extends ConsumerWidget { }, ), - // Change cover _PlaylistOptionTile( icon: Icons.image_outlined, title: context.l10n.collectionPlaylistChangeCover, @@ -235,7 +232,6 @@ class LibraryPlaylistsScreen extends ConsumerWidget { }, ), - // Delete _PlaylistOptionTile( icon: Icons.delete_outline, iconColor: colorScheme.error, diff --git a/lib/screens/library_tracks_folder_screen.dart b/lib/screens/library_tracks_folder_screen.dart index d2442c22..ab437ec6 100644 --- a/lib/screens/library_tracks_folder_screen.dart +++ b/lib/screens/library_tracks_folder_screen.dart @@ -37,7 +37,6 @@ class _LibraryTracksFolderScreenState bool _showTitleInAppBar = false; final ScrollController _scrollController = ScrollController(); - // ── Multi-select state ── bool _isSelectionMode = false; final Set _selectedKeys = {}; @@ -145,8 +144,6 @@ class _LibraryTracksFolderScreenState return url; } - // ── Selection helpers ── - void _enterSelectionMode(String key) { HapticFeedback.mediumImpact(); setState(() { @@ -181,8 +178,6 @@ class _LibraryTracksFolderScreenState }); } - // ── Batch actions ── - Future _removeSelected(List entries) async { final keysToRemove = _selectedKeys.toSet(); if (keysToRemove.isEmpty) return; @@ -426,7 +421,6 @@ class _LibraryTracksFolderScreenState child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Drag handle Container( width: 32, height: 4, @@ -437,7 +431,6 @@ class _LibraryTracksFolderScreenState ), ), - // Header: [X close] [count] [Select All / Deselect] Row( children: [ IconButton.filledTonal( @@ -493,7 +486,6 @@ class _LibraryTracksFolderScreenState const SizedBox(height: 12), - // Action buttons row Row( children: [ if (isWishlist) @@ -525,7 +517,6 @@ class _LibraryTracksFolderScreenState const SizedBox(height: 8), - // Remove button (full width, red) SizedBox( width: double.infinity, child: FilledButton.icon( @@ -714,7 +705,6 @@ class _LibraryTracksFolderScreenState ) else coverFallback, - // Bottom gradient for readability Positioned( left: 0, right: 0, @@ -733,7 +723,6 @@ class _LibraryTracksFolderScreenState ), ), ), - // Title and track count overlay Positioned( left: 20, right: 20, @@ -829,8 +818,6 @@ class _LibraryTracksFolderScreenState ); } - // ── Header actions ── - Widget _buildHeaderActionPlaceholder() => const SizedBox(width: 48, height: 48); Widget _buildDownloadAllCenterButton(List entries) { @@ -1263,7 +1250,6 @@ class _CollectionTrackTile extends ConsumerWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Header: drag handle + cover + track info Column( children: [ const SizedBox(height: 8), diff --git a/lib/screens/local_album_screen.dart b/lib/screens/local_album_screen.dart index 83298d72..cd8a4c7b 100644 --- a/lib/screens/local_album_screen.dart +++ b/lib/screens/local_album_screen.dart @@ -13,7 +13,6 @@ import 'package:spotiflac_android/services/platform_bridge.dart'; import 'package:spotiflac_android/providers/local_library_provider.dart'; import 'package:spotiflac_android/providers/playback_provider.dart'; -/// Screen to display tracks from a local library album class LocalAlbumScreen extends ConsumerStatefulWidget { final String albumName; final String artistName; @@ -83,7 +82,6 @@ class _LocalAlbumScreenState extends ConsumerState { List _buildSortedTracks() { final tracks = List.from(widget.tracks); tracks.sort((a, b) { - // Sort by disc number first, then by track number final aDisc = a.discNumber ?? 1; final bDisc = b.discNumber ?? 1; if (aDisc != bDisc) return aDisc.compareTo(bDisc); @@ -197,7 +195,6 @@ class _LocalAlbumScreenState extends ConsumerState { ), ); - // Go back if all tracks were deleted if (deletedCount == currentTracks.length) { Navigator.pop(context); } @@ -233,7 +230,6 @@ class _LocalAlbumScreenState extends ConsumerState { final bottomPadding = MediaQuery.of(context).padding.bottom; final tracks = _sortedTracksCache; - // Show empty state if no tracks found if (tracks.isEmpty) { return Scaffold( appBar: AppBar(title: Text(widget.albumName)), @@ -326,7 +322,6 @@ class _LocalAlbumScreenState extends ConsumerState { background: Stack( fit: StackFit.expand, children: [ - // Full-screen cover background if (widget.coverPath != null) Image.file( File(widget.coverPath!), @@ -343,7 +338,6 @@ class _LocalAlbumScreenState extends ConsumerState { color: colorScheme.onSurfaceVariant, ), ), - // Bottom gradient for readability Positioned( left: 0, right: 0, @@ -362,7 +356,6 @@ class _LocalAlbumScreenState extends ConsumerState { ), ), ), - // Album info overlay at bottom Positioned( left: 20, right: 20, @@ -888,7 +881,6 @@ class _LocalAlbumScreenState extends ConsumerState { return false; } - /// Batch re-enrich selected local tracks Future _reEnrichSelected(List allTracks) async { final tracksById = {for (final t in allTracks) t.id: t}; final selected = []; @@ -988,7 +980,6 @@ class _LocalAlbumScreenState extends ConsumerState { ).showSnackBar(SnackBar(content: Text(summary))); } - /// Show batch convert bottom sheet void _showBatchConvertSheet( BuildContext context, List allTracks, @@ -1261,7 +1252,6 @@ class _LocalAlbumScreenState extends ConsumerState { String? safTempPath; if (isSaf) { - // Copy SAF file to temp for conversion safTempPath = await PlatformBridge.copyContentUriToTemp( item.filePath, ); @@ -1296,7 +1286,6 @@ class _LocalAlbumScreenState extends ConsumerState { if (isSaf) { // For SAF: derive the parent tree URI and relative dir from the content URI, // then create new SAF file and delete old one - // // Parse the SAF URI to get the tree document path: // content://...tree/...document/.../oldName.flac // We need tree URI and relative dir to create the new file @@ -1375,14 +1364,12 @@ class _LocalAlbumScreenState extends ConsumerState { continue; } - // Delete old SAF file try { await PlatformBridge.safDelete(item.filePath); } catch (_) {} await localDb.deleteByPath(item.filePath); } - // Clean up temp files try { await File(newPath).delete(); } catch (_) {} @@ -1400,7 +1387,6 @@ class _LocalAlbumScreenState extends ConsumerState { } catch (_) {} } - // Reload local library to pick up converted files ref.read(localLibraryProvider.notifier).reloadFromStorage(); _exitSelectionMode(); @@ -1513,7 +1499,6 @@ class _LocalAlbumScreenState extends ConsumerState { ), const SizedBox(height: 12), - // Action buttons row: Re-enrich, Convert Row( children: [ Expanded( diff --git a/lib/screens/playlist_screen.dart b/lib/screens/playlist_screen.dart index d005678d..dc0f12ad 100644 --- a/lib/screens/playlist_screen.dart +++ b/lib/screens/playlist_screen.dart @@ -206,7 +206,6 @@ class _PlaylistScreenState extends ConsumerState { background: Stack( fit: StackFit.expand, children: [ - // Full-screen cover background if (widget.coverUrl != null) CachedNetworkImage( imageUrl: @@ -227,7 +226,6 @@ class _PlaylistScreenState extends ConsumerState { color: colorScheme.onSurfaceVariant, ), ), - // Bottom gradient for readability Positioned( left: 0, right: 0, @@ -246,7 +244,6 @@ class _PlaylistScreenState extends ConsumerState { ), ), ), - // Playlist info overlay at bottom Positioned( left: 20, right: 20, @@ -436,8 +433,6 @@ class _PlaylistScreenState extends ConsumerState { } } - // ── Shuffle / Love / Download buttons ── - Widget _buildCircleButton({ required IconData icon, required String tooltip, diff --git a/lib/screens/queue_tab.dart b/lib/screens/queue_tab.dart index ad462f37..774a6e26 100644 --- a/lib/screens/queue_tab.dart +++ b/lib/screens/queue_tab.dart @@ -211,7 +211,7 @@ class _GroupedAlbum { class _GroupedLocalAlbum { final String albumName; final String artistName; - final String? coverPath; // Local cover file path + final String? coverPath; final List tracks; final DateTime latestScanned; final String searchKey; @@ -229,12 +229,11 @@ class _GroupedLocalAlbum { class _HistoryStats { final Map albumCounts; - final Map localAlbumCounts; // For identifying local singles + final Map localAlbumCounts; final List<_GroupedAlbum> groupedAlbums; - final List<_GroupedLocalAlbum> groupedLocalAlbums; // Local library albums + final List<_GroupedLocalAlbum> groupedLocalAlbums; final int albumCount; final int singleTracks; - // Local library stats final int localAlbumCount; final int localSingleTracks; @@ -933,8 +932,6 @@ class _QueueTabState extends ConsumerState { overlay.insert(_playlistSelectionOverlayEntry!); } - // --- Playlist selection mode --- - void _enterPlaylistSelectionMode(String playlistId) { HapticFeedback.mediumImpact(); setState(() { @@ -1202,11 +1199,9 @@ class _QueueTabState extends ConsumerState { await deleteFile(cleanPath); } catch (_) {} - // Remove from appropriate database if (item.source == LibraryItemSource.downloaded) { historyNotifier.removeFromHistory(item.historyItem!.id); } else { - // Remove from local library database await localLibraryDb.deleteByPath(item.filePath); } deletedCount++; @@ -2024,7 +2019,6 @@ class _QueueTabState extends ConsumerState { Map albumCounts, [ String searchQuery = '', ]) { - // First apply search filter var filteredItems = items; if (searchQuery.isNotEmpty) { final query = searchQuery; @@ -2034,7 +2028,6 @@ class _QueueTabState extends ConsumerState { }).toList(); } - // Then apply filter mode if (filterMode == 'all') return filteredItems; switch (filterMode) { @@ -2639,7 +2632,6 @@ class _QueueTabState extends ConsumerState { ), ), - // Search bar - always at top if (allHistoryItems.isNotEmpty || hasQueueItems || localLibraryItems.isNotEmpty) diff --git a/lib/screens/settings/about_page.dart b/lib/screens/settings/about_page.dart index 5b5bc299..8ef0268c 100644 --- a/lib/screens/settings/about_page.dart +++ b/lib/screens/settings/about_page.dart @@ -49,7 +49,7 @@ class AboutPage extends StatelessWidget { title: Text( context.l10n.aboutTitle, style: TextStyle( - fontSize: 20 + (8 * expandRatio), // 20 -> 28 + fontSize: 20 + (8 * expandRatio), fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), @@ -462,7 +462,6 @@ class _ContributorItem extends StatelessWidget { } } -/// Translator data model class _Translator { final String name; final String crowdinUsername; @@ -477,7 +476,6 @@ class _Translator { }); } -/// Translators section with compact chip-style layout class _TranslatorsSection extends StatelessWidget { const _TranslatorsSection(); @@ -558,7 +556,6 @@ class _TranslatorsSection extends StatelessWidget { } } -/// Individual translator chip class _TranslatorChip extends StatelessWidget { final _Translator translator; diff --git a/lib/screens/settings/appearance_settings_page.dart b/lib/screens/settings/appearance_settings_page.dart index 4778e95f..f5a4560b 100644 --- a/lib/screens/settings/appearance_settings_page.dart +++ b/lib/screens/settings/appearance_settings_page.dart @@ -148,7 +148,6 @@ class AppearanceSettingsPage extends ConsumerWidget { } } -/// A simplified preview of how the app looks with current settings class _ThemePreviewCard extends StatelessWidget { @override Widget build(BuildContext context) { @@ -423,7 +422,6 @@ class _ColorPaletteItem extends StatelessWidget { } } -/// Optimized app bar title with animation class _AppBarTitle extends StatelessWidget { final String title; final double topPadding; @@ -440,14 +438,14 @@ class _AppBarTitle extends StatelessWidget { final expandRatio = ((constraints.maxHeight - minHeight) / (maxHeight - minHeight)) .clamp(0.0, 1.0); - final leftPadding = 56 - (32 * expandRatio); // 56 -> 24 + final leftPadding = 56 - (32 * expandRatio); return FlexibleSpaceBar( expandedTitleScale: 1.0, titlePadding: EdgeInsets.only(left: leftPadding, bottom: 16), title: Text( title, style: TextStyle( - fontSize: 20 + (8 * expandRatio), // 20 -> 28 + fontSize: 20 + (8 * expandRatio), fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), diff --git a/lib/screens/settings/donate_page.dart b/lib/screens/settings/donate_page.dart index 4f20d118..80466739 100644 --- a/lib/screens/settings/donate_page.dart +++ b/lib/screens/settings/donate_page.dart @@ -56,17 +56,14 @@ class DonatePage extends StatelessWidget { padding: const EdgeInsets.all(16), child: Column( children: [ - // Donate links card _DonateLinksCard(colorScheme: colorScheme), const SizedBox(height: 24), - // Recent donors section _RecentDonorsCard(colorScheme: colorScheme), const SizedBox(height: 16), - // Combined notice card Card( elevation: 0, color: colorScheme.secondaryContainer.withValues( diff --git a/lib/screens/settings/download_settings_page.dart b/lib/screens/settings/download_settings_page.dart index 2e1e8e8c..fbf92f63 100644 --- a/lib/screens/settings/download_settings_page.dart +++ b/lib/screens/settings/download_settings_page.dart @@ -23,7 +23,7 @@ class DownloadSettingsPage extends ConsumerStatefulWidget { } class _DownloadSettingsPageState extends ConsumerState { - static const _builtInServices = ['tidal', 'qobuz', 'amazon', 'deezer']; + static const _builtInServices = ['tidal', 'qobuz', 'deezer']; static const _songLinkRegions = [ 'AD', 'AE', @@ -326,7 +326,7 @@ class _DownloadSettingsPageState extends ConsumerState { ((constraints.maxHeight - minHeight) / (maxHeight - minHeight)) .clamp(0.0, 1.0); - final leftPadding = 56 - (32 * expandRatio); // 56 -> 24 + final leftPadding = 56 - (32 * expandRatio); return FlexibleSpaceBar( expandedTitleScale: 1.0, titlePadding: EdgeInsets.only( @@ -336,7 +336,7 @@ class _DownloadSettingsPageState extends ConsumerState { title: Text( context.l10n.downloadTitle, style: TextStyle( - fontSize: 20 + (8 * expandRatio), // 20 -> 28 + fontSize: 20 + (8 * expandRatio), fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), @@ -450,7 +450,7 @@ class _DownloadSettingsPageState extends ConsumerState { const SizedBox(width: 8), Expanded( child: Text( - 'Select Tidal, Qobuz, or Amazon above to configure quality', + 'Select Tidal or Qobuz above to configure quality', style: Theme.of(context).textTheme.bodySmall ?.copyWith( color: colorScheme.onSurfaceVariant, @@ -720,7 +720,6 @@ class _DownloadSettingsPageState extends ConsumerState { ), ), - // Download Network Mode SliverToBoxAdapter( child: SettingsSectionHeader(title: context.l10n.sectionDownload), ), @@ -778,7 +777,6 @@ class _DownloadSettingsPageState extends ConsumerState { ), ), - // All Files Access section (Android 13+ only) if (Platform.isAndroid && _androidSdkVersion >= 33) ...[ SliverToBoxAdapter( child: SettingsSectionHeader( @@ -2039,7 +2037,7 @@ class _ServiceSelector extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final extState = ref.watch(extensionProvider); - final builtInServiceIds = ['tidal', 'qobuz', 'amazon', 'deezer', 'youtube']; + final builtInServiceIds = ['tidal', 'qobuz', 'deezer', 'youtube']; final extensionProviders = extState.extensions .where((e) => e.enabled && e.hasDownloadProvider) @@ -2076,15 +2074,6 @@ class _ServiceSelector extends ConsumerWidget { ), ), const SizedBox(width: 8), - Expanded( - child: _ServiceChip( - icon: Icons.shopping_bag_outlined, - label: 'Amazon', - isSelected: effectiveService == 'amazon', - onTap: () => onChanged('amazon'), - ), - ), - const SizedBox(width: 8), Expanded( child: _ServiceChip( icon: Icons.smart_display, diff --git a/lib/screens/settings/extension_detail_page.dart b/lib/screens/settings/extension_detail_page.dart index 2268a793..72a50a8e 100644 --- a/lib/screens/settings/extension_detail_page.dart +++ b/lib/screens/settings/extension_detail_page.dart @@ -658,7 +658,6 @@ class _SettingItemState extends State<_SettingItem> { ); } - // For button type, show a different layout if (widget.setting.type == 'button') { return Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/screens/settings/library_settings_page.dart b/lib/screens/settings/library_settings_page.dart index d8d32dda..2d91f4a1 100644 --- a/lib/screens/settings/library_settings_page.dart +++ b/lib/screens/settings/library_settings_page.dart @@ -271,7 +271,6 @@ class _LibrarySettingsPageState extends ConsumerState { ), ), - // Scan Settings Section SliverToBoxAdapter( child: SettingsSectionHeader( title: context.l10n.libraryScanSettings, @@ -442,7 +441,6 @@ class _LibrarySettingsPageState extends ConsumerState { ), ], - // Info Section SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), @@ -558,7 +556,6 @@ class _LibraryHeroCard extends StatelessWidget { clipBehavior: Clip.antiAlias, child: Stack( children: [ - // Background decorative elements Positioned( right: -20, top: -20, @@ -581,7 +578,6 @@ class _LibraryHeroCard extends StatelessWidget { ), ), - // Content Padding( padding: const EdgeInsets.all(24), child: Column( diff --git a/lib/screens/settings/lyrics_provider_priority_page.dart b/lib/screens/settings/lyrics_provider_priority_page.dart index a33e8a50..426d82c8 100644 --- a/lib/screens/settings/lyrics_provider_priority_page.dart +++ b/lib/screens/settings/lyrics_provider_priority_page.dart @@ -122,8 +122,6 @@ class _LyricsProviderPriorityPageState ); } - // ── State mutations ── - void _enableProvider(String id) { setState(() => _enabledProviders.add(id)); _markChanged(); @@ -142,8 +140,6 @@ class _LyricsProviderPriorityPageState _markChanged(); } - // ── Save / Discard ── - Future _saveChanges() async { ref .read(settingsProvider.notifier) @@ -180,8 +176,6 @@ class _LyricsProviderPriorityPageState return result ?? false; } - // ── Provider metadata ── - static _LyricsProviderInfo _getLyricsProviderInfo(String id) { switch (id) { case 'spotify_api': @@ -230,10 +224,6 @@ class _LyricsProviderPriorityPageState } } -// ═══════════════════════════════════════════════════════════════════════════ -// Enabled provider card (reorderable) -// ═══════════════════════════════════════════════════════════════════════════ - class _EnabledProviderItem extends StatelessWidget { final String providerId; final _LyricsProviderInfo info; @@ -273,7 +263,6 @@ class _EnabledProviderItem extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ - // Numbered badge Container( width: 28, height: 28, @@ -296,10 +285,8 @@ class _EnabledProviderItem extends StatelessWidget { ), ), const SizedBox(width: 16), - // Icon Icon(info.icon, color: colorScheme.primary), const SizedBox(width: 12), - // Name + description Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -319,7 +306,6 @@ class _EnabledProviderItem extends StatelessWidget { ], ), ), - // Enable/disable switch SizedBox( height: 32, child: FittedBox( @@ -327,7 +313,6 @@ class _EnabledProviderItem extends StatelessWidget { ), ), const SizedBox(width: 4), - // Drag handle Icon(Icons.drag_handle, color: colorScheme.onSurfaceVariant), ], ), @@ -338,10 +323,6 @@ class _EnabledProviderItem extends StatelessWidget { } } -// ═══════════════════════════════════════════════════════════════════════════ -// Disabled provider card -// ═══════════════════════════════════════════════════════════════════════════ - class _DisabledProviderItem extends StatelessWidget { final String providerId; final _LyricsProviderInfo info; @@ -383,10 +364,8 @@ class _DisabledProviderItem extends StatelessWidget { // Empty space aligned with numbered badge const SizedBox(width: 28), const SizedBox(width: 16), - // Icon (muted) Icon(info.icon, color: colorScheme.outline), const SizedBox(width: 12), - // Name + description Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -407,7 +386,6 @@ class _DisabledProviderItem extends StatelessWidget { ], ), ), - // Switch SizedBox( height: 32, child: FittedBox( @@ -424,10 +402,6 @@ class _DisabledProviderItem extends StatelessWidget { } } -// ═══════════════════════════════════════════════════════════════════════════ -// Provider info model -// ═══════════════════════════════════════════════════════════════════════════ - class _LyricsProviderInfo { final String name; final String description; diff --git a/lib/screens/settings/options_settings_page.dart b/lib/screens/settings/options_settings_page.dart index a5a6e07c..9016586c 100644 --- a/lib/screens/settings/options_settings_page.dart +++ b/lib/screens/settings/options_settings_page.dart @@ -43,7 +43,7 @@ class OptionsSettingsPage extends ConsumerWidget { ((constraints.maxHeight - minHeight) / (maxHeight - minHeight)) .clamp(0.0, 1.0); - final leftPadding = 56 - (32 * expandRatio); // 56 -> 24 + final leftPadding = 56 - (32 * expandRatio); return FlexibleSpaceBar( expandedTitleScale: 1.0, titlePadding: EdgeInsets.only( @@ -53,7 +53,7 @@ class OptionsSettingsPage extends ConsumerWidget { title: Text( context.l10n.optionsTitle, style: TextStyle( - fontSize: 20 + (8 * expandRatio), // 20 -> 28 + fontSize: 20 + (8 * expandRatio), fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), @@ -331,7 +331,6 @@ class OptionsSettingsPage extends ConsumerWidget { BuildContext context, WidgetRef ref, ) async { - // Show loading indicator showDialog( context: context, barrierDismissible: false, diff --git a/lib/screens/settings/provider_priority_page.dart b/lib/screens/settings/provider_priority_page.dart index ed44ca46..a5a19f15 100644 --- a/lib/screens/settings/provider_priority_page.dart +++ b/lib/screens/settings/provider_priority_page.dart @@ -333,12 +333,6 @@ class _ProviderItem extends StatelessWidget { ); case 'qobuz': return _ProviderInfo(name: 'Qobuz', icon: Icons.album, isBuiltIn: true); - case 'amazon': - return _ProviderInfo( - name: 'Amazon Music', - icon: Icons.shopping_bag, - isBuiltIn: true, - ); case 'youtube': return _ProviderInfo( name: 'YouTube', diff --git a/lib/screens/setup_screen.dart b/lib/screens/setup_screen.dart index ede9017d..57a5caac 100644 --- a/lib/screens/setup_screen.dart +++ b/lib/screens/setup_screen.dart @@ -22,7 +22,6 @@ class _SetupScreenState extends ConsumerState { final PageController _pageController = PageController(); int _currentStep = 0; - // State variables bool _storagePermissionGranted = false; bool _notificationPermissionGranted = false; String? _selectedDirectory; @@ -474,7 +473,6 @@ class _SetupScreenState extends ConsumerState { Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - // Calculate progress final progress = (_currentStep + 1) / _totalSteps; return Scaffold( @@ -482,7 +480,6 @@ class _SetupScreenState extends ConsumerState { body: SafeArea( child: Column( children: [ - // Top Bar Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Row( @@ -497,9 +494,8 @@ class _SetupScreenState extends ConsumerState { ), ) else - const SizedBox(width: 48), // Spacer + const SizedBox(width: 48), const Spacer(), - // Progress Indicator SizedBox( width: 48, height: 48, @@ -530,7 +526,6 @@ class _SetupScreenState extends ConsumerState { ), ), - // Content Expanded( child: PageView( controller: _pageController, diff --git a/lib/screens/track_metadata_screen.dart b/lib/screens/track_metadata_screen.dart index c34e7400..d39c04bc 100644 --- a/lib/screens/track_metadata_screen.dart +++ b/lib/screens/track_metadata_screen.dart @@ -627,7 +627,6 @@ class _TrackMetadataScreenState extends ConsumerState { return Stack( fit: StackFit.expand, children: [ - // Full-screen cover background if (_hasPath(_embeddedCoverPreviewPath)) Image.file( File(_embeddedCoverPreviewPath!), @@ -657,7 +656,6 @@ class _TrackMetadataScreenState extends ConsumerState { color: colorScheme.onSurfaceVariant, ), ), - // Bottom gradient for readability Positioned( left: 0, right: 0, @@ -676,7 +674,6 @@ class _TrackMetadataScreenState extends ConsumerState { ), ), ), - // Track info overlay at bottom Positioned( left: 20, right: 20, @@ -2606,7 +2603,6 @@ class _TrackMetadataScreenState extends ConsumerState { ), const SizedBox(height: 20), - // Target format Text( context.l10n.trackConvertTargetFormat, style: Theme.of(context).textTheme.titleSmall?.copyWith( @@ -2639,7 +2635,6 @@ class _TrackMetadataScreenState extends ConsumerState { ), const SizedBox(height: 16), - // Bitrate Text( context.l10n.trackConvertBitrate, style: Theme.of(context).textTheme.titleSmall?.copyWith( @@ -2664,7 +2659,6 @@ class _TrackMetadataScreenState extends ConsumerState { ), const SizedBox(height: 24), - // Convert button SizedBox( width: double.infinity, child: FilledButton( @@ -2750,7 +2744,6 @@ class _TrackMetadataScreenState extends ConsumerState { SnackBar(content: Text(context.l10n.trackConvertConverting)), ); - // Step 1: Read metadata from file (fallback to known item metadata). final metadata = _buildFallbackMetadata(); try { final result = await PlatformBridge.readFileMetadata(cleanFilePath); @@ -2768,7 +2761,6 @@ class _TrackMetadataScreenState extends ConsumerState { _log.w('readFileMetadata threw, using fallback metadata: $e'); } - // Step 2: Extract cover art to temp file String? coverPath; try { final tempDir = await getTemporaryDirectory(); @@ -2783,7 +2775,6 @@ class _TrackMetadataScreenState extends ConsumerState { } } catch (_) {} - // Step 3: Handle SAF vs regular file String workingPath = cleanFilePath; final isSaf = _isSafFile; String? safTempPath; @@ -2803,7 +2794,6 @@ class _TrackMetadataScreenState extends ConsumerState { workingPath = safTempPath; } - // Step 4: Convert final newPath = await FFmpegService.convertAudioFormat( inputPath: workingPath, targetFormat: targetFormat.toLowerCase(), @@ -2838,7 +2828,6 @@ class _TrackMetadataScreenState extends ConsumerState { final newQuality = _buildConvertedQualityLabel(targetFormat, bitrate); - // Step 5: Handle SAF write-back if (isSaf) { final treeUri = _downloadItem?.downloadTreeUri; final relativeDir = _downloadItem?.safRelativeDir ?? ''; @@ -3064,7 +3053,6 @@ class _TrackMetadataScreenState extends ConsumerState { // Also remove from local library database // ref.read(localLibraryProvider.notifier).removeItem(_localLibraryItem!.id); } else { - // Existing download history deletion logic try { await deleteFile(cleanFilePath); } catch (e) { @@ -3661,7 +3649,6 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { expand: false, builder: (context, scrollController) => Column( children: [ - // Handle bar Padding( padding: const EdgeInsets.only(top: 12, bottom: 8), child: Container( @@ -3673,7 +3660,6 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { ), ), ), - // Title row Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Row( @@ -3698,7 +3684,6 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { ), ), const SizedBox(height: 12), - // Fields Expanded( child: ListView( controller: scrollController, diff --git a/lib/screens/tutorial_screen.dart b/lib/screens/tutorial_screen.dart index b93ed5b7..04dfcf9c 100644 --- a/lib/screens/tutorial_screen.dart +++ b/lib/screens/tutorial_screen.dart @@ -90,7 +90,6 @@ class _TutorialScreenState extends ConsumerState { body: SafeArea( child: Column( children: [ - // Top Navigation Bar Padding( padding: EdgeInsets.symmetric( horizontal: topBarPaddingH, @@ -112,7 +111,6 @@ class _TutorialScreenState extends ConsumerState { ), ), - // Skip button TextButton( onPressed: _skipTutorial, style: TextButton.styleFrom( @@ -131,7 +129,6 @@ class _TutorialScreenState extends ConsumerState { ), ), - // Main Content Area Expanded( child: PageView( controller: _pageController, @@ -218,12 +215,10 @@ class _TutorialScreenState extends ConsumerState { ), ), - // Bottom Control Area Padding( padding: const EdgeInsets.all(24), child: Column( children: [ - // Expressive Page Indicators Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(_totalPages, (index) { @@ -246,7 +241,6 @@ class _TutorialScreenState extends ConsumerState { }), ), SizedBox(height: bottomGap), - // Action Button SizedBox( width: double.infinity, height: actionButtonHeight, @@ -402,7 +396,6 @@ class _InteractiveSearchExampleState extends State<_InteractiveSearchExample> { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Search Input TextField( controller: _controller, onChanged: (value) { @@ -428,7 +421,6 @@ class _InteractiveSearchExampleState extends State<_InteractiveSearchExample> { ), ), - // Result Placeholder AnimatedSize( duration: const Duration(milliseconds: 400), curve: Curves.easeOutBack, @@ -541,7 +533,6 @@ class _InteractiveDownloadExampleState _isCompleted = true; }); - // Reset after a delay await Future.delayed(const Duration(seconds: 2)); if (mounted) { setState(() { diff --git a/lib/services/ffmpeg_service.dart b/lib/services/ffmpeg_service.dart index b352162f..2b28ca9d 100644 --- a/lib/services/ffmpeg_service.dart +++ b/lib/services/ffmpeg_service.dart @@ -198,7 +198,7 @@ class FFmpegService { final trimmedKey = decryptionKey.trim(); if (trimmedKey.isEmpty) return inputPath; - // Amazon encrypted streams are commonly MP4 container with FLAC audio. + // Encrypted streams are commonly MP4 container with FLAC audio. // Prefer FLAC output to avoid MP4 muxing errors during decrypt copy. final preferredExt = inputPath.toLowerCase().endsWith('.m4a') ? '.flac' @@ -217,7 +217,10 @@ class FFmpegService { required String key, }) { final audioMap = mapAudioOnly ? '-map 0:a ' : ''; - return '-v error -decryption_key "$key" -i "$inputPath" $audioMap-c copy "$outputPath" -y'; + // Force MOV demuxer: -decryption_key is only supported by the MOV/MP4 + // demuxer. The input may carry a .flac extension (SAF mode) while actually + // containing an encrypted M4A stream, so we must override auto-detection. + return '-v error -decryption_key "$key" -f mov -i "$inputPath" $audioMap-c copy "$outputPath" -y'; } final keyCandidates = _buildDecryptionKeyCandidates(trimmedKey); @@ -627,7 +630,7 @@ class FFmpegService { return null; } - static Future startAmazonLiveDecryptedStream({ + static Future startEncryptedLiveDecryptedStream({ required String encryptedStreamUrl, required String decryptionKey, String preferredFormat = 'flac', @@ -1225,7 +1228,6 @@ class FFmpegService { final extension = format == 'opus' ? '.opus' : '.mp3'; final outputPath = _buildOutputPath(inputPath, extension); - // Step 1: Convert audio String command; if (format == 'opus') { command = @@ -1245,7 +1247,6 @@ class FFmpegService { return null; } - // Step 2: Embed metadata + cover into the converted file. // Treat embed failure as conversion failure when metadata/cover was requested. final hasMetadata = metadata.values.any((v) => v.trim().isNotEmpty); final hasCover = coverPath != null && coverPath.trim().isNotEmpty; @@ -1281,7 +1282,6 @@ class FFmpegService { } } - // Step 3: Delete original if requested if (deleteOriginal) { try { await File(inputPath).delete(); diff --git a/lib/services/history_database.dart b/lib/services/history_database.dart index 5a3c1739..619b424d 100644 --- a/lib/services/history_database.dart +++ b/lib/services/history_database.dart @@ -104,8 +104,6 @@ class HistoryDatabase { } } - // ==================== iOS Path Normalization ==================== - /// Pattern to match iOS container paths /// Example: /var/mobile/Containers/Data/Application/UUID-HERE/Documents/... static final _iosContainerPattern = RegExp( @@ -325,8 +323,6 @@ class HistoryDatabase { }; } - // ==================== CRUD Operations ==================== - /// Insert or update a history item Future upsert(Map json) async { final db = await database; diff --git a/lib/services/platform_bridge.dart b/lib/services/platform_bridge.dart index a0b35d54..840958ab 100644 --- a/lib/services/platform_bridge.dart +++ b/lib/services/platform_bridge.dart @@ -403,8 +403,6 @@ class PlatformBridge { return jsonDecode(result as String) as Map; } - // ==================== LYRICS PROVIDER SETTINGS ==================== - /// Sets the lyrics provider order. Providers not in the list are disabled. static Future setLyricsProviders(List providers) async { final providersJSON = jsonEncode(providers); @@ -1060,8 +1058,6 @@ class PlatformBridge { } } - // ==================== LOCAL LIBRARY SCANNING ==================== - /// Set the directory for caching extracted cover art static Future setLibraryCoverCacheDir(String cacheDir) async { _log.i('setLibraryCoverCacheDir: $cacheDir'); @@ -1261,5 +1257,4 @@ class PlatformBridge { await _channel.invokeMethod('clearStoreCache'); } - // ==================== YOUTUBE / COBALT ==================== } diff --git a/lib/widgets/donate_icons.dart b/lib/widgets/donate_icons.dart index 16e83ef8..21ba4195 100644 --- a/lib/widgets/donate_icons.dart +++ b/lib/widgets/donate_icons.dart @@ -28,14 +28,12 @@ class _KofiPainter extends CustomPainter { ..color = color ..style = PaintingStyle.fill; - // Cup body final cup = RRect.fromRectAndRadius( Rect.fromLTWH(s * 0.08, s * 0.28, s * 0.62, s * 0.52), Radius.circular(s * 0.12), ); canvas.drawRRect(cup, paint); - // Handle final handlePaint = Paint() ..color = color ..style = PaintingStyle.stroke diff --git a/lib/widgets/download_service_picker.dart b/lib/widgets/download_service_picker.dart index 881b6101..69eb4f1d 100644 --- a/lib/widgets/download_service_picker.dart +++ b/lib/widgets/download_service_picker.dart @@ -67,17 +67,6 @@ const _builtInServices = [ ), ], ), - BuiltInService( - id: 'amazon', - label: 'Amazon', - qualityOptions: [ - QualityOption( - id: 'LOSSLESS', - label: 'FLAC Best Available', - description: 'Amazon API delivers the best available lossless quality', - ), - ], - ), BuiltInService( id: 'deezer', label: 'Deezer', @@ -209,7 +198,6 @@ class _DownloadServicePickerState extends ConsumerState { return ext.qualityOptions; } - // Default fallback options return [ const QualityOption( id: 'DEFAULT', diff --git a/pubspec.yaml b/pubspec.yaml index ff4f6473..638e579d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: spotiflac_android -description: Download Spotify tracks in FLAC from Tidal, Qobuz & Amazon Music +description: Download Spotify tracks in FLAC from Tidal, Qobuz & Deezer publish_to: "none" -version: 3.7.1+104 +version: 3.7.2+105 environment: sdk: ^3.10.0 diff --git a/site/docs.html b/site/docs.html index 2ad5c7f6..6a148a93 100644 --- a/site/docs.html +++ b/site/docs.html @@ -965,7 +965,7 @@ skipBuiltInFallback boolean No -If true, don't fallback to built-in providers (Tidal/Qobuz/Amazon) when extension download fails +If true, don't fallback to built-in providers (Tidal/Qobuz/Deezer) when extension download fails minAppVersion diff --git a/site/index.html b/site/index.html index 572d1152..47999b34 100644 --- a/site/index.html +++ b/site/index.html @@ -4,12 +4,12 @@ SpotiFLAC Mobile - Lossless Music Downloader - + - + @@ -404,7 +404,7 @@

SpotiFLAC Mobile

-

Download music in true lossless FLAC from Tidal, Qobuz & Amazon Music — no account required.

+

Download music in true lossless FLAC from Tidal, Qobuz & Deezer — no account required.

@@ -451,7 +451,7 @@

Multiple Providers

-

Download from Tidal, Qobuz, Amazon Music, and more. Automatic fallback if a source is unavailable.

+

Download from Tidal, Qobuz, Deezer, and more via extensions. Automatic fallback if a source is unavailable.

@@ -494,11 +494,11 @@
Why is my download failing with "Song not found"? -
The track may not be available on Tidal, Qobuz, or Amazon Music. Try enabling more download services in Settings > Download > Provider Priority, or install additional extensions from the Store.
+
The track may not be available on the streaming services. Try enabling more download services in Settings > Download > Provider Priority, or install additional extensions like Amazon Music from the Store.
Why are some tracks downloading in lower quality? -
Quality depends on what's available from the streaming service. Tidal offers up to 24-bit/192kHz, Qobuz up to 24-bit/192kHz, and Amazon up to 24-bit/48kHz. The app automatically selects the best available quality.
+
Quality depends on what's available from the streaming service and extensions. Built-in providers: Tidal offers up to 24-bit/192kHz, Qobuz up to 24-bit/192kHz, and Deezer up to 16-bit/44.1kHz.
Can I download entire playlists? From 2c897992c547043827075dead6a6a9dd380f5733 Mon Sep 17 00:00:00 2001 From: zarzet Date: Sun, 8 Mar 2026 04:28:16 +0700 Subject: [PATCH 089/104] feat: resolve audio metadata from file, backfill placeholder quality labels with actual bit depth and sample rate --- go_backend/exports.go | 7 + lib/providers/download_queue_provider.dart | 255 ++++++++++++++++++++- lib/screens/queue_tab.dart | 20 +- lib/screens/track_metadata_screen.dart | 139 ++++++++--- lib/services/history_database.dart | 23 ++ lib/utils/string_utils.dart | 44 ++++ 6 files changed, 441 insertions(+), 47 deletions(-) diff --git a/go_backend/exports.go b/go_backend/exports.go index 72edd394..08efb1fb 100644 --- a/go_backend/exports.go +++ b/go_backend/exports.go @@ -784,6 +784,7 @@ func CleanupConnections() { func ReadFileMetadata(filePath string) (string, error) { lower := strings.ToLower(filePath) isFlac := strings.HasSuffix(lower, ".flac") + isM4A := strings.HasSuffix(lower, ".m4a") || strings.HasSuffix(lower, ".aac") isMp3 := strings.HasSuffix(lower, ".mp3") isOgg := strings.HasSuffix(lower, ".opus") || strings.HasSuffix(lower, ".ogg") @@ -833,6 +834,12 @@ func ReadFileMetadata(filePath string) (string, error) { result["duration"] = int(quality.TotalSamples / int64(quality.SampleRate)) } } + } else if isM4A { + quality, qualityErr := GetM4AQuality(filePath) + if qualityErr == nil { + result["bit_depth"] = quality.BitDepth + result["sample_rate"] = quality.SampleRate + } } else if isMp3 { meta, err := ReadID3Tags(filePath) if err == nil && meta != nil { diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index 999a65a0..1e69a2fa 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -251,9 +251,11 @@ class DownloadHistoryState { class DownloadHistoryNotifier extends Notifier { static const int _safRepairBatchSize = 20; static const int _safRepairMaxPerLaunch = 60; + static const int _audioMetadataBackfillMaxPerLaunch = 24; final HistoryDatabase _db = HistoryDatabase.instance; bool _isLoaded = false; bool _isSafRepairInProgress = false; + bool _isAudioMetadataBackfillInProgress = false; @override DownloadHistoryState build() { @@ -298,9 +300,19 @@ class DownloadHistoryNotifier extends Notifier { maxItems: _safRepairMaxPerLaunch, ); await cleanupOrphanedDownloads(); + await _backfillAudioMetadata( + state.items, + maxItems: _audioMetadataBackfillMaxPerLaunch, + ); }); } else { - Future.microtask(() => cleanupOrphanedDownloads()); + Future.microtask(() async { + await cleanupOrphanedDownloads(); + await _backfillAudioMetadata( + state.items, + maxItems: _audioMetadataBackfillMaxPerLaunch, + ); + }); } } catch (e, stack) { _historyLog.e('Failed to load history from database: $e', e, stack); @@ -429,6 +441,157 @@ class DownloadHistoryNotifier extends Notifier { } } + int? _readPositiveInt(dynamic value) { + if (value == null) return null; + if (value is num) { + final asInt = value.toInt(); + return asInt > 0 ? asInt : null; + } + final parsed = int.tryParse(value.toString()); + if (parsed == null || parsed <= 0) return null; + return parsed; + } + + bool _supportsAudioMetadataProbe(String filePath) { + final trimmed = filePath.trim().toLowerCase(); + if (trimmed.isEmpty) return false; + if (trimmed.startsWith('content://')) return true; + return trimmed.endsWith('.flac') || + trimmed.endsWith('.m4a') || + trimmed.endsWith('.aac') || + trimmed.endsWith('.mp3') || + trimmed.endsWith('.opus') || + trimmed.endsWith('.ogg'); + } + + bool _shouldBackfillAudioMetadata(DownloadHistoryItem item) { + if (!_supportsAudioMetadataProbe(item.filePath)) { + return false; + } + + final trimmedPath = item.filePath.trim().toLowerCase(); + final hasResolvedSpecs = + item.bitDepth != null && + item.bitDepth! > 0 && + item.sampleRate != null && + item.sampleRate! > 0; + final needsLosslessSpecProbe = + !hasResolvedSpecs && + (trimmedPath.endsWith('.flac') || + trimmedPath.endsWith('.m4a') || + trimmedPath.endsWith('.aac') || + trimmedPath.startsWith('content://')); + + if (hasResolvedSpecs && !isPlaceholderQualityLabel(item.quality)) { + return false; + } + + return needsLosslessSpecProbe || + isPlaceholderQualityLabel(item.quality) || + normalizeOptionalString(item.quality) == null; + } + + Future?> _probeAudioMetadata( + String filePath, { + String? fallbackQuality, + }) async { + if (!_supportsAudioMetadataProbe(filePath)) { + return null; + } + + try { + final result = await PlatformBridge.readFileMetadata(filePath); + if (result['error'] != null) { + return null; + } + + final bitDepth = _readPositiveInt(result['bit_depth']); + final sampleRate = _readPositiveInt(result['sample_rate']); + final quality = buildDisplayAudioQuality( + bitDepth: bitDepth, + sampleRate: sampleRate, + storedQuality: fallbackQuality, + ); + + if (quality == null && bitDepth == null && sampleRate == null) { + return null; + } + + return { + 'quality': quality, + 'bitDepth': bitDepth, + 'sampleRate': sampleRate, + }; + } catch (e) { + _historyLog.d('Audio metadata probe failed for $filePath: $e'); + return null; + } + } + + Future _backfillAudioMetadata( + List items, { + required int maxItems, + }) async { + if (_isAudioMetadataBackfillInProgress || items.isEmpty) { + return; + } + _isAudioMetadataBackfillInProgress = true; + + try { + var refreshedCount = 0; + + for (final item in items) { + if (refreshedCount >= maxItems) { + break; + } + if (!_shouldBackfillAudioMetadata(item)) { + continue; + } + + final probed = await _probeAudioMetadata( + item.filePath, + fallbackQuality: item.quality, + ); + if (probed == null) { + continue; + } + + final resolvedQuality = normalizeOptionalString( + probed['quality'] as String?, + ); + final resolvedBitDepth = probed['bitDepth'] as int?; + final resolvedSampleRate = probed['sampleRate'] as int?; + + final qualityChanged = + resolvedQuality != null && resolvedQuality != item.quality; + final bitDepthChanged = + resolvedBitDepth != null && resolvedBitDepth != item.bitDepth; + final sampleRateChanged = + resolvedSampleRate != null && resolvedSampleRate != item.sampleRate; + + if (!qualityChanged && !bitDepthChanged && !sampleRateChanged) { + continue; + } + + await updateAudioMetadataForItem( + id: item.id, + quality: resolvedQuality, + bitDepth: resolvedBitDepth, + sampleRate: resolvedSampleRate, + ); + refreshedCount++; + } + + if (refreshedCount > 0) { + _historyLog.i( + 'Audio metadata backfill refreshed $refreshedCount items', + ); + } + } finally { + _isAudioMetadataBackfillInProgress = false; + } + } + Future reloadFromStorage() async { await _loadFromDatabase(); } @@ -509,6 +672,39 @@ class DownloadHistoryNotifier extends Notifier { return DownloadHistoryItem.fromJson(json); } + Future updateAudioMetadataForItem({ + required String id, + String? quality, + int? bitDepth, + int? sampleRate, + }) async { + final index = state.items.indexWhere((item) => item.id == id); + if (index < 0) return; + + final current = state.items[index]; + final updated = current.copyWith( + quality: quality, + bitDepth: bitDepth, + sampleRate: sampleRate, + ); + + if (updated.quality == current.quality && + updated.bitDepth == current.bitDepth && + updated.sampleRate == current.sampleRate) { + return; + } + + final updatedItems = [...state.items]; + updatedItems[index] = updated; + state = state.copyWith(items: updatedItems); + await _db.updateAudioMetadata( + id, + newQuality: quality, + newBitDepth: bitDepth, + newSampleRate: sampleRate, + ); + } + Future updateMetadataForItem({ required String id, required String trackName, @@ -3496,9 +3692,7 @@ class DownloadQueueNotifier extends Notifier { final decryptionKey = (result['decryption_key'] as String?)?.trim() ?? ''; - if (!wasExisting && - decryptionKey.isNotEmpty && - filePath != null) { + if (!wasExisting && decryptionKey.isNotEmpty && filePath != null) { _log.i('Encrypted stream detected, decrypting via FFmpeg...'); updateItemStatus(item.id, DownloadStatus.downloading, progress: 0.9); @@ -4331,6 +4525,50 @@ class DownloadQueueNotifier extends Notifier { normalizeOptionalString(copyright) ?? normalizeOptionalString(existingInHistory?.copyright); + int? finalBitDepth = backendBitDepth; + int? finalSampleRate = backendSampleRate; + final lowerFilePath = filePath.toLowerCase(); + final canProbeFinalMetadata = + filePath.startsWith('content://') || + lowerFilePath.endsWith('.flac') || + lowerFilePath.endsWith('.m4a') || + lowerFilePath.endsWith('.aac') || + lowerFilePath.endsWith('.mp3') || + lowerFilePath.endsWith('.opus') || + lowerFilePath.endsWith('.ogg'); + + if (canProbeFinalMetadata) { + try { + final metadata = await PlatformBridge.readFileMetadata(filePath); + if (metadata['error'] == null) { + final probedBitDepth = metadata['bit_depth'] is num + ? (metadata['bit_depth'] as num).toInt() + : int.tryParse(metadata['bit_depth']?.toString() ?? ''); + final probedSampleRate = metadata['sample_rate'] is num + ? (metadata['sample_rate'] as num).toInt() + : int.tryParse(metadata['sample_rate']?.toString() ?? ''); + + if (probedBitDepth != null && probedBitDepth > 0) { + finalBitDepth = probedBitDepth; + } + if (probedSampleRate != null && probedSampleRate > 0) { + finalSampleRate = probedSampleRate; + } + + final resolvedQuality = buildDisplayAudioQuality( + bitDepth: finalBitDepth, + sampleRate: finalSampleRate, + storedQuality: actualQuality, + ); + if (resolvedQuality != null) { + actualQuality = resolvedQuality; + } + } + } catch (e) { + _log.d('Final audio metadata probe failed for $filePath: $e'); + } + } + _log.d('Saving to history - coverUrl: ${trackToDownload.coverUrl}'); final historyAlbumArtist = @@ -4338,9 +4576,12 @@ class DownloadQueueNotifier extends Notifier { ? resolvedAlbumArtist : null; - final isMp3 = filePath.endsWith('.mp3'); - final historyBitDepth = isMp3 ? null : backendBitDepth; - final historySampleRate = isMp3 ? null : backendSampleRate; + final isLossyOutput = + lowerFilePath.endsWith('.mp3') || + lowerFilePath.endsWith('.opus') || + lowerFilePath.endsWith('.ogg'); + final historyBitDepth = isLossyOutput ? null : finalBitDepth; + final historySampleRate = isLossyOutput ? null : finalSampleRate; ref .read(downloadHistoryProvider.notifier) diff --git a/lib/screens/queue_tab.dart b/lib/screens/queue_tab.dart index 774a6e26..9299320c 100644 --- a/lib/screens/queue_tab.dart +++ b/lib/screens/queue_tab.dart @@ -29,6 +29,7 @@ import 'package:spotiflac_android/screens/downloaded_album_screen.dart'; import 'package:spotiflac_android/screens/library_tracks_folder_screen.dart'; import 'package:spotiflac_android/screens/local_album_screen.dart'; import 'package:spotiflac_android/utils/clickable_metadata.dart'; +import 'package:spotiflac_android/utils/string_utils.dart'; enum LibraryItemSource { downloaded, local } @@ -70,7 +71,11 @@ class UnifiedLibraryItem { albumName: item.albumName, coverUrl: item.coverUrl, filePath: item.filePath, - quality: item.quality, + quality: buildDisplayAudioQuality( + bitDepth: item.bitDepth, + sampleRate: item.sampleRate, + storedQuality: item.quality, + ), addedAt: item.downloadedAt, source: LibraryItemSource.downloaded, historyItem: item, @@ -80,15 +85,18 @@ class UnifiedLibraryItem { factory UnifiedLibraryItem.fromLocalLibrary(LocalLibraryItem item) { String? quality; if (item.bitrate != null && item.bitrate! > 0) { - // Lossy format with bitrate - final fmt = item.format?.toUpperCase() ?? ''; - quality = '$fmt ${item.bitrate}kbps'.trim(); + quality = buildDisplayAudioQuality( + bitrateKbps: item.bitrate, + format: item.format, + ); } else if (item.bitDepth != null && item.bitDepth! > 0 && item.sampleRate != null) { // Lossless format with actual bit depth - quality = - '${item.bitDepth}bit/${(item.sampleRate! / 1000).toStringAsFixed(1)}kHz'; + quality = buildDisplayAudioQuality( + bitDepth: item.bitDepth, + sampleRate: item.sampleRate, + ); } return UnifiedLibraryItem( id: 'local_${item.id}', diff --git a/lib/screens/track_metadata_screen.dart b/lib/screens/track_metadata_screen.dart index d39c04bc..b4a3238e 100644 --- a/lib/screens/track_metadata_screen.dart +++ b/lib/screens/track_metadata_screen.dart @@ -66,6 +66,7 @@ class _TrackMetadataScreenState extends ConsumerState { bool _isInstrumental = false; // Track if detected as instrumental bool _isConverting = false; // Track convert operation in progress bool _hasMetadataChanges = false; + bool _hasLoadedResolvedAudioMetadata = false; Map? _editedMetadata; // Overrides after metadata edit String? _embeddedCoverPreviewPath; final ScrollController _scrollController = ScrollController(); @@ -240,6 +241,12 @@ class _TrackMetadataScreenState extends ConsumerState { if (mounted && exists && _lyrics == null && !_lyricsLoading) { _fetchLyrics(); } + if (mounted && + exists && + !_isLocalItem && + !_hasLoadedResolvedAudioMetadata) { + unawaited(_refreshResolvedAudioMetadataFromFile()); + } if (mounted && exists && !_hasPath(_embeddedCoverPreviewPath)) { final cachedPath = _getCachedEmbeddedCoverPreviewPathIfValid( _coverCacheKey, @@ -274,6 +281,61 @@ class _TrackMetadataScreenState extends ConsumerState { await _cleanupTempFileAndParent(path); } + Future _refreshResolvedAudioMetadataFromFile() async { + if (_isLocalItem || + _downloadItem == null || + _hasLoadedResolvedAudioMetadata) { + return; + } + + _hasLoadedResolvedAudioMetadata = true; + + try { + final metadata = await PlatformBridge.readFileMetadata(cleanFilePath); + if (metadata['error'] != null) { + return; + } + + final resolvedBitDepth = _readPositiveInt(metadata['bit_depth']); + final resolvedSampleRate = _readPositiveInt(metadata['sample_rate']); + final resolvedQuality = buildDisplayAudioQuality( + bitDepth: resolvedBitDepth ?? bitDepth, + sampleRate: resolvedSampleRate ?? sampleRate, + storedQuality: _quality, + ); + final shouldPersistResolvedAudioMetadata = + resolvedBitDepth != null || + resolvedSampleRate != null || + (isPlaceholderQualityLabel(_quality) && resolvedQuality != null); + + if ((resolvedBitDepth != null || + resolvedSampleRate != null || + isPlaceholderQualityLabel(_quality)) && + mounted) { + setState(() { + _editedMetadata = { + ...?_editedMetadata, + if (resolvedBitDepth != null) 'bit_depth': resolvedBitDepth, + if (resolvedSampleRate != null) 'sample_rate': resolvedSampleRate, + }; + }); + } + + if (shouldPersistResolvedAudioMetadata) { + await ref + .read(downloadHistoryProvider.notifier) + .updateAudioMetadataForItem( + id: _downloadItem!.id, + quality: resolvedQuality, + bitDepth: resolvedBitDepth, + sampleRate: resolvedSampleRate, + ); + } + } catch (e) { + _log.w('Failed to resolve audio metadata from file: $e'); + } + } + void _cleanupTempFileAndParentSync(String? path) { if (!_hasPath(path)) return; final file = File(path!); @@ -426,9 +488,13 @@ class _TrackMetadataScreenState extends ConsumerState { int? get duration => _isLocalItem ? _localLibraryItem!.duration : _downloadItem!.duration; int? get bitDepth => - _isLocalItem ? _localLibraryItem!.bitDepth : _downloadItem!.bitDepth; + _readPositiveInt(_editedMetadata?['bit_depth']) ?? + (_isLocalItem ? _localLibraryItem!.bitDepth : _downloadItem!.bitDepth); int? get sampleRate => - _isLocalItem ? _localLibraryItem!.sampleRate : _downloadItem!.sampleRate; + _readPositiveInt(_editedMetadata?['sample_rate']) ?? + (_isLocalItem + ? _localLibraryItem!.sampleRate + : _downloadItem!.sampleRate); int? get _localBitrate => _isLocalItem ? _localLibraryItem!.bitrate : null; String get _filePath => @@ -452,6 +518,32 @@ class _TrackMetadataScreenState extends ConsumerState { String? get _quality => _isLocalItem ? null : _downloadItem!.quality; + int? _readPositiveInt(dynamic value) { + if (value == null) return null; + if (value is num) { + final asInt = value.toInt(); + return asInt > 0 ? asInt : null; + } + final parsed = int.tryParse(value.toString()); + if (parsed == null || parsed <= 0) return null; + return parsed; + } + + String? get _displayAudioQuality { + final fileName = _extractFileNameFromPathOrUri(cleanFilePath); + final fileExt = fileName.contains('.') + ? fileName.split('.').last.toUpperCase() + : null; + + return buildDisplayAudioQuality( + bitDepth: bitDepth, + sampleRate: sampleRate, + bitrateKbps: _isLocalItem ? _localBitrate : null, + format: _isLocalItem ? (_localLibraryItem!.format ?? fileExt) : fileExt, + storedQuality: _quality, + ); + } + String get cleanFilePath { final path = _filePath; return path.startsWith('EXISTS:') ? path.substring(7) : path; @@ -723,7 +815,8 @@ class _TrackMetadataScreenState extends ConsumerState { spacing: 8, runSpacing: 8, children: [ - if (_quality != null && _quality!.isNotEmpty) + if (_displayAudioQuality != null && + _displayAudioQuality!.isNotEmpty) Container( padding: const EdgeInsets.symmetric( horizontal: 12, @@ -734,7 +827,7 @@ class _TrackMetadataScreenState extends ConsumerState { borderRadius: BorderRadius.circular(20), ), child: Text( - _quality!, + _displayAudioQuality!, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w600, @@ -961,34 +1054,7 @@ class _TrackMetadataScreenState extends ConsumerState { } Widget _buildMetadataGrid(BuildContext context, ColorScheme colorScheme) { - // Determine audio quality string - prefer stored quality from download - String? audioQualityStr; - final fileName = _extractFileNameFromPathOrUri(cleanFilePath); - final fileExt = fileName.contains('.') - ? fileName.split('.').last.toUpperCase() - : ''; - - // Use stored quality from download history if available - if (_quality != null && _quality!.isNotEmpty) { - audioQualityStr = _quality; - } else if (_isLocalItem && _localBitrate != null && _localBitrate! > 0) { - // Lossy local file with bitrate info - final fmt = _localLibraryItem!.format?.toUpperCase() ?? fileExt; - audioQualityStr = '$fmt ${_localBitrate}kbps'; - } else if (bitDepth != null && bitDepth! > 0 && sampleRate != null) { - // Lossless file with actual bit depth (FLAC, ALAC) - final sampleRateKHz = (sampleRate! / 1000).toStringAsFixed(1); - audioQualityStr = '$bitDepth-bit/${sampleRateKHz}kHz'; - } else { - // Fallback based on file extension for legacy items - if (fileExt == 'MP3') { - audioQualityStr = 'MP3'; - } else if (fileExt == 'OPUS' || fileExt == 'OGG') { - audioQualityStr = 'Opus'; - } else if (fileExt == 'M4A' || fileExt == 'AAC') { - audioQualityStr = 'AAC'; - } - } + final audioQualityStr = _displayAudioQuality; final items = <_MetadataItem>[ _MetadataItem(context.l10n.trackTrackName, trackName), @@ -1090,7 +1156,8 @@ class _TrackMetadataScreenState extends ConsumerState { final fileExtension = fileName.contains('.') ? fileName.split('.').last.toUpperCase() : 'Unknown'; - final lossyBitrateLabel = _extractLossyBitrateLabel(_quality); + final resolvedQuality = _displayAudioQuality; + final lossyBitrateLabel = _extractLossyBitrateLabel(resolvedQuality); return Card( elevation: 0, @@ -1220,7 +1287,11 @@ class _TrackMetadataScreenState extends ConsumerState { borderRadius: BorderRadius.circular(20), ), child: Text( - '$bitDepth-bit/${(sampleRate! / 1000).toStringAsFixed(1)}kHz', + buildDisplayAudioQuality( + bitDepth: bitDepth, + sampleRate: sampleRate, + ) ?? + '', style: TextStyle( color: colorScheme.onTertiaryContainer, fontWeight: FontWeight.w600, diff --git a/lib/services/history_database.dart b/lib/services/history_database.dart index 619b424d..75bfaada 100644 --- a/lib/services/history_database.dart +++ b/lib/services/history_database.dart @@ -498,6 +498,29 @@ class HistoryDatabase { await db.update('history', values, where: 'id = ?', whereArgs: [id]); } + Future updateAudioMetadata( + String id, { + String? newQuality, + int? newBitDepth, + int? newSampleRate, + }) async { + final db = await database; + final values = {}; + if (newQuality != null) { + values['quality'] = newQuality; + } + if (newBitDepth != null) { + values['bit_depth'] = newBitDepth; + } + if (newSampleRate != null) { + values['sample_rate'] = newSampleRate; + } + if (values.isEmpty) { + return; + } + await db.update('history', values, where: 'id = ?', whereArgs: [id]); + } + /// Get all file paths from download history /// Used to exclude downloaded files from local library scan Future> getAllFilePaths() async { diff --git a/lib/utils/string_utils.dart b/lib/utils/string_utils.dart index 8394d430..9f4cc164 100644 --- a/lib/utils/string_utils.dart +++ b/lib/utils/string_utils.dart @@ -5,3 +5,47 @@ String? normalizeOptionalString(String? value) { if (trimmed.toLowerCase() == 'null') return null; return trimmed; } + +String formatSampleRateKHz(int sampleRate) { + final khz = sampleRate / 1000; + final precision = sampleRate % 1000 == 0 ? 0 : 1; + return '${khz.toStringAsFixed(precision)}kHz'; +} + +String? buildDisplayAudioQuality({ + int? bitDepth, + int? sampleRate, + int? bitrateKbps, + String? format, + String? storedQuality, +}) { + if (bitrateKbps != null && bitrateKbps > 0) { + final normalizedFormat = normalizeOptionalString(format)?.toUpperCase(); + return normalizedFormat != null + ? '$normalizedFormat ${bitrateKbps}kbps' + : '${bitrateKbps}kbps'; + } + + if (bitDepth != null && + bitDepth > 0 && + sampleRate != null && + sampleRate > 0) { + return '$bitDepth-bit/${formatSampleRateKHz(sampleRate)}'; + } + + return normalizeOptionalString(storedQuality); +} + +bool isPlaceholderQualityLabel(String? quality) { + final normalized = normalizeOptionalString(quality)?.toLowerCase(); + if (normalized == null) return false; + + return const { + 'best', + 'lossless', + 'hi-res', + 'hi-res-max', + 'high', + 'cd', + }.contains(normalized); +} From c35857bb61a28229e063eb9837241f1de7204a9c Mon Sep 17 00:00:00 2001 From: zarzet Date: Sun, 8 Mar 2026 14:57:13 +0700 Subject: [PATCH 090/104] fix(ios): local library scan fails on iOS due to missing security-scoped bookmark access --- ios/Runner/AppDelegate.swift | 129 ++++ lib/models/settings.dart | 5 + lib/models/settings.g.dart | 2 + lib/providers/local_library_provider.dart | 58 +- lib/providers/settings_provider.dart | 13 + lib/screens/local_album_screen.dart | 14 +- lib/screens/queue_tab.dart | 563 ++++++++++-------- .../settings/cache_management_page.dart | 8 +- .../settings/library_settings_page.dart | 42 +- lib/services/platform_bridge.dart | 41 ++ 10 files changed, 602 insertions(+), 273 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index f3c93676..4cb53238 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -15,6 +15,9 @@ import Gobackend // Import Go framework private var libraryScanProgressEventSink: FlutterEventSink? private var lastLibraryScanProgressPayload: String? + /// Currently accessed security-scoped URL for library folder + private var activeSecurityScopedURL: URL? + override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? @@ -915,6 +918,26 @@ import Gobackend // Import Go framework let response = GobackendReadAudioMetadataJSON(filePath, &error) if let error = error { throw error } return response + + // iOS Security-Scoped Bookmark for Local Library + case "resolveIosBookmark": + let args = call.arguments as! [String: Any] + let bookmarkBase64 = args["bookmark"] as! String + return try resolveIosBookmark(bookmarkBase64) + + case "startAccessingIosBookmark": + let args = call.arguments as! [String: Any] + let bookmarkBase64 = args["bookmark"] as! String + return try startAccessingIosBookmark(bookmarkBase64) + + case "stopAccessingIosBookmark": + stopAccessingIosBookmark() + return nil + + case "createIosBookmarkFromPath": + let args = call.arguments as! [String: Any] + let path = args["path"] as! String + return try createIosBookmarkFromPath(path) // Lyrics Provider Settings case "setLyricsProviders": @@ -954,6 +977,112 @@ import Gobackend // Import Go framework ) } } + + // MARK: - iOS Security-Scoped Bookmark Helpers + + /// Create a security-scoped bookmark from a filesystem path (e.g. from FilePicker). + /// The path must currently be accessible (within the same picker session). + /// Returns base64-encoded bookmark data. + private func createIosBookmarkFromPath(_ path: String) throws -> String { + let url = URL(fileURLWithPath: path) + do { + let bookmarkData = try url.bookmarkData( + options: .minimalBookmark, + includingResourceValuesForKeys: nil, + relativeTo: nil + ) + return bookmarkData.base64EncodedString() + } catch { + throw NSError( + domain: "SpotiFLAC", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Failed to create bookmark for path \(path): \(error.localizedDescription)"] + ) + } + } + + /// Resolve a base64-encoded security-scoped bookmark and return the resolved path. + /// Does NOT start accessing the resource. + private func resolveIosBookmark(_ bookmarkBase64: String) throws -> String { + guard let bookmarkData = Data(base64Encoded: bookmarkBase64) else { + throw NSError( + domain: "SpotiFLAC", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Invalid base64 bookmark data"] + ) + } + + var isStale = false + let url: URL + do { + url = try URL( + resolvingBookmarkData: bookmarkData, + options: [], + relativeTo: nil, + bookmarkDataIsStale: &isStale + ) + } catch { + throw NSError( + domain: "SpotiFLAC", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Failed to resolve bookmark: \(error.localizedDescription)"] + ) + } + + return url.path + } + + /// Resolve a base64-encoded bookmark, start accessing the security-scoped resource, + /// and return the resolved filesystem path. The resource stays accessed until + /// `stopAccessingIosBookmark()` is called. + private func startAccessingIosBookmark(_ bookmarkBase64: String) throws -> String { + // Stop any previously accessed resource first + stopAccessingIosBookmark() + + guard let bookmarkData = Data(base64Encoded: bookmarkBase64) else { + throw NSError( + domain: "SpotiFLAC", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Invalid base64 bookmark data"] + ) + } + + var isStale = false + let url: URL + do { + url = try URL( + resolvingBookmarkData: bookmarkData, + options: [], + relativeTo: nil, + bookmarkDataIsStale: &isStale + ) + } catch { + throw NSError( + domain: "SpotiFLAC", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Failed to resolve bookmark: \(error.localizedDescription)"] + ) + } + + guard url.startAccessingSecurityScopedResource() else { + throw NSError( + domain: "SpotiFLAC", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Failed to start accessing security-scoped resource at \(url.path)"] + ) + } + + activeSecurityScopedURL = url + return url.path + } + + /// Stop accessing the currently active security-scoped resource, if any. + private func stopAccessingIosBookmark() { + if let url = activeSecurityScopedURL { + url.stopAccessingSecurityScopedResource() + activeSecurityScopedURL = nil + } + } } private final class ClosureStreamHandler: NSObject, FlutterStreamHandler { diff --git a/lib/models/settings.dart b/lib/models/settings.dart index d42b9fa6..8501a4e3 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -57,6 +57,8 @@ class AppSettings { final bool localLibraryEnabled; // Enable local library scanning final String localLibraryPath; // Path to scan for audio files + final String + localLibraryBookmark; // Base64-encoded iOS security-scoped bookmark final bool localLibraryShowDuplicates; // Show indicator when searching for existing tracks @@ -122,6 +124,7 @@ class AppSettings { this.songLinkRegion = 'US', this.localLibraryEnabled = false, this.localLibraryPath = '', + this.localLibraryBookmark = '', this.localLibraryShowDuplicates = true, this.hasCompletedTutorial = false, this.lyricsProviders = const [ @@ -185,6 +188,7 @@ class AppSettings { String? songLinkRegion, bool? localLibraryEnabled, String? localLibraryPath, + String? localLibraryBookmark, bool? localLibraryShowDuplicates, bool? hasCompletedTutorial, List? lyricsProviders, @@ -249,6 +253,7 @@ class AppSettings { songLinkRegion: songLinkRegion ?? this.songLinkRegion, localLibraryEnabled: localLibraryEnabled ?? this.localLibraryEnabled, localLibraryPath: localLibraryPath ?? this.localLibraryPath, + localLibraryBookmark: localLibraryBookmark ?? this.localLibraryBookmark, localLibraryShowDuplicates: localLibraryShowDuplicates ?? this.localLibraryShowDuplicates, hasCompletedTutorial: hasCompletedTutorial ?? this.hasCompletedTutorial, diff --git a/lib/models/settings.g.dart b/lib/models/settings.g.dart index 933178e3..cc47ca8e 100644 --- a/lib/models/settings.g.dart +++ b/lib/models/settings.g.dart @@ -55,6 +55,7 @@ AppSettings _$AppSettingsFromJson(Map json) => AppSettings( songLinkRegion: json['songLinkRegion'] as String? ?? 'US', localLibraryEnabled: json['localLibraryEnabled'] as bool? ?? false, localLibraryPath: json['localLibraryPath'] as String? ?? '', + localLibraryBookmark: json['localLibraryBookmark'] as String? ?? '', localLibraryShowDuplicates: json['localLibraryShowDuplicates'] as bool? ?? true, hasCompletedTutorial: json['hasCompletedTutorial'] as bool? ?? false, @@ -128,6 +129,7 @@ Map _$AppSettingsToJson( 'songLinkRegion': instance.songLinkRegion, 'localLibraryEnabled': instance.localLibraryEnabled, 'localLibraryPath': instance.localLibraryPath, + 'localLibraryBookmark': instance.localLibraryBookmark, 'localLibraryShowDuplicates': instance.localLibraryShowDuplicates, 'hasCompletedTutorial': instance.hasCompletedTutorial, 'lyricsProviders': instance.lyricsProviders, diff --git a/lib/providers/local_library_provider.dart b/lib/providers/local_library_provider.dart index afa25f66..fe9f21ad 100644 --- a/lib/providers/local_library_provider.dart +++ b/lib/providers/local_library_provider.dart @@ -272,6 +272,7 @@ class LocalLibraryNotifier extends Notifier { Future startScan( String folderPath, { bool forceFullScan = false, + String? iosBookmark, }) async { if (state.isScanning) { _log.w('Scan already in progress'); @@ -316,8 +317,27 @@ class LocalLibraryNotifier extends Notifier { _startProgressPolling(); + // On iOS, start accessing the security-scoped bookmark so the Go backend + // can read files outside the app sandbox. + String? resolvedPath; + bool didStartSecurityAccess = false; + if (Platform.isIOS && iosBookmark != null && iosBookmark.isNotEmpty) { + resolvedPath = + await PlatformBridge.startAccessingIosBookmark(iosBookmark); + if (resolvedPath != null) { + didStartSecurityAccess = true; + _log.i('Started iOS security-scoped access: $resolvedPath'); + } else { + _log.w( + 'Failed to start iOS security-scoped access, ' + 'falling back to original path', + ); + } + } + final effectiveFolderPath = resolvedPath ?? folderPath; + try { - final isSaf = folderPath.startsWith('content://'); + final isSaf = effectiveFolderPath.startsWith('content://'); // Get all file paths from download history to exclude them. // Merge DB + in-memory state to avoid race when a fresh download has not @@ -344,8 +364,8 @@ class LocalLibraryNotifier extends Notifier { if (forceFullScan) { // Full scan path - ignores existing data final results = isSaf - ? await PlatformBridge.scanSafTree(folderPath) - : await PlatformBridge.scanLibraryFolder(folderPath); + ? await PlatformBridge.scanSafTree(effectiveFolderPath) + : await PlatformBridge.scanLibraryFolder(effectiveFolderPath); if (_scanCancelRequested) { state = state.copyWith(isScanning: false, scanWasCancelled: true); await _showScanCancelledNotification(); @@ -424,12 +444,12 @@ class LocalLibraryNotifier extends Notifier { final Map result; if (isSaf) { result = await PlatformBridge.scanSafTreeIncremental( - folderPath, + effectiveFolderPath, existingFiles, ); } else { result = await PlatformBridge.scanLibraryFolderIncremental( - folderPath, + effectiveFolderPath, existingFiles, ); } @@ -553,6 +573,10 @@ class LocalLibraryNotifier extends Notifier { state = state.copyWith(isScanning: false, scanWasCancelled: false); await _showScanFailedNotification(e.toString()); } finally { + if (didStartSecurityAccess) { + await PlatformBridge.stopAccessingIosBookmark(); + _log.i('Stopped iOS security-scoped access'); + } _stopProgressPolling(); } } @@ -807,12 +831,26 @@ class LocalLibraryNotifier extends Notifier { return decoded; } - Future cleanupMissingFiles() async { - final removed = await _db.cleanupMissingFiles(); - if (removed > 0) { - await reloadFromStorage(); + Future cleanupMissingFiles({String? iosBookmark}) async { + bool didStartSecurityAccess = false; + if (Platform.isIOS && iosBookmark != null && iosBookmark.isNotEmpty) { + final resolved = + await PlatformBridge.startAccessingIosBookmark(iosBookmark); + if (resolved != null) { + didStartSecurityAccess = true; + } + } + try { + final removed = await _db.cleanupMissingFiles(); + if (removed > 0) { + await reloadFromStorage(); + } + return removed; + } finally { + if (didStartSecurityAccess) { + await PlatformBridge.stopAccessingIosBookmark(); + } } - return removed; } Future clearLibrary() async { diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 133bdeec..a7858319 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -532,6 +532,19 @@ class SettingsNotifier extends Notifier { _saveSettings(); } + void setLocalLibraryBookmark(String bookmark) { + state = state.copyWith(localLibraryBookmark: bookmark); + _saveSettings(); + } + + void setLocalLibraryPathAndBookmark(String path, String bookmark) { + state = state.copyWith( + localLibraryPath: path, + localLibraryBookmark: bookmark, + ); + _saveSettings(); + } + void setLocalLibraryShowDuplicates(bool show) { state = state.copyWith(localLibraryShowDuplicates: show); _saveSettings(); diff --git a/lib/screens/local_album_screen.dart b/lib/screens/local_album_screen.dart index cd8a4c7b..79eca05b 100644 --- a/lib/screens/local_album_screen.dart +++ b/lib/screens/local_album_screen.dart @@ -487,6 +487,7 @@ class _LocalAlbumScreenState extends ConsumerState { }, ), leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( @@ -726,6 +727,7 @@ class _LocalAlbumScreenState extends ConsumerState { trailing: _isSelectionMode ? null : IconButton( + tooltip: 'Play track', onPressed: () => _openFile(track), icon: Icon(Icons.play_arrow, color: colorScheme.primary), style: IconButton.styleFrom( @@ -950,13 +952,18 @@ class _LocalAlbumScreenState extends ConsumerState { return; } - final localLibraryPath = ref.read(settingsProvider).localLibraryPath.trim(); + final settings = ref.read(settingsProvider); + final localLibraryPath = settings.localLibraryPath.trim(); + final iosBookmark = settings.localLibraryBookmark; try { if (localLibraryPath.isNotEmpty && !ref.read(localLibraryProvider).isScanning) { await ref .read(localLibraryProvider.notifier) - .startScan(localLibraryPath); + .startScan( + localLibraryPath, + iosBookmark: iosBookmark.isNotEmpty ? iosBookmark : null, + ); } else { await ref.read(localLibraryProvider.notifier).reloadFromStorage(); } @@ -1447,6 +1454,9 @@ class _LocalAlbumScreenState extends ConsumerState { children: [ IconButton.filledTonal( onPressed: _exitSelectionMode, + tooltip: MaterialLocalizations.of( + context, + ).closeButtonTooltip, icon: const Icon(Icons.close), style: IconButton.styleFrom( backgroundColor: colorScheme.surfaceContainerHighest, diff --git a/lib/screens/queue_tab.dart b/lib/screens/queue_tab.dart index 9299320c..1aa3bad3 100644 --- a/lib/screens/queue_tab.dart +++ b/lib/screens/queue_tab.dart @@ -1064,6 +1064,9 @@ class _QueueTabState extends ConsumerState { children: [ IconButton.filledTonal( onPressed: _exitPlaylistSelectionMode, + tooltip: MaterialLocalizations.of( + context, + ).closeButtonTooltip, icon: const Icon(Icons.close), style: IconButton.styleFrom( backgroundColor: colorScheme.surfaceContainerHighest, @@ -3068,37 +3071,41 @@ class _QueueTabState extends ConsumerState { ), ); - return GestureDetector( - onTap: onTap, - onLongPress: onLongPress, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: 1, - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: cover, + return Semantics( + button: true, + label: 'Open $title, $count ${count == 1 ? 'item' : 'items'}', + child: GestureDetector( + onTap: onTap, + onLongPress: onLongPress, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: 1, + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: cover, + ), ), - ), - const SizedBox(height: 6), - Text( - title, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith(fontWeight: FontWeight.w500), - ), - Text( - '$count ${count == 1 ? 'item' : 'items'}', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.labelSmall?.copyWith( - color: colorScheme.onSurfaceVariant, + const SizedBox(height: 6), + Text( + title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of( + context, + ).textTheme.bodySmall?.copyWith(fontWeight: FontWeight.w500), ), - ), - ], + Text( + '$count ${count == 1 ? 'item' : 'items'}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), ), ); } @@ -3913,112 +3920,117 @@ class _QueueTabState extends ConsumerState { final embeddedCoverPath = _resolveDownloadedEmbeddedCoverPath( album.sampleFilePath, ); - return GestureDetector( - onTap: () => _navigateToDownloadedAlbum(album), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Stack( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(12), - child: embeddedCoverPath != null - ? Image.file( - File(embeddedCoverPath), - fit: BoxFit.cover, - width: double.infinity, - height: double.infinity, - cacheWidth: 300, - cacheHeight: 300, - errorBuilder: (context, error, stackTrace) => - Container( - color: colorScheme.surfaceContainerHighest, - child: Center( - child: Icon( - Icons.album, - color: colorScheme.onSurfaceVariant, - size: 48, + return Semantics( + button: true, + label: + 'Open album ${album.albumName} by ${album.artistName}, ${album.tracks.length} tracks', + child: GestureDetector( + onTap: () => _navigateToDownloadedAlbum(album), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: embeddedCoverPath != null + ? Image.file( + File(embeddedCoverPath), + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + cacheWidth: 300, + cacheHeight: 300, + errorBuilder: (context, error, stackTrace) => + Container( + color: colorScheme.surfaceContainerHighest, + child: Center( + child: Icon( + Icons.album, + color: colorScheme.onSurfaceVariant, + size: 48, + ), ), ), + ) + : album.coverUrl != null + ? CachedNetworkImage( + imageUrl: album.coverUrl!, + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + memCacheWidth: 300, + memCacheHeight: 300, + cacheManager: CoverCacheManager.instance, + ) + : Container( + color: colorScheme.surfaceContainerHighest, + child: Center( + child: Icon( + Icons.album, + color: colorScheme.onSurfaceVariant, + size: 48, ), - ) - : album.coverUrl != null - ? CachedNetworkImage( - imageUrl: album.coverUrl!, - fit: BoxFit.cover, - width: double.infinity, - height: double.infinity, - memCacheWidth: 300, - memCacheHeight: 300, - cacheManager: CoverCacheManager.instance, - ) - : Container( - color: colorScheme.surfaceContainerHighest, - child: Center( - child: Icon( - Icons.album, - color: colorScheme.onSurfaceVariant, - size: 48, ), ), - ), - ), - Positioned( - right: 8, - bottom: 8, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.music_note, - size: 12, - color: colorScheme.onPrimaryContainer, - ), - const SizedBox(width: 4), - Text( - '${album.tracks.length}', - style: TextStyle( + ), + Positioned( + right: 8, + bottom: 8, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.music_note, + size: 12, color: colorScheme.onPrimaryContainer, - fontSize: 12, - fontWeight: FontWeight.bold, ), - ), - ], + const SizedBox(width: 4), + Text( + '${album.tracks.length}', + style: TextStyle( + color: colorScheme.onPrimaryContainer, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], + ), ), ), - ), - ], + ], + ), ), - ), - const SizedBox(height: 8), - Text( - album.albumName, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of( - context, - ).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600), - ), - ClickableArtistName( - artistName: album.artistName, - coverUrl: album.coverUrl, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: colorScheme.onSurfaceVariant, + const SizedBox(height: 8), + Text( + album.albumName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600), ), - ), - ], + ClickableArtistName( + artistName: album.artistName, + coverUrl: album.coverUrl, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), ), ); } @@ -4029,102 +4041,107 @@ class _QueueTabState extends ConsumerState { _GroupedLocalAlbum album, ColorScheme colorScheme, ) { - return GestureDetector( - onTap: () => _navigateToLocalAlbum(album), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Stack( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(12), - child: album.coverPath != null - ? Image.file( - File(album.coverPath!), - fit: BoxFit.cover, - width: double.infinity, - height: double.infinity, - cacheWidth: 300, - cacheHeight: 300, - errorBuilder: (context, error, stackTrace) => - Container( - color: colorScheme.surfaceContainerHighest, - child: Center( - child: Icon( - Icons.album, - color: colorScheme.onSurfaceVariant, - size: 48, + return Semantics( + button: true, + label: + 'Open local album ${album.albumName} by ${album.artistName}, ${album.tracks.length} tracks', + child: GestureDetector( + onTap: () => _navigateToLocalAlbum(album), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: album.coverPath != null + ? Image.file( + File(album.coverPath!), + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + cacheWidth: 300, + cacheHeight: 300, + errorBuilder: (context, error, stackTrace) => + Container( + color: colorScheme.surfaceContainerHighest, + child: Center( + child: Icon( + Icons.album, + color: colorScheme.onSurfaceVariant, + size: 48, + ), ), ), + ) + : Container( + color: colorScheme.surfaceContainerHighest, + child: Center( + child: Icon( + Icons.album, + color: colorScheme.onSurfaceVariant, + size: 48, ), - ) - : Container( - color: colorScheme.surfaceContainerHighest, - child: Center( - child: Icon( - Icons.album, - color: colorScheme.onSurfaceVariant, - size: 48, ), ), - ), - ), - // "Local" badge instead of track count - Positioned( - right: 8, - bottom: 8, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: colorScheme.tertiaryContainer, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.folder, - size: 12, - color: colorScheme.onTertiaryContainer, - ), - const SizedBox(width: 4), - Text( - '${album.tracks.length}', - style: TextStyle( + ), + // "Local" badge instead of track count + Positioned( + right: 8, + bottom: 8, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: colorScheme.tertiaryContainer, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.folder, + size: 12, color: colorScheme.onTertiaryContainer, - fontSize: 12, - fontWeight: FontWeight.bold, ), - ), - ], + const SizedBox(width: 4), + Text( + '${album.tracks.length}', + style: TextStyle( + color: colorScheme.onTertiaryContainer, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], + ), ), ), - ), - ], + ], + ), ), - ), - const SizedBox(height: 8), - Text( - album.albumName, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of( - context, - ).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600), - ), - ClickableArtistName( - artistName: album.artistName, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: colorScheme.onSurfaceVariant, + const SizedBox(height: 8), + Text( + album.albumName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600), ), - ), - ], + ClickableArtistName( + artistName: album.artistName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), ), ); } @@ -4356,13 +4373,18 @@ class _QueueTabState extends ConsumerState { return; } - final localLibraryPath = ref.read(settingsProvider).localLibraryPath.trim(); + final settings = ref.read(settingsProvider); + final localLibraryPath = settings.localLibraryPath.trim(); + final iosBookmark = settings.localLibraryBookmark; try { if (localLibraryPath.isNotEmpty && !ref.read(localLibraryProvider).isScanning) { await ref .read(localLibraryProvider.notifier) - .startScan(localLibraryPath); + .startScan( + localLibraryPath, + iosBookmark: iosBookmark.isNotEmpty ? iosBookmark : null, + ); } else { await ref.read(localLibraryProvider.notifier).reloadFromStorage(); } @@ -4996,6 +5018,9 @@ class _QueueTabState extends ConsumerState { children: [ IconButton.filledTonal( onPressed: _exitSelectionMode, + tooltip: MaterialLocalizations.of( + context, + ).closeButtonTooltip, icon: const Icon(Icons.close), style: IconButton.styleFrom( backgroundColor: colorScheme.surfaceContainerHighest, @@ -5275,18 +5300,27 @@ class _QueueTabState extends ConsumerState { ), ); case DownloadStatus.finalizing: - return SizedBox( - width: 40, - height: 40, - child: Stack( - alignment: Alignment.center, - children: [ - CircularProgressIndicator( - strokeWidth: 3, - color: colorScheme.tertiary, - ), - Icon(Icons.edit_note, color: colorScheme.tertiary, size: 16), - ], + return Semantics( + label: 'Finalizing download', + child: SizedBox( + width: 40, + height: 40, + child: Stack( + alignment: Alignment.center, + children: [ + CircularProgressIndicator( + strokeWidth: 3, + color: colorScheme.tertiary, + ), + ExcludeSemantics( + child: Icon( + Icons.edit_note, + color: colorScheme.tertiary, + size: 16, + ), + ), + ], + ), ), ); case DownloadStatus.completed: @@ -5314,18 +5348,32 @@ class _QueueTabState extends ConsumerState { ), ) else - Icon(Icons.error_outline, color: colorScheme.error, size: 20), - const SizedBox(width: 4), - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: colorScheme.primaryContainer, - shape: BoxShape.circle, + Semantics( + label: 'Downloaded file missing', + child: ExcludeSemantics( + child: Icon( + Icons.error_outline, + color: colorScheme.error, + size: 20, + ), + ), ), - child: Icon( - Icons.check, - color: colorScheme.onPrimaryContainer, - size: 20, + const SizedBox(width: 4), + Semantics( + label: 'Download completed', + child: ExcludeSemantics( + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: colorScheme.primaryContainer, + shape: BoxShape.circle, + ), + child: Icon( + Icons.check, + color: colorScheme.onPrimaryContainer, + size: 20, + ), + ), ), ), ], @@ -5888,27 +5936,34 @@ class _QueueTabState extends ConsumerState { valueListenable: fileExistsListenable, builder: (context, fileExists, child) { return fileExists - ? GestureDetector( - onTap: () => _openFile( - item.filePath, - title: item.trackName, - artist: item.artistName, - album: item.albumName, - coverUrl: - item.coverUrl ?? - item.localCoverPath ?? - '', - ), - child: Container( - padding: const EdgeInsets.all(6), - decoration: BoxDecoration( - color: colorScheme.primary, - shape: BoxShape.circle, + ? Semantics( + button: true, + label: + 'Play ${item.trackName} by ${item.artistName}', + child: GestureDetector( + onTap: () => _openFile( + item.filePath, + title: item.trackName, + artist: item.artistName, + album: item.albumName, + coverUrl: + item.coverUrl ?? + item.localCoverPath ?? + '', ), - child: Icon( - Icons.play_arrow, - color: colorScheme.onPrimary, - size: 16, + child: Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: colorScheme.primary, + shape: BoxShape.circle, + ), + child: ExcludeSemantics( + child: Icon( + Icons.play_arrow, + color: colorScheme.onPrimary, + size: 16, + ), + ), ), ), ) diff --git a/lib/screens/settings/cache_management_page.dart b/lib/screens/settings/cache_management_page.dart index c01c685f..edb3eed3 100644 --- a/lib/screens/settings/cache_management_page.dart +++ b/lib/screens/settings/cache_management_page.dart @@ -9,6 +9,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/providers/download_queue_provider.dart'; import 'package:spotiflac_android/providers/local_library_provider.dart'; +import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/services/cover_cache_manager.dart'; import 'package:spotiflac_android/services/platform_bridge.dart'; import 'package:spotiflac_android/utils/app_bar_layout.dart'; @@ -311,9 +312,12 @@ class _CacheManagementPageState extends ConsumerState { final orphanedDownloads = await ref .read(downloadHistoryProvider.notifier) .cleanupOrphanedDownloads(); + final iosBookmark = ref.read(settingsProvider).localLibraryBookmark; final missingLibraryEntries = await ref .read(localLibraryProvider.notifier) - .cleanupMissingFiles(); + .cleanupMissingFiles( + iosBookmark: iosBookmark.isNotEmpty ? iosBookmark : null, + ); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( @@ -384,11 +388,13 @@ class _CacheManagementPageState extends ConsumerState { backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), actions: [ IconButton( + tooltip: 'Refresh', onPressed: _isBusy ? null : _refreshOverview, icon: const Icon(Icons.refresh), ), diff --git a/lib/screens/settings/library_settings_page.dart b/lib/screens/settings/library_settings_page.dart index 2d91f4a1..e71c1046 100644 --- a/lib/screens/settings/library_settings_page.dart +++ b/lib/screens/settings/library_settings_page.dart @@ -132,7 +132,23 @@ class _LibrarySettingsPageState extends ConsumerState { // Fallback for older devices final result = await FilePicker.platform.getDirectoryPath(); if (result != null) { - ref.read(settingsProvider.notifier).setLocalLibraryPath(result); + if (Platform.isIOS) { + // On iOS, create a security-scoped bookmark so we can access + // this folder across app restarts and from the Go backend. + final bookmark = + await PlatformBridge.createIosBookmarkFromPath(result); + if (bookmark != null && bookmark.isNotEmpty) { + ref + .read(settingsProvider.notifier) + .setLocalLibraryPathAndBookmark(result, bookmark); + } else { + // Bookmark creation failed; save path anyway (works for + // app-internal folders like Documents/). + ref.read(settingsProvider.notifier).setLocalLibraryPath(result); + } + } else { + ref.read(settingsProvider.notifier).setLocalLibraryPath(result); + } } } } @@ -140,6 +156,7 @@ class _LibrarySettingsPageState extends ConsumerState { Future _startScan({bool forceFullScan = false}) async { final settings = ref.read(settingsProvider); final libraryPath = settings.localLibraryPath; + final iosBookmark = settings.localLibraryBookmark; if (libraryPath.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( @@ -148,7 +165,14 @@ class _LibrarySettingsPageState extends ConsumerState { return; } - if (!libraryPath.startsWith('content://') && + // On iOS with a bookmark, try resolving the bookmark first to validate + // access instead of checking the path directly (which may fail outside + // the app sandbox). + if (Platform.isIOS && iosBookmark.isNotEmpty) { + // Bookmark will be resolved inside startScan; skip Directory.exists + // check since security-scoped paths are not accessible without the + // bookmark being activated. + } else if (!libraryPath.startsWith('content://') && !await Directory(libraryPath).exists()) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( @@ -158,9 +182,11 @@ class _LibrarySettingsPageState extends ConsumerState { return; } - await ref - .read(localLibraryProvider.notifier) - .startScan(libraryPath, forceFullScan: forceFullScan); + await ref.read(localLibraryProvider.notifier).startScan( + libraryPath, + forceFullScan: forceFullScan, + iosBookmark: iosBookmark.isNotEmpty ? iosBookmark : null, + ); } Future _cancelScan() async { @@ -200,9 +226,12 @@ class _LibrarySettingsPageState extends ConsumerState { } Future _cleanupMissingFiles() async { + final iosBookmark = ref.read(settingsProvider).localLibraryBookmark; final removed = await ref .read(localLibraryProvider.notifier) - .cleanupMissingFiles(); + .cleanupMissingFiles( + iosBookmark: iosBookmark.isNotEmpty ? iosBookmark : null, + ); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -230,6 +259,7 @@ class _LibrarySettingsPageState extends ConsumerState { backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), diff --git a/lib/services/platform_bridge.dart b/lib/services/platform_bridge.dart index 840958ab..86ae6d0d 100644 --- a/lib/services/platform_bridge.dart +++ b/lib/services/platform_bridge.dart @@ -1155,6 +1155,47 @@ class PlatformBridge { await _channel.invokeMethod('cancelLibraryScan'); } + // MARK: - iOS Security-Scoped Bookmark + + /// Create a security-scoped bookmark from a filesystem path picked by + /// FilePicker on iOS. Must be called while the picker session is still active. + /// Returns base64-encoded bookmark data, or null on failure. + static Future createIosBookmarkFromPath(String path) async { + try { + final result = await _channel.invokeMethod('createIosBookmarkFromPath', { + 'path': path, + }); + return result as String?; + } catch (e) { + _log.w('Failed to create iOS bookmark from path: $e'); + return null; + } + } + + /// Resolve a base64-encoded iOS security-scoped bookmark and start accessing + /// the resource. Returns the resolved filesystem path. + /// The resource stays accessed until [stopAccessingIosBookmark] is called. + static Future startAccessingIosBookmark(String bookmark) async { + try { + final result = await _channel.invokeMethod('startAccessingIosBookmark', { + 'bookmark': bookmark, + }); + return result as String?; + } catch (e) { + _log.w('Failed to start accessing iOS bookmark: $e'); + return null; + } + } + + /// Stop accessing the currently active iOS security-scoped resource. + static Future stopAccessingIosBookmark() async { + try { + await _channel.invokeMethod('stopAccessingIosBookmark'); + } catch (e) { + _log.w('Failed to stop accessing iOS bookmark: $e'); + } + } + /// Read metadata from a single audio file static Future?> readAudioMetadata( String filePath, From 75a2bec8d5c52c338098a18fb3a2f5ef29004d13 Mon Sep 17 00:00:00 2001 From: zarzet Date: Sun, 8 Mar 2026 15:08:13 +0700 Subject: [PATCH 091/104] chore: accessibility improvements, Semantics wrappers, and tooltip additions across screens --- CHANGELOG.md | 33 + lib/screens/album_screen.dart | 1 + lib/screens/artist_screen.dart | 248 +++--- lib/screens/downloaded_album_screen.dart | 5 + lib/screens/home_tab.dart | 221 +++--- lib/screens/library_playlists_screen.dart | 1 + lib/screens/library_tracks_folder_screen.dart | 10 +- lib/screens/playlist_screen.dart | 1 + lib/screens/search_screen.dart | 6 +- lib/screens/settings/about_page.dart | 1 + .../settings/appearance_settings_page.dart | 17 +- lib/screens/settings/donate_page.dart | 30 +- .../settings/download_settings_page.dart | 1 + .../settings/extension_detail_page.dart | 737 +++++++++--------- lib/screens/settings/extensions_page.dart | 1 + lib/screens/settings/log_screen.dart | 251 +++--- .../settings/options_settings_page.dart | 1 + .../settings/provider_priority_page.dart | 1 + lib/screens/setup_screen.dart | 4 + .../store/extension_details_screen.dart | 22 +- lib/screens/store_tab.dart | 1 + lib/screens/track_metadata_screen.dart | 2 + lib/screens/tutorial_screen.dart | 81 +- lib/widgets/collapsing_header.dart | 29 +- lib/widgets/priority_settings_scaffold.dart | 1 + .../track_collection_quick_actions.dart | 1 + 26 files changed, 971 insertions(+), 736 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49976882..ec8b7254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,43 @@ ### Fixed - **Deezer Downloads Timing Out**: Deezer downloads were failing with "context deadline exceeded" on larger files. Now uses the proper download timeout, matching Tidal and Qobuz. +- **iOS Local Library Scan Fails**: Local library scanning was failing on iOS because the app lost access to user-picked folders after the FilePicker session ended. Implemented iOS security-scoped bookmark system: + - When a library folder is picked on iOS, a security-scoped bookmark is created and persisted in settings (`localLibraryBookmark`) + - Before each scan, the bookmark is resolved and security-scoped access is started; access is released in `finally` block after scan completes + - `cleanupMissingFiles` also activates the bookmark before checking file existence on iOS + - New `AppDelegate.swift` method channel handlers: `createIosBookmarkFromPath`, `startAccessingIosBookmark`, `stopAccessingIosBookmark`, `resolveIosBookmark` + - New `PlatformBridge` methods: `createIosBookmarkFromPath()`, `startAccessingIosBookmark()`, `stopAccessingIosBookmark()` + - All scan call-sites (Library Settings, Queue tab, Local Album screen) now pass the iOS bookmark to `startScan()` ### Added - **Amazon Music Extension**: Available in `extension/Amazon-SpotiFLAC/` — same functionality as before, now as an installable extension. +- **Accessibility Tooltips**: Added localized tooltips to all `IconButton` and `PopupMenuButton` widgets across the entire UI for screen reader and long-press discoverability + - Back buttons use `MaterialLocalizations.backButtonTooltip` + - Close buttons use `MaterialLocalizations.closeButtonTooltip` + - Menu buttons use `MaterialLocalizations.showMenuTooltip` + - Search buttons use `MaterialLocalizations.searchFieldLabel` + - Contextual actions use descriptive labels: "Play track", "Dismiss", "Clear search", "Change folder", "Refresh" + - Screens affected: Album, Artist, Playlist, Downloaded Album, Local Album, Home, Search, Queue, Library Playlists, Library Tracks Folder, Setup, Tutorial, Track Metadata, Store, Extension Store Details, and all Settings sub-pages (About, Appearance, Cache Management, Donate, Download, Extensions, Extension Detail, Library, Log, Options, Provider Priority) +- **Semantics Wrappers**: Added `Semantics` widgets to interactive elements that previously had no accessibility information + - Album tiles in Artist screen: announces selection state and album name + - Recently downloaded track tiles in Home tab: announces track name and artist + - Explore items (albums/artists/playlists) in Home tab: announces item type and name + - Color palette picker in Appearance settings: announces selected state and color hex value + - Download button demo in Tutorial screen: added `ExcludeSemantics` on icon to prevent duplicate screen reader announcements + - Queue tab playlist cards: announces playlist name and item count + - Queue tab downloaded album cards: announces album name, artist, and track count + - Queue tab local album cards: announces album name, artist, and track count + - Queue tab play button on completed downloads: announces track name and artist with `ExcludeSemantics` on icon + - Queue tab download status indicators: "Finalizing download", "Download completed", "Downloaded file missing" labels with `ExcludeSemantics` on icons + +### Improved + +- **Code Formatting**: Reformatted and corrected indentation across multiple files to comply with Dart style guidelines + - `extension_detail_page.dart`: Fixed `SliverAppBar` and all subsequent slivers indentation (was 2 spaces short) + - `log_screen.dart`: Fixed `SliverAppBar` indentation alignment + - `donate_page.dart`: Reformatted ternary expressions and `_cr` function body + - `library_tracks_folder_screen.dart`: Minor line-break formatting --- diff --git a/lib/screens/album_screen.dart b/lib/screens/album_screen.dart index f226a80f..ba8d9d1d 100644 --- a/lib/screens/album_screen.dart +++ b/lib/screens/album_screen.dart @@ -488,6 +488,7 @@ class _AlbumScreenState extends ConsumerState { }, ), leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( diff --git a/lib/screens/artist_screen.dart b/lib/screens/artist_screen.dart index cdf58522..a1c37333 100644 --- a/lib/screens/artist_screen.dart +++ b/lib/screens/artist_screen.dart @@ -1184,6 +1184,7 @@ class _ArtistScreenState extends ConsumerState { ], ), leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( @@ -1571,44 +1572,61 @@ class _ArtistScreenState extends ConsumerState { }) { final isSelected = _selectedAlbumIds.contains(album.id); - return GestureDetector( - onTap: () { - if (_isSelectionMode) { - _toggleAlbumSelection(album.id); - } else { - _navigateToAlbum(album); - } - }, - onLongPress: () { - if (!_isSelectionMode) { - _enterSelectionMode(album.id); - } - }, - child: Container( - width: tileSize, - height: sectionHeight, - margin: const EdgeInsets.symmetric(horizontal: 4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Stack( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(8), - child: album.coverUrl != null - ? CachedNetworkImage( - imageUrl: album.coverUrl!, - width: tileSize, - height: tileSize, - fit: BoxFit.cover, - memCacheWidth: (tileSize * 2).round(), - cacheManager: CoverCacheManager.instance, - placeholder: (context, url) => Container( + return Semantics( + button: true, + selected: _isSelectionMode && isSelected, + label: _isSelectionMode + ? 'Select album ${album.name}' + : 'Open album ${album.name}', + child: GestureDetector( + onTap: () { + if (_isSelectionMode) { + _toggleAlbumSelection(album.id); + } else { + _navigateToAlbum(album); + } + }, + onLongPress: () { + if (!_isSelectionMode) { + _enterSelectionMode(album.id); + } + }, + child: Container( + width: tileSize, + height: sectionHeight, + margin: const EdgeInsets.symmetric(horizontal: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: album.coverUrl != null + ? CachedNetworkImage( + imageUrl: album.coverUrl!, width: tileSize, height: tileSize, - color: colorScheme.surfaceContainerHighest, - ), - errorWidget: (context, url, error) => Container( + fit: BoxFit.cover, + memCacheWidth: (tileSize * 2).round(), + cacheManager: CoverCacheManager.instance, + placeholder: (context, url) => Container( + width: tileSize, + height: tileSize, + color: colorScheme.surfaceContainerHighest, + ), + errorWidget: (context, url, error) => Container( + width: tileSize, + height: tileSize, + color: colorScheme.surfaceContainerHighest, + child: Icon( + Icons.album, + color: colorScheme.onSurfaceVariant, + size: 40, + ), + ), + ) + : Container( width: tileSize, height: tileSize, color: colorScheme.surfaceContainerHighest, @@ -1618,97 +1636,87 @@ class _ArtistScreenState extends ConsumerState { size: 40, ), ), - ) - : Container( - width: tileSize, - height: tileSize, - color: colorScheme.surfaceContainerHighest, - child: Icon( - Icons.album, - color: colorScheme.onSurfaceVariant, - size: 40, + ), + if (_isSelectionMode) + Positioned.fill( + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: isSelected + ? colorScheme.primary.withValues(alpha: 0.3) + : Colors.black.withValues(alpha: 0.1), + border: isSelected + ? Border.all(color: colorScheme.primary, width: 3) + : null, + ), + ), + ), + if (_isSelectionMode) + Positioned( + top: 8, + right: 8, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 28, + height: 28, + decoration: BoxDecoration( + color: isSelected + ? colorScheme.primary + : colorScheme.surface.withValues(alpha: 0.9), + shape: BoxShape.circle, + border: Border.all( + color: isSelected + ? colorScheme.primary + : colorScheme.outline, + width: 2, ), ), - ), - if (_isSelectionMode) - Positioned.fill( - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: isSelected - ? colorScheme.primary.withValues(alpha: 0.3) - : Colors.black.withValues(alpha: 0.1), - border: isSelected - ? Border.all(color: colorScheme.primary, width: 3) + child: isSelected + ? Icon( + Icons.check, + color: colorScheme.onPrimary, + size: 18, + ) : null, ), ), - ), - if (_isSelectionMode) - Positioned( - top: 8, - right: 8, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - width: 28, - height: 28, - decoration: BoxDecoration( - color: isSelected - ? colorScheme.primary - : colorScheme.surface.withValues(alpha: 0.9), - shape: BoxShape.circle, - border: Border.all( - color: isSelected - ? colorScheme.primary - : colorScheme.outline, - width: 2, - ), - ), - child: isSelected - ? Icon( - Icons.check, - color: colorScheme.onPrimary, - size: 18, - ) - : null, - ), - ), - ], - ), - const SizedBox(height: 8), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: Text( - album.name, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w500, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ), - const SizedBox(height: 2), - Text( - album.totalTracks > 0 - ? '${album.releaseDate.length >= 4 ? album.releaseDate.substring(0, 4) : album.releaseDate} ${context.l10n.tracksCount(album.totalTracks)}' - : album.releaseDate.length >= 4 - ? album.releaseDate.substring(0, 4) - : album.releaseDate, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), ], ), - ), - ], + const SizedBox(height: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( + album.name, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(height: 2), + Text( + album.totalTracks > 0 + ? '${album.releaseDate.length >= 4 ? album.releaseDate.substring(0, 4) : album.releaseDate} ${context.l10n.tracksCount(album.totalTracks)}' + : album.releaseDate.length >= 4 + ? album.releaseDate.substring(0, 4) + : album.releaseDate, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), ), ), ); diff --git a/lib/screens/downloaded_album_screen.dart b/lib/screens/downloaded_album_screen.dart index 425dfd4b..468d7aff 100644 --- a/lib/screens/downloaded_album_screen.dart +++ b/lib/screens/downloaded_album_screen.dart @@ -630,6 +630,7 @@ class _DownloadedAlbumScreenState extends ConsumerState { }, ), leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( @@ -851,6 +852,7 @@ class _DownloadedAlbumScreenState extends ConsumerState { trailing: _isSelectionMode ? null : IconButton( + tooltip: 'Play track', onPressed: () => _openFile(track), icon: Icon(Icons.play_arrow, color: colorScheme.primary), style: IconButton.styleFrom( @@ -1326,6 +1328,9 @@ class _DownloadedAlbumScreenState extends ConsumerState { children: [ IconButton.filledTonal( onPressed: _exitSelectionMode, + tooltip: MaterialLocalizations.of( + context, + ).closeButtonTooltip, icon: const Icon(Icons.close), style: IconButton.styleFrom( backgroundColor: colorScheme.surfaceContainerHighest, diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index b20d7b90..f1c3f807 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -1332,24 +1332,49 @@ class _HomeTabState extends ConsumerState ); return KeyedSubtree( key: ValueKey(item.id), - child: GestureDetector( - onTap: () => _navigateToMetadataScreen(item), - child: Container( - width: coverSize, - margin: const EdgeInsets.only(right: 12), - child: Column( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(12), - child: embeddedCoverPath != null - ? Image.file( - File(embeddedCoverPath), - width: coverSize, - height: coverSize, - fit: BoxFit.cover, - cacheWidth: (coverSize * 2).round(), - cacheHeight: (coverSize * 2).round(), - errorBuilder: (_, _, _) => Container( + child: Semantics( + button: true, + label: 'Open track ${item.trackName} by ${item.artistName}', + child: GestureDetector( + onTap: () => _navigateToMetadataScreen(item), + child: Container( + width: coverSize, + margin: const EdgeInsets.only(right: 12), + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: embeddedCoverPath != null + ? Image.file( + File(embeddedCoverPath), + width: coverSize, + height: coverSize, + fit: BoxFit.cover, + cacheWidth: (coverSize * 2).round(), + cacheHeight: (coverSize * 2).round(), + errorBuilder: (_, _, _) => Container( + width: coverSize, + height: coverSize, + color: + colorScheme.surfaceContainerHighest, + child: Icon( + Icons.music_note, + color: colorScheme.onSurfaceVariant, + size: 32, + ), + ), + ) + : item.coverUrl != null + ? CachedNetworkImage( + imageUrl: item.coverUrl!, + width: coverSize, + height: coverSize, + fit: BoxFit.cover, + memCacheWidth: (coverSize * 2).round(), + memCacheHeight: (coverSize * 2).round(), + cacheManager: CoverCacheManager.instance, + ) + : Container( width: coverSize, height: coverSize, color: colorScheme.surfaceContainerHighest, @@ -1359,38 +1384,18 @@ class _HomeTabState extends ConsumerState size: 32, ), ), - ) - : item.coverUrl != null - ? CachedNetworkImage( - imageUrl: item.coverUrl!, - width: coverSize, - height: coverSize, - fit: BoxFit.cover, - memCacheWidth: (coverSize * 2).round(), - memCacheHeight: (coverSize * 2).round(), - cacheManager: CoverCacheManager.instance, - ) - : Container( - width: coverSize, - height: coverSize, - color: colorScheme.surfaceContainerHighest, - child: Icon( - Icons.music_note, - color: colorScheme.onSurfaceVariant, - size: 32, - ), - ), - ), - const SizedBox(height: 6), - Text( - item.trackName, - style: Theme.of(context).textTheme.bodySmall - ?.copyWith(color: colorScheme.onSurfaceVariant), - maxLines: 1, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - ), - ], + ), + const SizedBox(height: 6), + Text( + item.trackName, + style: Theme.of(context).textTheme.bodySmall + ?.copyWith(color: colorScheme.onSurfaceVariant), + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ), + ], + ), ), ), ), @@ -1494,31 +1499,45 @@ class _HomeTabState extends ConsumerState final cardSize = _exploreCardSize(context); final iconSize = cardSize * 0.3; - return GestureDetector( - onTap: () => _navigateToExploreItem(item), - child: SizedBox( - width: cardSize, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 6), - child: Column( - crossAxisAlignment: isArtist - ? CrossAxisAlignment.center - : CrossAxisAlignment.start, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular( - isArtist ? cardSize / 2 : 8, - ), - child: item.coverUrl != null && item.coverUrl!.isNotEmpty - ? CachedNetworkImage( - imageUrl: item.coverUrl!, - width: cardSize, - height: cardSize, - fit: BoxFit.cover, - memCacheWidth: (cardSize * 2).round(), - memCacheHeight: (cardSize * 2).round(), - cacheManager: CoverCacheManager.instance, - errorWidget: (context, url, error) => Container( + return Semantics( + button: true, + label: 'Open ${item.type} ${item.name}', + child: GestureDetector( + onTap: () => _navigateToExploreItem(item), + child: SizedBox( + width: cardSize, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: Column( + crossAxisAlignment: isArtist + ? CrossAxisAlignment.center + : CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + isArtist ? cardSize / 2 : 8, + ), + child: item.coverUrl != null && item.coverUrl!.isNotEmpty + ? CachedNetworkImage( + imageUrl: item.coverUrl!, + width: cardSize, + height: cardSize, + fit: BoxFit.cover, + memCacheWidth: (cardSize * 2).round(), + memCacheHeight: (cardSize * 2).round(), + cacheManager: CoverCacheManager.instance, + errorWidget: (context, url, error) => Container( + width: cardSize, + height: cardSize, + color: colorScheme.surfaceContainerHighest, + child: Icon( + _getIconForType(item.type), + color: colorScheme.onSurfaceVariant, + size: iconSize, + ), + ), + ) + : Container( width: cardSize, height: cardSize, color: colorScheme.surfaceContainerHighest, @@ -1528,42 +1547,32 @@ class _HomeTabState extends ConsumerState size: iconSize, ), ), - ) - : Container( - width: cardSize, - height: cardSize, - color: colorScheme.surfaceContainerHighest, - child: Icon( - _getIconForType(item.type), - color: colorScheme.onSurfaceVariant, - size: iconSize, - ), - ), - ), - const SizedBox(height: 8), - Text( - item.name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - textAlign: isArtist ? TextAlign.center : TextAlign.start, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - fontWeight: FontWeight.w500, - color: colorScheme.onSurface, ), - ), - if (item.artists.isNotEmpty && !isArtist) - ClickableArtistName( - artistName: item.artists, - coverUrl: item.coverUrl, - extensionId: item.providerId, + const SizedBox(height: 8), + Text( + item.name, maxLines: 1, overflow: TextOverflow.ellipsis, + textAlign: isArtist ? TextAlign.center : TextAlign.start, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: colorScheme.onSurfaceVariant, - fontSize: 11, + fontWeight: FontWeight.w500, + color: colorScheme.onSurface, ), ), - ], + if (item.artists.isNotEmpty && !isArtist) + ClickableArtistName( + artistName: item.artists, + coverUrl: item.coverUrl, + extensionId: item.providerId, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, + fontSize: 11, + ), + ), + ], + ), ), ), ), @@ -2018,6 +2027,7 @@ class _HomeTabState extends ConsumerState ), ), IconButton( + tooltip: 'Dismiss', icon: Icon( Icons.close, size: 20, @@ -4379,6 +4389,7 @@ class _QuickPicksPageViewState extends State<_QuickPicksPageView> { ), ), IconButton( + tooltip: MaterialLocalizations.of(context).showMenuTooltip, icon: Icon( Icons.more_vert, color: widget.colorScheme.onSurfaceVariant, diff --git a/lib/screens/library_playlists_screen.dart b/lib/screens/library_playlists_screen.dart index df68b16f..503937f6 100644 --- a/lib/screens/library_playlists_screen.dart +++ b/lib/screens/library_playlists_screen.dart @@ -32,6 +32,7 @@ class LibraryPlaylistsScreen extends ConsumerWidget { backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), diff --git a/lib/screens/library_tracks_folder_screen.dart b/lib/screens/library_tracks_folder_screen.dart index ab437ec6..d6d1fc5e 100644 --- a/lib/screens/library_tracks_folder_screen.dart +++ b/lib/screens/library_tracks_folder_screen.dart @@ -435,6 +435,9 @@ class _LibraryTracksFolderScreenState children: [ IconButton.filledTonal( onPressed: _exitSelectionMode, + tooltip: MaterialLocalizations.of( + context, + ).closeButtonTooltip, icon: const Icon(Icons.close), style: IconButton.styleFrom( backgroundColor: colorScheme.surfaceContainerHighest, @@ -800,6 +803,9 @@ class _LibraryTracksFolderScreenState }, ), leading: IconButton( + tooltip: _isSelectionMode + ? MaterialLocalizations.of(context).closeButtonTooltip + : MaterialLocalizations.of(context).backButtonTooltip, icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( @@ -818,7 +824,8 @@ class _LibraryTracksFolderScreenState ); } - Widget _buildHeaderActionPlaceholder() => const SizedBox(width: 48, height: 48); + Widget _buildHeaderActionPlaceholder() => + const SizedBox(width: 48, height: 48); Widget _buildDownloadAllCenterButton(List entries) { final tracks = entries.map((e) => e.track).toList(growable: false); @@ -1139,6 +1146,7 @@ class _CollectionTrackTile extends ConsumerWidget { trailing: isSelectionMode ? null : IconButton( + tooltip: MaterialLocalizations.of(context).showMenuTooltip, icon: Icon( Icons.more_vert, color: colorScheme.onSurfaceVariant, diff --git a/lib/screens/playlist_screen.dart b/lib/screens/playlist_screen.dart index dc0f12ad..7ffbd18e 100644 --- a/lib/screens/playlist_screen.dart +++ b/lib/screens/playlist_screen.dart @@ -321,6 +321,7 @@ class _PlaylistScreenState extends ConsumerState { }, ), leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( diff --git a/lib/screens/search_screen.dart b/lib/screens/search_screen.dart index a348ecac..4523fbee 100644 --- a/lib/screens/search_screen.dart +++ b/lib/screens/search_screen.dart @@ -84,7 +84,11 @@ class _SearchScreenState extends ConsumerState { autofocus: widget.query.isEmpty, ), actions: [ - IconButton(icon: const Icon(Icons.search), onPressed: _search), + IconButton( + tooltip: MaterialLocalizations.of(context).searchFieldLabel, + icon: const Icon(Icons.search), + onPressed: _search, + ), ], ), body: Column( diff --git a/lib/screens/settings/about_page.dart b/lib/screens/settings/about_page.dart index 8ef0268c..81fd1aa5 100644 --- a/lib/screens/settings/about_page.dart +++ b/lib/screens/settings/about_page.dart @@ -28,6 +28,7 @@ class AboutPage extends StatelessWidget { backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), diff --git a/lib/screens/settings/appearance_settings_page.dart b/lib/screens/settings/appearance_settings_page.dart index f5a4560b..4884328a 100644 --- a/lib/screens/settings/appearance_settings_page.dart +++ b/lib/screens/settings/appearance_settings_page.dart @@ -30,6 +30,7 @@ class AppearanceSettingsPage extends ConsumerWidget { backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), @@ -347,11 +348,21 @@ class _ColorPalettePicker extends StatelessWidget { child: Row( children: _colors.map((color) { final isSelected = color.toARGB32() == currentColor; + final colorHex = color + .toARGB32() + .toRadixString(16) + .padLeft(8, '0') + .toUpperCase(); return Padding( padding: const EdgeInsets.only(right: 12), - child: GestureDetector( - onTap: () => onColorSelected(color), - child: _ColorPaletteItem(color: color, isSelected: isSelected), + child: Semantics( + button: true, + selected: isSelected, + label: 'Select accent color $colorHex', + child: GestureDetector( + onTap: () => onColorSelected(color), + child: _ColorPaletteItem(color: color, isSelected: isSelected), + ), ), ); }).toList(), diff --git a/lib/screens/settings/donate_page.dart b/lib/screens/settings/donate_page.dart index 80466739..175b0793 100644 --- a/lib/screens/settings/donate_page.dart +++ b/lib/screens/settings/donate_page.dart @@ -24,6 +24,7 @@ class DonatePage extends StatelessWidget { backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), @@ -215,13 +216,17 @@ class _RecentDonorsCard extends StatelessWidget { Icon( Icons.emoji_events_outlined, size: 32, - color: colorScheme.onSurfaceVariant.withValues(alpha: 0.4), + color: colorScheme.onSurfaceVariant.withValues( + alpha: 0.4, + ), ), const SizedBox(height: 8), Text( 'No supporters yet — be the first!', style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6), + color: colorScheme.onSurfaceVariant.withValues( + alpha: 0.6, + ), ), ), ], @@ -468,9 +473,12 @@ class _CryptoWalletItem extends StatelessWidget { int _cr(String v) { int r = 0x1F; - for (final c in v.codeUnits) { r = (r * 31 + c) & 0x7FFFFFFF; } + for (final c in v.codeUnits) { + r = (r * 31 + c) & 0x7FFFFFFF; + } return r; } + // Highlighted supporters (hashes of names): none for now. const _cv = {}; @@ -487,16 +495,10 @@ class _SupporterChip extends StatelessWidget { const goldAccentColor = Color(0xFFB8860B); const goldDarkChipColor = Color(0xFF3A3000); - final chipColor = e - ? goldChipColor - : colorScheme.secondaryContainer; - final accentColor = e - ? goldAccentColor - : colorScheme.primary; + final chipColor = e ? goldChipColor : colorScheme.secondaryContainer; + final accentColor = e ? goldAccentColor : colorScheme.primary; final isDark = Theme.of(context).brightness == Brightness.dark; - final effectiveChipColor = e && isDark - ? goldDarkChipColor - : chipColor; + final effectiveChipColor = e && isDark ? goldDarkChipColor : chipColor; return Material( color: effectiveChipColor, @@ -533,9 +535,7 @@ class _SupporterChip extends StatelessWidget { Text( name, style: Theme.of(context).textTheme.labelLarge?.copyWith( - color: e - ? accentColor - : colorScheme.onSecondaryContainer, + color: e ? accentColor : colorScheme.onSecondaryContainer, fontWeight: e ? FontWeight.w600 : FontWeight.w500, ), ), diff --git a/lib/screens/settings/download_settings_page.dart b/lib/screens/settings/download_settings_page.dart index fbf92f63..1987bb2d 100644 --- a/lib/screens/settings/download_settings_page.dart +++ b/lib/screens/settings/download_settings_page.dart @@ -315,6 +315,7 @@ class _DownloadSettingsPageState extends ConsumerState { backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), diff --git a/lib/screens/settings/extension_detail_page.dart b/lib/screens/settings/extension_detail_page.dart index 72a50a8e..0a702946 100644 --- a/lib/screens/settings/extension_detail_page.dart +++ b/lib/screens/settings/extension_detail_page.dart @@ -15,7 +15,8 @@ class ExtensionDetailPage extends ConsumerStatefulWidget { const ExtensionDetailPage({super.key, required this.extensionId}); @override - ConsumerState createState() => _ExtensionDetailPageState(); + ConsumerState createState() => + _ExtensionDetailPageState(); } class _ExtensionDetailPageState extends ConsumerState { @@ -65,320 +66,373 @@ class _ExtensionDetailPageState extends ConsumerState { body: CustomScrollView( slivers: [ SliverAppBar( - expandedHeight: 120 + topPadding, - collapsedHeight: kToolbarHeight, - floating: false, - pinned: true, - backgroundColor: colorScheme.surface, - surfaceTintColor: Colors.transparent, - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => Navigator.pop(context), - ), - flexibleSpace: LayoutBuilder( - builder: (context, constraints) { - final maxHeight = 120 + topPadding; - final minHeight = kToolbarHeight + topPadding; - final expandRatio = ((constraints.maxHeight - minHeight) / - (maxHeight - minHeight)) - .clamp(0.0, 1.0); - final leftPadding = 56 - (32 * expandRatio); - return FlexibleSpaceBar( - expandedTitleScale: 1.0, - titlePadding: EdgeInsets.only(left: leftPadding, bottom: 16), - title: Text( - extension.displayName, - style: TextStyle( - fontSize: 20 + (8 * expandRatio), - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, + expandedHeight: 120 + topPadding, + collapsedHeight: kToolbarHeight, + floating: false, + pinned: true, + backgroundColor: colorScheme.surface, + surfaceTintColor: Colors.transparent, + leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context), + ), + flexibleSpace: LayoutBuilder( + builder: (context, constraints) { + final maxHeight = 120 + topPadding; + final minHeight = kToolbarHeight + topPadding; + final expandRatio = + ((constraints.maxHeight - minHeight) / + (maxHeight - minHeight)) + .clamp(0.0, 1.0); + final leftPadding = 56 - (32 * expandRatio); + return FlexibleSpaceBar( + expandedTitleScale: 1.0, + titlePadding: EdgeInsets.only( + left: leftPadding, + bottom: 16, ), - ), - ); - }, + title: Text( + extension.displayName, + style: TextStyle( + fontSize: 20 + (8 * expandRatio), + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + ); + }, + ), ), - ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.all(16), - child: Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(20), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - width: 56, - height: 56, - decoration: BoxDecoration( - color: hasError - ? colorScheme.errorContainer - : colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(16), - ), - child: extension.iconPath != null && extension.iconPath!.isNotEmpty - ? ClipRRect( - borderRadius: BorderRadius.circular(16), - child: Image.file( - File(extension.iconPath!), - width: 56, - height: 56, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) => Icon( - hasError ? Icons.error_outline : Icons.extension, - size: 28, - color: hasError - ? colorScheme.error - : colorScheme.onPrimaryContainer, + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(16), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHighest.withValues( + alpha: 0.3, + ), + borderRadius: BorderRadius.circular(20), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: hasError + ? colorScheme.errorContainer + : colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(16), + ), + child: + extension.iconPath != null && + extension.iconPath!.isNotEmpty + ? ClipRRect( + borderRadius: BorderRadius.circular(16), + child: Image.file( + File(extension.iconPath!), + width: 56, + height: 56, + fit: BoxFit.cover, + errorBuilder: + (context, error, stackTrace) => Icon( + hasError + ? Icons.error_outline + : Icons.extension, + size: 28, + color: hasError + ? colorScheme.error + : colorScheme + .onPrimaryContainer, + ), ), + ) + : Icon( + hasError + ? Icons.error_outline + : Icons.extension, + size: 28, + color: hasError + ? colorScheme.error + : colorScheme.onPrimaryContainer, ), - ) - : Icon( - hasError ? Icons.error_outline : Icons.extension, - size: 28, - color: hasError - ? colorScheme.error - : colorScheme.onPrimaryContainer, - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - extension.displayName, - style: Theme.of(context).textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - Text( - 'v${extension.version}', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ], ), - ), - Switch( - value: extension.enabled, - onChanged: hasError - ? null - : (enabled) => ref - .read(extensionProvider.notifier) - .setExtensionEnabled(widget.extensionId, enabled), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + extension.displayName, + style: Theme.of(context).textTheme.titleLarge + ?.copyWith(fontWeight: FontWeight.bold), + ), + Text( + 'v${extension.version}', + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + Switch( + value: extension.enabled, + onChanged: hasError + ? null + : (enabled) => ref + .read(extensionProvider.notifier) + .setExtensionEnabled( + widget.extensionId, + enabled, + ), + ), + ], + ), + if (extension.description.isNotEmpty) ...[ + const SizedBox(height: 16), + Text( + extension.description, + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith(color: colorScheme.onSurfaceVariant), ), ], - ), - if (extension.description.isNotEmpty) ...[ const SizedBox(height: 16), - Text( - extension.description, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ], - const SizedBox(height: 16), - _InfoRow(label: context.l10n.extensionAuthor, value: extension.author), - _InfoRow(label: context.l10n.extensionId, value: extension.id), - _InfoRow(label: context.l10n.extensionsVersion(extension.version), value: ''), - if (hasError && extension.errorMessage != null) _InfoRow( - label: context.l10n.extensionError, - value: extension.errorMessage!, - isError: true, + label: context.l10n.extensionAuthor, + value: extension.author, ), - ], + _InfoRow( + label: context.l10n.extensionId, + value: extension.id, + ), + _InfoRow( + label: context.l10n.extensionsVersion( + extension.version, + ), + value: '', + ), + if (hasError && extension.errorMessage != null) + _InfoRow( + label: context.l10n.extensionError, + value: extension.errorMessage!, + isError: true, + ), + ], + ), ), ), ), - ), - SliverToBoxAdapter( - child: SettingsSectionHeader(title: context.l10n.extensionCapabilities), - ), - SliverToBoxAdapter( - child: SettingsGroup( - children: [ - _CapabilityItem( - icon: Icons.search, - title: context.l10n.extensionMetadataProvider, - enabled: extension.hasMetadataProvider, - ), - _CapabilityItem( - icon: Icons.download, - title: context.l10n.extensionDownloadProvider, - enabled: extension.hasDownloadProvider, - ), - _CapabilityItem( - icon: Icons.lyrics, - title: context.l10n.extensionLyricsProvider, - enabled: extension.hasLyricsProvider, - ), - _CapabilityItem( - icon: Icons.manage_search, - title: context.l10n.extensionsSearchProvider, - enabled: extension.hasCustomSearch, - subtitle: extension.searchBehavior?.placeholder, - ), - _CapabilityItem( - icon: Icons.compare_arrows, - title: context.l10n.extensionCustomTrackMatching, - enabled: extension.hasCustomMatching, - subtitle: extension.trackMatching?.strategy != null - ? context.l10n.extensionStrategy(extension.trackMatching!.strategy!) - : null, - ), - _CapabilityItem( - icon: Icons.auto_fix_high, - title: context.l10n.extensionPostProcessing, - enabled: extension.hasPostProcessing, - subtitle: extension.postProcessing?.hooks.isNotEmpty == true - ? context.l10n.extensionHooksAvailable(extension.postProcessing!.hooks.length) - : null, - ), - _CapabilityItem( - icon: Icons.link, - title: context.l10n.extensionUrlHandler, - enabled: extension.hasURLHandler, - subtitle: extension.urlHandler?.patterns.isNotEmpty == true - ? context.l10n.extensionPatternsCount(extension.urlHandler!.patterns.length) - : null, - showDivider: false, - ), - ], - ), - ), - - if (extension.hasURLHandler && extension.urlHandler!.patterns.isNotEmpty) ...[ SliverToBoxAdapter( - child: SettingsSectionHeader(title: context.l10n.extensionUrlHandler), + child: SettingsSectionHeader( + title: context.l10n.extensionCapabilities, + ), ), SliverToBoxAdapter( child: SettingsGroup( children: [ - _URLHandlerInfo( - patterns: extension.urlHandler!.patterns, + _CapabilityItem( + icon: Icons.search, + title: context.l10n.extensionMetadataProvider, + enabled: extension.hasMetadataProvider, + ), + _CapabilityItem( + icon: Icons.download, + title: context.l10n.extensionDownloadProvider, + enabled: extension.hasDownloadProvider, + ), + _CapabilityItem( + icon: Icons.lyrics, + title: context.l10n.extensionLyricsProvider, + enabled: extension.hasLyricsProvider, + ), + _CapabilityItem( + icon: Icons.manage_search, + title: context.l10n.extensionsSearchProvider, + enabled: extension.hasCustomSearch, + subtitle: extension.searchBehavior?.placeholder, + ), + _CapabilityItem( + icon: Icons.compare_arrows, + title: context.l10n.extensionCustomTrackMatching, + enabled: extension.hasCustomMatching, + subtitle: extension.trackMatching?.strategy != null + ? context.l10n.extensionStrategy( + extension.trackMatching!.strategy!, + ) + : null, + ), + _CapabilityItem( + icon: Icons.auto_fix_high, + title: context.l10n.extensionPostProcessing, + enabled: extension.hasPostProcessing, + subtitle: extension.postProcessing?.hooks.isNotEmpty == true + ? context.l10n.extensionHooksAvailable( + extension.postProcessing!.hooks.length, + ) + : null, + ), + _CapabilityItem( + icon: Icons.link, + title: context.l10n.extensionUrlHandler, + enabled: extension.hasURLHandler, + subtitle: extension.urlHandler?.patterns.isNotEmpty == true + ? context.l10n.extensionPatternsCount( + extension.urlHandler!.patterns.length, + ) + : null, + showDivider: false, ), ], ), ), - ], - if (extension.hasDownloadProvider && extension.qualityOptions.isNotEmpty) ...[ - SliverToBoxAdapter( - child: SettingsSectionHeader(title: context.l10n.extensionQualityOptions), - ), - SliverToBoxAdapter( - child: SettingsGroup( - children: extension.qualityOptions.asMap().entries.map((entry) { - final index = entry.key; - final quality = entry.value; - return _QualityOptionItem( - quality: quality, - showDivider: index < extension.qualityOptions.length - 1, - ); - }).toList(), - ), - ), - ], - - if (extension.hasPostProcessing && extension.postProcessing!.hooks.isNotEmpty) ...[ - SliverToBoxAdapter( - child: SettingsSectionHeader(title: context.l10n.extensionPostProcessingHooks), - ), - SliverToBoxAdapter( - child: SettingsGroup( - children: extension.postProcessing!.hooks.asMap().entries.map((entry) { - final index = entry.key; - final hook = entry.value; - return _PostProcessingHookItem( - hook: hook, - showDivider: index < extension.postProcessing!.hooks.length - 1, - ); - }).toList(), - ), - ), - ], - - if (extension.permissions.isNotEmpty) ...[ - SliverToBoxAdapter( - child: SettingsSectionHeader(title: context.l10n.extensionPermissions), - ), - SliverToBoxAdapter( - child: SettingsGroup( - children: extension.permissions.asMap().entries.map((entry) { - final index = entry.key; - final permission = entry.value; - return _PermissionItem( - permission: permission, - showDivider: index < extension.permissions.length - 1, - ); - }).toList(), - ), - ), - ], - - if (extension.settings.isNotEmpty) ...[ - SliverToBoxAdapter( - child: SettingsSectionHeader(title: context.l10n.extensionSettings), - ), - if (_isLoadingSettings) - const SliverToBoxAdapter( - child: Padding( - padding: EdgeInsets.all(32), - child: Center(child: CircularProgressIndicator()), + if (extension.hasURLHandler && + extension.urlHandler!.patterns.isNotEmpty) ...[ + SliverToBoxAdapter( + child: SettingsSectionHeader( + title: context.l10n.extensionUrlHandler, ), - ) - else + ), SliverToBoxAdapter( child: SettingsGroup( - children: extension.settings.asMap().entries.map((entry) { + children: [ + _URLHandlerInfo(patterns: extension.urlHandler!.patterns), + ], + ), + ), + ], + + if (extension.hasDownloadProvider && + extension.qualityOptions.isNotEmpty) ...[ + SliverToBoxAdapter( + child: SettingsSectionHeader( + title: context.l10n.extensionQualityOptions, + ), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: extension.qualityOptions.asMap().entries.map(( + entry, + ) { final index = entry.key; - final setting = entry.value; - return _SettingItem( - setting: setting, - value: _settings[setting.key] ?? setting.defaultValue, - showDivider: index < extension.settings.length - 1, - onChanged: (value) => _updateSetting(setting.key, value), - extensionId: widget.extensionId, + final quality = entry.value; + return _QualityOptionItem( + quality: quality, + showDivider: index < extension.qualityOptions.length - 1, ); }).toList(), ), ), - ], + ], - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.all(16), - child: OutlinedButton.icon( - onPressed: () => _confirmRemove(context), - icon: const Icon(Icons.delete_outline), - label: Text(context.l10n.extensionRemoveButton), - style: OutlinedButton.styleFrom( - foregroundColor: colorScheme.error, - side: BorderSide(color: colorScheme.error), - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + if (extension.hasPostProcessing && + extension.postProcessing!.hooks.isNotEmpty) ...[ + SliverToBoxAdapter( + child: SettingsSectionHeader( + title: context.l10n.extensionPostProcessingHooks, + ), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: extension.postProcessing!.hooks.asMap().entries.map( + (entry) { + final index = entry.key; + final hook = entry.value; + return _PostProcessingHookItem( + hook: hook, + showDivider: + index < extension.postProcessing!.hooks.length - 1, + ); + }, + ).toList(), + ), + ), + ], + + if (extension.permissions.isNotEmpty) ...[ + SliverToBoxAdapter( + child: SettingsSectionHeader( + title: context.l10n.extensionPermissions, + ), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: extension.permissions.asMap().entries.map((entry) { + final index = entry.key; + final permission = entry.value; + return _PermissionItem( + permission: permission, + showDivider: index < extension.permissions.length - 1, + ); + }).toList(), + ), + ), + ], + + if (extension.settings.isNotEmpty) ...[ + SliverToBoxAdapter( + child: SettingsSectionHeader( + title: context.l10n.extensionSettings, + ), + ), + if (_isLoadingSettings) + const SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.all(32), + child: Center(child: CircularProgressIndicator()), + ), + ) + else + SliverToBoxAdapter( + child: SettingsGroup( + children: extension.settings.asMap().entries.map((entry) { + final index = entry.key; + final setting = entry.value; + return _SettingItem( + setting: setting, + value: _settings[setting.key] ?? setting.defaultValue, + showDivider: index < extension.settings.length - 1, + onChanged: (value) => + _updateSetting(setting.key, value), + extensionId: widget.extensionId, + ); + }).toList(), + ), + ), + ], + + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(16), + child: OutlinedButton.icon( + onPressed: () => _confirmRemove(context), + icon: const Icon(Icons.delete_outline), + label: Text(context.l10n.extensionRemoveButton), + style: OutlinedButton.styleFrom( + foregroundColor: colorScheme.error, + side: BorderSide(color: colorScheme.error), + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), ), ), ), ), - ), - const SliverToBoxAdapter(child: SizedBox(height: 32)), - ], + const SliverToBoxAdapter(child: SizedBox(height: 32)), + ], + ), ), - ), ); } @@ -397,9 +451,7 @@ class _ExtensionDetailPageState extends ConsumerState { context: context, builder: (context) => AlertDialog( title: Text(context.l10n.dialogRemoveExtension), - content: Text( - context.l10n.dialogRemoveExtensionMessage, - ), + content: Text(context.l10n.dialogRemoveExtensionMessage), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), @@ -407,9 +459,7 @@ class _ExtensionDetailPageState extends ConsumerState { ), FilledButton( onPressed: () => Navigator.pop(context, true), - style: FilledButton.styleFrom( - backgroundColor: colorScheme.error, - ), + style: FilledButton.styleFrom(backgroundColor: colorScheme.error), child: Text(context.l10n.dialogRemove), ), ], @@ -504,10 +554,7 @@ class _CapabilityItem extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - title, - style: Theme.of(context).textTheme.bodyLarge, - ), + Text(title, style: Theme.of(context).textTheme.bodyLarge), if (subtitle != null && enabled) ...[ const SizedBox(height: 2), Text( @@ -544,18 +591,15 @@ class _PermissionItem extends StatelessWidget { final String permission; final bool showDivider; - const _PermissionItem({ - required this.permission, - this.showDivider = true, - }); + const _PermissionItem({required this.permission, this.showDivider = true}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - + IconData icon = Icons.security; String description = permission; - + if (permission.startsWith('network:')) { icon = Icons.language; description = 'Network access to: ${permission.substring(8)}'; @@ -673,9 +717,8 @@ class _SettingItemState extends State<_SettingItem> { if (widget.setting.description != null) ...[ Text( widget.setting.description!, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith(color: colorScheme.onSurfaceVariant), ), const SizedBox(height: 12), ], @@ -702,7 +745,8 @@ class _SettingItemState extends State<_SettingItem> { mainAxisSize: MainAxisSize.min, children: [ InkWell( - onTap: widget.setting.type == 'string' || widget.setting.type == 'number' + onTap: + widget.setting.type == 'string' || widget.setting.type == 'number' ? () => _showEditDialog(context) : null, child: Padding( @@ -721,18 +765,17 @@ class _SettingItemState extends State<_SettingItem> { const SizedBox(height: 2), Text( widget.setting.description!, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: colorScheme.onSurfaceVariant, - ), + style: Theme.of(context).textTheme.bodySmall + ?.copyWith(color: colorScheme.onSurfaceVariant), ), ], - if (widget.setting.type == 'string' || widget.setting.type == 'number') ...[ + if (widget.setting.type == 'string' || + widget.setting.type == 'number') ...[ const SizedBox(height: 4), Text( widget.value?.toString() ?? 'Not set', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: colorScheme.primary, - ), + style: Theme.of(context).textTheme.bodySmall + ?.copyWith(color: colorScheme.primary), ), ], ], @@ -775,23 +818,23 @@ class _SettingItemState extends State<_SettingItem> { final success = result['success'] as bool? ?? false; if (!success) { final error = result['error'] as String? ?? 'Action failed'; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(error)), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(error))); } else { final message = result['message'] as String?; if (message != null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(message)), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(message))); } } } } catch (e) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Error: $e')), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Error: $e'))); } } finally { if (mounted) { @@ -801,7 +844,9 @@ class _SettingItemState extends State<_SettingItem> { } void _showEditDialog(BuildContext context) { - final controller = TextEditingController(text: widget.value?.toString() ?? ''); + final controller = TextEditingController( + text: widget.value?.toString() ?? '', + ); final colorScheme = Theme.of(context).colorScheme; showDialog( @@ -816,7 +861,9 @@ class _SettingItemState extends State<_SettingItem> { decoration: InputDecoration( hintText: widget.setting.description ?? 'Enter value', filled: true, - fillColor: colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), + fillColor: colorScheme.surfaceContainerHighest.withValues( + alpha: 0.3, + ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, @@ -848,15 +895,12 @@ class _PostProcessingHookItem extends StatelessWidget { final PostProcessingHook hook; final bool showDivider; - const _PostProcessingHookItem({ - required this.hook, - this.showDivider = true, - }); + const _PostProcessingHookItem({required this.hook, this.showDivider = true}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - + return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -903,16 +947,20 @@ class _PostProcessingHookItem extends StatelessWidget { spacing: 4, children: hook.supportedFormats.map((format) { return Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), decoration: BoxDecoration( color: colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(4), ), child: Text( format.toUpperCase(), - style: Theme.of(context).textTheme.labelSmall?.copyWith( - color: colorScheme.onSurfaceVariant, - ), + style: Theme.of(context).textTheme.labelSmall + ?.copyWith( + color: colorScheme.onSurfaceVariant, + ), ), ); }).toList(), @@ -923,7 +971,10 @@ class _PostProcessingHookItem extends StatelessWidget { ), if (hook.defaultEnabled) Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), decoration: BoxDecoration( color: colorScheme.primaryContainer, borderRadius: BorderRadius.circular(8), @@ -951,19 +1002,15 @@ class _PostProcessingHookItem extends StatelessWidget { } } - - class _URLHandlerInfo extends StatelessWidget { final List patterns; - const _URLHandlerInfo({ - required this.patterns, - }); + const _URLHandlerInfo({required this.patterns}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - + return Padding( padding: const EdgeInsets.all(16), child: Column( @@ -1013,7 +1060,10 @@ class _URLHandlerInfo extends StatelessWidget { runSpacing: 8, children: patterns.map((pattern) { return Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), decoration: BoxDecoration( color: colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), @@ -1021,11 +1071,7 @@ class _URLHandlerInfo extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon( - Icons.language, - size: 16, - color: colorScheme.primary, - ), + Icon(Icons.language, size: 16, color: colorScheme.primary), const SizedBox(width: 6), Text( pattern, @@ -1048,11 +1094,7 @@ class _URLHandlerInfo extends StatelessWidget { ), child: Row( children: [ - Icon( - Icons.info_outline, - size: 20, - color: colorScheme.primary, - ), + Icon(Icons.info_outline, size: 20, color: colorScheme.primary), const SizedBox(width: 12), Expanded( child: Text( @@ -1075,15 +1117,12 @@ class _QualityOptionItem extends StatelessWidget { final QualityOption quality; final bool showDivider; - const _QualityOptionItem({ - required this.quality, - this.showDivider = true, - }); + const _QualityOptionItem({required this.quality, this.showDivider = true}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - + return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -1115,7 +1154,8 @@ class _QualityOptionItem extends StatelessWidget { fontWeight: FontWeight.w500, ), ), - if (quality.description != null && quality.description!.isNotEmpty) ...[ + if (quality.description != null && + quality.description!.isNotEmpty) ...[ const SizedBox(height: 2), Text( quality.description!, @@ -1137,7 +1177,10 @@ class _QualityOptionItem extends StatelessWidget { ), if (quality.settings.isNotEmpty) Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), decoration: BoxDecoration( color: colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), diff --git a/lib/screens/settings/extensions_page.dart b/lib/screens/settings/extensions_page.dart index d339e24d..196dd0aa 100644 --- a/lib/screens/settings/extensions_page.dart +++ b/lib/screens/settings/extensions_page.dart @@ -72,6 +72,7 @@ class _ExtensionsPageState extends ConsumerState { backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), diff --git a/lib/screens/settings/log_screen.dart b/lib/screens/settings/log_screen.dart index 310d0805..611db06f 100644 --- a/lib/screens/settings/log_screen.dart +++ b/lib/screens/settings/log_screen.dart @@ -6,8 +6,10 @@ import 'package:spotiflac_android/utils/app_bar_layout.dart'; import 'package:spotiflac_android/utils/logger.dart'; import 'package:spotiflac_android/widgets/settings_group.dart'; -final RegExp _domainPattern = - RegExp(r'domain:\s*([^\s,]+)', caseSensitive: false); +final RegExp _domainPattern = RegExp( + r'domain:\s*([^\s,]+)', + caseSensitive: false, +); class LogScreen extends StatefulWidget { const LogScreen({super.key}); @@ -17,7 +19,6 @@ class LogScreen extends StatefulWidget { } class _LogScreenState extends State { - final ScrollController _scrollController = ScrollController(); final TextEditingController _searchController = TextEditingController(); String _selectedLevel = 'ALL'; @@ -74,7 +75,9 @@ class _LogScreenState extends State { SnackBar( content: Text(context.l10n.logCopied), behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), duration: const Duration(seconds: 2), ), ); @@ -83,7 +86,9 @@ class _LogScreenState extends State { void _shareLogs() async { final logs = await LogBuffer().exportWithDeviceInfo(); - SharePlus.instance.share(ShareParams(text: logs, subject: 'SpotiFLAC Logs')); + SharePlus.instance.share( + ShareParams(text: logs, subject: 'SpotiFLAC Logs'), + ); } void _clearLogs() { @@ -137,52 +142,58 @@ class _LogScreenState extends State { controller: _scrollController, slivers: [ SliverAppBar( - expandedHeight: 120 + topPadding, - collapsedHeight: kToolbarHeight, - floating: false, - pinned: true, - backgroundColor: colorScheme.surface, - surfaceTintColor: Colors.transparent, - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => Navigator.pop(context), - ), - actions: [ - IconButton( - icon: Icon(_autoScroll ? Icons.vertical_align_bottom : Icons.vertical_align_center), - tooltip: _autoScroll ? 'Auto-scroll ON' : 'Auto-scroll OFF', - onPressed: () => setState(() => _autoScroll = !_autoScroll), + expandedHeight: 120 + topPadding, + collapsedHeight: kToolbarHeight, + floating: false, + pinned: true, + backgroundColor: colorScheme.surface, + surfaceTintColor: Colors.transparent, + leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context), ), - IconButton( - icon: const Icon(Icons.copy), - tooltip: 'Copy logs', - onPressed: _copyLogs, - ), - PopupMenuButton( - icon: const Icon(Icons.more_vert), - onSelected: (value) { - switch (value) { - case 'share': - _shareLogs(); - break; - case 'clear': - _clearLogs(); - break; - } - }, - itemBuilder: (context) => [ - PopupMenuItem( - value: 'share', - child: ListTile( - leading: const Icon(Icons.share), - title: Text(context.l10n.logShareLogs), - contentPadding: EdgeInsets.zero, - ), + actions: [ + IconButton( + icon: Icon( + _autoScroll + ? Icons.vertical_align_bottom + : Icons.vertical_align_center, ), - PopupMenuItem( - value: 'clear', - child: ListTile( - leading: const Icon(Icons.delete_outline), + tooltip: _autoScroll ? 'Auto-scroll ON' : 'Auto-scroll OFF', + onPressed: () => setState(() => _autoScroll = !_autoScroll), + ), + IconButton( + icon: const Icon(Icons.copy), + tooltip: 'Copy logs', + onPressed: _copyLogs, + ), + PopupMenuButton( + icon: const Icon(Icons.more_vert), + tooltip: MaterialLocalizations.of(context).showMenuTooltip, + onSelected: (value) { + switch (value) { + case 'share': + _shareLogs(); + break; + case 'clear': + _clearLogs(); + break; + } + }, + itemBuilder: (context) => [ + PopupMenuItem( + value: 'share', + child: ListTile( + leading: const Icon(Icons.share), + title: Text(context.l10n.logShareLogs), + contentPadding: EdgeInsets.zero, + ), + ), + PopupMenuItem( + value: 'clear', + child: ListTile( + leading: const Icon(Icons.delete_outline), title: Text(context.l10n.logClearLogs), contentPadding: EdgeInsets.zero, ), @@ -194,11 +205,17 @@ class _LogScreenState extends State { builder: (context, constraints) { final maxHeight = 120 + topPadding; final minHeight = kToolbarHeight + topPadding; - final expandRatio = ((constraints.maxHeight - minHeight) / (maxHeight - minHeight)).clamp(0.0, 1.0); + final expandRatio = + ((constraints.maxHeight - minHeight) / + (maxHeight - minHeight)) + .clamp(0.0, 1.0); final leftPadding = 56 - (32 * expandRatio); return FlexibleSpaceBar( expandedTitleScale: 1.0, - titlePadding: EdgeInsets.only(left: leftPadding, bottom: 16), + titlePadding: EdgeInsets.only( + left: leftPadding, + bottom: 16, + ), title: Text( context.l10n.logTitle, style: TextStyle( @@ -213,28 +230,40 @@ class _LogScreenState extends State { ), SliverToBoxAdapter( - child: SettingsSectionHeader(title: context.l10n.logFilterSection), + child: SettingsSectionHeader( + title: context.l10n.logFilterSection, + ), ), SliverToBoxAdapter( child: SettingsGroup( children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 12, + ), child: Row( children: [ - Icon(Icons.filter_list, color: colorScheme.onSurfaceVariant), + Icon( + Icons.filter_list, + color: colorScheme.onSurfaceVariant, + ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(context.l10n.logFilterLevel, style: Theme.of(context).textTheme.bodyLarge), + Text( + context.l10n.logFilterLevel, + style: Theme.of(context).textTheme.bodyLarge, + ), const SizedBox(height: 2), Text( context.l10n.logFilterBySeverity, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith( + color: colorScheme.onSurfaceVariant, + ), ), ], ), @@ -248,8 +277,8 @@ class _LogScreenState extends State { child: Text( level, style: TextStyle( - color: level == 'ALL' - ? colorScheme.onSurface + color: level == 'ALL' + ? colorScheme.onSurface : _getLevelColor(level, colorScheme), fontWeight: FontWeight.w500, ), @@ -272,7 +301,10 @@ class _LogScreenState extends State { color: colorScheme.outlineVariant.withValues(alpha: 0.3), ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 12, + ), child: Row( children: [ Icon(Icons.search, color: colorScheme.onSurfaceVariant), @@ -295,6 +327,7 @@ class _LogScreenState extends State { fillColor: colorScheme.surfaceContainerHighest, suffixIcon: _searchQuery.isNotEmpty ? IconButton( + tooltip: 'Clear search', icon: const Icon(Icons.clear, size: 20), onPressed: () { _searchController.clear(); @@ -317,16 +350,16 @@ class _LogScreenState extends State { SliverToBoxAdapter( child: SettingsSectionHeader( - title: _selectedLevel != 'ALL' || _searchQuery.isNotEmpty + title: _selectedLevel != 'ALL' || _searchQuery.isNotEmpty ? context.l10n.logEntriesFiltered(logs.length) : context.l10n.logEntries(logs.length), ), ), - + SliverToBoxAdapter( child: _LogSummaryCard(logs: LogBuffer().entries), ), - + logs.isEmpty ? SliverToBoxAdapter( child: SettingsGroup( @@ -339,21 +372,26 @@ class _LogScreenState extends State { Icon( Icons.article_outlined, size: 48, - color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5), + color: colorScheme.onSurfaceVariant.withValues( + alpha: 0.5, + ), ), const SizedBox(height: 16), Text( context.l10n.logNoLogsYet, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: colorScheme.onSurfaceVariant, - ), + style: Theme.of(context).textTheme.bodyLarge + ?.copyWith( + color: colorScheme.onSurfaceVariant, + ), ), const SizedBox(height: 4), Text( context.l10n.logNoLogsYetSubtitle, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant.withValues(alpha: 0.7), - ), + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith( + color: colorScheme.onSurfaceVariant + .withValues(alpha: 0.7), + ), ), ], ), @@ -408,7 +446,7 @@ class _LogEntryTile extends StatelessWidget { width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), decoration: BoxDecoration( - color: isError + color: isError ? colorScheme.errorContainer.withValues(alpha: 0.2) : null, ), @@ -427,7 +465,10 @@ class _LogEntryTile extends StatelessWidget { ), const SizedBox(width: 8), Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), decoration: BoxDecoration( color: levelColor.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(6), @@ -444,7 +485,10 @@ class _LogEntryTile extends StatelessWidget { if (entry.isFromGo) ...[ const SizedBox(width: 4), Container( - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 2, + ), decoration: BoxDecoration( color: Colors.teal.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(4), @@ -519,9 +563,9 @@ class _LogSummaryCard extends StatelessWidget { @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - + final analysis = _analyzeLogs(); - + if (!analysis.hasIssues) { return const SizedBox.shrink(); } @@ -530,7 +574,7 @@ class _LogSummaryCard extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), child: Card( elevation: 0, - color: analysis.hasISPBlocking + color: analysis.hasISPBlocking ? colorScheme.errorContainer.withValues(alpha: 0.5) : colorScheme.tertiaryContainer.withValues(alpha: 0.5), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), @@ -542,9 +586,13 @@ class _LogSummaryCard extends StatelessWidget { Row( children: [ Icon( - analysis.hasISPBlocking ? Icons.block : Icons.warning_amber_rounded, + analysis.hasISPBlocking + ? Icons.block + : Icons.warning_amber_rounded, size: 20, - color: analysis.hasISPBlocking ? colorScheme.error : colorScheme.tertiary, + color: analysis.hasISPBlocking + ? colorScheme.error + : colorScheme.tertiary, ), const SizedBox(width: 8), Text( @@ -557,19 +605,21 @@ class _LogSummaryCard extends StatelessWidget { ], ), const SizedBox(height: 12), - + if (analysis.hasISPBlocking) ...[ _IssueBadge( icon: Icons.block, label: 'ISP BLOCKING DETECTED', - description: 'Your ISP may be blocking access to download services', - suggestion: 'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8', + description: + 'Your ISP may be blocking access to download services', + suggestion: + 'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8', color: colorScheme.error, domains: analysis.blockedDomains, ), const SizedBox(height: 8), ], - + if (analysis.hasRateLimit) ...[ _IssueBadge( icon: Icons.speed, @@ -580,7 +630,7 @@ class _LogSummaryCard extends StatelessWidget { ), const SizedBox(height: 8), ], - + if (analysis.hasNetworkError && !analysis.hasISPBlocking) ...[ _IssueBadge( icon: Icons.wifi_off, @@ -591,17 +641,19 @@ class _LogSummaryCard extends StatelessWidget { ), const SizedBox(height: 8), ], - + if (analysis.hasNotFound) ...[ _IssueBadge( icon: Icons.search_off, label: 'TRACK NOT FOUND', - description: 'Some tracks could not be found on download services', - suggestion: 'The track may not be available in lossless quality', + description: + 'Some tracks could not be found on download services', + suggestion: + 'The track may not be available in lossless quality', color: colorScheme.onSurfaceVariant, ), ], - + const SizedBox(height: 12), Text( 'Total errors: ${analysis.errorCount}', @@ -639,7 +691,7 @@ class _LogSummaryCard extends StatelessWidget { combined.contains('connection reset') || combined.contains('connection refused')) { hasISPBlocking = true; - + final domainMatch = _domainPattern.firstMatch(combined); if (domainMatch != null) { blockedDomains.add(domainMatch.group(1)!); @@ -694,7 +746,12 @@ class _LogAnalysis { required this.blockedDomains, }); - bool get hasIssues => errorCount > 0 || hasISPBlocking || hasRateLimit || hasNetworkError || hasNotFound; + bool get hasIssues => + errorCount > 0 || + hasISPBlocking || + hasRateLimit || + hasNetworkError || + hasNotFound; } class _IssueBadge extends StatelessWidget { @@ -717,7 +774,7 @@ class _IssueBadge extends StatelessWidget { @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - + return Container( width: double.infinity, padding: const EdgeInsets.all(12), @@ -746,9 +803,9 @@ class _IssueBadge extends StatelessWidget { const SizedBox(height: 6), Text( description, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: colorScheme.onSurface, - ), + style: Theme.of( + context, + ).textTheme.bodySmall?.copyWith(color: colorScheme.onSurface), ), if (domains != null && domains!.isNotEmpty) ...[ const SizedBox(height: 4), @@ -765,7 +822,11 @@ class _IssueBadge extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon(Icons.lightbulb_outline, size: 14, color: colorScheme.primary), + Icon( + Icons.lightbulb_outline, + size: 14, + color: colorScheme.primary, + ), const SizedBox(width: 4), Expanded( child: Text( diff --git a/lib/screens/settings/options_settings_page.dart b/lib/screens/settings/options_settings_page.dart index 9016586c..d57558fa 100644 --- a/lib/screens/settings/options_settings_page.dart +++ b/lib/screens/settings/options_settings_page.dart @@ -32,6 +32,7 @@ class OptionsSettingsPage extends ConsumerWidget { backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), diff --git a/lib/screens/settings/provider_priority_page.dart b/lib/screens/settings/provider_priority_page.dart index a5a19f15..e28ad7e3 100644 --- a/lib/screens/settings/provider_priority_page.dart +++ b/lib/screens/settings/provider_priority_page.dart @@ -66,6 +66,7 @@ class _ProviderPriorityPageState extends ConsumerState { backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: const Icon(Icons.arrow_back), onPressed: () async { if (_hasChanges) { diff --git a/lib/screens/setup_screen.dart b/lib/screens/setup_screen.dart index 57a5caac..7c74d92e 100644 --- a/lib/screens/setup_screen.dart +++ b/lib/screens/setup_screen.dart @@ -487,6 +487,9 @@ class _SetupScreenState extends ConsumerState { if (_currentStep > 0) IconButton.filledTonal( onPressed: _prevPage, + tooltip: MaterialLocalizations.of( + context, + ).backButtonTooltip, icon: const Icon(Icons.arrow_back), style: IconButton.styleFrom( backgroundColor: colorScheme.surfaceContainerHighest, @@ -708,6 +711,7 @@ class _SetupScreenState extends ConsumerState { overflow: TextOverflow.ellipsis, ), trailing: IconButton( + tooltip: 'Change folder', icon: const Icon(Icons.edit), onPressed: _selectDirectory, ), diff --git a/lib/screens/store/extension_details_screen.dart b/lib/screens/store/extension_details_screen.dart index 86610974..efb2c7e4 100644 --- a/lib/screens/store/extension_details_screen.dart +++ b/lib/screens/store/extension_details_screen.dart @@ -17,7 +17,6 @@ class ExtensionDetailsScreen extends ConsumerStatefulWidget { class _ExtensionDetailsScreenState extends ConsumerState { - @override Widget build(BuildContext context) { final storeState = ref.watch(storeProvider); @@ -116,6 +115,7 @@ class _ExtensionDetailsScreenState ), ), leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), @@ -171,7 +171,7 @@ class _ExtensionDetailsScreenState color: colorScheme.onSurface, ), ), - const SizedBox(height: 4), + const SizedBox(height: 4), Text( context.l10n.extensionsAuthor(ext.author), style: Theme.of(context).textTheme.bodyLarge @@ -222,7 +222,9 @@ class _ExtensionDetailsScreenState FilledButton.icon( onPressed: () => _updateExtension(ext), icon: const Icon(Icons.update), - label: Text('${context.l10n.storeUpdate} v${ext.version}'), + label: Text( + '${context.l10n.storeUpdate} v${ext.version}', + ), style: FilledButton.styleFrom( minimumSize: const Size.fromHeight(52), shape: RoundedRectangleBorder( @@ -405,7 +407,8 @@ class _ExtensionDetailsScreenState StoreExtension ext, ColorScheme colorScheme, ) { - final isMetadataProvider = ext.category == 'metadata' || ext.category == 'integration'; + final isMetadataProvider = + ext.category == 'metadata' || ext.category == 'integration'; final isDownloadProvider = ext.category == 'download'; final isLyricsProvider = ext.category == 'lyrics'; final isUtility = ext.category == 'utility'; @@ -458,7 +461,7 @@ class _ExtensionDetailsScreenState final date = DateTime.parse(dateStr); final now = DateTime.now(); final diff = now.difference(date); - + if (diff.inDays == 0) { return context.l10n.dateToday; } else if (diff.inDays == 1) { @@ -560,7 +563,9 @@ class _ExtensionDetailsScreenState context: context, builder: (context) => AlertDialog( title: Text(context.l10n.dialogUninstallExtension), - content: Text(context.l10n.dialogUninstallExtensionMessage(ext.displayName)), + content: Text( + context.l10n.dialogUninstallExtensionMessage(ext.displayName), + ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), @@ -718,10 +723,7 @@ class _CapabilityRow extends StatelessWidget { Expanded( child: Text( label, - style: TextStyle( - color: colorScheme.onSurface, - fontSize: 14, - ), + style: TextStyle(color: colorScheme.onSurface, fontSize: 14), ), ), Icon( diff --git a/lib/screens/store_tab.dart b/lib/screens/store_tab.dart index d8d83e7b..ff97c194 100644 --- a/lib/screens/store_tab.dart +++ b/lib/screens/store_tab.dart @@ -122,6 +122,7 @@ class _StoreTabState extends ConsumerState { prefixIcon: const Icon(Icons.search), suffixIcon: value.text.isNotEmpty ? IconButton( + tooltip: 'Clear search', icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); diff --git a/lib/screens/track_metadata_screen.dart b/lib/screens/track_metadata_screen.dart index b4a3238e..fdcd3eef 100644 --- a/lib/screens/track_metadata_screen.dart +++ b/lib/screens/track_metadata_screen.dart @@ -650,6 +650,7 @@ class _TrackMetadataScreenState extends ConsumerState { }, ), leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( @@ -662,6 +663,7 @@ class _TrackMetadataScreenState extends ConsumerState { ), actions: [ IconButton( + tooltip: MaterialLocalizations.of(context).showMenuTooltip, icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( diff --git a/lib/screens/tutorial_screen.dart b/lib/screens/tutorial_screen.dart index 04dfcf9c..a944f6a8 100644 --- a/lib/screens/tutorial_screen.dart +++ b/lib/screens/tutorial_screen.dart @@ -103,6 +103,9 @@ class _TutorialScreenState extends ConsumerState { opacity: _currentPage > 0 ? 1.0 : 0.0, child: IconButton.filledTonal( onPressed: _currentPage > 0 ? _prevPage : null, + tooltip: MaterialLocalizations.of( + context, + ).backButtonTooltip, icon: const Icon(Icons.arrow_back), style: IconButton.styleFrom( backgroundColor: colorScheme.surfaceContainerHighest, @@ -613,40 +616,52 @@ class _InteractiveDownloadExampleState ), ), const SizedBox(width: 16), - GestureDetector( - onTap: _startDownload, - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - padding: EdgeInsets.all(buttonPadding), - decoration: BoxDecoration( - color: _isCompleted ? Colors.green : colorScheme.primary, - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: - (_isCompleted ? Colors.green : colorScheme.primary) - .withValues(alpha: 0.3), - blurRadius: 12, - offset: const Offset(0, 6), - ), - ], - ), - child: _isDownloading - ? SizedBox( - width: buttonIconSize, - height: buttonIconSize, - child: CircularProgressIndicator( - strokeWidth: 3, - color: colorScheme.onPrimary, - ), - ) - : Icon( - _isCompleted - ? Icons.check_rounded - : Icons.download_rounded, - color: colorScheme.onPrimary, - size: buttonIconSize, + Semantics( + button: true, + label: _isCompleted + ? 'Download completed' + : _isDownloading + ? 'Download in progress' + : 'Start download', + child: GestureDetector( + onTap: _startDownload, + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + padding: EdgeInsets.all(buttonPadding), + decoration: BoxDecoration( + color: _isCompleted ? Colors.green : colorScheme.primary, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: + (_isCompleted + ? Colors.green + : colorScheme.primary) + .withValues(alpha: 0.3), + blurRadius: 12, + offset: const Offset(0, 6), ), + ], + ), + child: _isDownloading + ? SizedBox( + width: buttonIconSize, + height: buttonIconSize, + child: CircularProgressIndicator( + strokeWidth: 3, + color: colorScheme.onPrimary, + ), + ) + : ExcludeSemantics( + child: Icon( + _isCompleted + ? Icons.check_rounded + : Icons.download_rounded, + color: colorScheme.onPrimary, + size: buttonIconSize, + ), + ), + ), ), ), ], diff --git a/lib/widgets/collapsing_header.dart b/lib/widgets/collapsing_header.dart index 276f8f43..04ee5213 100644 --- a/lib/widgets/collapsing_header.dart +++ b/lib/widgets/collapsing_header.dart @@ -32,6 +32,7 @@ class CollapsingHeader extends StatelessWidget { surfaceTintColor: Colors.transparent, leading: showBackButton ? IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ) @@ -39,7 +40,10 @@ class CollapsingHeader extends StatelessWidget { automaticallyImplyLeading: false, flexibleSpace: LayoutBuilder( builder: (context, constraints) { - final expandRatio = _calculateExpandRatio(constraints, topPadding); + final expandRatio = _calculateExpandRatio( + constraints, + topPadding, + ); final animation = AlwaysStoppedAnimation(expandRatio); return FlexibleSpaceBar( @@ -48,13 +52,22 @@ class CollapsingHeader extends StatelessWidget { title: Container( alignment: Alignment.bottomLeft, padding: EdgeInsets.only( - left: Tween(begin: showBackButton ? 56 : 24, end: 24).evaluate(animation), - bottom: Tween(begin: 16, end: 24).evaluate(animation), + left: Tween( + begin: showBackButton ? 56 : 24, + end: 24, + ).evaluate(animation), + bottom: Tween( + begin: 16, + end: 24, + ).evaluate(animation), ), child: Text( title, style: TextStyle( - fontSize: Tween(begin: 20, end: 28).evaluate(animation), + fontSize: Tween( + begin: 20, + end: 28, + ).evaluate(animation), fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), @@ -142,8 +155,12 @@ class InfoCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: Theme.of(context).textTheme.bodyLarge), - Text(subtitle, style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant)), + Text( + subtitle, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), ], ), ], diff --git a/lib/widgets/priority_settings_scaffold.dart b/lib/widgets/priority_settings_scaffold.dart index f866b46f..acd9a664 100644 --- a/lib/widgets/priority_settings_scaffold.dart +++ b/lib/widgets/priority_settings_scaffold.dart @@ -61,6 +61,7 @@ class PrioritySettingsScaffold extends StatelessWidget { backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, leading: IconButton( + tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: const Icon(Icons.arrow_back), onPressed: () => _handleBack(context), ), diff --git a/lib/widgets/track_collection_quick_actions.dart b/lib/widgets/track_collection_quick_actions.dart index d21a67fe..b8f75708 100644 --- a/lib/widgets/track_collection_quick_actions.dart +++ b/lib/widgets/track_collection_quick_actions.dart @@ -36,6 +36,7 @@ class TrackCollectionQuickActions extends ConsumerWidget { final colorScheme = Theme.of(context).colorScheme; return IconButton( + tooltip: MaterialLocalizations.of(context).showMenuTooltip, icon: Icon( Icons.more_vert, color: colorScheme.onSurfaceVariant, From 7e41ab4460d05bf7e885fbca201abd05d3870814 Mon Sep 17 00:00:00 2001 From: zarzet Date: Sun, 8 Mar 2026 22:13:01 +0700 Subject: [PATCH 092/104] fix: YouTube Music share intent not recognizing album/browse URLs --- lib/services/share_intent_service.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/services/share_intent_service.dart b/lib/services/share_intent_service.dart index 274bd234..4f8867b1 100644 --- a/lib/services/share_intent_service.dart +++ b/lib/services/share_intent_service.dart @@ -31,7 +31,12 @@ class ShareIntentService { // YouTube Music patterns static final RegExp _ytMusicUrlPattern = RegExp( - r'https?://music\.youtube\.com/(watch\?v=|playlist\?list=|channel/)[a-zA-Z0-9_-]+(\&[^\s]*)?', + r'https?://music\.youtube\.com/(watch\?v=|playlist\?list=|channel/|browse/)[a-zA-Z0-9_-]+([?&][^\s]*)?', + ); + + // Standard YouTube patterns (youtu.be short links and www.youtube.com/watch) + static final RegExp _youtubeUrlPattern = RegExp( + r'https?://(youtu\.be/[a-zA-Z0-9_-]+|www\.youtube\.com/watch\?v=[a-zA-Z0-9_-]+)([?&][^\s]*)?', ); final _sharedUrlController = StreamController.broadcast(); @@ -101,14 +106,15 @@ class ShareIntentService { _deezerShortLinkPattern, _tidalUrlPattern, _ytMusicUrlPattern, + _youtubeUrlPattern, ]; for (final pattern in patterns) { final match = pattern.firstMatch(text); if (match != null) { final fullUrl = match.group(0)!; - // Remove query params for cleaner URL (except for YT Music which needs them) - if (pattern == _ytMusicUrlPattern) { + // Keep query params for YouTube URLs (needed for ?v=, ?list=, etc.) + if (pattern == _ytMusicUrlPattern || pattern == _youtubeUrlPattern) { return fullUrl; } final queryIndex = fullUrl.indexOf('?'); From a4899144c51bf8818ff59a82961d397545ed87d1 Mon Sep 17 00:00:00 2001 From: zarzet Date: Sun, 8 Mar 2026 23:12:48 +0700 Subject: [PATCH 093/104] fix: show error feedback for unrecognized URLs and fix l10n warnings --- lib/l10n/app_localizations.dart | 18 + lib/l10n/app_localizations_de.dart | 714 +++---- lib/l10n/app_localizations_en.dart | 11 + lib/l10n/app_localizations_es.dart | 11 + lib/l10n/app_localizations_fr.dart | 15 +- lib/l10n/app_localizations_hi.dart | 15 +- lib/l10n/app_localizations_id.dart | 166 +- lib/l10n/app_localizations_ja.dart | 174 +- lib/l10n/app_localizations_ko.dart | 471 +++-- lib/l10n/app_localizations_nl.dart | 15 +- lib/l10n/app_localizations_pt.dart | 11 + lib/l10n/app_localizations_ru.dart | 239 ++- lib/l10n/app_localizations_tr.dart | 11 + lib/l10n/app_localizations_zh.dart | 477 ++++- lib/l10n/arb/app_de.arb | 18 +- lib/l10n/arb/app_en.arb | 12 + lib/l10n/arb/app_es-ES.arb | 3105 ---------------------------- lib/l10n/arb/app_id.arb | 12 + lib/l10n/arb/app_pt-PT.arb | 3105 ---------------------------- lib/l10n/arb/app_ru.arb | 22 +- lib/l10n/arb/app_tr-TR.arb | 3105 ---------------------------- lib/l10n/arb/app_zh_CN.arb | 2 +- lib/l10n/arb/app_zh_TW.arb | 2 +- lib/providers/track_provider.dart | 13 + lib/screens/home_tab.dart | 74 +- lib/screens/main_shell.dart | 18 +- 26 files changed, 1630 insertions(+), 10206 deletions(-) delete mode 100644 lib/l10n/arb/app_es-ES.arb delete mode 100644 lib/l10n/arb/app_pt-PT.arb delete mode 100644 lib/l10n/arb/app_tr-TR.arb diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 822982eb..c059621b 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1306,6 +1306,24 @@ abstract class AppLocalizations { /// **'No tracks found'** String get errorNoTracksFound; + /// Error title - URL not handled by any extension or service + /// + /// In en, this message translates to: + /// **'Link not recognized'** + String get errorUrlNotRecognized; + + /// Error message - URL not recognized explanation + /// + /// In en, this message translates to: + /// **'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'** + String get errorUrlNotRecognizedMessage; + + /// Error message - generic URL fetch failure + /// + /// In en, this message translates to: + /// **'Failed to load content from this link. Please try again.'** + String get errorUrlFetchFailed; + /// Error - extension source not available /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index af8f7b73..246f5314 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -15,7 +15,7 @@ class AppLocalizationsDe extends AppLocalizations { String get navHome => 'Startseite'; @override - String get navLibrary => 'Archiv'; + String get navLibrary => 'Bibliothek'; @override String get navSettings => 'Einstellungen'; @@ -365,7 +365,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get aboutAppDescription => - 'Lade Spotify-Titel in verlustfreier Qualität von Tidal und Qobuz herunter.'; + 'Lade Spotify-Titel in verlustfreier Qualität von Tidal, Qobuz und Amazon Music herunter.'; @override String get artistAlbums => 'Alben'; @@ -500,7 +500,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get setupNotificationBackgroundDescription => - 'Werde benachrichtigt über Download-Fortschritt und -Fertigstellung. Dies hilft Ihnen, Downloads zu verfolgen, wenn die App im Hintergrund ist.'; + 'Erhalte Benachrichtigungen über den Fortschritt und die Fertigstellung deiner Downloads, selbst wenn die App im Hintergrund läuft.'; @override String get setupSkipForNow => 'Vorerst überspringen'; @@ -592,11 +592,11 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get dialogImportPlaylistTitle => 'Wiedergabeliste importieren'; + String get dialogImportPlaylistTitle => 'Playlist importieren'; @override String dialogImportPlaylistMessage(int count) { - return '$count Titel in CSV gefunden. Zur Warteschlange hinzufügen?'; + return '$count Titel gefunden hinzufügen?'; } @override @@ -606,12 +606,12 @@ class AppLocalizationsDe extends AppLocalizations { @override String snackbarAddedToQueue(String trackName) { - return '\"$trackName\" zur Warteschlange hinzugefügt'; + return '\"$trackName\" hinzugefügt'; } @override String snackbarAddedTracksToQueue(int count) { - return '$count Titel zur Warteschlange hinzugefügt'; + return '$count Titel hinzugefügt'; } @override @@ -701,6 +701,17 @@ class AppLocalizationsDe extends AppLocalizations { @override String get errorNoTracksFound => 'Keine Titel gefunden'; + @override + String get errorUrlNotRecognized => 'Link not recognized'; + + @override + String get errorUrlNotRecognizedMessage => + 'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'; + + @override + String get errorUrlFetchFailed => + 'Failed to load content from this link. Please try again.'; + @override String errorMissingExtensionSource(String item) { return 'Kann $item nicht lade wegen fehlender Erweiterungsquelle'; @@ -765,11 +776,11 @@ class AppLocalizationsDe extends AppLocalizations { String get filenameFormat => 'Dateinamenformat'; @override - String get filenameShowAdvancedTags => 'Show advanced tags'; + String get filenameShowAdvancedTags => 'Erweiterte Tags anzeigen'; @override String get filenameShowAdvancedTagsDescription => - 'Enable formatted tags for track padding and date patterns'; + 'Formatierte Tags für Track-Padding und Datumsmuster aktivieren'; @override String get folderOrganizationNone => 'Keine Organisation'; @@ -918,11 +929,11 @@ class AppLocalizationsDe extends AppLocalizations { @override String logEntries(int count) { - return 'Entries ($count)'; + return '$count Einträge'; } @override - String get credentialsTitle => 'Spotify Credentials'; + String get credentialsTitle => 'Spotify-Anmeldedaten'; @override String get credentialsDescription => @@ -984,7 +995,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Wähle wie Songtexte mit deinen Downloads gespeichert werden'; @override - String get lyricsModeEmbed => 'In Datei einbinden'; + String get lyricsModeEmbed => 'In Datei einbetten'; @override String get lyricsModeEmbedSubtitle => 'Lyrics in FLAC Metadaten gespeichert'; @@ -1001,7 +1012,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get lyricsModeBothSubtitle => - 'Lyrics einbinden und als .lrc speichern'; + 'Lyrics einbetten und als .lrc speichern'; @override String get sectionColor => 'Farbe'; @@ -1019,29 +1030,30 @@ class AppLocalizationsDe extends AppLocalizations { String get appearanceLanguage => 'App Sprache'; @override - String get settingsAppearanceSubtitle => 'Theme, colors, display'; + String get settingsAppearanceSubtitle => 'Design, Farben, Anzeige'; @override - String get settingsDownloadSubtitle => 'Service, quality, filename format'; + String get settingsDownloadSubtitle => 'Dienst, Qualität, Dateinamen-Format'; @override - String get settingsOptionsSubtitle => 'Fallback, lyrics, cover art, updates'; + String get settingsOptionsSubtitle => 'Fallback, Lyrics, Covers, Updates'; @override - String get settingsExtensionsSubtitle => 'Manage download providers'; + String get settingsExtensionsSubtitle => 'Download-Anbieter verwalten'; @override - String get settingsLogsSubtitle => 'View app logs for debugging'; + String get settingsLogsSubtitle => 'App-Logs zum Debuggen anzeigen'; @override - String get loadingSharedLink => 'Loading shared link...'; + String get loadingSharedLink => 'Link wird geladen...'; @override - String get pressBackAgainToExit => 'Press back again to exit'; + String get pressBackAgainToExit => + 'Drücke wieder \"zurück\" um die App zu beenden'; @override String downloadAllCount(int count) { - return 'Download All ($count)'; + return 'Alle $count Titel herunterladen'; } @override @@ -1049,65 +1061,65 @@ class AppLocalizationsDe extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count tracks', - one: '1 track', + other: '$count Titel', + one: '1 Titel', ); return '$_temp0'; } @override - String get trackCopyFilePath => 'Copy file path'; + String get trackCopyFilePath => 'Dateipfad kopieren'; @override - String get trackRemoveFromDevice => 'Remove from device'; + String get trackRemoveFromDevice => 'Vom Gerät entfernen'; @override - String get trackLoadLyrics => 'Load Lyrics'; + String get trackLoadLyrics => 'Lade Lyrics'; @override - String get trackMetadata => 'Metadata'; + String get trackMetadata => 'Metadaten'; @override - String get trackFileInfo => 'File Info'; + String get trackFileInfo => 'Datei-Info'; @override String get trackLyrics => 'Lyrics'; @override - String get trackFileNotFound => 'File not found'; + String get trackFileNotFound => 'Datei nicht gefunden'; @override - String get trackOpenInDeezer => 'Open in Deezer'; + String get trackOpenInDeezer => 'In Deezer öffnen'; @override - String get trackOpenInSpotify => 'Open in Spotify'; + String get trackOpenInSpotify => 'In Spotify öffnen'; @override - String get trackTrackName => 'Track name'; + String get trackTrackName => 'Name des Titels'; @override - String get trackArtist => 'Artist'; + String get trackArtist => 'Künstler'; @override - String get trackAlbumArtist => 'Album artist'; + String get trackAlbumArtist => 'Album Künstler'; @override String get trackAlbum => 'Album'; @override - String get trackTrackNumber => 'Track number'; + String get trackTrackNumber => 'Titelnummer'; @override - String get trackDiscNumber => 'Disc number'; + String get trackDiscNumber => 'CD-Nummer'; @override - String get trackDuration => 'Duration'; + String get trackDuration => 'Länge'; @override - String get trackAudioQuality => 'Audio quality'; + String get trackAudioQuality => 'Audioqualität'; @override - String get trackReleaseDate => 'Release date'; + String get trackReleaseDate => 'Erscheinungsdatum'; @override String get trackGenre => 'Genre'; @@ -1116,71 +1128,73 @@ class AppLocalizationsDe extends AppLocalizations { String get trackLabel => 'Label'; @override - String get trackCopyright => 'Copyright'; + String get trackCopyright => 'Urheberrecht'; @override - String get trackDownloaded => 'Downloaded'; + String get trackDownloaded => 'Heruntergeladen'; @override - String get trackCopyLyrics => 'Copy lyrics'; + String get trackCopyLyrics => 'Lyrics kopieren'; @override - String get trackLyricsNotAvailable => 'Lyrics not available for this track'; + String get trackLyricsNotAvailable => + 'Lyrics sind für diesen Titel nicht verfügbar'; @override - String get trackLyricsTimeout => 'Request timed out. Try again later.'; + String get trackLyricsTimeout => + 'Anfrage Timeout. Versuche es später erneut.'; @override - String get trackLyricsLoadFailed => 'Failed to load lyrics'; + String get trackLyricsLoadFailed => 'Fehler beim Laden der Lyrics'; @override - String get trackEmbedLyrics => 'Embed Lyrics'; + String get trackEmbedLyrics => 'Lyrics einbetten'; @override - String get trackLyricsEmbedded => 'Lyrics embedded successfully'; + String get trackLyricsEmbedded => 'Lyrics erfolgreich eingebettet'; @override - String get trackInstrumental => 'Instrumental track'; + String get trackInstrumental => 'Instrumentalspur'; @override - String get trackCopiedToClipboard => 'Copied to clipboard'; + String get trackCopiedToClipboard => 'In Zwischenablage kopiert'; @override - String get trackDeleteConfirmTitle => 'Remove from device?'; + String get trackDeleteConfirmTitle => 'Vom Gerät entfernen?'; @override String get trackDeleteConfirmMessage => - 'This will permanently delete the downloaded file and remove it from your history.'; + 'Dies wird die heruntergeladene Datei dauerhaft löschen und sie aus deinem Verlauf entfernen.'; @override - String get dateToday => 'Today'; + String get dateToday => 'Heute'; @override - String get dateYesterday => 'Yesterday'; + String get dateYesterday => 'Gestern'; @override String dateDaysAgo(int count) { - return '$count days ago'; + return 'Vor $count Tagen'; } @override String dateWeeksAgo(int count) { - return '$count weeks ago'; + return 'Vor $count Wochen'; } @override String dateMonthsAgo(int count) { - return '$count months ago'; + return 'Vor $count Monaten'; } @override - String get storeFilterAll => 'All'; + String get storeFilterAll => 'Alle'; @override - String get storeFilterMetadata => 'Metadata'; + String get storeFilterMetadata => 'Metadaten'; @override - String get storeFilterDownload => 'Download'; + String get storeFilterDownload => 'Herunterladen'; @override String get storeFilterUtility => 'Utility'; @@ -1192,142 +1206,146 @@ class AppLocalizationsDe extends AppLocalizations { String get storeFilterIntegration => 'Integration'; @override - String get storeClearFilters => 'Clear filters'; + String get storeClearFilters => 'Filter entfernen'; @override - String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; + String get extensionDefaultProvider => 'Standard (Deezer/Spotify)'; @override - String get extensionDefaultProviderSubtitle => 'Use built-in search'; + String get extensionDefaultProviderSubtitle => 'Eingebaute Suche verwenden'; @override - String get extensionAuthor => 'Author'; + String get extensionAuthor => 'Entwickler'; @override String get extensionId => 'ID'; @override - String get extensionError => 'Error'; + String get extensionError => 'Fehler'; @override - String get extensionCapabilities => 'Capabilities'; + String get extensionCapabilities => 'Eigenschaften'; @override - String get extensionMetadataProvider => 'Metadata Provider'; + String get extensionMetadataProvider => 'Metadaten-Anbieter'; @override - String get extensionDownloadProvider => 'Download Provider'; + String get extensionDownloadProvider => 'Download-Anbieter'; @override - String get extensionLyricsProvider => 'Lyrics Provider'; + String get extensionLyricsProvider => 'Lyrics-Anbieter'; @override String get extensionUrlHandler => 'URL Handler'; @override - String get extensionQualityOptions => 'Quality Options'; + String get extensionQualityOptions => 'Qualitätsoptionen'; @override String get extensionPostProcessingHooks => 'Post-Processing Hooks'; @override - String get extensionPermissions => 'Permissions'; + String get extensionPermissions => 'Berechtigungen'; @override - String get extensionSettings => 'Settings'; + String get extensionSettings => 'Einstellungen'; @override - String get extensionRemoveButton => 'Remove Extension'; + String get extensionRemoveButton => 'Erweiterung entfernen'; @override - String get extensionUpdated => 'Updated'; + String get extensionUpdated => 'Aktualisiert'; @override - String get extensionMinAppVersion => 'Min App Version'; + String get extensionMinAppVersion => 'Min App-Version'; @override - String get extensionCustomTrackMatching => 'Custom Track Matching'; + String get extensionCustomTrackMatching => + 'Benutzerdefiniertes Track-Matching'; @override - String get extensionPostProcessing => 'Post-Processing'; + String get extensionPostProcessing => 'Post-processing'; @override String extensionHooksAvailable(int count) { - return '$count hook(s) available'; + return '$count Hook(s) verfügbar'; } @override String extensionPatternsCount(int count) { - return '$count pattern(s)'; + return '$count Muster'; } @override String extensionStrategy(String strategy) { - return 'Strategy: $strategy'; + return 'Strategie: $strategy'; } @override - String get extensionsProviderPrioritySection => 'Provider Priority'; + String get extensionsProviderPrioritySection => 'Provider-Priorität'; @override - String get extensionsInstalledSection => 'Installed Extensions'; + String get extensionsInstalledSection => 'Installierte Erweiterungen'; @override - String get extensionsNoExtensions => 'No extensions installed'; + String get extensionsNoExtensions => 'Keine Erweiterungen installiert'; @override String get extensionsNoExtensionsSubtitle => - 'Install .spotiflac-ext files to add new providers'; + 'Installiere .spotiflac-ext Dateien um neue Anbieter hinzuzufügen'; @override - String get extensionsInstallButton => 'Install Extension'; + String get extensionsInstallButton => 'Erweiterung installieren'; @override String get extensionsInfoTip => - 'Extensions can add new metadata and download providers. Only install extensions from trusted sources.'; + 'Erweiterungen können neue Metadaten und Download-Anbieter hinzufügen. Installiere nur Erweiterungen von vertrauenswürdigen Quellen.'; @override - String get extensionsInstalledSuccess => 'Extension installed successfully'; + String get extensionsInstalledSuccess => + 'Erweiterung erfolgreich installiert'; @override - String get extensionsDownloadPriority => 'Download Priority'; + String get extensionsDownloadPriority => 'Download-Priorität'; @override - String get extensionsDownloadPrioritySubtitle => 'Set download service order'; + String get extensionsDownloadPrioritySubtitle => + 'Download-Service-Reihenfolge festlegen'; @override String get extensionsNoDownloadProvider => - 'No extensions with download provider'; + 'Keine Erweiterungen mit Download-Provider'; @override - String get extensionsMetadataPriority => 'Metadata Priority'; + String get extensionsMetadataPriority => 'Metadaten Priorität'; @override String get extensionsMetadataPrioritySubtitle => - 'Set search & metadata source order'; + 'Reihenfolge der Such- und Metadaten quellen festlegen'; @override String get extensionsNoMetadataProvider => - 'No extensions with metadata provider'; + 'Keine Erweiterungen mit Metadaten-Anbieter'; @override - String get extensionsSearchProvider => 'Search Provider'; + String get extensionsSearchProvider => 'Such-Provider'; @override - String get extensionsNoCustomSearch => 'No extensions with custom search'; + String get extensionsNoCustomSearch => + 'Keine Erweiterungen mit benutzerdefinierter Suche'; @override String get extensionsSearchProviderDescription => - 'Choose which service to use for searching tracks'; + 'Wähle den Dienst für die Suche von Titel'; @override - String get extensionsCustomSearch => 'Custom search'; + String get extensionsCustomSearch => 'Benutzerdefinierte Suche'; @override - String get extensionsErrorLoading => 'Error loading extension'; + String get extensionsErrorLoading => 'Fehler beim Laden der Erweiterung'; @override - String get qualityFlacLossless => 'FLAC Lossless'; + String get qualityFlacLossless => 'FLAC Verlustfrei'; @override String get qualityFlacLosslessSubtitle => '16-bit / 44.1kHz'; @@ -1336,21 +1354,21 @@ class AppLocalizationsDe extends AppLocalizations { String get qualityHiResFlac => 'Hi-Res FLAC'; @override - String get qualityHiResFlacSubtitle => '24-bit / up to 96kHz'; + String get qualityHiResFlacSubtitle => '24-Bit / bis 96kHz'; @override String get qualityHiResFlacMax => 'Hi-Res FLAC Max'; @override - String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz'; + String get qualityHiResFlacMaxSubtitle => '24-Bit / bis 192kHz'; @override String get qualityNote => - 'Actual quality depends on track availability from the service'; + 'Die eigentliche Qualität hängt von der Verfügbarkeit des Dienstes ab'; @override String get youtubeQualityNote => - 'YouTube provides lossy audio only. Not part of lossless fallback.'; + 'YouTube bietet nur verlustbehaftete Audioqualität. Deswegen ist es kein Teil des verlustfreien Fallbacks.'; @override String get youtubeOpusBitrateTitle => 'YouTube Opus Bitrate'; @@ -1359,13 +1377,13 @@ class AppLocalizationsDe extends AppLocalizations { String get youtubeMp3BitrateTitle => 'YouTube MP3 Bitrate'; @override - String get downloadAskBeforeDownload => 'Ask Before Download'; + String get downloadAskBeforeDownload => 'Qualität vor Download fragen'; @override - String get downloadDirectory => 'Download Directory'; + String get downloadDirectory => 'Downloadverzeichnis'; @override - String get downloadSeparateSinglesFolder => 'Separate Singles Folder'; + String get downloadSeparateSinglesFolder => 'Singles Ordner trennen'; @override String get downloadAlbumFolderStructure => 'Album Folder Structure'; @@ -1378,53 +1396,53 @@ class AppLocalizationsDe extends AppLocalizations { @override String get downloadUsePrimaryArtistOnlyEnabled => - 'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)'; + 'Vorgestellte Künstler aus dem Ordnernamen entfernt (z.B. Justin Bieber, Quavo → Justin Bieber)'; @override String get downloadUsePrimaryArtistOnlyDisabled => 'Full artist string used for folder name'; @override - String get downloadSelectQuality => 'Select Quality'; + String get downloadSelectQuality => 'Qualität wählen'; @override - String get downloadFrom => 'Download From'; + String get downloadFrom => 'Herunterladen von'; @override - String get appearanceAmoledDark => 'AMOLED Dark'; + String get appearanceAmoledDark => 'AMOLED Schwarz'; @override - String get appearanceAmoledDarkSubtitle => 'Pure black background'; + String get appearanceAmoledDarkSubtitle => 'AMOLED Hintergrund'; @override - String get queueClearAll => 'Clear All'; + String get queueClearAll => 'Alles löschen'; @override String get queueClearAllMessage => - 'Are you sure you want to clear all downloads?'; + 'Bist du dir sicher, dass du alle Downloads löschen möchten?'; @override String get settingsAutoExportFailed => 'Auto-export failed downloads'; @override String get settingsAutoExportFailedSubtitle => - 'Save failed downloads to TXT file automatically'; + 'Fehlgeschlagene Downloads automatisch in eine TXT-Datei speichern'; @override - String get settingsDownloadNetwork => 'Download Network'; + String get settingsDownloadNetwork => 'Download Netzwerk'; @override - String get settingsDownloadNetworkAny => 'WiFi + Mobile Data'; + String get settingsDownloadNetworkAny => 'WLAN + Mobile Daten'; @override - String get settingsDownloadNetworkWifiOnly => 'WiFi Only'; + String get settingsDownloadNetworkWifiOnly => 'Nur WLAN'; @override String get settingsDownloadNetworkSubtitle => - 'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.'; + 'Wähle aus, welches Netzwerk für Downloads verwendet werden soll. Wenn nur WLAN aktiviert wird, werden Downloads auf mobilen Daten angehalten.'; @override - String get albumFolderArtistAlbum => 'Artist / Album'; + String get albumFolderArtistAlbum => 'Künstler/Album'; @override String get albumFolderArtistAlbumSubtitle => 'Albums/Artist Name/Album Name/'; @@ -1434,13 +1452,13 @@ class AppLocalizationsDe extends AppLocalizations { @override String get albumFolderArtistYearAlbumSubtitle => - 'Albums/Artist Name/[2005] Album Name/'; + 'Albums/Künster Name/[2005] Album Name/'; @override - String get albumFolderAlbumOnly => 'Album Only'; + String get albumFolderAlbumOnly => 'Nur Alben'; @override - String get albumFolderAlbumOnlySubtitle => 'Albums/Album Name/'; + String get albumFolderAlbumOnlySubtitle => 'Alben/Album Name/'; @override String get albumFolderYearAlbum => '[Year] Album'; @@ -1456,39 +1474,39 @@ class AppLocalizationsDe extends AppLocalizations { 'Artist/Album/ and Artist/Singles/'; @override - String get downloadedAlbumDeleteSelected => 'Delete Selected'; + String get downloadedAlbumDeleteSelected => 'Ausgewählte löschen'; @override String downloadedAlbumDeleteMessage(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'tracks', - one: 'track', + other: 'Titel', + one: 'Titel', ); - return 'Delete $count $_temp0 from this album?\n\nThis will also delete the files from storage.'; + return '$count $_temp0 aus diesem Album löschen?\n\nDadurch werden auch die Dateien aus dem Speicher gelöscht.'; } @override String downloadedAlbumSelectedCount(int count) { - return '$count selected'; + return '$count ausgewählt'; } @override - String get downloadedAlbumAllSelected => 'All tracks selected'; + String get downloadedAlbumAllSelected => 'Alle Titel sind ausgewählt'; @override - String get downloadedAlbumTapToSelect => 'Tap tracks to select'; + String get downloadedAlbumTapToSelect => 'Tippe auf Titel zum Auswählen'; @override String downloadedAlbumDeleteCount(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'tracks', - one: 'track', + other: 'Titel', + one: 'Titel', ); - return 'Delete $count $_temp0'; + return 'Lösche $count $_temp0'; } @override @@ -1500,22 +1518,22 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get recentTypeArtist => 'Artist'; + String get recentTypeArtist => 'Künstler'; @override String get recentTypeAlbum => 'Album'; @override - String get recentTypeSong => 'Song'; + String get recentTypeSong => 'Titel'; @override String get recentTypePlaylist => 'Playlist'; @override - String get recentEmpty => 'No recent items yet'; + String get recentEmpty => 'Noch keine aktuellen Einträge'; @override - String get recentShowAllDownloads => 'Show All Downloads'; + String get recentShowAllDownloads => 'Alle Downloads anzeigen'; @override String recentPlaylistInfo(String name) { @@ -1523,41 +1541,41 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get discographyDownload => 'Download Discography'; + String get discographyDownload => 'Diskographie herunterladen'; @override - String get discographyDownloadAll => 'Download All'; + String get discographyDownloadAll => 'Alle Herunterladen'; @override String discographyDownloadAllSubtitle(int count, int albumCount) { - return '$count tracks from $albumCount releases'; + return '$count Titel von $albumCount Releases'; } @override - String get discographyAlbumsOnly => 'Albums Only'; + String get discographyAlbumsOnly => 'Nur Alben'; @override String discographyAlbumsOnlySubtitle(int count, int albumCount) { - return '$count tracks from $albumCount albums'; + return '$count Titel von $albumCount Albums'; } @override - String get discographySinglesOnly => 'Singles & EPs Only'; + String get discographySinglesOnly => 'Nur Singles & EPs'; @override String discographySinglesOnlySubtitle(int count, int albumCount) { - return '$count tracks from $albumCount singles'; + return '$count Titel von $albumCount Singles'; } @override - String get discographySelectAlbums => 'Select Albums...'; + String get discographySelectAlbums => 'Alben auswählen...'; @override String get discographySelectAlbumsSubtitle => 'Choose specific albums or singles'; @override - String get discographyFetchingTracks => 'Fetching tracks...'; + String get discographyFetchingTracks => 'Lade Titel...'; @override String discographyFetchingAlbum(int current, int total) { @@ -1566,11 +1584,11 @@ class AppLocalizationsDe extends AppLocalizations { @override String discographySelectedCount(int count) { - return '$count selected'; + return '$count ausgewählt'; } @override - String get discographyDownloadSelected => 'Download Selected'; + String get discographyDownloadSelected => 'Auswahl herunterladen'; @override String discographyAddedToQueue(int count) { @@ -1579,20 +1597,20 @@ class AppLocalizationsDe extends AppLocalizations { @override String discographySkippedDownloaded(int added, int skipped) { - return '$added added, $skipped already downloaded'; + return '$added hinzugefügt, $skipped bereits heruntergeladen'; } @override - String get discographyNoAlbums => 'No albums available'; + String get discographyNoAlbums => 'Es sind keine Alben verfügbar'; @override String get discographyFailedToFetch => 'Failed to fetch some albums'; @override - String get sectionStorageAccess => 'Storage Access'; + String get sectionStorageAccess => 'Speicherzugriff'; @override - String get allFilesAccess => 'All Files Access'; + String get allFilesAccess => 'Zugriff auf alle Dateien'; @override String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; @@ -1602,170 +1620,170 @@ class AppLocalizationsDe extends AppLocalizations { @override String get allFilesAccessDescription => - 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + 'Aktiviere die Option, wenn beim Speichern in benutzerdefinierten Ordnern Schreibfehler auftreten. Weil Android 13+ standardmäßig den Zugriff auf bestimmte Verzeichnisse einschränkt.'; @override String get allFilesAccessDeniedMessage => - 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + 'Zugriff verweigert. Bitte aktiviere \"Zugriff auf alle Dateien\" manuell in den Systemeinstellungen.'; @override String get allFilesAccessDisabledMessage => - 'All Files Access disabled. The app will use limited storage access.'; + 'Zugriff auf alle Dateien ist deaktiviert. Die App verwendet nur begrenzten Zugriff auf den Speicher.'; @override - String get settingsLocalLibrary => 'Local Library'; + String get settingsLocalLibrary => 'Lokale Bibliothek'; @override String get settingsLocalLibrarySubtitle => 'Scan music & detect duplicates'; @override - String get settingsCache => 'Storage & Cache'; + String get settingsCache => 'Speicher & Cache'; @override String get settingsCacheSubtitle => 'View size and clear cached data'; @override - String get libraryTitle => 'Local Library'; + String get libraryTitle => 'Lokale Bibliothek'; @override - String get libraryScanSettings => 'Scan Settings'; + String get libraryScanSettings => 'Scan Einstellungen'; @override - String get libraryEnableLocalLibrary => 'Enable Local Library'; + String get libraryEnableLocalLibrary => 'Lokale Bibliothek aktivieren'; @override String get libraryEnableLocalLibrarySubtitle => 'Scan and track your existing music'; @override - String get libraryFolder => 'Library Folder'; + String get libraryFolder => 'Bibliotheksordner'; @override - String get libraryFolderHint => 'Tap to select folder'; + String get libraryFolderHint => 'Tippe um Ordner auszuwählen'; @override String get libraryShowDuplicateIndicator => 'Show Duplicate Indicator'; @override String get libraryShowDuplicateIndicatorSubtitle => - 'Show when searching for existing tracks'; + 'Bei der Suche nach vorhandenen Titeln anzeigen'; @override - String get libraryActions => 'Actions'; + String get libraryActions => 'Aktionen'; @override - String get libraryScan => 'Scan Library'; + String get libraryScan => 'Bibliothek scannen'; @override - String get libraryScanSubtitle => 'Scan for audio files'; + String get libraryScanSubtitle => 'Suche nach Audiodateien'; @override - String get libraryScanSelectFolderFirst => 'Select a folder first'; + String get libraryScanSelectFolderFirst => 'Wähle zuerst einen Ordner'; @override - String get libraryCleanupMissingFiles => 'Cleanup Missing Files'; + String get libraryCleanupMissingFiles => 'Fehlende Dateien bereinigen'; @override String get libraryCleanupMissingFilesSubtitle => - 'Remove entries for files that no longer exist'; + 'Verlaufseinträge für Dateien löschen, die nicht mehr existieren'; @override - String get libraryClear => 'Clear Library'; + String get libraryClear => 'Bibliothek löschen'; @override - String get libraryClearSubtitle => 'Remove all scanned tracks'; + String get libraryClearSubtitle => 'Alle gescannten Titel entfernen'; @override - String get libraryClearConfirmTitle => 'Clear Library'; + String get libraryClearConfirmTitle => 'Bibliothek löschen'; @override String get libraryClearConfirmMessage => - 'This will remove all scanned tracks from your library. Your actual music files will not be deleted.'; + 'Dadurch werden alle gescannten Titel aus Ihrer Bibliothek entfernt. Ihre eigentlichen Musikdateien werden nicht gelöscht.'; @override - String get libraryAbout => 'About Local Library'; + String get libraryAbout => 'Über die lokale Bibliothek'; @override String get libraryAboutDescription => - 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.'; + 'Durchsucht deine bestehende Musiksammlung, um Duplikate beim Herunterladen zu erkennen. Unterstützt die Formate FLAC, M4A, MP3, Opus und OGG. Metadaten werden, sofern verfügbar, aus den Dateitags gelesen.'; @override String libraryTracksUnit(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'tracks', - one: 'track', + other: '$count Titel', + one: '1 Titel', ); return '$_temp0'; } @override String libraryLastScanned(String time) { - return 'Last scanned: $time'; + return 'Zuletzt gescannt: $time'; } @override - String get libraryLastScannedNever => 'Never'; + String get libraryLastScannedNever => 'Nie'; @override - String get libraryScanning => 'Scanning...'; + String get libraryScanning => 'Scannen...'; @override String libraryScanProgress(String progress, int total) { - return '$progress% of $total files'; + return '$progress% von $total Dateien'; } @override - String get libraryInLibrary => 'In Library'; + String get libraryInLibrary => 'In Bibliothek'; @override String libraryRemovedMissingFiles(int count) { - return 'Removed $count missing files from library'; + return 'Entfernte $count fehlende Dateien aus der Bibliothek'; } @override - String get libraryCleared => 'Library cleared'; + String get libraryCleared => 'Bibliothek geleert'; @override - String get libraryStorageAccessRequired => 'Storage Access Required'; + String get libraryStorageAccessRequired => 'Speicherzugriff erforderlich'; @override String get libraryStorageAccessMessage => - 'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.'; + 'SpotiFLAC benötigt Speicherzugriff, um deine Musikbibliothek zu scannen. Bitte erteile die Berechtigung in den Einstellungen.'; @override - String get libraryFolderNotExist => 'Selected folder does not exist'; + String get libraryFolderNotExist => 'Der ausgewählte Ordner existiert nicht'; @override - String get librarySourceDownloaded => 'Downloaded'; + String get librarySourceDownloaded => 'Heruntergeladen'; @override - String get librarySourceLocal => 'Local'; + String get librarySourceLocal => 'Lokal'; @override - String get libraryFilterAll => 'All'; + String get libraryFilterAll => 'Alle'; @override - String get libraryFilterDownloaded => 'Downloaded'; + String get libraryFilterDownloaded => 'Heruntergeladen'; @override - String get libraryFilterLocal => 'Local'; + String get libraryFilterLocal => 'Lokal'; @override - String get libraryFilterTitle => 'Filters'; + String get libraryFilterTitle => 'Filter'; @override - String get libraryFilterReset => 'Reset'; + String get libraryFilterReset => 'Zurücksetzen'; @override - String get libraryFilterApply => 'Apply'; + String get libraryFilterApply => 'Anwenden'; @override - String get libraryFilterSource => 'Source'; + String get libraryFilterSource => 'Quelle'; @override - String get libraryFilterQuality => 'Quality'; + String get libraryFilterQuality => 'Qualität'; @override String get libraryFilterQualityHiRes => 'Hi-Res (24bit)'; @@ -1774,30 +1792,30 @@ class AppLocalizationsDe extends AppLocalizations { String get libraryFilterQualityCD => 'CD (16bit)'; @override - String get libraryFilterQualityLossy => 'Lossy'; + String get libraryFilterQualityLossy => 'Verlustbehaftet'; @override String get libraryFilterFormat => 'Format'; @override - String get libraryFilterSort => 'Sort'; + String get libraryFilterSort => 'Sortieren'; @override - String get libraryFilterSortLatest => 'Latest'; + String get libraryFilterSortLatest => 'Neuste'; @override - String get libraryFilterSortOldest => 'Oldest'; + String get libraryFilterSortOldest => 'Älteste'; @override - String get timeJustNow => 'Just now'; + String get timeJustNow => 'Gerade eben'; @override String timeMinutesAgo(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count minutes ago', - one: '1 minute ago', + other: 'vor $count Minuten', + one: 'vor $count Minute', ); return '$_temp0'; } @@ -1807,199 +1825,201 @@ class AppLocalizationsDe extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count hours ago', - one: '1 hour ago', + other: 'vor $count Stunden', + one: 'vor $count Stunde', ); return '$_temp0'; } @override - String get tutorialWelcomeTitle => 'Welcome to SpotiFLAC!'; + String get tutorialWelcomeTitle => 'Willkommen bei SpotiFLAC!'; @override String get tutorialWelcomeDesc => - 'Let\'s learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.'; + 'Lass uns lernen, wie du deine Lieblingsmusik in verlustfreier Qualität herunterlädst. Dieses schnelle Tutorial zeigt dir die Grundlagen.'; @override String get tutorialWelcomeTip1 => - 'Download music from Spotify, Deezer, or paste any supported URL'; + 'Lade Musik von Spotify, Deezer herunter oder jeden unterstützten Link einfügen'; @override String get tutorialWelcomeTip2 => - 'FLAC-Qualität von Tidal, Qobuz oder Deezer'; + 'Hole dir FLAC Audio von Tidal, Qobuz oder Amazon Musik'; @override String get tutorialWelcomeTip3 => - 'Automatic metadata, cover art, and lyrics embedding'; + 'Automatische Metadaten, Cover und Lyrics einbetten'; @override - String get tutorialSearchTitle => 'Finding Music'; + String get tutorialSearchTitle => 'Suche Musik'; @override String get tutorialSearchDesc => - 'There are two easy ways to find music you want to download.'; + 'Es gibt zwei einfache Möglichkeiten, Musik zu finden, die du herunterladen möchtest.'; @override - String get tutorialDownloadTitle => 'Downloading Music'; + String get tutorialDownloadTitle => 'Musik wird heruntergeladen'; @override String get tutorialDownloadDesc => - 'Downloading music is simple and fast. Here\'s how it works.'; + 'Das Herunterladen von Musik ist einfach und schnell. So funktioniert es.'; @override - String get tutorialLibraryTitle => 'Your Library'; + String get tutorialLibraryTitle => 'Deine Bibliothek'; @override String get tutorialLibraryDesc => - 'All your downloaded music is organized in the Library tab.'; + 'Die gesamte heruntergeladene Musik ist in der Bibliothek organisiert.'; @override String get tutorialLibraryTip1 => - 'View download progress and queue in the Library tab'; + 'Fortschritt und Warteschlange im Bibliothek‑Tab anzeigen'; @override String get tutorialLibraryTip2 => - 'Tap any track to play it with your music player'; + 'Tippe auf einen Titel, um ihn mit deinem Musikplayer abzuspielen'; @override String get tutorialLibraryTip3 => - 'Switch between list and grid view for better browsing'; + 'Wechsle zwischen Listen- und Gitteransicht für ein besseres Surfen'; @override - String get tutorialExtensionsTitle => 'Extensions'; + String get tutorialExtensionsTitle => 'Erweiterungen'; @override String get tutorialExtensionsDesc => - 'Extend the app\'s capabilities with community extensions.'; + 'Erweitere die Fähigkeiten der App mit Community-Erweiterungen.'; @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Im Store Tab findest du nützliche Erweiterungen'; @override String get tutorialExtensionsTip2 => - 'Add new download providers or search sources'; + 'Neue Download- oder Suchanbieter hinzufügen'; @override String get tutorialExtensionsTip3 => - 'Get lyrics, enhanced metadata, and more features'; + 'Lyrics, erweiterte Metadaten und mehr Funktionen erhalten'; @override - String get tutorialSettingsTitle => 'Customize Your Experience'; + String get tutorialSettingsTitle => 'Passe deine Benutzererfahrung an'; @override String get tutorialSettingsDesc => - 'Personalize the app in Settings to match your preferences.'; + 'Personalisiere die App in den Einstellungen nach deiner Präferenz.'; @override String get tutorialSettingsTip1 => - 'Change download location and folder organization'; + 'Downloadverzeichnis und Ordnerorganisation ändern'; @override String get tutorialSettingsTip2 => - 'Set default audio quality and format preferences'; + 'Standard Audioqualität und Formateinstellungen festlegen'; @override - String get tutorialSettingsTip3 => 'Customize app theme and appearance'; + String get tutorialSettingsTip3 => 'App-Design und Aussehen anpassen'; @override String get tutorialReadyMessage => - 'You\'re all set! Start downloading your favorite music now.'; + 'Das ist alles! Lade jetzt deine Lieblingsmusik herunter.'; @override - String get libraryForceFullScan => 'Force Full Scan'; + String get libraryForceFullScan => 'Vollen Neu-Scan erzwingen'; @override - String get libraryForceFullScanSubtitle => 'Rescan all files, ignoring cache'; + String get libraryForceFullScanSubtitle => + 'Alle Dateien erneut scannen und Cache ignorieren'; @override - String get cleanupOrphanedDownloads => 'Cleanup Orphaned Downloads'; + String get cleanupOrphanedDownloads => 'Verwaiste Downloads bereinigen'; @override String get cleanupOrphanedDownloadsSubtitle => - 'Remove history entries for files that no longer exist'; + 'Verlaufseinträge für Dateien löschen, die nicht mehr existieren'; @override String cleanupOrphanedDownloadsResult(int count) { - return 'Removed $count orphaned entries from history'; + return 'Entfernte $count verwaiste Einträge aus dem Verlauf'; } @override - String get cleanupOrphanedDownloadsNone => 'No orphaned entries found'; + String get cleanupOrphanedDownloadsNone => + 'Keine verwaisten Einträge gefunden'; @override - String get cacheTitle => 'Storage & Cache'; + String get cacheTitle => 'Speicher & Cache'; @override - String get cacheSummaryTitle => 'Cache overview'; + String get cacheSummaryTitle => 'Cache-Übersicht'; @override String get cacheSummarySubtitle => - 'Clearing cache will not remove downloaded music files.'; + 'Das Leeren des Caches entfernt nicht heruntergeladene Musikdateien.'; @override String cacheEstimatedTotal(String size) { - return 'Estimated cache usage: $size'; + return 'Geschätzte Cache-Größe: $size'; } @override - String get cacheSectionStorage => 'Cached Data'; + String get cacheSectionStorage => 'Zwischengespeicherte Daten'; @override - String get cacheSectionMaintenance => 'Maintenance'; + String get cacheSectionMaintenance => 'Wartung'; @override - String get cacheAppDirectory => 'App cache directory'; + String get cacheAppDirectory => 'App-Cache Verzeichnis'; @override String get cacheAppDirectoryDesc => - 'HTTP responses, WebView data, and other temporary app data.'; + 'HTTP-Antworten, WebView Daten und andere temporäre App-Daten.'; @override - String get cacheTempDirectory => 'Temporary directory'; + String get cacheTempDirectory => 'Temporäres Verzeichnis'; @override String get cacheTempDirectoryDesc => - 'Temporary files from downloads and audio conversion.'; + 'Temporäre Dateien von Downloads und Audio-Konvertierung.'; @override - String get cacheCoverImage => 'Cover image cache'; + String get cacheCoverImage => 'Cover-Cache'; @override String get cacheCoverImageDesc => - 'Downloaded album and track cover art. Will re-download when viewed.'; + 'Album- und Titelcover heruntergeladen. Werden erneut heruntergeladen.'; @override - String get cacheLibraryCover => 'Library cover cache'; + String get cacheLibraryCover => 'Bibliotheks-Cover-Cache'; @override String get cacheLibraryCoverDesc => - 'Cover art extracted from local music files. Will re-extract on next scan.'; + 'Cover aus lokalen Musikdateien extrahiert. Wird beim nächsten Scannen neu extrahiert.'; @override - String get cacheExploreFeed => 'Explore feed cache'; + String get cacheExploreFeed => 'Feed-Cache entdecken'; @override String get cacheExploreFeedDesc => - 'Explore tab content (new releases, trending). Will refresh on next visit.'; + 'Startseiten-Inhalt (neue Releases, Trends). Wird bei einem Neustart aktualisiert.'; @override - String get cacheTrackLookup => 'Track lookup cache'; + String get cacheTrackLookup => 'Titel Such-Cache'; @override String get cacheTrackLookupDesc => - 'Spotify/Deezer track ID lookups. Clearing may slow next few searches.'; + 'Spotify/Deezer Track-ID-Lookups. Das Löschen kann die nächsten Suchergebnisse verlangsamen.'; @override String get cacheCleanupUnusedDesc => - 'Remove orphaned download history and library entries for missing files.'; + 'Verwaisten Downloadverlauf und Bibliothekseinträge für fehlende Dateien entfernen.'; @override - String get cacheNoData => 'No cached data'; + String get cacheNoData => 'Keine gecachten Daten'; @override String cacheSizeWithFiles(String size, int count) { - return '$size in $count files'; + return '$size in $count Dateien'; } @override @@ -2009,71 +2029,71 @@ class AppLocalizationsDe extends AppLocalizations { @override String cacheEntries(int count) { - return '$count entries'; + return '$count Einträge'; } @override String cacheClearSuccess(String target) { - return 'Cleared: $target'; + return 'Entfernt: $target'; } @override - String get cacheClearConfirmTitle => 'Clear cache?'; + String get cacheClearConfirmTitle => 'Cache leeren?'; @override String cacheClearConfirmMessage(String target) { - return 'This will clear cached data for $target. Downloaded music files will not be deleted.'; + return 'Dies löscht zwischengespeicherte Daten in $target. Die Musikdateien werden nicht gelöscht.'; } @override - String get cacheClearAllConfirmTitle => 'Clear all cache?'; + String get cacheClearAllConfirmTitle => 'Gesamten Cache leeren?'; @override String get cacheClearAllConfirmMessage => - 'This will clear all cache categories on this page. Downloaded music files will not be deleted.'; + 'Dadurch werden alle Cache-Kategorien auf dieser Seite gelöscht. Heruntergeladene Musikdateien werden nicht gelöscht.'; @override - String get cacheClearAll => 'Clear all cache'; + String get cacheClearAll => 'Gesamten Cache leeren'; @override - String get cacheCleanupUnused => 'Cleanup unused data'; + String get cacheCleanupUnused => 'Unbenutzte Daten bereinigen'; @override String get cacheCleanupUnusedSubtitle => - 'Remove orphaned download history and missing library entries'; + 'Verwaisten Downloadverlauf und fehlende Bibliothekseinträge löschen'; @override String cacheCleanupResult(int downloadCount, int libraryCount) { - return 'Cleanup completed: $downloadCount orphaned downloads, $libraryCount missing library entries'; + return 'Bereinigung: $downloadCount verwaiste Downloads, $libraryCount fehlende Bibliothekseinträge'; } @override - String get cacheRefreshStats => 'Refresh stats'; + String get cacheRefreshStats => 'Statistik aktualisieren'; @override - String get trackSaveCoverArt => 'Save Cover Art'; + String get trackSaveCoverArt => 'Cover speichern'; @override - String get trackSaveCoverArtSubtitle => 'Save album art as .jpg file'; + String get trackSaveCoverArtSubtitle => 'Albumcover als .jpg Datei speichern'; @override - String get trackSaveLyrics => 'Save Lyrics (.lrc)'; + String get trackSaveLyrics => 'Lyrics als .lrc speichern'; @override - String get trackSaveLyricsSubtitle => 'Fetch and save lyrics as .lrc file'; + String get trackSaveLyricsSubtitle => 'Lade Lyrics als .lrc Datei'; @override - String get trackSaveLyricsProgress => 'Saving lyrics...'; + String get trackSaveLyricsProgress => 'Speichere Lyrics...'; @override - String get trackReEnrich => 'Re-enrich'; + String get trackReEnrich => 'Neu-anreichern'; @override String get trackReEnrichOnlineSubtitle => - 'Search metadata online and embed into file'; + 'Metadaten online suchen und in Datei einbinden'; @override - String get trackEditMetadata => 'Edit Metadata'; + String get trackEditMetadata => 'Metadaten bearbeiten'; @override String trackCoverSaved(String fileName) { @@ -2085,43 +2105,44 @@ class AppLocalizationsDe extends AppLocalizations { @override String trackLyricsSaved(String fileName) { - return 'Lyrics saved to $fileName'; + return 'Lyrics in $fileName gespeichert'; } @override - String get trackReEnrichProgress => 'Re-enriching metadata...'; + String get trackReEnrichProgress => 'Metadaten neu anreichern...'; @override - String get trackReEnrichSearching => 'Searching metadata online...'; + String get trackReEnrichSearching => 'Suche Metadaten online...'; @override - String get trackReEnrichSuccess => 'Metadata re-enriched successfully'; + String get trackReEnrichSuccess => 'Metadaten erfolgreich neu angereichert'; @override - String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed'; + String get trackReEnrichFfmpegFailed => + 'FFmpeg Metadaten-Einbettung fehlgeschlagen'; @override String trackSaveFailed(String error) { - return 'Failed: $error'; + return 'Fehler: $error'; } @override - String get trackConvertFormat => 'Convert Format'; + String get trackConvertFormat => 'Format konvertieren'; @override - String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + String get trackConvertFormatSubtitle => 'In MP3 oder Opus konvertieren'; @override - String get trackConvertTitle => 'Convert Audio'; + String get trackConvertTitle => 'Audio konvertieren'; @override - String get trackConvertTargetFormat => 'Target Format'; + String get trackConvertTargetFormat => 'Zielformat'; @override String get trackConvertBitrate => 'Bitrate'; @override - String get trackConvertConfirmTitle => 'Confirm Conversion'; + String get trackConvertConfirmTitle => 'Konvertierung bestätigen'; @override String trackConvertConfirmMessage( @@ -2129,198 +2150,199 @@ class AppLocalizationsDe extends AppLocalizations { String targetFormat, String bitrate, ) { - return 'Convert from $sourceFormat to $targetFormat at $bitrate?\n\nThe original file will be deleted after conversion.'; + return 'Konvertieren von $sourceFormat in $targetFormat bei $bitrate?\n\nDie Originaldatei wird nach der Konvertierung gelöscht.'; } @override - String get trackConvertConverting => 'Converting audio...'; + String get trackConvertConverting => 'Konvertiere Audio...'; @override String trackConvertSuccess(String format) { - return 'Converted to $format successfully'; + return 'Konvertiert in $format erfolgreich'; } @override - String get trackConvertFailed => 'Conversion failed'; + String get trackConvertFailed => 'Konvertierung fehlgeschlagen'; @override - String get actionCreate => 'Create'; + String get actionCreate => 'Erstellen'; @override - String get collectionFoldersTitle => 'My folders'; + String get collectionFoldersTitle => 'Meine Ordner'; @override - String get collectionWishlist => 'Wishlist'; + String get collectionWishlist => 'Wunschliste'; @override - String get collectionLoved => 'Loved'; + String get collectionLoved => 'Lieblingssongs'; @override - String get collectionPlaylists => 'Playlists'; + String get collectionPlaylists => 'Playlisten'; @override String get collectionPlaylist => 'Playlist'; @override - String get collectionAddToPlaylist => 'Add to playlist'; + String get collectionAddToPlaylist => 'Zur Playlist hinzufügen'; @override - String get collectionCreatePlaylist => 'Create playlist'; + String get collectionCreatePlaylist => 'Playlist erstellen'; @override - String get collectionNoPlaylistsYet => 'No playlists yet'; + String get collectionNoPlaylistsYet => 'Noch keine Playlists'; @override String get collectionNoPlaylistsSubtitle => - 'Create a playlist to start categorizing tracks'; + 'Playlist erstellen, um Titel zu kategorisieren'; @override String collectionPlaylistTracks(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count tracks', - one: '1 track', + other: '$count Titel', + one: '1 Titel', ); return '$_temp0'; } @override String collectionAddedToPlaylist(String playlistName) { - return 'Added to \"$playlistName\"'; + return 'Zu \"$playlistName \" hinzugefügt'; } @override String collectionAlreadyInPlaylist(String playlistName) { - return 'Already in \"$playlistName\"'; + return 'Bereits in \"$playlistName\"'; } @override - String get collectionPlaylistCreated => 'Playlist created'; + String get collectionPlaylistCreated => 'Playlist erstellt'; @override - String get collectionPlaylistNameHint => 'Playlist name'; + String get collectionPlaylistNameHint => 'Playlist-Name'; @override - String get collectionPlaylistNameRequired => 'Playlist name is required'; + String get collectionPlaylistNameRequired => 'Playlist-Name ist erforderlich'; @override - String get collectionRenamePlaylist => 'Rename playlist'; + String get collectionRenamePlaylist => 'Playlist umbenennen'; @override - String get collectionDeletePlaylist => 'Delete playlist'; + String get collectionDeletePlaylist => 'Playlist löschen'; @override String collectionDeletePlaylistMessage(String playlistName) { - return 'Delete \"$playlistName\" and all tracks inside it?'; + return 'Willst du \"$playlistName\" und alle darin enthaltenen Titel löschen?'; } @override - String get collectionPlaylistDeleted => 'Playlist deleted'; + String get collectionPlaylistDeleted => 'Playlist gelöscht'; @override - String get collectionPlaylistRenamed => 'Playlist renamed'; + String get collectionPlaylistRenamed => 'Playlist umbenannt'; @override - String get collectionWishlistEmptyTitle => 'Wishlist is empty'; + String get collectionWishlistEmptyTitle => 'Wunschliste ist leer'; @override String get collectionWishlistEmptySubtitle => - 'Tap + on tracks to save what you want to download later'; + 'Tippe auf das + bei den Titeln, um sie zum späteren Herunterladen zu speichern'; @override - String get collectionLovedEmptyTitle => 'Loved folder is empty'; + String get collectionLovedEmptyTitle => 'Lieblingssongs sind leer'; @override String get collectionLovedEmptySubtitle => - 'Tap love on tracks to keep your favorites'; + 'Tippe auf das Herz, um deine Favoriten zu behalten'; @override - String get collectionPlaylistEmptyTitle => 'Playlist is empty'; + String get collectionPlaylistEmptyTitle => 'Die Playlist ist leer'; @override String get collectionPlaylistEmptySubtitle => - 'Long-press + on any track to add it here'; + 'Drücke lange + auf einem beliebigen Titel, um ihn hier hinzuzufügen'; @override - String get collectionRemoveFromPlaylist => 'Remove from playlist'; + String get collectionRemoveFromPlaylist => 'Von Playlist entfernen'; @override - String get collectionRemoveFromFolder => 'Remove from folder'; + String get collectionRemoveFromFolder => 'Aus Ordner entfernen'; @override String collectionRemoved(String trackName) { - return '\"$trackName\" removed'; + return '\"$trackName\" entfernt'; } @override String collectionAddedToLoved(String trackName) { - return '\"$trackName\" added to Loved'; + return '\"$trackName\" zu Lieblingssongs hinzugefügt'; } @override String collectionRemovedFromLoved(String trackName) { - return '\"$trackName\" removed from Loved'; + return '\"$trackName\" aus Lieblingssongs entfernt'; } @override String collectionAddedToWishlist(String trackName) { - return '\"$trackName\" added to Wishlist'; + return '\"$trackName\" zur Wunschliste hinzugefügt'; } @override String collectionRemovedFromWishlist(String trackName) { - return '\"$trackName\" removed from Wishlist'; + return '\"$trackName\" aus der Wunschliste entfernt'; } @override - String get trackOptionAddToLoved => 'Add to Loved'; + String get trackOptionAddToLoved => 'Zu Lieblingssongs hinzufügen'; @override - String get trackOptionRemoveFromLoved => 'Remove from Loved'; + String get trackOptionRemoveFromLoved => 'Aus Lieblingssongs entfernt'; @override - String get trackOptionAddToWishlist => 'Add to Wishlist'; + String get trackOptionAddToWishlist => 'Zur Wunschliste hinzufügen'; @override - String get trackOptionRemoveFromWishlist => 'Remove from Wishlist'; + String get trackOptionRemoveFromWishlist => 'Von der Wunschliste entfernen'; @override - String get collectionPlaylistChangeCover => 'Change cover image'; + String get collectionPlaylistChangeCover => 'Coverbild ändern'; @override - String get collectionPlaylistRemoveCover => 'Remove cover image'; + String get collectionPlaylistRemoveCover => 'Cover entfernen'; @override String selectionShareCount(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'tracks', - one: 'track', + other: 'Titel', + one: 'Titel', ); - return 'Share $count $_temp0'; + return 'Teile $count $_temp0'; } @override - String get selectionShareNoFiles => 'No shareable files found'; + String get selectionShareNoFiles => 'Keine teilbare Dateien gefunden'; @override String selectionConvertCount(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'tracks', - one: 'track', + other: 'Titel', + one: 'Titel', ); - return 'Convert $count $_temp0'; + return 'Konvertiere $count $_temp0'; } @override - String get selectionConvertNoConvertible => 'No convertible tracks selected'; + String get selectionConvertNoConvertible => + 'Keine konvertierbare Titel ausgewählt'; @override - String get selectionBatchConvertConfirmTitle => 'Batch Convert'; + String get selectionBatchConvertConfirmTitle => 'Batch-Konvertierung'; @override String selectionBatchConvertConfirmMessage( @@ -2331,30 +2353,30 @@ class AppLocalizationsDe extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'tracks', - one: 'track', + other: 'Titel', + one: 'Titel', ); - return 'Convert $count $_temp0 to $format at $bitrate?\n\nOriginal files will be deleted after conversion.'; + return 'Konvertiere $count $format $_temp0 zu $bitrate?\n\nOriginaldateien werden nach der Konvertierung gelöscht.'; } @override String selectionBatchConvertProgress(int current, int total) { - return 'Converting $current of $total...'; + return 'Konvertiere $current von $total...'; } @override String selectionBatchConvertSuccess(int success, int total, String format) { - return 'Converted $success of $total tracks to $format'; + return '$success von $total Titeln in $format konvertiert'; } @override String downloadedAlbumDownloadedCount(int count) { - return '$count downloaded'; + return '$count heruntergeladen'; } @override String get downloadUseAlbumArtistForFoldersAlbumSubtitle => - 'Artist folders use Album Artist when available'; + 'Künstlerordner verwenden den Album-Interpreten, wenn verfügbar'; @override String get downloadUseAlbumArtistForFoldersTrackSubtitle => diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index c98cf0ee..c583ab11 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -688,6 +688,17 @@ class AppLocalizationsEn extends AppLocalizations { @override String get errorNoTracksFound => 'No tracks found'; + @override + String get errorUrlNotRecognized => 'Link not recognized'; + + @override + String get errorUrlNotRecognizedMessage => + 'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'; + + @override + String get errorUrlFetchFailed => + 'Failed to load content from this link. Please try again.'; + @override String errorMissingExtensionSource(String item) { return 'Cannot load $item: missing extension source'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index efc7a016..f4758a27 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -688,6 +688,17 @@ class AppLocalizationsEs extends AppLocalizations { @override String get errorNoTracksFound => 'No tracks found'; + @override + String get errorUrlNotRecognized => 'Link not recognized'; + + @override + String get errorUrlNotRecognizedMessage => + 'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'; + + @override + String get errorUrlFetchFailed => + 'Failed to load content from this link. Please try again.'; + @override String errorMissingExtensionSource(String item) { return 'Cannot load $item: missing extension source'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 89d94355..c93b863f 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -358,7 +358,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; + 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; @override String get artistAlbums => 'Albums'; @@ -690,6 +690,17 @@ class AppLocalizationsFr extends AppLocalizations { @override String get errorNoTracksFound => 'No tracks found'; + @override + String get errorUrlNotRecognized => 'Link not recognized'; + + @override + String get errorUrlNotRecognizedMessage => + 'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'; + + @override + String get errorUrlFetchFailed => + 'Failed to load content from this link. Please try again.'; + @override String errorMissingExtensionSource(String item) { return 'Cannot load $item: missing extension source'; @@ -1811,7 +1822,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Audio en qualité FLAC depuis Tidal, Qobuz ou Deezer'; + 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index 5b6ff05f..a12620b5 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -356,7 +356,7 @@ class AppLocalizationsHi extends AppLocalizations { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; + 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; @override String get artistAlbums => 'Albums'; @@ -688,6 +688,17 @@ class AppLocalizationsHi extends AppLocalizations { @override String get errorNoTracksFound => 'No tracks found'; + @override + String get errorUrlNotRecognized => 'Link not recognized'; + + @override + String get errorUrlNotRecognizedMessage => + 'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'; + + @override + String get errorUrlFetchFailed => + 'Failed to load content from this link. Please try again.'; + @override String errorMissingExtensionSource(String item) { return 'Cannot load $item: missing extension source'; @@ -1809,7 +1820,7 @@ class AppLocalizationsHi extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Tidal, Qobuz, या Deezer से FLAC गुणवत्ता ऑडियो प्राप्त करें'; + 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index 4312a41a..e408e8ae 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -15,7 +15,7 @@ class AppLocalizationsId extends AppLocalizations { String get navHome => 'Beranda'; @override - String get navLibrary => 'Library'; + String get navLibrary => 'Pustaka'; @override String get navSettings => 'Pengaturan'; @@ -45,7 +45,7 @@ class AppLocalizationsId extends AppLocalizations { String get historyFilterSingles => 'Single'; @override - String get historySearchHint => 'Search history...'; + String get historySearchHint => 'Cari riwayat...'; @override String get settingsTitle => 'Pengaturan'; @@ -104,7 +104,7 @@ class AppLocalizationsId extends AppLocalizations { String get appearanceHistoryViewList => 'Daftar'; @override - String get appearanceHistoryViewGrid => 'Grid'; + String get appearanceHistoryViewGrid => 'Kisi'; @override String get optionsTitle => 'Opsi'; @@ -126,7 +126,7 @@ class AppLocalizationsId extends AppLocalizations { 'Ketuk Deezer atau Spotify untuk beralih dari ekstensi'; @override - String get optionsAutoFallback => 'Auto Fallback'; + String get optionsAutoFallback => 'Cadangan Otomatis'; @override String get optionsAutoFallbackSubtitle => @@ -217,7 +217,7 @@ class AppLocalizationsId extends AppLocalizations { @override String optionsSpotifyCredentialsConfigured(String clientId) { - return 'Client ID: $clientId...'; + return 'ID Klien: $clientId...'; } @override @@ -230,7 +230,7 @@ class AppLocalizationsId extends AppLocalizations { @override String get optionsSpotifyDeprecationWarning => - 'Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.'; + 'Pencarian Spotify akan dihentikan pada 3 Maret 2026 karena perubahan API Spotify. Silakan beralih ke Deezer.'; @override String get extensionsTitle => 'Ekstensi'; @@ -283,7 +283,7 @@ class AppLocalizationsId extends AppLocalizations { 'Seniman berbakat yang membuat logo aplikasi kita yang indah!'; @override - String get aboutTranslators => 'Translators'; + String get aboutTranslators => 'Penerjemah'; @override String get aboutSpecialThanks => 'Terima Kasih Khusus'; @@ -311,19 +311,19 @@ class AppLocalizationsId extends AppLocalizations { 'Sarankan fitur baru untuk aplikasi'; @override - String get aboutTelegramChannel => 'Telegram Channel'; + String get aboutTelegramChannel => 'Saluran Telegram'; @override - String get aboutTelegramChannelSubtitle => 'Announcements and updates'; + String get aboutTelegramChannelSubtitle => 'Pengumuman dan pembaruan'; @override - String get aboutTelegramChat => 'Telegram Community'; + String get aboutTelegramChat => 'Komunitas Telegram'; @override - String get aboutTelegramChatSubtitle => 'Chat with other users'; + String get aboutTelegramChatSubtitle => 'Berbincang dengan pengguna lain'; @override - String get aboutSocial => 'Social'; + String get aboutSocial => 'Sosial'; @override String get aboutApp => 'Aplikasi'; @@ -341,7 +341,7 @@ class AppLocalizationsId extends AppLocalizations { @override String get aboutSjdonadoDesc => - 'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!'; + 'Pencipta I Don\'t Have Spotify (IDHS). Penyelesai tautan cadangan yang menyelamatkan keadaan!'; @override String get aboutDabMusic => 'DAB Music'; @@ -355,11 +355,11 @@ class AppLocalizationsId extends AppLocalizations { @override String get aboutSpotiSaverDesc => - 'Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!'; + 'Tidal perangkat streaming FLAC resolusi tinggi. Bagian penting dari teka-teki tanpa kehilangan kualitas!'; @override String get aboutAppDescription => - 'Unduh lagu Spotify dalam kualitas lossless dari Tidal dan Qobuz.'; + 'Unduh lagu Spotify dalam kualitas lossless dari Tidal, Qobuz, dan Amazon Music.'; @override String get artistAlbums => 'Album'; @@ -456,7 +456,7 @@ class AppLocalizationsId extends AppLocalizations { @override String get setupIcloudNotSupported => - 'iCloud Drive is not supported. Please use the app Documents folder.'; + 'iCloud Drive tidak didukung. Silakan gunakan folder Dokumen di aplikasi.'; @override String get setupDownloadInFlac => 'Unduh lagu Spotify dalam format FLAC'; @@ -593,7 +593,7 @@ class AppLocalizationsId extends AppLocalizations { @override String csvImportTracks(int count) { - return '$count tracks from CSV'; + return '$count trek dari CSV'; } @override @@ -613,7 +613,7 @@ class AppLocalizationsId extends AppLocalizations { @override String snackbarAlreadyInLibrary(String trackName) { - return '\"$trackName\" already exists in your library'; + return '\"$trackName\" sudah ada di perpustakaan Anda'; } @override @@ -691,6 +691,17 @@ class AppLocalizationsId extends AppLocalizations { @override String get errorNoTracksFound => 'Tidak ada lagu ditemukan'; + @override + String get errorUrlNotRecognized => 'Link tidak dikenali'; + + @override + String get errorUrlNotRecognizedMessage => + 'Link ini tidak didukung. Pastikan URL benar dan ekstensi yang kompatibel sudah terpasang.'; + + @override + String get errorUrlFetchFailed => + 'Gagal memuat konten dari link ini. Silakan coba lagi.'; + @override String errorMissingExtensionSource(String item) { return 'Tidak dapat memuat $item: sumber ekstensi tidak ada'; @@ -755,11 +766,11 @@ class AppLocalizationsId extends AppLocalizations { String get filenameFormat => 'Format Nama File'; @override - String get filenameShowAdvancedTags => 'Tampilkan tag lanjutan'; + String get filenameShowAdvancedTags => 'Show advanced tags'; @override String get filenameShowAdvancedTagsDescription => - 'Aktifkan tag format untuk padding nomor lagu dan pola tanggal'; + 'Enable formatted tags for track padding and date patterns'; @override String get folderOrganizationNone => 'Tidak ada'; @@ -1343,10 +1354,10 @@ class AppLocalizationsId extends AppLocalizations { 'YouTube provides lossy audio only. Not part of lossless fallback.'; @override - String get youtubeOpusBitrateTitle => 'Bitrate Opus YouTube'; + String get youtubeOpusBitrateTitle => 'YouTube Opus Bitrate'; @override - String get youtubeMp3BitrateTitle => 'Bitrate MP3 YouTube'; + String get youtubeMp3BitrateTitle => 'YouTube MP3 Bitrate'; @override String get downloadAskBeforeDownload => 'Tanya Sebelum Unduh'; @@ -1684,8 +1695,8 @@ class AppLocalizationsId extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'trek', - one: 'trek', + other: 'tracks', + one: 'track', ); return '$_temp0'; } @@ -1816,7 +1827,7 @@ class AppLocalizationsId extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Dapatkan audio kualitas FLAC dari Tidal, Qobuz, atau Deezer'; + 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; @override String get tutorialWelcomeTip3 => @@ -2134,10 +2145,10 @@ class AppLocalizationsId extends AppLocalizations { String get trackConvertFailed => 'Conversion failed'; @override - String get actionCreate => 'Buat'; + String get actionCreate => 'Create'; @override - String get collectionFoldersTitle => 'Folder saya'; + String get collectionFoldersTitle => 'My folders'; @override String get collectionWishlist => 'Wishlist'; @@ -2146,172 +2157,171 @@ class AppLocalizationsId extends AppLocalizations { String get collectionLoved => 'Loved'; @override - String get collectionPlaylists => 'Playlist'; + String get collectionPlaylists => 'Playlists'; @override String get collectionPlaylist => 'Playlist'; @override - String get collectionAddToPlaylist => 'Tambahkan ke playlist'; + String get collectionAddToPlaylist => 'Add to playlist'; @override - String get collectionCreatePlaylist => 'Buat playlist'; + String get collectionCreatePlaylist => 'Create playlist'; @override - String get collectionNoPlaylistsYet => 'Belum ada playlist'; + String get collectionNoPlaylistsYet => 'No playlists yet'; @override String get collectionNoPlaylistsSubtitle => - 'Buat playlist untuk mulai mengategorikan lagu'; + 'Create a playlist to start categorizing tracks'; @override String collectionPlaylistTracks(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count lagu', - one: '1 lagu', + other: '$count tracks', + one: '1 track', ); return '$_temp0'; } @override String collectionAddedToPlaylist(String playlistName) { - return 'Ditambahkan ke \"$playlistName\"'; + return 'Added to \"$playlistName\"'; } @override String collectionAlreadyInPlaylist(String playlistName) { - return 'Sudah ada di \"$playlistName\"'; + return 'Already in \"$playlistName\"'; } @override - String get collectionPlaylistCreated => 'Playlist berhasil dibuat'; + String get collectionPlaylistCreated => 'Playlist created'; @override - String get collectionPlaylistNameHint => 'Nama playlist'; + String get collectionPlaylistNameHint => 'Playlist name'; @override - String get collectionPlaylistNameRequired => 'Nama playlist wajib diisi'; + String get collectionPlaylistNameRequired => 'Playlist name is required'; @override - String get collectionRenamePlaylist => 'Ubah nama playlist'; + String get collectionRenamePlaylist => 'Rename playlist'; @override - String get collectionDeletePlaylist => 'Hapus playlist'; + String get collectionDeletePlaylist => 'Delete playlist'; @override String collectionDeletePlaylistMessage(String playlistName) { - return 'Hapus \"$playlistName\" beserta semua lagunya?'; + return 'Delete \"$playlistName\" and all tracks inside it?'; } @override - String get collectionPlaylistDeleted => 'Playlist dihapus'; + String get collectionPlaylistDeleted => 'Playlist deleted'; @override - String get collectionPlaylistRenamed => 'Nama playlist diperbarui'; + String get collectionPlaylistRenamed => 'Playlist renamed'; @override - String get collectionWishlistEmptyTitle => 'Wishlist masih kosong'; + String get collectionWishlistEmptyTitle => 'Wishlist is empty'; @override String get collectionWishlistEmptySubtitle => - 'Tap + di lagu untuk menyimpan yang ingin diunduh nanti'; + 'Tap + on tracks to save what you want to download later'; @override - String get collectionLovedEmptyTitle => 'Folder Loved masih kosong'; + String get collectionLovedEmptyTitle => 'Loved folder is empty'; @override String get collectionLovedEmptySubtitle => - 'Tap love di lagu untuk menyimpan favoritmu'; + 'Tap love on tracks to keep your favorites'; @override - String get collectionPlaylistEmptyTitle => 'Playlist masih kosong'; + String get collectionPlaylistEmptyTitle => 'Playlist is empty'; @override String get collectionPlaylistEmptySubtitle => - 'Tekan lama tombol + pada lagu untuk menambahkannya ke sini'; + 'Long-press + on any track to add it here'; @override - String get collectionRemoveFromPlaylist => 'Hapus dari playlist'; + String get collectionRemoveFromPlaylist => 'Remove from playlist'; @override - String get collectionRemoveFromFolder => 'Hapus dari folder'; + String get collectionRemoveFromFolder => 'Remove from folder'; @override String collectionRemoved(String trackName) { - return '\"$trackName\" dihapus'; + return '\"$trackName\" removed'; } @override String collectionAddedToLoved(String trackName) { - return '\"$trackName\" ditambahkan ke Loved'; + return '\"$trackName\" added to Loved'; } @override String collectionRemovedFromLoved(String trackName) { - return '\"$trackName\" dihapus dari Loved'; + return '\"$trackName\" removed from Loved'; } @override String collectionAddedToWishlist(String trackName) { - return '\"$trackName\" ditambahkan ke Wishlist'; + return '\"$trackName\" added to Wishlist'; } @override String collectionRemovedFromWishlist(String trackName) { - return '\"$trackName\" dihapus dari Wishlist'; + return '\"$trackName\" removed from Wishlist'; } @override - String get trackOptionAddToLoved => 'Tambahkan ke Loved'; + String get trackOptionAddToLoved => 'Add to Loved'; @override - String get trackOptionRemoveFromLoved => 'Hapus dari Loved'; + String get trackOptionRemoveFromLoved => 'Remove from Loved'; @override - String get trackOptionAddToWishlist => 'Tambahkan ke Wishlist'; + String get trackOptionAddToWishlist => 'Add to Wishlist'; @override - String get trackOptionRemoveFromWishlist => 'Hapus dari Wishlist'; + String get trackOptionRemoveFromWishlist => 'Remove from Wishlist'; @override - String get collectionPlaylistChangeCover => 'Ubah gambar sampul'; + String get collectionPlaylistChangeCover => 'Change cover image'; @override - String get collectionPlaylistRemoveCover => 'Hapus gambar sampul'; + String get collectionPlaylistRemoveCover => 'Remove cover image'; @override String selectionShareCount(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'trek', - one: 'trek', + other: 'tracks', + one: 'track', ); - return 'Bagikan $count $_temp0'; + return 'Share $count $_temp0'; } @override - String get selectionShareNoFiles => 'Tidak ada file yang dapat dibagikan'; + String get selectionShareNoFiles => 'No shareable files found'; @override String selectionConvertCount(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'trek', - one: 'trek', + other: 'tracks', + one: 'track', ); - return 'Konversi $count $_temp0'; + return 'Convert $count $_temp0'; } @override - String get selectionConvertNoConvertible => - 'Tidak ada trek yang dapat dikonversi dipilih'; + String get selectionConvertNoConvertible => 'No convertible tracks selected'; @override - String get selectionBatchConvertConfirmTitle => 'Konversi Massal'; + String get selectionBatchConvertConfirmTitle => 'Batch Convert'; @override String selectionBatchConvertConfirmMessage( @@ -2322,20 +2332,20 @@ class AppLocalizationsId extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'trek', - one: 'trek', + other: 'tracks', + one: 'track', ); - return 'Konversi $count $_temp0 ke $format pada $bitrate?\n\nFile asli akan dihapus setelah konversi.'; + return 'Convert $count $_temp0 to $format at $bitrate?\n\nOriginal files will be deleted after conversion.'; } @override String selectionBatchConvertProgress(int current, int total) { - return 'Mengonversi $current dari $total...'; + return 'Converting $current of $total...'; } @override String selectionBatchConvertSuccess(int success, int total, String format) { - return 'Berhasil mengonversi $success dari $total trek ke $format'; + return 'Converted $success of $total tracks to $format'; } @override diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 9aa5a2fb..9e898bdf 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -15,7 +15,7 @@ class AppLocalizationsJa extends AppLocalizations { String get navHome => 'ホーム'; @override - String get navLibrary => 'Library'; + String get navLibrary => 'ライブラリ'; @override String get navSettings => '設定'; @@ -160,7 +160,7 @@ class AppLocalizationsJa extends AppLocalizations { @override String optionsConcurrentParallel(int count) { - return '$count parallel downloads'; + return '$count 件の分割ダウンロード'; } @override @@ -352,7 +352,7 @@ class AppLocalizationsJa extends AppLocalizations { @override String get aboutAppDescription => - 'Tidal、Qobuz から Spotify のトラックをロスレス品質でダウンロードします。'; + 'Tidal、Qobuz、Amazon Music から Spotify のトラックをロスレス品質でダウンロードします。'; @override String get artistAlbums => 'アルバム'; @@ -683,6 +683,17 @@ class AppLocalizationsJa extends AppLocalizations { @override String get errorNoTracksFound => 'トラックがありません'; + @override + String get errorUrlNotRecognized => 'Link not recognized'; + + @override + String get errorUrlNotRecognizedMessage => + 'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'; + + @override + String get errorUrlFetchFailed => + 'Failed to load content from this link. Please try again.'; + @override String errorMissingExtensionSource(String item) { return '$item を読み込めません: 拡張ソースがありません'; @@ -1111,7 +1122,7 @@ class AppLocalizationsJa extends AppLocalizations { String get trackLyricsLoadFailed => '歌詞の読み込みに失敗しました'; @override - String get trackEmbedLyrics => 'Embed Lyrics'; + String get trackEmbedLyrics => '歌詞を埋め込む'; @override String get trackLyricsEmbedded => 'Lyrics embedded successfully'; @@ -1325,10 +1336,10 @@ class AppLocalizationsJa extends AppLocalizations { 'YouTube provides lossy audio only. Not part of lossless fallback.'; @override - String get youtubeOpusBitrateTitle => 'YouTube Opus Bitrate'; + String get youtubeOpusBitrateTitle => 'YouTube Opus のビットレート'; @override - String get youtubeMp3BitrateTitle => 'YouTube MP3 Bitrate'; + String get youtubeMp3BitrateTitle => 'YouTube MP3 のビットレート'; @override String get downloadAskBeforeDownload => 'ダウンロード前に確認する'; @@ -1375,20 +1386,20 @@ class AppLocalizationsJa extends AppLocalizations { String get queueClearAllMessage => 'すべてのダウンロードを消去してもよろしいですか?'; @override - String get settingsAutoExportFailed => 'Auto-export failed downloads'; + String get settingsAutoExportFailed => 'ダウンロードの自動エクスポートに失敗しました'; @override String get settingsAutoExportFailedSubtitle => 'Save failed downloads to TXT file automatically'; @override - String get settingsDownloadNetwork => 'Download Network'; + String get settingsDownloadNetwork => 'ダウンロードネットワーク'; @override - String get settingsDownloadNetworkAny => 'WiFi + Mobile Data'; + String get settingsDownloadNetworkAny => 'Wi-Fi + モバイルデータ'; @override - String get settingsDownloadNetworkWifiOnly => 'WiFi Only'; + String get settingsDownloadNetworkWifiOnly => 'Wi-Fi のみ'; @override String get settingsDownloadNetworkSubtitle => @@ -1419,7 +1430,7 @@ class AppLocalizationsJa extends AppLocalizations { String get albumFolderYearAlbumSubtitle => 'アルバム/[2005] アルバム名/'; @override - String get albumFolderArtistAlbumSingles => 'Artist / Album + Singles'; + String get albumFolderArtistAlbumSingles => 'アーティスト / アルバム + シングル'; @override String get albumFolderArtistAlbumSinglesSubtitle => @@ -1485,7 +1496,7 @@ class AppLocalizationsJa extends AppLocalizations { String get recentEmpty => 'No recent items yet'; @override - String get recentShowAllDownloads => 'Show All Downloads'; + String get recentShowAllDownloads => 'すべてのダウンロードを表示'; @override String recentPlaylistInfo(String name) { @@ -1559,10 +1570,10 @@ class AppLocalizationsJa extends AppLocalizations { String get discographyFailedToFetch => '一部のアルバムの取得に失敗しました'; @override - String get sectionStorageAccess => 'Storage Access'; + String get sectionStorageAccess => 'ストレージアクセス'; @override - String get allFilesAccess => 'All Files Access'; + String get allFilesAccess => 'すべてのファイルへのアクセス'; @override String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; @@ -1583,35 +1594,35 @@ class AppLocalizationsJa extends AppLocalizations { 'All Files Access disabled. The app will use limited storage access.'; @override - String get settingsLocalLibrary => 'Local Library'; + String get settingsLocalLibrary => 'ローカルライブラリ'; @override String get settingsLocalLibrarySubtitle => 'Scan music & detect duplicates'; @override - String get settingsCache => 'Storage & Cache'; + String get settingsCache => 'ストレージとキャッシュ'; @override String get settingsCacheSubtitle => 'View size and clear cached data'; @override - String get libraryTitle => 'Local Library'; + String get libraryTitle => 'ローカルライブラリ'; @override - String get libraryScanSettings => 'Scan Settings'; + String get libraryScanSettings => 'スキャン設定'; @override - String get libraryEnableLocalLibrary => 'Enable Local Library'; + String get libraryEnableLocalLibrary => 'ローカルライブラリを有効'; @override String get libraryEnableLocalLibrarySubtitle => 'Scan and track your existing music'; @override - String get libraryFolder => 'Library Folder'; + String get libraryFolder => 'ライブラリのフォルダ'; @override - String get libraryFolderHint => 'Tap to select folder'; + String get libraryFolderHint => 'タップでフォルダを選択'; @override String get libraryShowDuplicateIndicator => 'Show Duplicate Indicator'; @@ -1621,13 +1632,13 @@ class AppLocalizationsJa extends AppLocalizations { 'Show when searching for existing tracks'; @override - String get libraryActions => 'Actions'; + String get libraryActions => 'アクション'; @override - String get libraryScan => 'Scan Library'; + String get libraryScan => 'ライブラリをスキャン'; @override - String get libraryScanSubtitle => 'Scan for audio files'; + String get libraryScanSubtitle => 'オーディオファイルをスキャン'; @override String get libraryScanSelectFolderFirst => 'Select a folder first'; @@ -1640,20 +1651,20 @@ class AppLocalizationsJa extends AppLocalizations { 'Remove entries for files that no longer exist'; @override - String get libraryClear => 'Clear Library'; + String get libraryClear => 'ライブラリを消去'; @override String get libraryClearSubtitle => 'Remove all scanned tracks'; @override - String get libraryClearConfirmTitle => 'Clear Library'; + String get libraryClearConfirmTitle => 'ライブラリを消去'; @override String get libraryClearConfirmMessage => 'This will remove all scanned tracks from your library. Your actual music files will not be deleted.'; @override - String get libraryAbout => 'About Local Library'; + String get libraryAbout => 'ローカルライブラリについて'; @override String get libraryAboutDescription => @@ -1672,14 +1683,14 @@ class AppLocalizationsJa extends AppLocalizations { @override String libraryLastScanned(String time) { - return 'Last scanned: $time'; + return '最終スキャン: $time'; } @override String get libraryLastScannedNever => 'Never'; @override - String get libraryScanning => 'Scanning...'; + String get libraryScanning => 'スキャン中...'; @override String libraryScanProgress(String progress, int total) { @@ -1687,7 +1698,7 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get libraryInLibrary => 'In Library'; + String get libraryInLibrary => 'ライブラリ内'; @override String libraryRemovedMissingFiles(int count) { @@ -1698,7 +1709,7 @@ class AppLocalizationsJa extends AppLocalizations { String get libraryCleared => 'Library cleared'; @override - String get libraryStorageAccessRequired => 'Storage Access Required'; + String get libraryStorageAccessRequired => 'ストレージアクセスが必要です'; @override String get libraryStorageAccessMessage => @@ -1708,37 +1719,37 @@ class AppLocalizationsJa extends AppLocalizations { String get libraryFolderNotExist => 'Selected folder does not exist'; @override - String get librarySourceDownloaded => 'Downloaded'; + String get librarySourceDownloaded => 'ダウンロード済み'; @override - String get librarySourceLocal => 'Local'; + String get librarySourceLocal => 'ローカル'; @override - String get libraryFilterAll => 'All'; + String get libraryFilterAll => 'すべて'; @override - String get libraryFilterDownloaded => 'Downloaded'; + String get libraryFilterDownloaded => 'ダウンロード済み'; @override - String get libraryFilterLocal => 'Local'; + String get libraryFilterLocal => 'ローカル'; @override - String get libraryFilterTitle => 'Filters'; + String get libraryFilterTitle => 'フィルター'; @override - String get libraryFilterReset => 'Reset'; + String get libraryFilterReset => 'リセット'; @override - String get libraryFilterApply => 'Apply'; + String get libraryFilterApply => '適用'; @override - String get libraryFilterSource => 'Source'; + String get libraryFilterSource => 'ソース'; @override - String get libraryFilterQuality => 'Quality'; + String get libraryFilterQuality => '品質'; @override - String get libraryFilterQualityHiRes => 'Hi-Res (24bit)'; + String get libraryFilterQualityHiRes => 'ハイレゾ (24bit)'; @override String get libraryFilterQualityCD => 'CD (16bit)'; @@ -1747,7 +1758,7 @@ class AppLocalizationsJa extends AppLocalizations { String get libraryFilterQualityLossy => 'Lossy'; @override - String get libraryFilterFormat => 'Format'; + String get libraryFilterFormat => '形式'; @override String get libraryFilterSort => 'Sort'; @@ -1766,8 +1777,8 @@ class AppLocalizationsJa extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count minutes ago', - one: '1 minute ago', + other: '$count 分前', + one: '1 分前', ); return '$_temp0'; } @@ -1777,14 +1788,14 @@ class AppLocalizationsJa extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count hours ago', - one: '1 hour ago', + other: '$count 時間前', + one: '1 時間前', ); return '$_temp0'; } @override - String get tutorialWelcomeTitle => 'Welcome to SpotiFLAC!'; + String get tutorialWelcomeTitle => 'SpotiFLAC へようこそ!'; @override String get tutorialWelcomeDesc => @@ -1795,7 +1806,8 @@ class AppLocalizationsJa extends AppLocalizations { 'Download music from Spotify, Deezer, or paste any supported URL'; @override - String get tutorialWelcomeTip2 => 'Tidal、Qobuz、Deezer から FLAC 品質のオーディオを取得'; + String get tutorialWelcomeTip2 => + 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; @override String get tutorialWelcomeTip3 => @@ -1809,14 +1821,14 @@ class AppLocalizationsJa extends AppLocalizations { 'There are two easy ways to find music you want to download.'; @override - String get tutorialDownloadTitle => 'Downloading Music'; + String get tutorialDownloadTitle => '音楽をダウンロード中'; @override String get tutorialDownloadDesc => 'Downloading music is simple and fast. Here\'s how it works.'; @override - String get tutorialLibraryTitle => 'Your Library'; + String get tutorialLibraryTitle => 'あなたのライブラリ'; @override String get tutorialLibraryDesc => @@ -1835,7 +1847,7 @@ class AppLocalizationsJa extends AppLocalizations { 'Switch between list and grid view for better browsing'; @override - String get tutorialExtensionsTitle => 'Extensions'; + String get tutorialExtensionsTitle => '拡張'; @override String get tutorialExtensionsDesc => @@ -1876,7 +1888,7 @@ class AppLocalizationsJa extends AppLocalizations { 'You\'re all set! Start downloading your favorite music now.'; @override - String get libraryForceFullScan => 'Force Full Scan'; + String get libraryForceFullScan => '強制フルスキャン'; @override String get libraryForceFullScanSubtitle => 'Rescan all files, ignoring cache'; @@ -1897,10 +1909,10 @@ class AppLocalizationsJa extends AppLocalizations { String get cleanupOrphanedDownloadsNone => 'No orphaned entries found'; @override - String get cacheTitle => 'Storage & Cache'; + String get cacheTitle => 'ストレージとキャッシュ'; @override - String get cacheSummaryTitle => 'Cache overview'; + String get cacheSummaryTitle => 'キャッシュの概要'; @override String get cacheSummarySubtitle => @@ -1912,34 +1924,34 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get cacheSectionStorage => 'Cached Data'; + String get cacheSectionStorage => 'キャッシュ済みデータ'; @override - String get cacheSectionMaintenance => 'Maintenance'; + String get cacheSectionMaintenance => 'メンテナンス'; @override - String get cacheAppDirectory => 'App cache directory'; + String get cacheAppDirectory => 'アプリキャッシュのディレクトリ'; @override String get cacheAppDirectoryDesc => 'HTTP responses, WebView data, and other temporary app data.'; @override - String get cacheTempDirectory => 'Temporary directory'; + String get cacheTempDirectory => '一時ディレクトリ'; @override String get cacheTempDirectoryDesc => 'Temporary files from downloads and audio conversion.'; @override - String get cacheCoverImage => 'Cover image cache'; + String get cacheCoverImage => 'カバー画像のキャッシュ'; @override String get cacheCoverImageDesc => 'Downloaded album and track cover art. Will re-download when viewed.'; @override - String get cacheLibraryCover => 'Library cover cache'; + String get cacheLibraryCover => 'ライブラリのカバーキャッシュ'; @override String get cacheLibraryCoverDesc => @@ -1964,7 +1976,7 @@ class AppLocalizationsJa extends AppLocalizations { 'Remove orphaned download history and library entries for missing files.'; @override - String get cacheNoData => 'No cached data'; + String get cacheNoData => 'キャッシュデータはありません'; @override String cacheSizeWithFiles(String size, int count) { @@ -1978,16 +1990,16 @@ class AppLocalizationsJa extends AppLocalizations { @override String cacheEntries(int count) { - return '$count entries'; + return '$count 個のエントリ'; } @override String cacheClearSuccess(String target) { - return 'Cleared: $target'; + return '消去済み: $target'; } @override - String get cacheClearConfirmTitle => 'Clear cache?'; + String get cacheClearConfirmTitle => 'キャッシュを消去しますか?'; @override String cacheClearConfirmMessage(String target) { @@ -1995,17 +2007,17 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get cacheClearAllConfirmTitle => 'Clear all cache?'; + String get cacheClearAllConfirmTitle => 'すべてのキャッシュを消去しますか?'; @override String get cacheClearAllConfirmMessage => 'This will clear all cache categories on this page. Downloaded music files will not be deleted.'; @override - String get cacheClearAll => 'Clear all cache'; + String get cacheClearAll => 'すべてのキャッシュを消去'; @override - String get cacheCleanupUnused => 'Cleanup unused data'; + String get cacheCleanupUnused => '未使用のデータを削除'; @override String get cacheCleanupUnusedSubtitle => @@ -2017,16 +2029,16 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get cacheRefreshStats => 'Refresh stats'; + String get cacheRefreshStats => '状態を更新'; @override - String get trackSaveCoverArt => 'Save Cover Art'; + String get trackSaveCoverArt => 'カバー画像を保存'; @override String get trackSaveCoverArtSubtitle => 'Save album art as .jpg file'; @override - String get trackSaveLyrics => 'Save Lyrics (.lrc)'; + String get trackSaveLyrics => '歌詞を保存 (.lrc)'; @override String get trackSaveLyricsSubtitle => 'Fetch and save lyrics as .lrc file'; @@ -2042,7 +2054,7 @@ class AppLocalizationsJa extends AppLocalizations { 'Search metadata online and embed into file'; @override - String get trackEditMetadata => 'Edit Metadata'; + String get trackEditMetadata => 'メタデータを編集'; @override String trackCoverSaved(String fileName) { @@ -2071,26 +2083,26 @@ class AppLocalizationsJa extends AppLocalizations { @override String trackSaveFailed(String error) { - return 'Failed: $error'; + return '失敗: $error'; } @override - String get trackConvertFormat => 'Convert Format'; + String get trackConvertFormat => '変換の形式'; @override - String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + String get trackConvertFormatSubtitle => 'MP3 または Opus に変換'; @override - String get trackConvertTitle => 'Convert Audio'; + String get trackConvertTitle => 'オーディオを変換'; @override - String get trackConvertTargetFormat => 'Target Format'; + String get trackConvertTargetFormat => 'ターゲットの形式'; @override - String get trackConvertBitrate => 'Bitrate'; + String get trackConvertBitrate => 'ビットレート'; @override - String get trackConvertConfirmTitle => 'Confirm Conversion'; + String get trackConvertConfirmTitle => '変換を確認'; @override String trackConvertConfirmMessage( @@ -2102,7 +2114,7 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get trackConvertConverting => 'Converting audio...'; + String get trackConvertConverting => 'オーディオを変換中...'; @override String trackConvertSuccess(String format) { @@ -2110,7 +2122,7 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get trackConvertFailed => 'Conversion failed'; + String get trackConvertFailed => '変換に失敗しました'; @override String get actionCreate => 'Create'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 3796e499..66c66e99 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -45,41 +45,40 @@ class AppLocalizationsKo extends AppLocalizations { String get historyFilterSingles => 'Singles'; @override - String get historySearchHint => 'Search history...'; + String get historySearchHint => '검색 기록...'; @override String get settingsTitle => 'Settings'; @override - String get settingsDownload => 'Download'; + String get settingsDownload => '다운로드'; @override - String get settingsAppearance => 'Appearance'; + String get settingsAppearance => '외관'; @override - String get settingsOptions => 'Options'; + String get settingsOptions => '옵션'; @override - String get settingsExtensions => 'Extensions'; + String get settingsExtensions => '확장 기능'; @override - String get settingsAbout => 'About'; + String get settingsAbout => '정보'; @override - String get downloadTitle => 'Download'; + String get downloadTitle => '다운로드'; @override - String get downloadAskQualitySubtitle => - 'Show quality picker for each download'; + String get downloadAskQualitySubtitle => '다운로드를 할 때마다 품질을 선택하도록 합니다'; @override - String get downloadFilenameFormat => 'Filename Format'; + String get downloadFilenameFormat => '파일 이름 형식'; @override - String get downloadFolderOrganization => 'Folder Organization'; + String get downloadFolderOrganization => '폴더 분류 형식'; @override - String get appearanceTitle => 'Appearance'; + String get appearanceTitle => '외관'; @override String get appearanceThemeSystem => 'System'; @@ -94,10 +93,10 @@ class AppLocalizationsKo extends AppLocalizations { String get appearanceDynamicColor => 'Dynamic Color'; @override - String get appearanceDynamicColorSubtitle => 'Use colors from your wallpaper'; + String get appearanceDynamicColorSubtitle => '배경 화면을 참고하여 강조 색상이 지정됩니다'; @override - String get appearanceHistoryView => 'History View'; + String get appearanceHistoryView => '기록 정렬 방식'; @override String get appearanceHistoryViewList => 'List'; @@ -106,112 +105,104 @@ class AppLocalizationsKo extends AppLocalizations { String get appearanceHistoryViewGrid => 'Grid'; @override - String get optionsTitle => 'Options'; + String get optionsTitle => '옵션'; @override - String get optionsPrimaryProvider => 'Primary Provider'; + String get optionsPrimaryProvider => '기본 제공자'; @override - String get optionsPrimaryProviderSubtitle => - 'Service used when searching by track name.'; + String get optionsPrimaryProviderSubtitle => '음반 이름으로 검색할 때 사용되는 서비스'; @override String optionsUsingExtension(String extensionName) { - return 'Using extension: $extensionName'; + return '확장 기능을 사용: $extensionName'; } @override - String get optionsSwitchBack => - 'Tap Deezer or Spotify to switch back from extension'; + String get optionsSwitchBack => 'Deezer 또는 Spotify를 탭하여 확장 기능에서 다시 전환하세요.'; @override - String get optionsAutoFallback => 'Auto Fallback'; + String get optionsAutoFallback => '자동 재시도'; @override String get optionsAutoFallbackSubtitle => '다운로드가 실패한 경우, 다른 서비스로 재시도'; @override - String get optionsUseExtensionProviders => 'Use Extension Providers'; + String get optionsUseExtensionProviders => '확장 기능 사용'; @override String get optionsUseExtensionProvidersOn => '확장 기능을 우선적으로 사용합니다'; @override - String get optionsUseExtensionProvidersOff => 'Using built-in providers only'; + String get optionsUseExtensionProvidersOff => '기본으로 제공되는 기능만 사용'; @override - String get optionsEmbedLyrics => 'Embed Lyrics'; + String get optionsEmbedLyrics => '가사 삽입'; @override - String get optionsEmbedLyricsSubtitle => - 'Embed synced lyrics into FLAC files'; + String get optionsEmbedLyricsSubtitle => 'FLAC 파일에 동기화된 가사를 삽입합니다'; @override - String get optionsMaxQualityCover => 'Max Quality Cover'; + String get optionsMaxQualityCover => '고품질 커버 이미지'; @override - String get optionsMaxQualityCoverSubtitle => - 'Download highest resolution cover art'; + String get optionsMaxQualityCoverSubtitle => '최고 품질의 커버 이미지를 다운로드'; @override - String get optionsConcurrentDownloads => 'Concurrent Downloads'; + String get optionsConcurrentDownloads => '동시 다운로드'; @override - String get optionsConcurrentSequential => 'Sequential (1 at a time)'; + String get optionsConcurrentSequential => '순차 다운로드 (한 번에 하나)'; @override String optionsConcurrentParallel(int count) { - return '$count parallel downloads'; + return '$count개 동시 다운로드'; } @override - String get optionsConcurrentWarning => - 'Parallel downloads may trigger rate limiting'; + String get optionsConcurrentWarning => '동시에 다수의 음반을 다운로드하면 속도 제한이 발생할 수 있습니다'; @override - String get optionsExtensionStore => 'Extension Store'; + String get optionsExtensionStore => '확장 기능 스토어'; @override - String get optionsExtensionStoreSubtitle => 'Show Store tab in navigation'; + String get optionsExtensionStoreSubtitle => '탐색 메뉴에 스토어 탭 표시'; @override - String get optionsCheckUpdates => 'Check for Updates'; + String get optionsCheckUpdates => '업데이트 확인'; @override - String get optionsCheckUpdatesSubtitle => - 'Notify when new version is available'; + String get optionsCheckUpdatesSubtitle => '새로운 버전이 출시되면 알림'; @override - String get optionsUpdateChannel => 'Update Channel'; + String get optionsUpdateChannel => '업데이트 채널'; @override - String get optionsUpdateChannelStable => 'Stable releases only'; + String get optionsUpdateChannelStable => '안정적인 버전만 수령'; @override - String get optionsUpdateChannelPreview => 'Get preview releases'; + String get optionsUpdateChannelPreview => '미리보기 버전을 수령'; @override - String get optionsUpdateChannelWarning => - 'Preview may contain bugs or incomplete features'; + String get optionsUpdateChannelWarning => '미리보기 버전은 불안정할 수 있습니다'; @override - String get optionsClearHistory => 'Clear Download History'; + String get optionsClearHistory => '다운로드 기록 삭제'; @override - String get optionsClearHistorySubtitle => - 'Remove all downloaded tracks from history'; + String get optionsClearHistorySubtitle => '기록에서 모든 다운로드 음반을 제거합니다'; @override - String get optionsDetailedLogging => 'Detailed Logging'; + String get optionsDetailedLogging => '상세 로깅'; @override - String get optionsDetailedLoggingOn => 'Detailed logs are being recorded'; + String get optionsDetailedLoggingOn => '상세한 로그가 기록되고 있습니다'; @override - String get optionsDetailedLoggingOff => 'Enable for bug reports'; + String get optionsDetailedLoggingOff => '버그 신고를 위한 기능입니다'; @override - String get optionsSpotifyCredentials => 'Spotify Credentials'; + String get optionsSpotifyCredentials => 'Spotify 자격 증명'; @override String optionsSpotifyCredentialsConfigured(String clientId) { @@ -219,21 +210,21 @@ class AppLocalizationsKo extends AppLocalizations { } @override - String get optionsSpotifyCredentialsRequired => 'Required - tap to configure'; + String get optionsSpotifyCredentialsRequired => '탭하여 설정'; @override String get optionsSpotifyWarning => - 'Spotify requires your own API credentials. Get them free from developer.spotify.com'; + 'Spotify는 사용자 고유의 API 자격 증명을 요구합니다. developer.spotify.com에서 무료로 발급받으세요'; @override String get optionsSpotifyDeprecationWarning => - 'Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.'; + 'Spotify API 변경으로 인해 Spotify 검색 기능은 2026년 3월 3일부터 더 이상 지원되지 않습니다. Deezer로 전환해 주세요'; @override - String get extensionsTitle => 'Extensions'; + String get extensionsTitle => '확장 기능'; @override - String get extensionsDisabled => 'Disabled'; + String get extensionsDisabled => '비활성화'; @override String extensionsVersion(String version) { @@ -246,80 +237,79 @@ class AppLocalizationsKo extends AppLocalizations { } @override - String get extensionsUninstall => 'Uninstall'; + String get extensionsUninstall => '삭제'; @override - String get storeTitle => 'Extension Store'; + String get storeTitle => '확장 기능 스토어'; @override - String get storeSearch => 'Search extensions...'; + String get storeSearch => '확장 기능 검색'; @override - String get storeInstall => 'Install'; + String get storeInstall => '설치'; @override - String get storeInstalled => 'Installed'; + String get storeInstalled => '설치됨'; @override - String get storeUpdate => 'Update'; + String get storeUpdate => '업데이트'; @override - String get aboutTitle => 'About'; + String get aboutTitle => '정보'; @override - String get aboutContributors => 'Contributors'; + String get aboutContributors => '기여자'; @override - String get aboutMobileDeveloper => 'Mobile version developer'; + String get aboutMobileDeveloper => '모바일 버전 개발자'; @override - String get aboutOriginalCreator => 'Creator of the original SpotiFLAC'; + String get aboutOriginalCreator => '오리지널 SpotiFLAC 제작자'; @override - String get aboutLogoArtist => - 'The talented artist who created our beautiful app logo!'; + String get aboutLogoArtist => '아름다운 로고를 만들어주신 재능 있는 아티스트!'; @override - String get aboutTranslators => 'Translators'; + String get aboutTranslators => '번역가들'; @override - String get aboutSpecialThanks => 'Special Thanks'; + String get aboutSpecialThanks => '특별 감사'; @override - String get aboutLinks => 'Links'; + String get aboutLinks => '바로가기'; @override String get aboutMobileSource => 'Mobile source code'; @override - String get aboutPCSource => 'PC source code'; + String get aboutPCSource => 'PC 소스 코드'; @override - String get aboutReportIssue => 'Report an issue'; + String get aboutReportIssue => '문제 신고'; @override - String get aboutReportIssueSubtitle => 'Report any problems you encounter'; + String get aboutReportIssueSubtitle => '발생하는 모든 문제를 신고하여 주세요.'; @override - String get aboutFeatureRequest => 'Feature request'; + String get aboutFeatureRequest => '기능 요청'; @override - String get aboutFeatureRequestSubtitle => 'Suggest new features for the app'; + String get aboutFeatureRequestSubtitle => '앱의 새로운 기능을 제안하여 주세요.'; @override String get aboutTelegramChannel => 'Telegram Channel'; @override - String get aboutTelegramChannelSubtitle => 'Announcements and updates'; + String get aboutTelegramChannelSubtitle => '공지 및 업데이트 안내'; @override String get aboutTelegramChat => 'Telegram Community'; @override - String get aboutTelegramChatSubtitle => 'Chat with other users'; + String get aboutTelegramChatSubtitle => '다른 이용자와 소통'; @override - String get aboutSocial => 'Social'; + String get aboutSocial => '소셜'; @override String get aboutApp => 'App'; @@ -329,244 +319,239 @@ class AppLocalizationsKo extends AppLocalizations { @override String get aboutBinimumDesc => - 'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!'; + 'QQDL 및 HiFi API 개발자입니다. 이 API가 없었다면 Tidal 다운로드는 불가능했을 것입니다!'; @override - String get aboutSachinsenalDesc => - 'The original HiFi project creator. The foundation of Tidal integration!'; + String get aboutSachinsenalDesc => '최초의 하이파이 프로젝트 창시자. 타이달 연동의 기반을 마련한 사람!'; @override String get aboutSjdonadoDesc => - 'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!'; + 'I Don\'t Have Spotify(IDHS) 개발자입니다. 위급 상황 발생 시 해결해 주는 대체 링크 해결 도구를 만들었습니다!'; @override String get aboutDabMusic => 'DAB Music'; @override String get aboutDabMusicDesc => - 'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!'; + '최고의 Qobuz 스트리밍 API입니다. 이 API가 없었다면 고해상도 다운로드는 불가능했을 겁니다!'; @override String get aboutSpotiSaver => 'SpotiSaver'; @override String get aboutSpotiSaverDesc => - 'Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!'; + 'Tidal Hi-Res FLAC 스트리밍 엔드포인트. 무손실 음원 재생의 핵심 요소!'; @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; + 'Tidal, Qobuz, Amazon Music에서 Spotify 트랙을 무손실 음질로 다운로드하세요.'; @override - String get artistAlbums => 'Albums'; + String get artistAlbums => '앨범'; @override - String get artistSingles => 'Singles & EPs'; + String get artistSingles => '싱글 및 EP'; @override - String get artistCompilations => 'Compilations'; + String get artistCompilations => '편집'; @override - String get artistPopular => 'Popular'; + String get artistPopular => '인기순'; @override String artistMonthlyListeners(String count) { - return '$count monthly listeners'; + return '월간 청취자: $count'; } @override - String get trackMetadataService => 'Service'; + String get trackMetadataService => '제공업체'; @override - String get trackMetadataPlay => 'Play'; + String get trackMetadataPlay => '재생'; @override - String get trackMetadataShare => 'Share'; + String get trackMetadataShare => '공유'; @override - String get trackMetadataDelete => 'Delete'; + String get trackMetadataDelete => '삭제'; @override - String get setupGrantPermission => 'Grant Permission'; + String get setupGrantPermission => '권한을 제공해 주세요.'; @override - String get setupSkip => 'Skip for now'; + String get setupSkip => '다음에 할래요'; @override - String get setupStorageAccessRequired => 'Storage Access Required'; + String get setupStorageAccessRequired => '스토리지 접근 권한 필요'; @override String get setupStorageAccessMessageAndroid11 => - 'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.'; + 'Android 11 이상 버전에서는 선택한 다운로드 폴더에 파일을 저장하려면 \"모든 파일 접근\" 권한이 필요합니다.'; @override - String get setupOpenSettings => 'Open Settings'; + String get setupOpenSettings => '설정으로 이동'; @override String get setupPermissionDeniedMessage => - 'Permission denied. Please grant all permissions to continue.'; + '권한이 거부되었습니다. 계속하려면 모든 권한을 허용해 주세요.'; @override String setupPermissionRequired(String permissionType) { - return '$permissionType Permission Required'; + return '$permissionType 권한 필요'; } @override String setupPermissionRequiredMessage(String permissionType) { - return '$permissionType permission is required for the best experience. You can change this later in Settings.'; + return '최상의 사용 경험을 위해 $permissionType 권한이 필요합니다. 설정에서 나중에 변경할 수 있습니다.'; } @override - String get setupUseDefaultFolder => 'Use Default Folder?'; + String get setupUseDefaultFolder => '기본 폴더를 사용하시겠습니까?'; @override - String get setupNoFolderSelected => - 'No folder selected. Would you like to use the default Music folder?'; + String get setupNoFolderSelected => '선택된 폴더가 없습니다. 기본 음악 폴더를 사용하시겠습니까?'; @override - String get setupUseDefault => 'Use Default'; + String get setupUseDefault => '기본값 사용'; @override - String get setupDownloadLocationTitle => 'Download Location'; + String get setupDownloadLocationTitle => '다운로드 경로'; @override String get setupDownloadLocationIosMessage => - 'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.'; + 'iOS에서는 다운로드한 파일이 앱의 문서 폴더에 저장됩니다. 파일 앱을 통해 해당 파일에 접근할 수 있습니다.'; @override - String get setupAppDocumentsFolder => 'App Documents Folder'; + String get setupAppDocumentsFolder => '앱 문서 폴더'; @override - String get setupAppDocumentsFolderSubtitle => - 'Recommended - accessible via Files app'; + String get setupAppDocumentsFolderSubtitle => '권장 사항 - 파일 앱을 통해 접근 가능'; @override - String get setupChooseFromFiles => 'Choose from Files'; + String get setupChooseFromFiles => '파일 탐색기에서 선택'; @override - String get setupChooseFromFilesSubtitle => 'Select iCloud or other location'; + String get setupChooseFromFilesSubtitle => 'iCloud 또는 다른 위치를 선택하세요'; @override String get setupIosEmptyFolderWarning => - 'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.'; + 'iOS 제한 사항: 빈 폴더는 선택할 수 없습니다. 파일이 하나 이상 있는 폴더를 선택하세요.'; @override String get setupIcloudNotSupported => - 'iCloud Drive is not supported. Please use the app Documents folder.'; + 'iCloud Drive는 지원되지 않습니다. 앱의 문서 폴더를 사용해 주세요.'; @override - String get setupDownloadInFlac => 'Download Spotify tracks in FLAC'; + String get setupDownloadInFlac => 'Spotify 음악을 FLAC 형식으로 다운로드하세요.'; @override - String get setupStorageGranted => 'Storage Permission Granted!'; + String get setupStorageGranted => '저장소 접근 권한이 부여되었습니다!'; @override - String get setupStorageRequired => 'Storage Permission Required'; + String get setupStorageRequired => '저장소 접근 권한이 필요합니다.'; @override String get setupStorageDescription => - 'SpotiFLAC needs storage permission to save your downloaded music files.'; + 'SpotiFLAC은 다운로드한 음악 파일을 저장하기 위해 저장소 접근 권한이 필요합니다.'; @override - String get setupNotificationGranted => 'Notification Permission Granted!'; + String get setupNotificationGranted => '알림 권한이 부여되었습니다!'; @override - String get setupNotificationEnable => 'Enable Notifications'; + String get setupNotificationEnable => '알림 활성화'; @override - String get setupFolderChoose => 'Choose Download Folder'; + String get setupFolderChoose => '다운로드 폴더를 선택하세요'; @override - String get setupFolderDescription => - 'Select a folder where your downloaded music will be saved.'; + String get setupFolderDescription => '다운로드한 음악 파일이 저장될 폴더를 선택하세요.'; @override - String get setupSelectFolder => 'Select Folder'; + String get setupSelectFolder => '폴더 선택'; @override - String get setupEnableNotifications => 'Enable Notifications'; + String get setupEnableNotifications => '알림 활성화'; @override String get setupNotificationBackgroundDescription => - 'Get notified about download progress and completion. This helps you track downloads when the app is in background.'; + '알림으로 다운로드 진행 상황을 확인하세요. 앱이 백그라운드에서 실행 중일 때 다운로드 상태와 완료 여부를 확인할 수 있습니다.'; @override - String get setupSkipForNow => 'Skip for now'; + String get setupSkipForNow => '다음에 할래요.'; @override - String get setupNext => 'Next'; + String get setupNext => '다음'; @override - String get setupGetStarted => 'Get Started'; + String get setupGetStarted => '시작하기'; @override String get setupAllowAccessToManageFiles => - 'Please enable \"Allow access to manage all files\" in the next screen.'; + '다음 화면에서 \"모든 파일 관리 권한 허용\"을 활성화해 주세요.'; @override - String get dialogCancel => 'Cancel'; + String get dialogCancel => '취소'; @override - String get dialogSave => 'Save'; + String get dialogSave => '저장'; @override - String get dialogDelete => 'Delete'; + String get dialogDelete => '삭제'; @override - String get dialogRetry => 'Retry'; + String get dialogRetry => '재시도'; @override - String get dialogClear => 'Clear'; + String get dialogClear => '지우기'; @override - String get dialogDone => 'Done'; + String get dialogDone => '완료'; @override - String get dialogImport => 'Import'; + String get dialogImport => '불러오기'; @override - String get dialogDiscard => 'Discard'; + String get dialogDiscard => '취소'; @override - String get dialogRemove => 'Remove'; + String get dialogRemove => '제거'; @override - String get dialogUninstall => 'Uninstall'; + String get dialogUninstall => '삭제'; @override - String get dialogDiscardChanges => 'Discard Changes?'; + String get dialogDiscardChanges => '변경사항 취소'; @override - String get dialogUnsavedChanges => - 'You have unsaved changes. Do you want to discard them?'; + String get dialogUnsavedChanges => '저장되지 않은 변경 사항이 있습니다. 삭제하시겠습니까?'; @override - String get dialogClearAll => 'Clear All'; + String get dialogClearAll => '모두 제거:'; @override - String get dialogRemoveExtension => 'Remove Extension'; + String get dialogRemoveExtension => '확장 프로그램 제거'; @override String get dialogRemoveExtensionMessage => - 'Are you sure you want to remove this extension? This cannot be undone.'; + '이 확장 프로그램을 정말로 제거하시겠습니까? 이 작업은 되돌릴 수 없습니다.'; @override - String get dialogUninstallExtension => 'Uninstall Extension?'; + String get dialogUninstallExtension => '확장 프로그램을 제거하시겠습니까?'; @override String dialogUninstallExtensionMessage(String extensionName) { - return 'Are you sure you want to remove $extensionName?'; + return '$extensionName을 정말로 삭제하시겠습니까?'; } @override - String get dialogClearHistoryTitle => 'Clear History'; + String get dialogClearHistoryTitle => '기록 삭제'; @override String get dialogClearHistoryMessage => - 'Are you sure you want to clear all download history? This cannot be undone.'; + '다운로드 기록을 모두 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.'; @override - String get dialogDeleteSelectedTitle => 'Delete Selected'; + String get dialogDeleteSelectedTitle => '선택한 항목 삭제'; @override String dialogDeleteSelectedMessage(int count) { @@ -576,50 +561,50 @@ class AppLocalizationsKo extends AppLocalizations { other: 'tracks', one: 'track', ); - return 'Delete $count $_temp0 from history?\n\nThis will also delete the files from storage.'; + return '기록에서 $count $_temp0를 삭제하시겠습니까?'; } @override - String get dialogImportPlaylistTitle => 'Import Playlist'; + String get dialogImportPlaylistTitle => '재생 목록 가져오기'; @override String dialogImportPlaylistMessage(int count) { - return 'Found $count tracks in CSV. Add them to download queue?'; + return 'CSV 파일에서 $count개의 트랙을 찾았습니다. 다운로드 대기열에 추가하시겠습니까?'; } @override String csvImportTracks(int count) { - return '$count tracks from CSV'; + return 'CSV 파일의 트랙: $count'; } @override String snackbarAddedToQueue(String trackName) { - return 'Added \"$trackName\" to queue'; + return '\"$trackName\"(을)를 대기열에 추가했습니다.'; } @override String snackbarAddedTracksToQueue(int count) { - return 'Added $count tracks to queue'; + return '대기열에 $count개의 트랙을 추가했습니다.'; } @override String snackbarAlreadyDownloaded(String trackName) { - return '\"$trackName\" already downloaded'; + return '\"$trackName\"(은)는 이미 다운로드되었습니다.'; } @override String snackbarAlreadyInLibrary(String trackName) { - return '\"$trackName\" already exists in your library'; + return '라이브러리에 \"$trackName\"(은)는 이미 존재합니다.'; } @override - String get snackbarHistoryCleared => 'History cleared'; + String get snackbarHistoryCleared => '기록 삭제됨'; @override - String get snackbarCredentialsSaved => 'Credentials saved'; + String get snackbarCredentialsSaved => '자격 증명이 저장되었습니다.'; @override - String get snackbarCredentialsCleared => 'Credentials cleared'; + String get snackbarCredentialsCleared => '자격 증명이 제거되었습니다.'; @override String snackbarDeletedTracks(int count) { @@ -629,136 +614,145 @@ class AppLocalizationsKo extends AppLocalizations { other: 'tracks', one: 'track', ); - return 'Deleted $count $_temp0'; + return '$count$_temp0 제거됨'; } @override String snackbarCannotOpenFile(String error) { - return 'Cannot open file: $error'; + return '파일을 열 수 없습니다: $error'; } @override - String get snackbarFillAllFields => 'Please fill all fields'; + String get snackbarFillAllFields => '모든 항목을 입력해 주세요.'; @override String get snackbarViewQueue => 'View Queue'; @override String snackbarUrlCopied(String platform) { - return '$platform URL copied to clipboard'; + return '$platform 링크가 클립보드에 저장됨'; } @override - String get snackbarFileNotFound => 'File not found'; + String get snackbarFileNotFound => '파일을 찾을 수 없음'; @override - String get snackbarSelectExtFile => 'Please select a .spotiflac-ext file'; + String get snackbarSelectExtFile => '.spotiflac-ext 확장자 파일을 선택'; @override - String get snackbarProviderPrioritySaved => 'Provider priority saved'; + String get snackbarProviderPrioritySaved => '제공자 우선순위 저장됨'; @override - String get snackbarMetadataProviderSaved => - 'Metadata provider priority saved'; + String get snackbarMetadataProviderSaved => '메타데이터 제공자 우선순위 저장됨'; @override String snackbarExtensionInstalled(String extensionName) { - return '$extensionName installed.'; + return '$extensionName(이)가 설치됨'; } @override String snackbarExtensionUpdated(String extensionName) { - return '$extensionName updated.'; + return '$extensionName(이)가 설치됨.'; } @override - String get snackbarFailedToInstall => 'Failed to install extension'; + String get snackbarFailedToInstall => '확장 프로그램 설치 실패'; @override - String get snackbarFailedToUpdate => 'Failed to update extension'; + String get snackbarFailedToUpdate => '확장 프로그램 업데이트 실패'; @override String get errorRateLimited => 'Rate Limited'; @override - String get errorRateLimitedMessage => - 'Too many requests. Please wait a moment before searching again.'; + String get errorRateLimitedMessage => '요청이 너무 많습니다. 잠시 후 다시 검색해 주세요.'; @override - String get errorNoTracksFound => 'No tracks found'; + String get errorNoTracksFound => '트랙을 찾을 수 없습니다'; + + @override + String get errorUrlNotRecognized => 'Link not recognized'; + + @override + String get errorUrlNotRecognizedMessage => + 'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'; + + @override + String get errorUrlFetchFailed => + 'Failed to load content from this link. Please try again.'; @override String errorMissingExtensionSource(String item) { - return 'Cannot load $item: missing extension source'; + return '확장 소스가 누락되어, $item(을)를 로드할 수 없습니다'; } @override - String get actionPause => 'Pause'; + String get actionPause => '멈추기'; @override - String get actionResume => 'Resume'; + String get actionResume => '재시작'; @override - String get actionCancel => 'Cancel'; + String get actionCancel => '취소'; @override - String get actionSelectAll => 'Select All'; + String get actionSelectAll => '모두 선택'; @override - String get actionDeselect => 'Deselect'; + String get actionDeselect => '선택 해제'; @override - String get actionRemoveCredentials => 'Remove Credentials'; + String get actionRemoveCredentials => '자격 증명 제거'; @override - String get actionSaveCredentials => 'Save Credentials'; + String get actionSaveCredentials => '자격 증명 저장'; @override String selectionSelected(int count) { - return '$count selected'; + return '$count개 선택됨'; } @override - String get selectionAllSelected => 'All tracks selected'; + String get selectionAllSelected => '모든 트랙 선택됨'; @override - String get selectionSelectToDelete => 'Select tracks to delete'; + String get selectionSelectToDelete => '삭제할 트랙을 선택'; @override String progressFetchingMetadata(int current, int total) { - return 'Fetching metadata... $current/$total'; + return '메타데이터 가져오는 중... $current/$total'; } @override - String get progressReadingCsv => 'Reading CSV...'; + String get progressReadingCsv => 'CSV 파일을 읽는 중...'; @override - String get searchSongs => 'Songs'; + String get searchSongs => '곡들'; @override - String get searchArtists => 'Artists'; + String get searchArtists => '아티스트들'; @override - String get searchAlbums => 'Albums'; + String get searchAlbums => '앨범들'; @override - String get searchPlaylists => 'Playlists'; + String get searchPlaylists => '재생목록들'; @override - String get tooltipPlay => 'Play'; + String get tooltipPlay => '재생'; @override - String get filenameFormat => 'Filename Format'; + String get filenameFormat => ''; @override - String get filenameShowAdvancedTags => 'Show advanced tags'; + String get filenameShowAdvancedTags => '고급 태그 표시'; @override String get filenameShowAdvancedTagsDescription => - 'Enable formatted tags for track padding and date patterns'; + '트랙 패딩 및 날짜 패턴에 대한 서식 있는 태그를 활성화합니다.'; @override - String get folderOrganizationNone => 'No organization'; + String get folderOrganizationNone => '정리하지 않음'; @override String get folderOrganizationByArtist => 'By Artist'; @@ -877,116 +871,114 @@ class AppLocalizationsKo extends AppLocalizations { String get logShareLogs => 'Share logs'; @override - String get logClearLogs => 'Clear logs'; + String get logClearLogs => '로그 제거'; @override - String get logClearLogsTitle => 'Clear Logs'; + String get logClearLogsTitle => '로그 제거'; @override - String get logClearLogsMessage => 'Are you sure you want to clear all logs?'; + String get logClearLogsMessage => '모든 로그를 삭제하시겠습니까?'; @override - String get logFilterBySeverity => 'Filter logs by severity'; + String get logFilterBySeverity => '심각성에 따라 로그 분류'; @override - String get logNoLogsYet => 'No logs yet'; + String get logNoLogsYet => '어떠한 로그도 없음'; @override - String get logNoLogsYetSubtitle => 'Logs will appear here as you use the app'; + String get logNoLogsYetSubtitle => '앱을 사용하는 동안 로그가 여기에 표시됩니다.'; @override String logEntriesFiltered(int count) { - return 'Entries ($count filtered)'; + return '($count filtered)개 항목 필터링'; } @override String logEntries(int count) { - return 'Entries ($count)'; + return '항목 수: ($count)'; } @override - String get credentialsTitle => 'Spotify Credentials'; + String get credentialsTitle => 'Spotify 자격 증명'; @override String get credentialsDescription => - 'Enter your Client ID and Secret to use your own Spotify application quota.'; + 'Spotify 애플리케이션 할당량을 사용하려면 클라이언트 ID와 비밀키를 입력하세요.'; @override String get credentialsClientId => 'Client ID'; @override - String get credentialsClientIdHint => 'Paste Client ID'; + String get credentialsClientIdHint => 'Client ID를 붙여넣으세요'; @override - String get credentialsClientSecret => 'Client Secret'; + String get credentialsClientSecret => '비밀키'; @override - String get credentialsClientSecretHint => 'Paste Client Secret'; + String get credentialsClientSecretHint => '비밀키를 붙여넣으세요'; @override - String get channelStable => 'Stable'; + String get channelStable => '안정'; @override - String get channelPreview => 'Preview'; + String get channelPreview => '베타'; @override - String get sectionSearchSource => 'Search Source'; + String get sectionSearchSource => '검색 소스'; @override - String get sectionDownload => 'Download'; + String get sectionDownload => '다운로드'; @override - String get sectionPerformance => 'Performance'; + String get sectionPerformance => '성능'; @override - String get sectionApp => 'App'; + String get sectionApp => '앱'; @override - String get sectionData => 'Data'; + String get sectionData => '데이터'; @override String get sectionDebug => 'Debug'; @override - String get sectionService => 'Service'; + String get sectionService => '서비스'; @override - String get sectionAudioQuality => 'Audio Quality'; + String get sectionAudioQuality => '오디오 품질'; @override - String get sectionFileSettings => 'File Settings'; + String get sectionFileSettings => '파일 설정'; @override - String get sectionLyrics => 'Lyrics'; + String get sectionLyrics => '가사'; @override - String get lyricsMode => 'Lyrics Mode'; + String get lyricsMode => '가사 설정'; @override - String get lyricsModeDescription => - 'Choose how lyrics are saved with your downloads'; + String get lyricsModeDescription => '다운로드한 파일에 가사를 저장하는 방법을 선택하세요.'; @override - String get lyricsModeEmbed => 'Embed in file'; + String get lyricsModeEmbed => '파일에 포함'; @override - String get lyricsModeEmbedSubtitle => 'Lyrics stored inside FLAC metadata'; + String get lyricsModeEmbedSubtitle => 'FLAC 메타데이터 내에 저장됩니다.'; @override - String get lyricsModeExternal => 'External .lrc file'; + String get lyricsModeExternal => '외부 .lrc 파일'; @override - String get lyricsModeExternalSubtitle => - 'Separate .lrc file for players like Samsung Music'; + String get lyricsModeExternalSubtitle => '삼성 뮤직과 같은 플레이어용 별도 .lrc 파일'; @override - String get lyricsModeBoth => 'Both'; + String get lyricsModeBoth => '둘 다'; @override - String get lyricsModeBothSubtitle => 'Embed and save .lrc file'; + String get lyricsModeBothSubtitle => '.lrc 파일을 삽입하고 저장합니다.'; @override - String get sectionColor => 'Color'; + String get sectionColor => '색상'; @override String get sectionTheme => 'Theme'; @@ -1807,7 +1799,8 @@ class AppLocalizationsKo extends AppLocalizations { 'Download music from Spotify, Deezer, or paste any supported URL'; @override - String get tutorialWelcomeTip2 => 'Tidal, Qobuz 또는 Deezer에서 FLAC 품질 오디오 받기'; + String get tutorialWelcomeTip2 => + 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 81ecfd12..6144f9b6 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -356,7 +356,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; + 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; @override String get artistAlbums => 'Albums'; @@ -688,6 +688,17 @@ class AppLocalizationsNl extends AppLocalizations { @override String get errorNoTracksFound => 'No tracks found'; + @override + String get errorUrlNotRecognized => 'Link not recognized'; + + @override + String get errorUrlNotRecognizedMessage => + 'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'; + + @override + String get errorUrlFetchFailed => + 'Failed to load content from this link. Please try again.'; + @override String errorMissingExtensionSource(String item) { return 'Cannot load $item: missing extension source'; @@ -1809,7 +1820,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get tutorialWelcomeTip2 => - 'Krijg FLAC-kwaliteit audio van Tidal, Qobuz of Deezer'; + 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; @override String get tutorialWelcomeTip3 => diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 24b813f3..d5c8d821 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -688,6 +688,17 @@ class AppLocalizationsPt extends AppLocalizations { @override String get errorNoTracksFound => 'No tracks found'; + @override + String get errorUrlNotRecognized => 'Link not recognized'; + + @override + String get errorUrlNotRecognizedMessage => + 'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'; + + @override + String get errorUrlFetchFailed => + 'Failed to load content from this link. Please try again.'; + @override String errorMissingExtensionSource(String item) { return 'Cannot load $item: missing extension source'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 77913294..909b5842 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -67,7 +67,7 @@ class AppLocalizationsRu extends AppLocalizations { String get settingsAbout => 'О программе'; @override - String get downloadTitle => 'Скачивание'; + String get downloadTitle => 'Скачать'; @override String get downloadAskQualitySubtitle => @@ -146,11 +146,11 @@ class AppLocalizationsRu extends AppLocalizations { 'Использование только встроенных провайдеров'; @override - String get optionsEmbedLyrics => 'Вставить текст песни'; + String get optionsEmbedLyrics => 'Вписать текст песни'; @override String get optionsEmbedLyricsSubtitle => - 'Вставить синхронизированные тексты в FLAC файлы'; + 'Вписать синхронизированные тексты во FLAC файлы'; @override String get optionsMaxQualityCover => 'Максимальное качество обложки'; @@ -337,7 +337,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get aboutBinimumDesc => - 'Создатель QQDL & HiFi API. Без этого API загрузки Tidal не существовали бы!'; + 'Создатель QQDL & HiFi API. Без него API загрузки Tidal не существовали бы!'; @override String get aboutSachinsenalDesc => @@ -363,7 +363,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get aboutAppDescription => - 'Скачайте треки Spotify в Lossless качестве из Tidal и Qobuz.'; + 'Скачайте треки Spotify в Lossless качестве из Tidal, Qobuz и Amazon Music.'; @override String get artistAlbums => 'Альбомы'; @@ -601,7 +601,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String csvImportTracks(int count) { - return '$count треков из CSV'; + return '$count трек(-ов) из CSV'; } @override @@ -702,6 +702,17 @@ class AppLocalizationsRu extends AppLocalizations { @override String get errorNoTracksFound => 'Треки не найдены'; + @override + String get errorUrlNotRecognized => 'Link not recognized'; + + @override + String get errorUrlNotRecognizedMessage => + 'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'; + + @override + String get errorUrlFetchFailed => + 'Failed to load content from this link. Please try again.'; + @override String errorMissingExtensionSource(String item) { return 'Невозможно загрузить $item: отсутствует источник расширения'; @@ -766,11 +777,11 @@ class AppLocalizationsRu extends AppLocalizations { String get filenameFormat => 'Формат имени файла'; @override - String get filenameShowAdvancedTags => 'Show advanced tags'; + String get filenameShowAdvancedTags => 'Показать расширенные теги'; @override String get filenameShowAdvancedTagsDescription => - 'Enable formatted tags for track padding and date patterns'; + 'Включить форматированные теги для отслеживания заполнения и шаблонов дат'; @override String get folderOrganizationNone => 'Без организации'; @@ -983,7 +994,7 @@ class AppLocalizationsRu extends AppLocalizations { 'Выберите как сохранить тексты песен при скачивании'; @override - String get lyricsModeEmbed => 'Вставить в файл'; + String get lyricsModeEmbed => 'Вписать в файл'; @override String get lyricsModeEmbedSubtitle => 'Встроить текст в метаданные FLAC'; @@ -999,7 +1010,7 @@ class AppLocalizationsRu extends AppLocalizations { String get lyricsModeBoth => 'Оба варианта'; @override - String get lyricsModeBothSubtitle => 'Вставить и сохранить файл .lrc'; + String get lyricsModeBothSubtitle => 'Вписать и сохранить .lrc файл'; @override String get sectionColor => 'Цвет'; @@ -1138,7 +1149,7 @@ class AppLocalizationsRu extends AppLocalizations { String get trackLyricsLoadFailed => 'Не удалось загрузить текст песни'; @override - String get trackEmbedLyrics => 'Вставить текст песни'; + String get trackEmbedLyrics => 'Вписать текст песни'; @override String get trackLyricsEmbedded => 'Текст успешно добавлен'; @@ -1361,10 +1372,10 @@ class AppLocalizationsRu extends AppLocalizations { 'YouTube обеспечивает только звук с потерями(Lossy).'; @override - String get youtubeOpusBitrateTitle => 'YouTube Opus Bitrate'; + String get youtubeOpusBitrateTitle => 'Битрейт YouTube Opus'; @override - String get youtubeMp3BitrateTitle => 'YouTube MP3 Bitrate'; + String get youtubeMp3BitrateTitle => 'Битрейт YouTube MP3'; @override String get downloadAskBeforeDownload => 'Спрашивать перед скачиванием'; @@ -1383,7 +1394,8 @@ class AppLocalizationsRu extends AppLocalizations { 'Использовать исполнителя альбома для папок'; @override - String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders'; + String get downloadUsePrimaryArtistOnly => + 'Основной исполнитель только для папок'; @override String get downloadUsePrimaryArtistOnlyEnabled => @@ -1391,7 +1403,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get downloadUsePrimaryArtistOnlyDisabled => - 'Full artist string used for folder name'; + 'Полная строка исполнителя, используемая для имени папки'; @override String get downloadSelectQuality => 'Выбор качества'; @@ -1423,7 +1435,7 @@ class AppLocalizationsRu extends AppLocalizations { String get settingsDownloadNetwork => 'Сеть для скачивания'; @override - String get settingsDownloadNetworkAny => 'WiFi и мобильная сеть'; + String get settingsDownloadNetworkAny => 'WiFi и Мобильная сеть'; @override String get settingsDownloadNetworkWifiOnly => 'Только WiFi'; @@ -1712,8 +1724,10 @@ class AppLocalizationsRu extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'tracks', - one: 'track', + other: 'треков', + many: 'треков', + few: 'трека', + one: 'трек', ); return '$_temp0'; } @@ -1800,7 +1814,7 @@ class AppLocalizationsRu extends AppLocalizations { String get libraryFilterQualityCD => 'CD (16 бит)'; @override - String get libraryFilterQualityLossy => 'С потерями'; + String get libraryFilterQualityLossy => 'Lossy'; @override String get libraryFilterFormat => 'Формат'; @@ -1855,7 +1869,8 @@ class AppLocalizationsRu extends AppLocalizations { 'Скачивайте музыку из Spotify, Deezer, или вставьте любой поддерживаемый URL'; @override - String get tutorialWelcomeTip2 => 'Скачайте FLAC с Tidal, Qobuz или Deezer'; + String get tutorialWelcomeTip2 => + 'Скачайте FLAC с Tidal, Qobuz или Amazon Music'; @override String get tutorialWelcomeTip3 => @@ -1903,7 +1918,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Просмотрите вкладку Магазина, чтобы найти полезные расширения'; @override String get tutorialExtensionsTip2 => @@ -1911,14 +1926,14 @@ class AppLocalizationsRu extends AppLocalizations { @override String get tutorialExtensionsTip3 => - 'Get lyrics, enhanced metadata, and more features'; + 'Получайте тексты песен, улучшенные метаданные и другие возможности'; @override String get tutorialSettingsTitle => 'Настройте приложение под себя'; @override String get tutorialSettingsDesc => - 'Personalize the app in Settings to match your preferences.'; + 'Персонализируйте приложение в Настройках, чтобы оно соответствовало вашим предпочтениям.'; @override String get tutorialSettingsTip1 => @@ -1943,11 +1958,11 @@ class AppLocalizationsRu extends AppLocalizations { 'Пересканировать все файлы, игнорировать кэш'; @override - String get cleanupOrphanedDownloads => 'Cleanup Orphaned Downloads'; + String get cleanupOrphanedDownloads => 'Очистка отложенных скачиваний'; @override String get cleanupOrphanedDownloadsSubtitle => - 'Remove history entries for files that no longer exist'; + 'Удалить историю записи для файлов, которых больше не существует'; @override String cleanupOrphanedDownloadsResult(int count) { @@ -1955,7 +1970,7 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get cleanupOrphanedDownloadsNone => 'No orphaned entries found'; + String get cleanupOrphanedDownloadsNone => 'Записей без описания не найдено'; @override String get cacheTitle => 'Хранилище и кэш'; @@ -1965,11 +1980,11 @@ class AppLocalizationsRu extends AppLocalizations { @override String get cacheSummarySubtitle => - 'Clearing cache will not remove downloaded music files.'; + 'Очистка кэша не приведет к удалению загруженных музыкальных файлов.'; @override String cacheEstimatedTotal(String size) { - return 'Estimated cache usage: $size'; + return 'Приблизительное использование кэша: $size'; } @override @@ -1983,42 +1998,42 @@ class AppLocalizationsRu extends AppLocalizations { @override String get cacheAppDirectoryDesc => - 'HTTP responses, WebView data, and other temporary app data.'; + 'HTTP-ответы, данные WebView и другие временные данные приложения.'; @override - String get cacheTempDirectory => 'Temporary directory'; + String get cacheTempDirectory => 'Временная директория'; @override String get cacheTempDirectoryDesc => - 'Temporary files from downloads and audio conversion.'; + 'Временные файлы из загрузок и аудио конвертации.'; @override - String get cacheCoverImage => 'Cover image cache'; + String get cacheCoverImage => 'Кэш обложек'; @override String get cacheCoverImageDesc => - 'Downloaded album and track cover art. Will re-download when viewed.'; + 'Скачанный альбом и трек обложки. Будет заново скачан после просмотра.'; @override - String get cacheLibraryCover => 'Library cover cache'; + String get cacheLibraryCover => 'Кэш обложек библиотеки'; @override String get cacheLibraryCoverDesc => - 'Cover art extracted from local music files. Will re-extract on next scan.'; + 'Обложка извлечена из локальных музыкальных файлов. Будет повторно извлечено при следующем сканировании.'; @override - String get cacheExploreFeed => 'Explore feed cache'; + String get cacheExploreFeed => 'Просмотреть кэш ленты'; @override String get cacheExploreFeedDesc => - 'Explore tab content (new releases, trending). Will refresh on next visit.'; + 'Изучите содержимое вкладки (новые релизы, тренды). Они обновятся при следующем посещении.'; @override - String get cacheTrackLookup => 'Track lookup cache'; + String get cacheTrackLookup => 'Отслеживать кэш поиска'; @override String get cacheTrackLookupDesc => - 'Spotify/Deezer track ID lookups. Clearing may slow next few searches.'; + 'Поиск ID трека в Spotify/Deezer. Очистка может замедлить следующие несколько поисков.'; @override String get cacheCleanupUnusedDesc => @@ -2039,7 +2054,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String cacheEntries(int count) { - return '$count entries'; + return '$count записей'; } @override @@ -2052,7 +2067,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String cacheClearConfirmMessage(String target) { - return 'This will clear cached data for $target. Downloaded music files will not be deleted.'; + return 'Это очистит кэш для $target. Загруженные музыкальные файлы не будут удалены.'; } @override @@ -2074,7 +2089,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String cacheCleanupResult(int downloadCount, int libraryCount) { - return 'Cleanup completed: $downloadCount orphaned downloads, $libraryCount missing library entries'; + return 'Очистка завершена: $downloadCount потерянных загрузок, $libraryCount отсутствующих записей в библиотеке'; } @override @@ -2094,14 +2109,14 @@ class AppLocalizationsRu extends AppLocalizations { 'Получить и сохранить текст песни в формате .lrc'; @override - String get trackSaveLyricsProgress => 'Saving lyrics...'; + String get trackSaveLyricsProgress => 'Сохранение текста...'; @override - String get trackReEnrich => 'Re-enrich'; + String get trackReEnrich => 'Обновить'; @override String get trackReEnrichOnlineSubtitle => - 'Search metadata online and embed into file'; + 'Поиск в сети метаданных и встраивание в файл'; @override String get trackEditMetadata => 'Редактировать метаданные'; @@ -2120,13 +2135,13 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get trackReEnrichProgress => 'Re-enriching metadata...'; + String get trackReEnrichProgress => 'Обновление метаданных...'; @override String get trackReEnrichSearching => 'Поиск метаданных в сети...'; @override - String get trackReEnrichSuccess => 'Metadata re-enriched successfully'; + String get trackReEnrichSuccess => 'Метаданные успешно обновлены'; @override String get trackReEnrichFfmpegFailed => @@ -2138,22 +2153,22 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get trackConvertFormat => 'Convert Format'; + String get trackConvertFormat => 'Переконвертировать формат'; @override - String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + String get trackConvertFormatSubtitle => 'Конвертировать в MP3 или Opus'; @override - String get trackConvertTitle => 'Convert Audio'; + String get trackConvertTitle => 'Конвертировать аудио'; @override - String get trackConvertTargetFormat => 'Target Format'; + String get trackConvertTargetFormat => 'Целевой формат'; @override - String get trackConvertBitrate => 'Bitrate'; + String get trackConvertBitrate => 'Битрейт'; @override - String get trackConvertConfirmTitle => 'Confirm Conversion'; + String get trackConvertConfirmTitle => 'Подтвердить конвертацию'; @override String trackConvertConfirmMessage( @@ -2161,177 +2176,181 @@ class AppLocalizationsRu extends AppLocalizations { String targetFormat, String bitrate, ) { - return 'Convert from $sourceFormat to $targetFormat at $bitrate?\n\nThe original file will be deleted after conversion.'; + return 'Конвертировать из $sourceFormat в $targetFormat $bitrate?\n\nОригинальный файл будет удален после конвертации.'; } @override - String get trackConvertConverting => 'Converting audio...'; + String get trackConvertConverting => 'Конвертация аудио...'; @override String trackConvertSuccess(String format) { - return 'Converted to $format successfully'; + return 'Успешно конвертировано в $format'; } @override - String get trackConvertFailed => 'Conversion failed'; + String get trackConvertFailed => 'Ошибка конвертации'; @override - String get actionCreate => 'Create'; + String get actionCreate => 'Создать'; @override - String get collectionFoldersTitle => 'My folders'; + String get collectionFoldersTitle => 'Мои папки'; @override - String get collectionWishlist => 'Wishlist'; + String get collectionWishlist => 'Список желаемого'; @override - String get collectionLoved => 'Loved'; + String get collectionLoved => 'Любимые'; @override - String get collectionPlaylists => 'Playlists'; + String get collectionPlaylists => 'Плейлисты'; @override - String get collectionPlaylist => 'Playlist'; + String get collectionPlaylist => 'Плейлист'; @override - String get collectionAddToPlaylist => 'Add to playlist'; + String get collectionAddToPlaylist => 'Добавить в плейлист'; @override - String get collectionCreatePlaylist => 'Create playlist'; + String get collectionCreatePlaylist => 'Создать плейлист'; @override - String get collectionNoPlaylistsYet => 'No playlists yet'; + String get collectionNoPlaylistsYet => 'Плейлисты отсутствуют'; @override String get collectionNoPlaylistsSubtitle => - 'Create a playlist to start categorizing tracks'; + 'Создайте плейлист, чтобы начать классифицировать треки'; @override String collectionPlaylistTracks(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count tracks', - one: '1 track', + other: '$count треков', + many: '$count треков', + few: '$count трека', + one: '$count трек', ); return '$_temp0'; } @override String collectionAddedToPlaylist(String playlistName) { - return 'Added to \"$playlistName\"'; + return 'Добавлено в \"$playlistName\"'; } @override String collectionAlreadyInPlaylist(String playlistName) { - return 'Already in \"$playlistName\"'; + return 'Уже в \"$playlistName\"'; } @override - String get collectionPlaylistCreated => 'Playlist created'; + String get collectionPlaylistCreated => 'Плейлист создан'; @override - String get collectionPlaylistNameHint => 'Playlist name'; + String get collectionPlaylistNameHint => 'Название плейлиста'; @override - String get collectionPlaylistNameRequired => 'Playlist name is required'; + String get collectionPlaylistNameRequired => 'Имя плейлиста обязательно'; @override - String get collectionRenamePlaylist => 'Rename playlist'; + String get collectionRenamePlaylist => 'Переименовать плейлист'; @override - String get collectionDeletePlaylist => 'Delete playlist'; + String get collectionDeletePlaylist => 'Удалить плейлист'; @override String collectionDeletePlaylistMessage(String playlistName) { - return 'Delete \"$playlistName\" and all tracks inside it?'; + return 'Удалить \"$playlistName\" и все треки внутри него?'; } @override - String get collectionPlaylistDeleted => 'Playlist deleted'; + String get collectionPlaylistDeleted => 'Плейлист удалён'; @override - String get collectionPlaylistRenamed => 'Playlist renamed'; + String get collectionPlaylistRenamed => 'Плейлист переименован'; @override - String get collectionWishlistEmptyTitle => 'Wishlist is empty'; + String get collectionWishlistEmptyTitle => 'Список желаний пуст'; @override String get collectionWishlistEmptySubtitle => - 'Tap + on tracks to save what you want to download later'; + 'Нажмите + на треках, чтобы сохранить то, что вы хотите скачать позже'; @override - String get collectionLovedEmptyTitle => 'Loved folder is empty'; + String get collectionLovedEmptyTitle => 'Папка Любимые пуста'; @override String get collectionLovedEmptySubtitle => - 'Tap love on tracks to keep your favorites'; + 'Нажмите \"любовь\" на треках, чтобы сохранить ваши избранные'; @override - String get collectionPlaylistEmptyTitle => 'Playlist is empty'; + String get collectionPlaylistEmptyTitle => 'Плейлист пуст'; @override String get collectionPlaylistEmptySubtitle => - 'Long-press + on any track to add it here'; + 'Удерживайте + на любом треке, чтобы добавить его сюда'; @override - String get collectionRemoveFromPlaylist => 'Remove from playlist'; + String get collectionRemoveFromPlaylist => 'Удалить из плейлиста'; @override - String get collectionRemoveFromFolder => 'Remove from folder'; + String get collectionRemoveFromFolder => 'Убрать из папки'; @override String collectionRemoved(String trackName) { - return '\"$trackName\" removed'; + return '\"$trackName\" удалён'; } @override String collectionAddedToLoved(String trackName) { - return '\"$trackName\" added to Loved'; + return '\"$trackName\" добавлен в Любимые'; } @override String collectionRemovedFromLoved(String trackName) { - return '\"$trackName\" removed from Loved'; + return '\"$trackName\" удалено из Любимых'; } @override String collectionAddedToWishlist(String trackName) { - return '\"$trackName\" added to Wishlist'; + return '\"$trackName\" добавлен в список желаний'; } @override String collectionRemovedFromWishlist(String trackName) { - return '\"$trackName\" removed from Wishlist'; + return '\"$trackName\" удалён из списка желаний'; } @override - String get trackOptionAddToLoved => 'Add to Loved'; + String get trackOptionAddToLoved => 'Добавить в Любимое'; @override - String get trackOptionRemoveFromLoved => 'Remove from Loved'; + String get trackOptionRemoveFromLoved => 'Исключить из Любимых'; @override - String get trackOptionAddToWishlist => 'Add to Wishlist'; + String get trackOptionAddToWishlist => 'Добавить в список желаний'; @override - String get trackOptionRemoveFromWishlist => 'Remove from Wishlist'; + String get trackOptionRemoveFromWishlist => 'Удалить из списка желаний'; @override - String get collectionPlaylistChangeCover => 'Change cover image'; + String get collectionPlaylistChangeCover => 'Изменить обложку'; @override - String get collectionPlaylistRemoveCover => 'Remove cover image'; + String get collectionPlaylistRemoveCover => 'Удалить обложку'; @override String selectionShareCount(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'tracks', - one: 'track', + other: 'треков', + many: 'треков', + few: 'трека', + one: 'трек', ); - return 'Share $count $_temp0'; + return 'Отправить $count $_temp0'; } @override @@ -2342,17 +2361,19 @@ class AppLocalizationsRu extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'tracks', - one: 'track', + other: 'треков', + many: 'треков', + few: 'трека', + one: 'трек', ); - return 'Convert $count $_temp0'; + return 'Конвертировать $count $_temp0'; } @override - String get selectionConvertNoConvertible => 'No convertible tracks selected'; + String get selectionConvertNoConvertible => 'Не выбраны конвертируемые треки'; @override - String get selectionBatchConvertConfirmTitle => 'Batch Convert'; + String get selectionBatchConvertConfirmTitle => 'Пакетная конвертация'; @override String selectionBatchConvertConfirmMessage( @@ -2371,12 +2392,12 @@ class AppLocalizationsRu extends AppLocalizations { @override String selectionBatchConvertProgress(int current, int total) { - return 'Converting $current of $total...'; + return 'Конвертация $current из $total...'; } @override String selectionBatchConvertSuccess(int success, int total, String format) { - return 'Converted $success of $total tracks to $format'; + return 'Конвертировано $success треков $total в $format'; } @override @@ -2386,7 +2407,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get downloadUseAlbumArtistForFoldersAlbumSubtitle => - 'Artist folders use Album Artist when available'; + 'Для папок исполнителей используется исполнитель альбома, если он указан'; @override String get downloadUseAlbumArtistForFoldersTrackSubtitle => diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index 53034492..b608cff7 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -693,6 +693,17 @@ class AppLocalizationsTr extends AppLocalizations { @override String get errorNoTracksFound => 'Parça bulunamadı'; + @override + String get errorUrlNotRecognized => 'Link not recognized'; + + @override + String get errorUrlNotRecognizedMessage => + 'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'; + + @override + String get errorUrlFetchFailed => + 'Failed to load content from this link. Please try again.'; + @override String errorMissingExtensionSource(String item) { return '$item yüklenemedi: Eksik eklenti kaynağı'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index a4fc7e77..4104845c 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -688,6 +688,17 @@ class AppLocalizationsZh extends AppLocalizations { @override String get errorNoTracksFound => 'No tracks found'; + @override + String get errorUrlNotRecognized => 'Link not recognized'; + + @override + String get errorUrlNotRecognizedMessage => + 'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'; + + @override + String get errorUrlFetchFailed => + 'Failed to load content from this link. Please try again.'; + @override String errorMissingExtensionSource(String item) { return 'Cannot load $item: missing extension source'; @@ -2696,7 +2707,7 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; + 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; @override String get artistAlbums => 'Albums'; @@ -3091,6 +3102,13 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get filenameFormat => 'Filename Format'; + @override + String get filenameShowAdvancedTags => 'Show advanced tags'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; + @override String get folderOrganizationNone => 'No organization'; @@ -3668,6 +3686,12 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { String get youtubeQualityNote => 'YouTube provides lossy audio only. Not part of lossless fallback.'; + @override + String get youtubeOpusBitrateTitle => 'YouTube Opus Bitrate'; + + @override + String get youtubeMp3BitrateTitle => 'YouTube MP3 Bitrate'; + @override String get downloadAskBeforeDownload => 'Ask Before Download'; @@ -3999,6 +4023,17 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { String get libraryAboutDescription => 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.'; + @override + String libraryTracksUnit(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return '$_temp0'; + } + @override String libraryLastScanned(String time) { return 'Last scanned: $time'; @@ -4124,7 +4159,8 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { 'Download music from Spotify, Deezer, or paste any supported URL'; @override - String get tutorialWelcomeTip2 => '从 Tidal、Qobuz 或 Deezer 获取 FLAC 品质音频'; + String get tutorialWelcomeTip2 => + 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; @override String get tutorialWelcomeTip3 => @@ -4441,6 +4477,210 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get trackConvertFailed => 'Conversion failed'; + @override + String get actionCreate => 'Create'; + + @override + String get collectionFoldersTitle => 'My folders'; + + @override + String get collectionWishlist => 'Wishlist'; + + @override + String get collectionLoved => 'Loved'; + + @override + String get collectionPlaylists => 'Playlists'; + + @override + String get collectionPlaylist => 'Playlist'; + + @override + String get collectionAddToPlaylist => 'Add to playlist'; + + @override + String get collectionCreatePlaylist => 'Create playlist'; + + @override + String get collectionNoPlaylistsYet => 'No playlists yet'; + + @override + String get collectionNoPlaylistsSubtitle => + 'Create a playlist to start categorizing tracks'; + + @override + String collectionPlaylistTracks(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count tracks', + one: '1 track', + ); + return '$_temp0'; + } + + @override + String collectionAddedToPlaylist(String playlistName) { + return 'Added to \"$playlistName\"'; + } + + @override + String collectionAlreadyInPlaylist(String playlistName) { + return 'Already in \"$playlistName\"'; + } + + @override + String get collectionPlaylistCreated => 'Playlist created'; + + @override + String get collectionPlaylistNameHint => 'Playlist name'; + + @override + String get collectionPlaylistNameRequired => 'Playlist name is required'; + + @override + String get collectionRenamePlaylist => 'Rename playlist'; + + @override + String get collectionDeletePlaylist => 'Delete playlist'; + + @override + String collectionDeletePlaylistMessage(String playlistName) { + return 'Delete \"$playlistName\" and all tracks inside it?'; + } + + @override + String get collectionPlaylistDeleted => 'Playlist deleted'; + + @override + String get collectionPlaylistRenamed => 'Playlist renamed'; + + @override + String get collectionWishlistEmptyTitle => 'Wishlist is empty'; + + @override + String get collectionWishlistEmptySubtitle => + 'Tap + on tracks to save what you want to download later'; + + @override + String get collectionLovedEmptyTitle => 'Loved folder is empty'; + + @override + String get collectionLovedEmptySubtitle => + 'Tap love on tracks to keep your favorites'; + + @override + String get collectionPlaylistEmptyTitle => 'Playlist is empty'; + + @override + String get collectionPlaylistEmptySubtitle => + 'Long-press + on any track to add it here'; + + @override + String get collectionRemoveFromPlaylist => 'Remove from playlist'; + + @override + String get collectionRemoveFromFolder => 'Remove from folder'; + + @override + String collectionRemoved(String trackName) { + return '\"$trackName\" removed'; + } + + @override + String collectionAddedToLoved(String trackName) { + return '\"$trackName\" added to Loved'; + } + + @override + String collectionRemovedFromLoved(String trackName) { + return '\"$trackName\" removed from Loved'; + } + + @override + String collectionAddedToWishlist(String trackName) { + return '\"$trackName\" added to Wishlist'; + } + + @override + String collectionRemovedFromWishlist(String trackName) { + return '\"$trackName\" removed from Wishlist'; + } + + @override + String get trackOptionAddToLoved => 'Add to Loved'; + + @override + String get trackOptionRemoveFromLoved => 'Remove from Loved'; + + @override + String get trackOptionAddToWishlist => 'Add to Wishlist'; + + @override + String get trackOptionRemoveFromWishlist => 'Remove from Wishlist'; + + @override + String get collectionPlaylistChangeCover => 'Change cover image'; + + @override + String get collectionPlaylistRemoveCover => 'Remove cover image'; + + @override + String selectionShareCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Share $count $_temp0'; + } + + @override + String get selectionShareNoFiles => 'No shareable files found'; + + @override + String selectionConvertCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Convert $count $_temp0'; + } + + @override + String get selectionConvertNoConvertible => 'No convertible tracks selected'; + + @override + String get selectionBatchConvertConfirmTitle => 'Batch Convert'; + + @override + String selectionBatchConvertConfirmMessage( + int count, + String format, + String bitrate, + ) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Convert $count $_temp0 to $format at $bitrate?\n\nOriginal files will be deleted after conversion.'; + } + + @override + String selectionBatchConvertProgress(int current, int total) { + return 'Converting $current of $total...'; + } + + @override + String selectionBatchConvertSuccess(int success, int total, String format) { + return 'Converted $success of $total tracks to $format'; + } + @override String downloadedAlbumDownloadedCount(int count) { return '$count downloaded'; @@ -4807,7 +5047,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; + 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; @override String get artistAlbums => 'Albums'; @@ -5202,6 +5442,13 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get filenameFormat => 'Filename Format'; + @override + String get filenameShowAdvancedTags => 'Show advanced tags'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; + @override String get folderOrganizationNone => 'No organization'; @@ -5779,6 +6026,12 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get youtubeQualityNote => 'YouTube provides lossy audio only. Not part of lossless fallback.'; + @override + String get youtubeOpusBitrateTitle => 'YouTube Opus Bitrate'; + + @override + String get youtubeMp3BitrateTitle => 'YouTube MP3 Bitrate'; + @override String get downloadAskBeforeDownload => 'Ask Before Download'; @@ -6110,6 +6363,17 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get libraryAboutDescription => 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.'; + @override + String libraryTracksUnit(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return '$_temp0'; + } + @override String libraryLastScanned(String time) { return 'Last scanned: $time'; @@ -6235,7 +6499,8 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { 'Download music from Spotify, Deezer, or paste any supported URL'; @override - String get tutorialWelcomeTip2 => '從 Tidal、Qobuz 或 Deezer 取得 FLAC 品質音訊'; + String get tutorialWelcomeTip2 => + 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; @override String get tutorialWelcomeTip3 => @@ -6552,6 +6817,210 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get trackConvertFailed => 'Conversion failed'; + @override + String get actionCreate => 'Create'; + + @override + String get collectionFoldersTitle => 'My folders'; + + @override + String get collectionWishlist => 'Wishlist'; + + @override + String get collectionLoved => 'Loved'; + + @override + String get collectionPlaylists => 'Playlists'; + + @override + String get collectionPlaylist => 'Playlist'; + + @override + String get collectionAddToPlaylist => 'Add to playlist'; + + @override + String get collectionCreatePlaylist => 'Create playlist'; + + @override + String get collectionNoPlaylistsYet => 'No playlists yet'; + + @override + String get collectionNoPlaylistsSubtitle => + 'Create a playlist to start categorizing tracks'; + + @override + String collectionPlaylistTracks(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count tracks', + one: '1 track', + ); + return '$_temp0'; + } + + @override + String collectionAddedToPlaylist(String playlistName) { + return 'Added to \"$playlistName\"'; + } + + @override + String collectionAlreadyInPlaylist(String playlistName) { + return 'Already in \"$playlistName\"'; + } + + @override + String get collectionPlaylistCreated => 'Playlist created'; + + @override + String get collectionPlaylistNameHint => 'Playlist name'; + + @override + String get collectionPlaylistNameRequired => 'Playlist name is required'; + + @override + String get collectionRenamePlaylist => 'Rename playlist'; + + @override + String get collectionDeletePlaylist => 'Delete playlist'; + + @override + String collectionDeletePlaylistMessage(String playlistName) { + return 'Delete \"$playlistName\" and all tracks inside it?'; + } + + @override + String get collectionPlaylistDeleted => 'Playlist deleted'; + + @override + String get collectionPlaylistRenamed => 'Playlist renamed'; + + @override + String get collectionWishlistEmptyTitle => 'Wishlist is empty'; + + @override + String get collectionWishlistEmptySubtitle => + 'Tap + on tracks to save what you want to download later'; + + @override + String get collectionLovedEmptyTitle => 'Loved folder is empty'; + + @override + String get collectionLovedEmptySubtitle => + 'Tap love on tracks to keep your favorites'; + + @override + String get collectionPlaylistEmptyTitle => 'Playlist is empty'; + + @override + String get collectionPlaylistEmptySubtitle => + 'Long-press + on any track to add it here'; + + @override + String get collectionRemoveFromPlaylist => 'Remove from playlist'; + + @override + String get collectionRemoveFromFolder => 'Remove from folder'; + + @override + String collectionRemoved(String trackName) { + return '\"$trackName\" removed'; + } + + @override + String collectionAddedToLoved(String trackName) { + return '\"$trackName\" added to Loved'; + } + + @override + String collectionRemovedFromLoved(String trackName) { + return '\"$trackName\" removed from Loved'; + } + + @override + String collectionAddedToWishlist(String trackName) { + return '\"$trackName\" added to Wishlist'; + } + + @override + String collectionRemovedFromWishlist(String trackName) { + return '\"$trackName\" removed from Wishlist'; + } + + @override + String get trackOptionAddToLoved => 'Add to Loved'; + + @override + String get trackOptionRemoveFromLoved => 'Remove from Loved'; + + @override + String get trackOptionAddToWishlist => 'Add to Wishlist'; + + @override + String get trackOptionRemoveFromWishlist => 'Remove from Wishlist'; + + @override + String get collectionPlaylistChangeCover => 'Change cover image'; + + @override + String get collectionPlaylistRemoveCover => 'Remove cover image'; + + @override + String selectionShareCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Share $count $_temp0'; + } + + @override + String get selectionShareNoFiles => 'No shareable files found'; + + @override + String selectionConvertCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Convert $count $_temp0'; + } + + @override + String get selectionConvertNoConvertible => 'No convertible tracks selected'; + + @override + String get selectionBatchConvertConfirmTitle => 'Batch Convert'; + + @override + String selectionBatchConvertConfirmMessage( + int count, + String format, + String bitrate, + ) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Convert $count $_temp0 to $format at $bitrate?\n\nOriginal files will be deleted after conversion.'; + } + + @override + String selectionBatchConvertProgress(int current, int total) { + return 'Converting $current of $total...'; + } + + @override + String selectionBatchConvertSuccess(int success, int total, String format) { + return 'Converted $success of $total tracks to $format'; + } + @override String downloadedAlbumDownloadedCount(int count) { return '$count downloaded'; diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 139b620e..e32aa56e 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -728,7 +728,7 @@ "@dialogDeleteSelectedTitle": { "description": "Dialog title - delete selected items" }, - "dialogDeleteSelectedMessage": "Lösche {count} {count, plural, one {}=1{Track} other{Tracks}} aus dem Verlauf?\n\nDies löscht auch die Dateien aus dem Speicher.", + "dialogDeleteSelectedMessage": "Lösche {count} {count, plural, one {Track} other{Tracks}} aus dem Verlauf?\n\nDies löscht auch die Dateien aus dem Speicher.", "@dialogDeleteSelectedMessage": { "description": "Dialog message - delete selected tracks", "placeholders": { @@ -807,7 +807,7 @@ "@snackbarCredentialsCleared": { "description": "Snackbar - Spotify credentials removed" }, - "snackbarDeletedTracks": "{count} {count, plural, one {}=1{Titel} other{Titel}}", + "snackbarDeletedTracks": "{count} {count, plural, one {Titel} other{Titel}}", "@snackbarDeletedTracks": { "description": "Snackbar - tracks deleted", "placeholders": { @@ -1889,7 +1889,7 @@ "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" }, - "downloadedAlbumDeleteMessage": "{count} {count, plural, one {}=1{Titel} other{Titel}} aus diesem Album löschen?\n\nDadurch werden auch die Dateien aus dem Speicher gelöscht.", + "downloadedAlbumDeleteMessage": "{count} {count, plural, one {Titel} other{Titel}} aus diesem Album löschen?\n\nDadurch werden auch die Dateien aus dem Speicher gelöscht.", "@downloadedAlbumDeleteMessage": { "description": "Delete confirmation with count", "placeholders": { @@ -1915,7 +1915,7 @@ "@downloadedAlbumTapToSelect": { "description": "Selection hint" }, - "downloadedAlbumDeleteCount": "Lösche {count} {count, plural, one {}=1{Titel}other{Titel}}", + "downloadedAlbumDeleteCount": "Lösche {count} {count, plural, one {Titel} other{Titel}}", "@downloadedAlbumDeleteCount": { "description": "Delete button text with count", "placeholders": { @@ -2353,7 +2353,7 @@ "@timeJustNow": { "description": "Relative time - less than a minute ago" }, - "timeMinutesAgo": "{count, plural, one {}=1{vor 1 Minute} other{vor {count} Minuten}}", + "timeMinutesAgo": "{count, plural, one {vor {count} Minute} other{vor {count} Minuten}}", "@timeMinutesAgo": { "description": "Relative time - minutes ago", "placeholders": { @@ -2362,7 +2362,7 @@ } } }, - "timeHoursAgo": "{count, plural, one {}=1{vor 1 Stunde} other{vor {count} Stunden}}", + "timeHoursAgo": "{count, plural, one {vor {count} Stunde} other{vor {count} Stunden}}", "@timeHoursAgo": { "description": "Relative time - hours ago", "placeholders": { @@ -3013,7 +3013,7 @@ "@collectionPlaylistRemoveCover": { "description": "Bottom sheet action to remove custom cover image from a playlist" }, - "selectionShareCount": "Teile {count} {count, plural, one {}=1{Titel}other{Titel}}", + "selectionShareCount": "Teile {count} {count, plural, one {Titel} other{Titel}}", "@selectionShareCount": { "description": "Share button text with count in selection mode", "placeholders": { @@ -3026,7 +3026,7 @@ "@selectionShareNoFiles": { "description": "Snackbar when no selected files exist on disk" }, - "selectionConvertCount": "Konvertiere {count} {count, plural, one {}=1{Titel}other{Titel}}", + "selectionConvertCount": "Konvertiere {count} {count, plural, one {Titel} other{Titel}}", "@selectionConvertCount": { "description": "Convert button text with count in selection mode", "placeholders": { @@ -3043,7 +3043,7 @@ "@selectionBatchConvertConfirmTitle": { "description": "Confirmation dialog title for batch conversion" }, - "selectionBatchConvertConfirmMessage": "Konvertiere {count} {format} {count, plural, one {}=1{Titel} other{Titel}} zu {bitrate}?\n\nOriginaldateien werden nach der Konvertierung gelöscht.", + "selectionBatchConvertConfirmMessage": "Konvertiere {count} {format} {count, plural, one {Titel} other{Titel}} zu {bitrate}?\n\nOriginaldateien werden nach der Konvertierung gelöscht.", "@selectionBatchConvertConfirmMessage": { "description": "Confirmation dialog message for batch conversion", "placeholders": { diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 7eee4649..bb8bac99 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -897,6 +897,18 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorUrlNotRecognized": "Link not recognized", + "@errorUrlNotRecognized": { + "description": "Error title - URL not handled by any extension or service" + }, + "errorUrlNotRecognizedMessage": "This link is not supported. Make sure the URL is correct and a compatible extension is installed.", + "@errorUrlNotRecognizedMessage": { + "description": "Error message - URL not recognized explanation" + }, + "errorUrlFetchFailed": "Failed to load content from this link. Please try again.", + "@errorUrlFetchFailed": { + "description": "Error message - generic URL fetch failure" + }, "errorMissingExtensionSource": "Cannot load {item}: missing extension source", "@errorMissingExtensionSource": { "description": "Error - extension source not available", diff --git a/lib/l10n/arb/app_es-ES.arb b/lib/l10n/arb/app_es-ES.arb deleted file mode 100644 index c48b16f4..00000000 --- a/lib/l10n/arb/app_es-ES.arb +++ /dev/null @@ -1,3105 +0,0 @@ -{ - "@@locale": "es-ES", - "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", - "@appName": { - "description": "App name - DO NOT TRANSLATE" - }, - "navHome": "Inicio", - "@navHome": { - "description": "Bottom navigation - Home tab" - }, - "navLibrary": "Biblioteca", - "@navLibrary": { - "description": "Bottom navigation - Library tab" - }, - "navSettings": "Ajustes", - "@navSettings": { - "description": "Bottom navigation - Settings tab" - }, - "navStore": "Tienda", - "@navStore": { - "description": "Bottom navigation - Extension store tab" - }, - "homeTitle": "Inicio", - "@homeTitle": { - "description": "Home screen title" - }, - "homeSubtitle": "Pegar enlace de Spotify o buscar por nombre", - "@homeSubtitle": { - "description": "Subtitle shown below search box" - }, - "homeSupports": "Soportes: Pista, Álbum, Lista de reproducción, URLs de Artistas", - "@homeSupports": { - "description": "Info text about supported URL types" - }, - "homeRecent": "Recientes", - "@homeRecent": { - "description": "Section header for recent searches" - }, - "historyFilterAll": "Todo", - "@historyFilterAll": { - "description": "Filter chip - show all items" - }, - "historyFilterAlbums": "Álbumes", - "@historyFilterAlbums": { - "description": "Filter chip - show albums only" - }, - "historyFilterSingles": "Pistas", - "@historyFilterSingles": { - "description": "Filter chip - show singles only" - }, - "historySearchHint": "Buscar en historial...", - "@historySearchHint": { - "description": "Search bar placeholder in history" - }, - "settingsTitle": "Ajustes", - "@settingsTitle": { - "description": "Settings screen title" - }, - "settingsDownload": "Descargar", - "@settingsDownload": { - "description": "Settings section - download options" - }, - "settingsAppearance": "Apariencia", - "@settingsAppearance": { - "description": "Settings section - visual customization" - }, - "settingsOptions": "Opciones", - "@settingsOptions": { - "description": "Settings section - app options" - }, - "settingsExtensions": "Extensiones", - "@settingsExtensions": { - "description": "Settings section - extension management" - }, - "settingsAbout": "Acerca de", - "@settingsAbout": { - "description": "Settings section - app info" - }, - "downloadTitle": "Descargar", - "@downloadTitle": { - "description": "Download settings page title" - }, - "downloadAskQualitySubtitle": "Mostrar selector de calidad para cada descarga", - "@downloadAskQualitySubtitle": { - "description": "Subtitle for ask quality toggle" - }, - "downloadFilenameFormat": "Formato del nombre del archivo", - "@downloadFilenameFormat": { - "description": "Setting for output filename pattern" - }, - "downloadFolderOrganization": "Organización de carpetas", - "@downloadFolderOrganization": { - "description": "Setting for folder structure" - }, - "appearanceTitle": "Apariencia", - "@appearanceTitle": { - "description": "Appearance settings page title" - }, - "appearanceThemeSystem": "Sistema", - "@appearanceThemeSystem": { - "description": "Follow system theme" - }, - "appearanceThemeLight": "Claro", - "@appearanceThemeLight": { - "description": "Light theme" - }, - "appearanceThemeDark": "Oscuro", - "@appearanceThemeDark": { - "description": "Dark theme" - }, - "appearanceDynamicColor": "Color dinámico", - "@appearanceDynamicColor": { - "description": "Material You dynamic colors" - }, - "appearanceDynamicColorSubtitle": "Usar colores de tu fondo de pantalla", - "@appearanceDynamicColorSubtitle": { - "description": "Subtitle for dynamic color" - }, - "appearanceHistoryView": "Vista de Historial", - "@appearanceHistoryView": { - "description": "Layout style for history" - }, - "appearanceHistoryViewList": "Lista", - "@appearanceHistoryViewList": { - "description": "List layout option" - }, - "appearanceHistoryViewGrid": "Cuadrícula", - "@appearanceHistoryViewGrid": { - "description": "Grid layout option" - }, - "optionsTitle": "Opciones", - "@optionsTitle": { - "description": "Options settings page title" - }, - "optionsPrimaryProvider": "Proveedor Principal", - "@optionsPrimaryProvider": { - "description": "Main search provider setting" - }, - "optionsPrimaryProviderSubtitle": "Servicio usado al buscar por nombre de la pista.", - "@optionsPrimaryProviderSubtitle": { - "description": "Subtitle for primary provider" - }, - "optionsUsingExtension": "Usando la extensión: {extensionName}", - "@optionsUsingExtension": { - "description": "Shows active extension name", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "optionsSwitchBack": "Toque Deezer o Spotify para volver desde la extensión", - "@optionsSwitchBack": { - "description": "Hint to switch back to built-in providers" - }, - "optionsAutoFallback": "Alternativa automática", - "@optionsAutoFallback": { - "description": "Auto-retry with other services" - }, - "optionsAutoFallbackSubtitle": "Pruebe otros servicios si falla la descarga", - "@optionsAutoFallbackSubtitle": { - "description": "Subtitle for auto fallback" - }, - "optionsUseExtensionProviders": "Usar proveedores de extensiones", - "@optionsUseExtensionProviders": { - "description": "Enable extension download providers" - }, - "optionsUseExtensionProvidersOn": "Las extensiones serán probadas primero", - "@optionsUseExtensionProvidersOn": { - "description": "Status when extension providers enabled" - }, - "optionsUseExtensionProvidersOff": "Utilizando sólo proveedores integrados", - "@optionsUseExtensionProvidersOff": { - "description": "Status when extension providers disabled" - }, - "optionsEmbedLyrics": "Incrustar Letras", - "@optionsEmbedLyrics": { - "description": "Embed lyrics in audio files" - }, - "optionsEmbedLyricsSubtitle": "Insertar letras sincronizadas en archivos FLAC", - "@optionsEmbedLyricsSubtitle": { - "description": "Subtitle for embed lyrics" - }, - "optionsMaxQualityCover": "Carátula de calidad máxima", - "@optionsMaxQualityCover": { - "description": "Download highest quality album art" - }, - "optionsMaxQualityCoverSubtitle": "Descargar carátula de resolución máxima", - "@optionsMaxQualityCoverSubtitle": { - "description": "Subtitle for max quality cover" - }, - "optionsConcurrentDownloads": "Descargas Simultáneas", - "@optionsConcurrentDownloads": { - "description": "Number of parallel downloads" - }, - "optionsConcurrentSequential": "Secuencial (1 a la vez)", - "@optionsConcurrentSequential": { - "description": "Download one at a time" - }, - "optionsConcurrentParallel": "{count} descargas paralelas", - "@optionsConcurrentParallel": { - "description": "Multiple parallel downloads", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "optionsConcurrentWarning": "Las descargas paralelas pueden activar la limitación de velocidad", - "@optionsConcurrentWarning": { - "description": "Warning about rate limits" - }, - "optionsExtensionStore": "Tienda de extensiones", - "@optionsExtensionStore": { - "description": "Show/hide store tab" - }, - "optionsExtensionStoreSubtitle": "Mostrar pestaña de tienda en la navegación", - "@optionsExtensionStoreSubtitle": { - "description": "Subtitle for extension store toggle" - }, - "optionsCheckUpdates": "Comprobar actualizaciones", - "@optionsCheckUpdates": { - "description": "Auto update check toggle" - }, - "optionsCheckUpdatesSubtitle": "Notificar cuando una nueva versión esté disponible", - "@optionsCheckUpdatesSubtitle": { - "description": "Subtitle for update check" - }, - "optionsUpdateChannel": "Tipo de actualizaciones", - "@optionsUpdateChannel": { - "description": "Stable vs preview releases" - }, - "optionsUpdateChannelStable": "Sólo versiones estables", - "@optionsUpdateChannelStable": { - "description": "Only stable updates" - }, - "optionsUpdateChannelPreview": "Versión preliminar", - "@optionsUpdateChannelPreview": { - "description": "Include beta/preview updates" - }, - "optionsUpdateChannelWarning": "La Versión preliminar puede contener errores o características incompletas", - "@optionsUpdateChannelWarning": { - "description": "Warning about preview channel" - }, - "optionsClearHistory": "Borrar el historial de descargas", - "@optionsClearHistory": { - "description": "Delete all download history" - }, - "optionsClearHistorySubtitle": "Eliminar todas las pistas descargadas del historial", - "@optionsClearHistorySubtitle": { - "description": "Subtitle for clear history" - }, - "optionsDetailedLogging": "Registro detallado", - "@optionsDetailedLogging": { - "description": "Enable verbose logs for debugging" - }, - "optionsDetailedLoggingOn": "Registros detallados están siendo registrados", - "@optionsDetailedLoggingOn": { - "description": "Status when logging enabled" - }, - "optionsDetailedLoggingOff": "Habilitar para informes de errores", - "@optionsDetailedLoggingOff": { - "description": "Status when logging disabled" - }, - "optionsSpotifyCredentials": "Credenciales de Spotify", - "@optionsSpotifyCredentials": { - "description": "Spotify API credentials setting" - }, - "optionsSpotifyCredentialsConfigured": "ID de cliente: {clientId}...", - "@optionsSpotifyCredentialsConfigured": { - "description": "Shows configured client ID preview", - "placeholders": { - "clientId": { - "type": "String" - } - } - }, - "optionsSpotifyCredentialsRequired": "Requerido - toque para configurar", - "@optionsSpotifyCredentialsRequired": { - "description": "Prompt to set up credentials" - }, - "optionsSpotifyWarning": "Spotify requiere tus propias credenciales API. Obténgalas gratis de developer.spotify.com", - "@optionsSpotifyWarning": { - "description": "Info about Spotify API requirement" - }, - "optionsSpotifyDeprecationWarning": "Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.", - "@optionsSpotifyDeprecationWarning": { - "description": "Warning about Spotify API deprecation" - }, - "extensionsTitle": "Extensiones", - "@extensionsTitle": { - "description": "Extensions page title" - }, - "extensionsDisabled": "Deshabilitado", - "@extensionsDisabled": { - "description": "Extension status - inactive" - }, - "extensionsVersion": "Versión {version}", - "@extensionsVersion": { - "description": "Extension version display", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "extensionsAuthor": "por {author}", - "@extensionsAuthor": { - "description": "Extension author credit", - "placeholders": { - "author": { - "type": "String" - } - } - }, - "extensionsUninstall": "Desinstalar", - "@extensionsUninstall": { - "description": "Uninstall extension button" - }, - "storeTitle": "Tienda de extensiones", - "@storeTitle": { - "description": "Store screen title" - }, - "storeSearch": "Buscar extensiones...", - "@storeSearch": { - "description": "Store search placeholder" - }, - "storeInstall": "Instalar", - "@storeInstall": { - "description": "Install extension button" - }, - "storeInstalled": "Instalada", - "@storeInstalled": { - "description": "Already installed badge" - }, - "storeUpdate": "Actualizar", - "@storeUpdate": { - "description": "Update available button" - }, - "aboutTitle": "Acerca de", - "@aboutTitle": { - "description": "About page title" - }, - "aboutContributors": "Colaboradores", - "@aboutContributors": { - "description": "Section for contributors" - }, - "aboutMobileDeveloper": "Desarrollador de versiones móviles", - "@aboutMobileDeveloper": { - "description": "Role description for mobile dev" - }, - "aboutOriginalCreator": "Creador original de SpotiFLAC", - "@aboutOriginalCreator": { - "description": "Role description for original creator" - }, - "aboutLogoArtist": "¡El talentoso artista que creó nuestro hermoso logo!", - "@aboutLogoArtist": { - "description": "Role description for logo artist" - }, - "aboutTranslators": "Traductores", - "@aboutTranslators": { - "description": "Section for translators" - }, - "aboutSpecialThanks": "Agradecimientos especiales", - "@aboutSpecialThanks": { - "description": "Section for special thanks" - }, - "aboutLinks": "Enlaces", - "@aboutLinks": { - "description": "Section for external links" - }, - "aboutMobileSource": "Código fuente móvil", - "@aboutMobileSource": { - "description": "Link to mobile GitHub repo" - }, - "aboutPCSource": "Código fuente de PC", - "@aboutPCSource": { - "description": "Link to PC GitHub repo" - }, - "aboutReportIssue": "Reportar un problema", - "@aboutReportIssue": { - "description": "Link to report bugs" - }, - "aboutReportIssueSubtitle": "Reporta cualquier problema que encuentres", - "@aboutReportIssueSubtitle": { - "description": "Subtitle for report issue" - }, - "aboutFeatureRequest": "Sugerir una función", - "@aboutFeatureRequest": { - "description": "Link to suggest features" - }, - "aboutFeatureRequestSubtitle": "Sugerir nuevas funciones para la aplicación", - "@aboutFeatureRequestSubtitle": { - "description": "Subtitle for feature request" - }, - "aboutTelegramChannel": "Canal de Telegram", - "@aboutTelegramChannel": { - "description": "Link to Telegram channel" - }, - "aboutTelegramChannelSubtitle": "Anuncios y actualizaciones", - "@aboutTelegramChannelSubtitle": { - "description": "Subtitle for Telegram channel" - }, - "aboutTelegramChat": "Comunidad de Telegram", - "@aboutTelegramChat": { - "description": "Link to Telegram chat group" - }, - "aboutTelegramChatSubtitle": "Chatear con otros usuarios", - "@aboutTelegramChatSubtitle": { - "description": "Subtitle for Telegram chat" - }, - "aboutSocial": "Redes sociales", - "@aboutSocial": { - "description": "Section for social links" - }, - "aboutApp": "Aplicación", - "@aboutApp": { - "description": "Section for app info" - }, - "aboutVersion": "Versión", - "@aboutVersion": { - "description": "Version info label" - }, - "aboutBinimumDesc": "El creador de la API QQDL & Hi-Fi. ¡Sin esta API, las descargas de Tidal no existiría!", - "@aboutBinimumDesc": { - "description": "Credit description for binimum" - }, - "aboutSachinsenalDesc": "El creador original del proyecto Hi-Fi. ¡La base de la integración de Tidal!", - "@aboutSachinsenalDesc": { - "description": "Credit description for sachinsenal0x64" - }, - "aboutSjdonadoDesc": "Creador de I No tengo Spotify (IDHS). ¡La solución de enlace de reserva que salva el día!", - "@aboutSjdonadoDesc": { - "description": "Credit description for sjdonado" - }, - "aboutDabMusic": "Música DAB", - "@aboutDabMusic": { - "description": "Name of Qobuz API service - DO NOT TRANSLATE" - }, - "aboutDabMusicDesc": "La mejor API de streaming de Qobuz. ¡Las descargas de Hi-Res no serían posibles sin esto!", - "@aboutDabMusicDesc": { - "description": "Credit for DAB Music API" - }, - "aboutSpotiSaver": "SpotiSaver", - "@aboutSpotiSaver": { - "description": "Name of SpotiSaver API service - DO NOT TRANSLATE" - }, - "aboutSpotiSaverDesc": "Tidal de transmisión Hi-Res FLAC. ¡Una pieza clave del rompecabezas sin pérdida!", - "@aboutSpotiSaverDesc": { - "description": "Credit for SpotiSaver API" - }, - "aboutAppDescription": "Descarga pistas de Spotify con calidad sin pérdida de Tidal, Qobuz y Amazon Music.", - "@aboutAppDescription": { - "description": "App description in header card" - }, - "artistAlbums": "Álbumes", - "@artistAlbums": { - "description": "Section header for artist albums" - }, - "artistSingles": "Pistas y EPs", - "@artistSingles": { - "description": "Section header for singles/EPs" - }, - "artistCompilations": "Compilaciones", - "@artistCompilations": { - "description": "Section header for compilations" - }, - "artistPopular": "Populares", - "@artistPopular": { - "description": "Section header for popular/top tracks" - }, - "artistMonthlyListeners": "{count} oyentes mensuales", - "@artistMonthlyListeners": { - "description": "Monthly listener count display", - "placeholders": { - "count": { - "type": "String", - "description": "Formatted listener count" - } - } - }, - "trackMetadataService": "Servicio", - "@trackMetadataService": { - "description": "Metadata field - download service used" - }, - "trackMetadataPlay": "Reproducir", - "@trackMetadataPlay": { - "description": "Action button - play track" - }, - "trackMetadataShare": "Compartir", - "@trackMetadataShare": { - "description": "Action button - share track" - }, - "trackMetadataDelete": "Eliminar", - "@trackMetadataDelete": { - "description": "Action button - delete track" - }, - "setupGrantPermission": "Conceder permiso", - "@setupGrantPermission": { - "description": "Button to request permission" - }, - "setupSkip": "Omitir por ahora", - "@setupSkip": { - "description": "Skip current step button" - }, - "setupStorageAccessRequired": "Acceso al almacenamiento requerido", - "@setupStorageAccessRequired": { - "description": "Title when storage access needed" - }, - "setupStorageAccessMessageAndroid11": "Android 11+ requiere permiso \"Todos los archivos de acceso\" para guardar los archivos en la carpeta de descargas elegida.", - "@setupStorageAccessMessageAndroid11": { - "description": "Android 11+ specific explanation" - }, - "setupOpenSettings": "Abrir ajustes", - "@setupOpenSettings": { - "description": "Button to open system settings" - }, - "setupPermissionDeniedMessage": "Permiso denegado. Por favor, conceda todos los permisos para continuar.", - "@setupPermissionDeniedMessage": { - "description": "Error when permission denied" - }, - "setupPermissionRequired": "Permiso de {permissionType} requerido", - "@setupPermissionRequired": { - "description": "Generic permission required title", - "placeholders": { - "permissionType": { - "type": "String", - "description": "Type of permission (Storage/Notification)" - } - } - }, - "setupPermissionRequiredMessage": "Se requiere un permiso {permissionType} para la mejor experiencia. Puedes cambiar esto más tarde en ajustes.", - "@setupPermissionRequiredMessage": { - "description": "Generic permission required message", - "placeholders": { - "permissionType": { - "type": "String" - } - } - }, - "setupUseDefaultFolder": "¿Usar carpeta por defecto?", - "@setupUseDefaultFolder": { - "description": "Dialog title for default folder" - }, - "setupNoFolderSelected": "No se ha seleccionado ninguna carpeta. ¿Desea utilizar la carpeta por defecto?", - "@setupNoFolderSelected": { - "description": "Prompt when no folder selected" - }, - "setupUseDefault": "Usar por defecto", - "@setupUseDefault": { - "description": "Button to use default folder" - }, - "setupDownloadLocationTitle": "Ubicación de descarga", - "@setupDownloadLocationTitle": { - "description": "Download location dialog title" - }, - "setupDownloadLocationIosMessage": "En iOS, las descargas se guardan en la carpeta de documentos de la aplicación. Puede acceder a ellas desde la aplicación Archivos.", - "@setupDownloadLocationIosMessage": { - "description": "iOS-specific folder info" - }, - "setupAppDocumentsFolder": "Carpeta de documentos de App", - "@setupAppDocumentsFolder": { - "description": "iOS documents folder option" - }, - "setupAppDocumentsFolderSubtitle": "Recomendado - accesible desde la aplicación Archivos", - "@setupAppDocumentsFolderSubtitle": { - "description": "Subtitle for documents folder" - }, - "setupChooseFromFiles": "Elegir de archivos", - "@setupChooseFromFiles": { - "description": "iOS file picker option" - }, - "setupChooseFromFilesSubtitle": "Seleccione iCloud u otra ubicación", - "@setupChooseFromFilesSubtitle": { - "description": "Subtitle for file picker" - }, - "setupIosEmptyFolderWarning": "Limitación de iOS: No se pueden seleccionar carpetas vacías. Elige una carpeta con al menos un archivo.", - "@setupIosEmptyFolderWarning": { - "description": "iOS folder selection warning" - }, - "setupIcloudNotSupported": "iCloud Drive no es compatible. Utilice la carpeta Documentos de la aplicación.", - "@setupIcloudNotSupported": { - "description": "Error when user selects iCloud Drive on iOS" - }, - "setupDownloadInFlac": "Descargar pistas de Spotify en FLAC", - "@setupDownloadInFlac": { - "description": "App tagline in setup" - }, - "setupStorageGranted": "¡Permiso de almacenamiento concedido!", - "@setupStorageGranted": { - "description": "Success message for storage permission" - }, - "setupStorageRequired": "Permiso de almacenamiento requerido", - "@setupStorageRequired": { - "description": "Title when storage permission needed" - }, - "setupStorageDescription": "SpotiFLAC necesita permiso de almacenamiento para guardar sus archivos de música descargados.", - "@setupStorageDescription": { - "description": "Explanation for storage permission" - }, - "setupNotificationGranted": "¡Acceso a las notificaciones permitido!", - "@setupNotificationGranted": { - "description": "Success message for notification permission" - }, - "setupNotificationEnable": "Activar notificaciones", - "@setupNotificationEnable": { - "description": "Button to enable notifications" - }, - "setupFolderChoose": "Cambiar carpeta de descargas", - "@setupFolderChoose": { - "description": "Button to choose folder" - }, - "setupFolderDescription": "Seleccione una carpeta donde se guardará la música descargada.", - "@setupFolderDescription": { - "description": "Explanation for folder selection" - }, - "setupSelectFolder": "Seleccionar Carpeta", - "@setupSelectFolder": { - "description": "Button to select folder" - }, - "setupEnableNotifications": "Activar notificaciones", - "@setupEnableNotifications": { - "description": "Button to enable notifications" - }, - "setupNotificationBackgroundDescription": "Recibe notificaciones sobre el progreso de la descarga y la finalización. Esto te ayuda a rastrear las descargas cuando la aplicación está en segundo plano.", - "@setupNotificationBackgroundDescription": { - "description": "Detailed notification explanation" - }, - "setupSkipForNow": "Omitir por ahora", - "@setupSkipForNow": { - "description": "Skip button text" - }, - "setupNext": "Siguiente", - "@setupNext": { - "description": "Next button text" - }, - "setupGetStarted": "Empezar", - "@setupGetStarted": { - "description": "Final setup button" - }, - "setupAllowAccessToManageFiles": "Por favor, activa \"Permitir el acceso para gestionar todos los archivos\" en la siguiente pantalla.", - "@setupAllowAccessToManageFiles": { - "description": "Instruction for file access permission" - }, - "dialogCancel": "Cancelar", - "@dialogCancel": { - "description": "Dialog button - cancel action" - }, - "dialogSave": "Guardar", - "@dialogSave": { - "description": "Dialog button - save changes" - }, - "dialogDelete": "Eliminar", - "@dialogDelete": { - "description": "Dialog button - delete item" - }, - "dialogRetry": "Volver a intentar", - "@dialogRetry": { - "description": "Dialog button - retry action" - }, - "dialogClear": "Borrar", - "@dialogClear": { - "description": "Dialog button - clear items" - }, - "dialogDone": "Hecho", - "@dialogDone": { - "description": "Dialog button - action completed" - }, - "dialogImport": "Importar", - "@dialogImport": { - "description": "Dialog button - import data" - }, - "dialogDiscard": "Descartar", - "@dialogDiscard": { - "description": "Dialog button - discard changes" - }, - "dialogRemove": "Eliminar", - "@dialogRemove": { - "description": "Dialog button - remove item" - }, - "dialogUninstall": "Desinstalar", - "@dialogUninstall": { - "description": "Dialog button - uninstall extension" - }, - "dialogDiscardChanges": "¿Descartar cambios?", - "@dialogDiscardChanges": { - "description": "Dialog title - unsaved changes warning" - }, - "dialogUnsavedChanges": "Tienes cambios sin guardar. ¿Quieres descartarlos?", - "@dialogUnsavedChanges": { - "description": "Dialog message - unsaved changes" - }, - "dialogClearAll": "Eliminar todo", - "@dialogClearAll": { - "description": "Dialog title - clear all items" - }, - "dialogRemoveExtension": "Eliminar extensión", - "@dialogRemoveExtension": { - "description": "Dialog title - uninstall extension" - }, - "dialogRemoveExtensionMessage": "¿Estás seguro de que quieres eliminar esta extensión? Esto no se puede deshacer.", - "@dialogRemoveExtensionMessage": { - "description": "Dialog message - uninstall confirmation" - }, - "dialogUninstallExtension": "¿Desinstalar extensión?", - "@dialogUninstallExtension": { - "description": "Dialog title - uninstall extension" - }, - "dialogUninstallExtensionMessage": "¿Estás seguro de que quieres eliminar {extensionName}?", - "@dialogUninstallExtensionMessage": { - "description": "Dialog message - uninstall specific extension", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "dialogClearHistoryTitle": "Borrar historial", - "@dialogClearHistoryTitle": { - "description": "Dialog title - clear download history" - }, - "dialogClearHistoryMessage": "¿Estás seguro de que quieres borrar todo el historial de descargas? Esta acción no se puede deshacer.", - "@dialogClearHistoryMessage": { - "description": "Dialog message - clear history confirmation" - }, - "dialogDeleteSelectedTitle": "Borrar Seleccionados", - "@dialogDeleteSelectedTitle": { - "description": "Dialog title - delete selected items" - }, - "dialogDeleteSelectedMessage": "¿Eliminar {count} {count, plural, one {}=1{pista} other{pistas}} del historial?\n\nEsto también eliminará los archivos del almacenamiento.", - "@dialogDeleteSelectedMessage": { - "description": "Dialog message - delete selected tracks", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "dialogImportPlaylistTitle": "Importar lista de reproducción", - "@dialogImportPlaylistTitle": { - "description": "Dialog title - import CSV playlist" - }, - "dialogImportPlaylistMessage": "Se han encontrado pistas {count} en CSV. ¿Añadirlas para descargar la cola?", - "csvImportTracks": "{count} pistas de CSV", - "@csvImportTracks": { - "description": "Label shown in quality picker for CSV import", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "@dialogImportPlaylistMessage": { - "description": "Dialog message - import playlist confirmation", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "snackbarAddedToQueue": "Añadido \"{trackName}\" a la cola", - "@snackbarAddedToQueue": { - "description": "Snackbar - track added to download queue", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "snackbarAddedTracksToQueue": "Añadidas pistas {count} a la cola", - "@snackbarAddedTracksToQueue": { - "description": "Snackbar - multiple tracks added to queue", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "snackbarAlreadyDownloaded": "\"{trackName}\" ya descargado", - "@snackbarAlreadyDownloaded": { - "description": "Snackbar - track already exists", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "snackbarAlreadyInLibrary": "\"{trackName}\" ya existe en tu biblioteca", - "@snackbarAlreadyInLibrary": { - "description": "Snackbar - track already exists in local library", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "snackbarHistoryCleared": "Historial borrado", - "@snackbarHistoryCleared": { - "description": "Snackbar - history deleted" - }, - "snackbarCredentialsSaved": "Credenciales guardadas", - "@snackbarCredentialsSaved": { - "description": "Snackbar - Spotify credentials saved" - }, - "snackbarCredentialsCleared": "Credenciales borradas", - "@snackbarCredentialsCleared": { - "description": "Snackbar - Spotify credentials removed" - }, - "snackbarDeletedTracks": "Eliminado {count} {count, plural, one {}=1{pista} other{pistas}}", - "@snackbarDeletedTracks": { - "description": "Snackbar - tracks deleted", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "snackbarCannotOpenFile": "No se puede abrir el archivo: {error}", - "@snackbarCannotOpenFile": { - "description": "Snackbar - file open error", - "placeholders": { - "error": { - "type": "String" - } - } - }, - "snackbarFillAllFields": "Por favor, completa todos los campos", - "@snackbarFillAllFields": { - "description": "Snackbar - validation error" - }, - "snackbarViewQueue": "Ver cola", - "@snackbarViewQueue": { - "description": "Snackbar action - view download queue" - }, - "snackbarUrlCopied": "URL {platform} copiada al portapapeles", - "@snackbarUrlCopied": { - "description": "Snackbar - URL copied", - "placeholders": { - "platform": { - "type": "String", - "description": "Platform name (Spotify/Deezer)" - } - } - }, - "snackbarFileNotFound": "Archivo no encontrado", - "@snackbarFileNotFound": { - "description": "Snackbar - file doesn't exist" - }, - "snackbarSelectExtFile": "Por favor, seleccione un archivo .spotiflac-ext", - "@snackbarSelectExtFile": { - "description": "Snackbar - wrong file type selected" - }, - "snackbarProviderPrioritySaved": "Prioridad de proveedor guardada", - "@snackbarProviderPrioritySaved": { - "description": "Snackbar - provider order saved" - }, - "snackbarMetadataProviderSaved": "Prioridad de proveedor de metadatos guardada", - "@snackbarMetadataProviderSaved": { - "description": "Snackbar - metadata provider order saved" - }, - "snackbarExtensionInstalled": "{extensionName} instalado.", - "@snackbarExtensionInstalled": { - "description": "Snackbar - extension installed successfully", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "snackbarExtensionUpdated": "{extensionName} actualizada.", - "@snackbarExtensionUpdated": { - "description": "Snackbar - extension updated successfully", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "snackbarFailedToInstall": "Fallo al instalar la extensión", - "@snackbarFailedToInstall": { - "description": "Snackbar - extension install error" - }, - "snackbarFailedToUpdate": "Error al actualizar la extensión", - "@snackbarFailedToUpdate": { - "description": "Snackbar - extension update error" - }, - "errorRateLimited": "Límite Excedido", - "@errorRateLimited": { - "description": "Error title - too many requests" - }, - "errorRateLimitedMessage": "Demasiadas solicitudes. Por favor, espere un momento antes de buscar de nuevo.", - "@errorRateLimitedMessage": { - "description": "Error message - rate limit explanation" - }, - "errorNoTracksFound": "No se encontraron pistas", - "@errorNoTracksFound": { - "description": "Error - search returned no results" - }, - "errorMissingExtensionSource": "No se puede cargar {item}: falta una fuente de extensión", - "@errorMissingExtensionSource": { - "description": "Error - extension source not available", - "placeholders": { - "item": { - "type": "String" - } - } - }, - "actionPause": "Pausar", - "@actionPause": { - "description": "Action button - pause download" - }, - "actionResume": "Reanudar", - "@actionResume": { - "description": "Action button - resume download" - }, - "actionCancel": "Cancelar", - "@actionCancel": { - "description": "Action button - cancel operation" - }, - "actionSelectAll": "Seleccionar Todo", - "@actionSelectAll": { - "description": "Action button - select all items" - }, - "actionDeselect": "Deseleccionar", - "@actionDeselect": { - "description": "Action button - deselect all" - }, - "actionRemoveCredentials": "Eliminar credenciales", - "@actionRemoveCredentials": { - "description": "Action button - delete Spotify credentials" - }, - "actionSaveCredentials": "Guardar credenciales", - "@actionSaveCredentials": { - "description": "Action button - save Spotify credentials" - }, - "selectionSelected": "{count} seleccionado", - "@selectionSelected": { - "description": "Selection count indicator", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "selectionAllSelected": "Todas las pistas seleccionadas", - "@selectionAllSelected": { - "description": "Status - all items selected" - }, - "selectionSelectToDelete": "Seleccionar pistas a eliminar", - "@selectionSelectToDelete": { - "description": "Placeholder when nothing selected" - }, - "progressFetchingMetadata": "Obteniendo metadatos... {current}/{total}", - "@progressFetchingMetadata": { - "description": "Progress indicator - loading track info", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } - }, - "progressReadingCsv": "Leyendo CSV...", - "@progressReadingCsv": { - "description": "Progress indicator - parsing CSV file" - }, - "searchSongs": "Canciones", - "@searchSongs": { - "description": "Search result category - songs" - }, - "searchArtists": "Artistas", - "@searchArtists": { - "description": "Search result category - artists" - }, - "searchAlbums": "Álbumes", - "@searchAlbums": { - "description": "Search result category - albums" - }, - "searchPlaylists": "Listas de reproducción", - "@searchPlaylists": { - "description": "Search result category - playlists" - }, - "tooltipPlay": "Reproducir", - "@tooltipPlay": { - "description": "Tooltip - play button" - }, - "filenameFormat": "Formato del nombre del archivo", - "@filenameFormat": { - "description": "Setting title - filename pattern" - }, - "filenameShowAdvancedTags": "Show advanced tags", - "@filenameShowAdvancedTags": { - "description": "Toggle label for showing advanced filename tags" - }, - "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", - "@filenameShowAdvancedTagsDescription": { - "description": "Description for advanced filename tag toggle" - }, - "folderOrganizationNone": "Ninguna organización", - "@folderOrganizationNone": { - "description": "Folder option - flat structure" - }, - "folderOrganizationByArtist": "Por Artista", - "@folderOrganizationByArtist": { - "description": "Folder option - artist folders" - }, - "folderOrganizationByAlbum": "Por Álbum", - "@folderOrganizationByAlbum": { - "description": "Folder option - album folders" - }, - "folderOrganizationByArtistAlbum": "Artista/Álbum", - "@folderOrganizationByArtistAlbum": { - "description": "Folder option - nested folders" - }, - "folderOrganizationDescription": "Organizar los archivos descargados en carpetas", - "@folderOrganizationDescription": { - "description": "Folder organization sheet description" - }, - "folderOrganizationNoneSubtitle": "Todos los archivos de la carpeta de descargas", - "@folderOrganizationNoneSubtitle": { - "description": "Subtitle for no organization option" - }, - "folderOrganizationByArtistSubtitle": "Carpeta separada para cada artista", - "@folderOrganizationByArtistSubtitle": { - "description": "Subtitle for artist folder option" - }, - "folderOrganizationByAlbumSubtitle": "Carpeta separada para cada artista", - "@folderOrganizationByAlbumSubtitle": { - "description": "Subtitle for album folder option" - }, - "folderOrganizationByArtistAlbumSubtitle": "Carpetas organizadas por artista y álbum", - "@folderOrganizationByArtistAlbumSubtitle": { - "description": "Subtitle for nested folder option" - }, - "updateAvailable": "Actualización Disponible", - "@updateAvailable": { - "description": "Update dialog title" - }, - "updateLater": "Más tarde", - "@updateLater": { - "description": "Update button - dismiss" - }, - "updateStartingDownload": "Iniciando descarga...", - "@updateStartingDownload": { - "description": "Update status - initializing" - }, - "updateDownloadFailed": "Descarga fallida", - "@updateDownloadFailed": { - "description": "Update error title" - }, - "updateFailedMessage": "Error al descargar la actualización", - "@updateFailedMessage": { - "description": "Update error message" - }, - "updateNewVersionReady": "Una nueva versión está lista", - "@updateNewVersionReady": { - "description": "Update subtitle" - }, - "updateCurrent": "Actual", - "@updateCurrent": { - "description": "Label for current version" - }, - "updateNew": "Nuevo", - "@updateNew": { - "description": "Label for new version" - }, - "updateDownloading": "Descargando...", - "@updateDownloading": { - "description": "Update status - downloading" - }, - "updateWhatsNew": "Novedades", - "@updateWhatsNew": { - "description": "Changelog section title" - }, - "updateDownloadInstall": "Descargar & Instalar", - "@updateDownloadInstall": { - "description": "Update button - download and install" - }, - "updateDontRemind": "No recordar", - "@updateDontRemind": { - "description": "Update button - skip this version" - }, - "providerPriorityTitle": "Prioridad del proveedor", - "@providerPriorityTitle": { - "description": "Provider priority page title" - }, - "providerPriorityDescription": "Arrastra para reordenar los proveedores de descarga. La aplicación intentará usar los proveedores de arriba hacia abajo al descargar las pistas.", - "@providerPriorityDescription": { - "description": "Provider priority page description" - }, - "providerPriorityInfo": "Si una pista no está disponible en el primer proveedor, la aplicación intentará automáticamente el siguiente.", - "@providerPriorityInfo": { - "description": "Info tip about fallback behavior" - }, - "providerBuiltIn": "Integrado", - "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" - }, - "providerExtension": "Extensión", - "@providerExtension": { - "description": "Label for extension-provided providers" - }, - "metadataProviderPriorityTitle": "Prioridad de los metadatos", - "@metadataProviderPriorityTitle": { - "description": "Metadata priority page title" - }, - "metadataProviderPriorityDescription": "Arrastra para reordenar los proveedores de metadatos. La aplicación probará los proveedores de arriba hacia abajo al buscar pistas y obtener los metadatos.", - "@metadataProviderPriorityDescription": { - "description": "Metadata priority page description" - }, - "metadataProviderPriorityInfo": "Deezer no tiene límites de tasa y se recomienda como principal. Spotify puede valorar el límite después de muchas solicitudes.", - "@metadataProviderPriorityInfo": { - "description": "Info tip about rate limits" - }, - "metadataNoRateLimits": "Sin límites de tasa", - "@metadataNoRateLimits": { - "description": "Deezer provider description" - }, - "metadataMayRateLimit": "Sin límites de tasa", - "@metadataMayRateLimit": { - "description": "Spotify provider description" - }, - "logTitle": "Registros", - "@logTitle": { - "description": "Logs screen title" - }, - "logCopied": "Registros copiados al portapapeles", - "@logCopied": { - "description": "Snackbar - logs copied" - }, - "logSearchHint": "Buscar registros...", - "@logSearchHint": { - "description": "Log search placeholder" - }, - "logFilterLevel": "Nivel", - "@logFilterLevel": { - "description": "Filter by log level" - }, - "logFilterSection": "Filtrar", - "@logFilterSection": { - "description": "Filter section title" - }, - "logShareLogs": "Compartir registros", - "@logShareLogs": { - "description": "Share button tooltip" - }, - "logClearLogs": "Borrar registros", - "@logClearLogs": { - "description": "Clear button tooltip" - }, - "logClearLogsTitle": "Limpiar registros", - "@logClearLogsTitle": { - "description": "Clear logs dialog title" - }, - "logClearLogsMessage": "¿Estás seguro que deseas limpiar todos los registros?", - "@logClearLogsMessage": { - "description": "Clear logs confirmation message" - }, - "logFilterBySeverity": "Filtrar los registros por gravedad", - "@logFilterBySeverity": { - "description": "Filter dialog title" - }, - "logNoLogsYet": "No hay registros aún", - "@logNoLogsYet": { - "description": "Empty state title" - }, - "logNoLogsYetSubtitle": "Los registros aparecerán aquí mientras usas la aplicación", - "@logNoLogsYetSubtitle": { - "description": "Empty state subtitle" - }, - "logEntriesFiltered": "Entradas ({count} filtradas)", - "@logEntriesFiltered": { - "description": "Log count with filter active", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logEntries": "Entradas ({count})", - "@logEntries": { - "description": "Total log count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "credentialsTitle": "Credenciales de Spotify", - "@credentialsTitle": { - "description": "Credentials dialog title" - }, - "credentialsDescription": "Introduzca su ID de cliente y secreto para utilizar su propia cuota de aplicación de Spotify.", - "@credentialsDescription": { - "description": "Credentials dialog explanation" - }, - "credentialsClientId": "ID del cliente", - "@credentialsClientId": { - "description": "Client ID field label - DO NOT TRANSLATE" - }, - "credentialsClientIdHint": "Pegar ID de cliente", - "@credentialsClientIdHint": { - "description": "Client ID placeholder" - }, - "credentialsClientSecret": "Client Secret", - "@credentialsClientSecret": { - "description": "Client Secret field label - DO NOT TRANSLATE" - }, - "credentialsClientSecretHint": "Pegar Client Secret", - "@credentialsClientSecretHint": { - "description": "Client Secret placeholder" - }, - "channelStable": "Estable", - "@channelStable": { - "description": "Update channel - stable releases" - }, - "channelPreview": "Vista previa", - "@channelPreview": { - "description": "Update channel - beta/preview releases" - }, - "sectionSearchSource": "Buscar Fuente", - "@sectionSearchSource": { - "description": "Settings section header" - }, - "sectionDownload": "Descargar", - "@sectionDownload": { - "description": "Settings section header" - }, - "sectionPerformance": "Alto rendimiento", - "@sectionPerformance": { - "description": "Settings section header" - }, - "sectionApp": "Aplicación", - "@sectionApp": { - "description": "Settings section header" - }, - "sectionData": "Datos", - "@sectionData": { - "description": "Settings section header" - }, - "sectionDebug": "Depuración", - "@sectionDebug": { - "description": "Settings section header" - }, - "sectionService": "Servicio", - "@sectionService": { - "description": "Settings section header" - }, - "sectionAudioQuality": "Calidad de Sonido", - "@sectionAudioQuality": { - "description": "Settings section header" - }, - "sectionFileSettings": "Ajustes del archivo", - "@sectionFileSettings": { - "description": "Settings section header" - }, - "sectionLyrics": "Letras", - "@sectionLyrics": { - "description": "Settings section header" - }, - "lyricsMode": "Modo Letras", - "@lyricsMode": { - "description": "Setting - how to save lyrics" - }, - "lyricsModeDescription": "Elige cómo se guardan las letras de tus descargas", - "@lyricsModeDescription": { - "description": "Lyrics mode picker description" - }, - "lyricsModeEmbed": "Insertar en archivo", - "@lyricsModeEmbed": { - "description": "Lyrics mode option - embed in audio file" - }, - "lyricsModeEmbedSubtitle": "Letras almacenadas en los metadatos FLAC", - "@lyricsModeEmbedSubtitle": { - "description": "Subtitle for embed option" - }, - "lyricsModeExternal": "Archivo .lrc externo", - "@lyricsModeExternal": { - "description": "Lyrics mode option - separate LRC file" - }, - "lyricsModeExternalSubtitle": "Archivo .lrc separado para reproductores como Samsung Music", - "@lyricsModeExternalSubtitle": { - "description": "Subtitle for external option" - }, - "lyricsModeBoth": "Ambos", - "@lyricsModeBoth": { - "description": "Lyrics mode option - embed and external" - }, - "lyricsModeBothSubtitle": "Insertar y guardar archivo .lrc", - "@lyricsModeBothSubtitle": { - "description": "Subtitle for both option" - }, - "sectionColor": "Colores", - "@sectionColor": { - "description": "Settings section header" - }, - "sectionTheme": "Tema", - "@sectionTheme": { - "description": "Settings section header" - }, - "sectionLayout": "Diseño", - "@sectionLayout": { - "description": "Settings section header" - }, - "sectionLanguage": "Idioma", - "@sectionLanguage": { - "description": "Settings section header for language" - }, - "appearanceLanguage": "Idioma de la aplicación", - "@appearanceLanguage": { - "description": "Language setting title" - }, - "settingsAppearanceSubtitle": "Tema, colores, pantalla", - "@settingsAppearanceSubtitle": { - "description": "Appearance settings description" - }, - "settingsDownloadSubtitle": "Servicio, calidad, formato del nombre del archivo", - "@settingsDownloadSubtitle": { - "description": "Download settings description" - }, - "settingsOptionsSubtitle": "Alternativa, letras, carátula, actualizaciones", - "@settingsOptionsSubtitle": { - "description": "Options settings description" - }, - "settingsExtensionsSubtitle": "Administrar proveedores de descarga", - "@settingsExtensionsSubtitle": { - "description": "Extensions settings description" - }, - "settingsLogsSubtitle": "Ver registros de aplicaciones para depuración", - "@settingsLogsSubtitle": { - "description": "Logs settings description" - }, - "loadingSharedLink": "Cargando enlace compartido...", - "@loadingSharedLink": { - "description": "Status when opening shared URL" - }, - "pressBackAgainToExit": "Presione de nuevo para salir", - "@pressBackAgainToExit": { - "description": "Exit confirmation message" - }, - "downloadAllCount": "Descargar Todo ({count})", - "@downloadAllCount": { - "description": "Download all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "tracksCount": "{count, plural, one {}=1{1 pista} other{{count} pistas}}", - "@tracksCount": { - "description": "Track count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "trackCopyFilePath": "Copiar ruta de archivo", - "@trackCopyFilePath": { - "description": "Action - copy file path" - }, - "trackRemoveFromDevice": "Eliminar del dispositivo", - "@trackRemoveFromDevice": { - "description": "Action - delete downloaded file" - }, - "trackLoadLyrics": "Cargar letras", - "@trackLoadLyrics": { - "description": "Action - fetch lyrics" - }, - "trackMetadata": "Metadatos", - "@trackMetadata": { - "description": "Tab title - track metadata" - }, - "trackFileInfo": "Información de archivo", - "@trackFileInfo": { - "description": "Tab title - file information" - }, - "trackLyrics": "Letras", - "@trackLyrics": { - "description": "Tab title - lyrics" - }, - "trackFileNotFound": "Archivo no encontrado", - "@trackFileNotFound": { - "description": "Error - file doesn't exist" - }, - "trackOpenInDeezer": "Abrir en Deezer", - "@trackOpenInDeezer": { - "description": "Action - open track in Deezer app" - }, - "trackOpenInSpotify": "Abrir en Spotify", - "@trackOpenInSpotify": { - "description": "Action - open track in Spotify app" - }, - "trackTrackName": "Nombre de pista", - "@trackTrackName": { - "description": "Metadata label - track title" - }, - "trackArtist": "Artista", - "@trackArtist": { - "description": "Metadata label - artist name" - }, - "trackAlbumArtist": "Artista del álbum", - "@trackAlbumArtist": { - "description": "Metadata label - album artist" - }, - "trackAlbum": "Álbum", - "@trackAlbum": { - "description": "Metadata label - album name" - }, - "trackTrackNumber": "Número de pista", - "@trackTrackNumber": { - "description": "Metadata label - track number" - }, - "trackDiscNumber": "Número de disco", - "@trackDiscNumber": { - "description": "Metadata label - disc number" - }, - "trackDuration": "Duración", - "@trackDuration": { - "description": "Metadata label - track length" - }, - "trackAudioQuality": "Calidad del sonido", - "@trackAudioQuality": { - "description": "Metadata label - audio quality" - }, - "trackReleaseDate": "Fecha de lanzamiento", - "@trackReleaseDate": { - "description": "Metadata label - release date" - }, - "trackGenre": "Género", - "@trackGenre": { - "description": "Metadata label - music genre" - }, - "trackLabel": "Etiqueta", - "@trackLabel": { - "description": "Metadata label - record label" - }, - "trackCopyright": "Derechos de autor", - "@trackCopyright": { - "description": "Metadata label - copyright information" - }, - "trackDownloaded": "Descargado", - "@trackDownloaded": { - "description": "Metadata label - download date" - }, - "trackCopyLyrics": "Copiar letras", - "@trackCopyLyrics": { - "description": "Action - copy lyrics to clipboard" - }, - "trackLyricsNotAvailable": "Letras no disponibles para este tema", - "@trackLyricsNotAvailable": { - "description": "Message when lyrics not found" - }, - "trackLyricsTimeout": "Tiempo de espera agotado. Inténtalo de nuevo más tarde.", - "@trackLyricsTimeout": { - "description": "Message when lyrics request times out" - }, - "trackLyricsLoadFailed": "Error al cargar la letra", - "@trackLyricsLoadFailed": { - "description": "Message when lyrics loading fails" - }, - "trackEmbedLyrics": "Incrustar Letras", - "@trackEmbedLyrics": { - "description": "Action - embed lyrics into audio file" - }, - "trackLyricsEmbedded": "Letra incrustada con éxito", - "@trackLyricsEmbedded": { - "description": "Snackbar - lyrics saved to file" - }, - "trackInstrumental": "Pista intrumental", - "@trackInstrumental": { - "description": "Message when track is instrumental (no lyrics)" - }, - "trackCopiedToClipboard": "Copiado al portapapeles", - "@trackCopiedToClipboard": { - "description": "Snackbar - content copied" - }, - "trackDeleteConfirmTitle": "¿Eliminar del dispositivo?", - "@trackDeleteConfirmTitle": { - "description": "Delete confirmation title" - }, - "trackDeleteConfirmMessage": "Esto eliminará permanentemente el archivo descargado y lo eliminará de tu historial.", - "@trackDeleteConfirmMessage": { - "description": "Delete confirmation message" - }, - "dateToday": "Hoy", - "@dateToday": { - "description": "Relative date - today" - }, - "dateYesterday": "Ayer", - "@dateYesterday": { - "description": "Relative date - yesterday" - }, - "dateDaysAgo": "Hace {count} días", - "@dateDaysAgo": { - "description": "Relative date - days ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "dateWeeksAgo": "{count} semanas antes", - "@dateWeeksAgo": { - "description": "Relative date - weeks ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "dateMonthsAgo": "{count} meses atrás", - "@dateMonthsAgo": { - "description": "Relative date - months ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storeFilterAll": "Todo", - "@storeFilterAll": { - "description": "Store filter - all extensions" - }, - "storeFilterMetadata": "Metadatos", - "@storeFilterMetadata": { - "description": "Store filter - metadata providers" - }, - "storeFilterDownload": "Descargar", - "@storeFilterDownload": { - "description": "Store filter - download providers" - }, - "storeFilterUtility": "Utilidad", - "@storeFilterUtility": { - "description": "Store filter - utility extensions" - }, - "storeFilterLyrics": "Letras", - "@storeFilterLyrics": { - "description": "Store filter - lyrics providers" - }, - "storeFilterIntegration": "Integración", - "@storeFilterIntegration": { - "description": "Store filter - integrations" - }, - "storeClearFilters": "Limpiar filtros", - "@storeClearFilters": { - "description": "Button to clear all filters" - }, - "extensionDefaultProvider": "Por defecto (Deezer/Spotify)", - "@extensionDefaultProvider": { - "description": "Default search provider option" - }, - "extensionDefaultProviderSubtitle": "Usar búsqueda integrada", - "@extensionDefaultProviderSubtitle": { - "description": "Subtitle for default provider" - }, - "extensionAuthor": "Autor/a", - "@extensionAuthor": { - "description": "Extension detail - author" - }, - "extensionId": "ID", - "@extensionId": { - "description": "Extension detail - unique ID" - }, - "extensionError": "Error", - "@extensionError": { - "description": "Extension detail - error message" - }, - "extensionCapabilities": "Recursos", - "@extensionCapabilities": { - "description": "Section header - extension features" - }, - "extensionMetadataProvider": "Proveedor de metadatos", - "@extensionMetadataProvider": { - "description": "Capability - provides metadata" - }, - "extensionDownloadProvider": "Proveedor de descargas", - "@extensionDownloadProvider": { - "description": "Capability - provides downloads" - }, - "extensionLyricsProvider": "Proveedor de letras", - "@extensionLyricsProvider": { - "description": "Capability - provides lyrics" - }, - "extensionUrlHandler": "Gestor de URL", - "@extensionUrlHandler": { - "description": "Capability - handles URLs" - }, - "extensionQualityOptions": "Opciones de calidad", - "@extensionQualityOptions": { - "description": "Capability - quality selection" - }, - "extensionPostProcessingHooks": "Hooks post-procesamiento", - "@extensionPostProcessingHooks": { - "description": "Capability - post-processing" - }, - "extensionPermissions": "Permisos", - "@extensionPermissions": { - "description": "Section header - required permissions" - }, - "extensionSettings": "Ajustes", - "@extensionSettings": { - "description": "Section header - extension settings" - }, - "extensionRemoveButton": "Eliminar extensión", - "@extensionRemoveButton": { - "description": "Button to uninstall extension" - }, - "extensionUpdated": "Actualizado", - "@extensionUpdated": { - "description": "Extension detail - last update" - }, - "extensionMinAppVersion": "Versión Mínima de la aplicación", - "@extensionMinAppVersion": { - "description": "Extension detail - minimum app version" - }, - "extensionCustomTrackMatching": "Coincidencia de pista personalizada", - "@extensionCustomTrackMatching": { - "description": "Capability - custom track matching algorithm" - }, - "extensionPostProcessing": "Post-Procesamiento", - "@extensionPostProcessing": { - "description": "Capability - post-download processing" - }, - "extensionHooksAvailable": "{count} hook(s) disponibles", - "@extensionHooksAvailable": { - "description": "Post-processing hooks count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "extensionPatternsCount": "Patrón(es) {count}", - "@extensionPatternsCount": { - "description": "URL patterns count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "extensionStrategy": "Estrategia: {strategy}", - "@extensionStrategy": { - "description": "Track matching strategy name", - "placeholders": { - "strategy": { - "type": "String" - } - } - }, - "extensionsProviderPrioritySection": "Prioridad del proveedor", - "@extensionsProviderPrioritySection": { - "description": "Section header - provider priority" - }, - "extensionsInstalledSection": "Extensiones instaladas", - "@extensionsInstalledSection": { - "description": "Section header - installed extensions" - }, - "extensionsNoExtensions": "No hay extensiones instaladas", - "@extensionsNoExtensions": { - "description": "Empty state - no extensions" - }, - "extensionsNoExtensionsSubtitle": "Instalar archivos .spotiflac-ext para añadir nuevos proveedores", - "@extensionsNoExtensionsSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsInstallButton": "Instalar extensión", - "@extensionsInstallButton": { - "description": "Button to install extension from file" - }, - "extensionsInfoTip": "Las extensiones pueden añadir nuevos metadatos y proveedores de descargas. Sólo instalar extensiones desde fuentes confiables.", - "@extensionsInfoTip": { - "description": "Security warning about extensions" - }, - "extensionsInstalledSuccess": "Extensión instalada correctamente", - "@extensionsInstalledSuccess": { - "description": "Success message after install" - }, - "extensionsDownloadPriority": "Prioridad de descarga", - "@extensionsDownloadPriority": { - "description": "Setting - download provider order" - }, - "extensionsDownloadPrioritySubtitle": "Establecer orden de servicio de descarga", - "@extensionsDownloadPrioritySubtitle": { - "description": "Subtitle for download priority" - }, - "extensionsNoDownloadProvider": "No hay extensiones con proveedor de descargas", - "@extensionsNoDownloadProvider": { - "description": "Empty state - no download providers" - }, - "extensionsMetadataPriority": "Prioridad de los metadatos", - "@extensionsMetadataPriority": { - "description": "Setting - metadata provider order" - }, - "extensionsMetadataPrioritySubtitle": "Establecer orden de búsqueda y metadatos", - "@extensionsMetadataPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, - "extensionsNoMetadataProvider": "No hay extensiones con el proveedor de metadatos", - "@extensionsNoMetadataProvider": { - "description": "Empty state - no metadata providers" - }, - "extensionsSearchProvider": "Proveedor de búsqueda", - "@extensionsSearchProvider": { - "description": "Setting - search provider selection" - }, - "extensionsNoCustomSearch": "No hay extensiones con búsqueda personalizada", - "@extensionsNoCustomSearch": { - "description": "Empty state - no search providers" - }, - "extensionsSearchProviderDescription": "Elegir qué servicio usar para buscar pistas", - "@extensionsSearchProviderDescription": { - "description": "Search provider setting description" - }, - "extensionsCustomSearch": "Búsqueda personalizada", - "@extensionsCustomSearch": { - "description": "Label for custom search provider" - }, - "extensionsErrorLoading": "Error al cargar la extensión", - "@extensionsErrorLoading": { - "description": "Error message when extension fails to load" - }, - "qualityFlacLossless": "FLAC Lossless", - "@qualityFlacLossless": { - "description": "Quality option - CD quality FLAC" - }, - "qualityFlacLosslessSubtitle": "16-bit / 44.1kHz", - "@qualityFlacLosslessSubtitle": { - "description": "Technical spec for lossless" - }, - "qualityHiResFlac": "Hi-Res FLAC", - "@qualityHiResFlac": { - "description": "Quality option - high resolution FLAC" - }, - "qualityHiResFlacSubtitle": "24 bits/hasta 96kHz", - "@qualityHiResFlacSubtitle": { - "description": "Technical spec for hi-res" - }, - "qualityHiResFlacMax": "Hi-Res FLAC Max", - "@qualityHiResFlacMax": { - "description": "Quality option - maximum resolution FLAC" - }, - "qualityHiResFlacMaxSubtitle": "24 bits / hasta 192kHz", - "@qualityHiResFlacMaxSubtitle": { - "description": "Technical spec for hi-res max" - }, - "qualityNote": "La calidad real depende de la disponibilidad de la pista del servicio", - "@qualityNote": { - "description": "Note about quality availability" - }, - "youtubeQualityNote": "YouTube provides lossy audio only. Not part of lossless fallback.", - "@youtubeQualityNote": { - "description": "Note for YouTube service explaining lossy-only quality" - }, - "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", - "@youtubeOpusBitrateTitle": { - "description": "Title for YouTube Opus bitrate setting" - }, - "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", - "@youtubeMp3BitrateTitle": { - "description": "Title for YouTube MP3 bitrate setting" - }, - "downloadAskBeforeDownload": "Preguntar antes de descargar", - "@downloadAskBeforeDownload": { - "description": "Setting - show quality picker" - }, - "downloadDirectory": "Carpeta de descarga", - "@downloadDirectory": { - "description": "Setting - download folder" - }, - "downloadSeparateSinglesFolder": "Carpeta separada para pistas", - "@downloadSeparateSinglesFolder": { - "description": "Setting - separate folder for singles" - }, - "downloadAlbumFolderStructure": "Estructura de carpeta del álbum", - "@downloadAlbumFolderStructure": { - "description": "Setting - album folder organization" - }, - "downloadUseAlbumArtistForFolders": "Use Album Artist for folders", - "@downloadUseAlbumArtistForFolders": { - "description": "Setting - choose whether artist folders use Album Artist or Track Artist" - }, - "downloadUsePrimaryArtistOnly": "Primary artist only for folders", - "@downloadUsePrimaryArtistOnly": { - "description": "Setting - strip featured artists from folder name" - }, - "downloadUsePrimaryArtistOnlyEnabled": "Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)", - "@downloadUsePrimaryArtistOnlyEnabled": { - "description": "Subtitle when primary artist only is enabled" - }, - "downloadUsePrimaryArtistOnlyDisabled": "Full artist string used for folder name", - "@downloadUsePrimaryArtistOnlyDisabled": { - "description": "Subtitle when primary artist only is disabled" - }, - "downloadSelectQuality": "Seleccionar Calidad", - "@downloadSelectQuality": { - "description": "Dialog title - choose audio quality" - }, - "downloadFrom": "Descargar Desde", - "@downloadFrom": { - "description": "Label - download source" - }, - "appearanceAmoledDark": "AMOLED Oscuro", - "@appearanceAmoledDark": { - "description": "Theme option - pure black" - }, - "appearanceAmoledDarkSubtitle": "Fondo negro puro", - "@appearanceAmoledDarkSubtitle": { - "description": "Subtitle for AMOLED dark" - }, - "queueClearAll": "Eliminar todo", - "@queueClearAll": { - "description": "Button - clear all queue items" - }, - "queueClearAllMessage": "¿Estás seguro de que quieres borrar todas las descargas?", - "@queueClearAllMessage": { - "description": "Clear queue confirmation" - }, - "settingsAutoExportFailed": "Autoexportar descargas fallidas", - "@settingsAutoExportFailed": { - "description": "Setting toggle for auto-export" - }, - "settingsAutoExportFailedSubtitle": "Guardar descargas fallidas en el archivo TXT automáticamente", - "@settingsAutoExportFailedSubtitle": { - "description": "Subtitle for auto-export setting" - }, - "settingsDownloadNetwork": "Red de descarga", - "@settingsDownloadNetwork": { - "description": "Setting for network type preference" - }, - "settingsDownloadNetworkAny": "WiFi + Datos móviles", - "@settingsDownloadNetworkAny": { - "description": "Network option - use any connection" - }, - "settingsDownloadNetworkWifiOnly": "Iniciar solo por Wifi", - "@settingsDownloadNetworkWifiOnly": { - "description": "Network option - only use WiFi" - }, - "settingsDownloadNetworkSubtitle": "Elegir qué red usar para descargas. Cuando se establece en WiFi solamente, las descargas se detendrán en los datos móviles.", - "@settingsDownloadNetworkSubtitle": { - "description": "Subtitle explaining network preference" - }, - "albumFolderArtistAlbum": "Artista / Álbum", - "@albumFolderArtistAlbum": { - "description": "Album folder option" - }, - "albumFolderArtistAlbumSubtitle": "Álbumes/Nombre del Artista/Nombre del Álbum/", - "@albumFolderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "albumFolderArtistYearAlbum": "Artista / [Año] Álbum", - "@albumFolderArtistYearAlbum": { - "description": "Album folder option with year" - }, - "albumFolderArtistYearAlbumSubtitle": "Álbumes/Nombre del Artista /[2005] Nombre del Álbum/", - "@albumFolderArtistYearAlbumSubtitle": { - "description": "Folder structure example" - }, - "albumFolderAlbumOnly": "Sólo álbum", - "@albumFolderAlbumOnly": { - "description": "Album folder option" - }, - "albumFolderAlbumOnlySubtitle": "Álbumes/Nombre del Álbum/", - "@albumFolderAlbumOnlySubtitle": { - "description": "Folder structure example" - }, - "albumFolderYearAlbum": "Álbum [Año]", - "@albumFolderYearAlbum": { - "description": "Album folder option with year" - }, - "albumFolderYearAlbumSubtitle": "Álbumes/[2005] Nombre del Álbum/", - "@albumFolderYearAlbumSubtitle": { - "description": "Folder structure example" - }, - "albumFolderArtistAlbumSingles": "Artista / Álbum + Pistas", - "@albumFolderArtistAlbumSingles": { - "description": "Album folder option with singles inside artist" - }, - "albumFolderArtistAlbumSinglesSubtitle": "Artista/Álbum/ y Artista/pistas/", - "@albumFolderArtistAlbumSinglesSubtitle": { - "description": "Folder structure example" - }, - "downloadedAlbumDeleteSelected": "Borrar Seleccionados", - "@downloadedAlbumDeleteSelected": { - "description": "Button - delete selected tracks" - }, - "downloadedAlbumDeleteMessage": "¿Eliminar {count} {count, plural, one {}=1{pista} other{pistas}} del historial?\n\nEsto también eliminará los archivos del almacenamiento.", - "@downloadedAlbumDeleteMessage": { - "description": "Delete confirmation with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadedAlbumSelectedCount": "{count} seleccionado", - "@downloadedAlbumSelectedCount": { - "description": "Selection count indicator", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadedAlbumAllSelected": "Todas las pistas seleccionadas", - "@downloadedAlbumAllSelected": { - "description": "Status - all items selected" - }, - "downloadedAlbumTapToSelect": "Toca las pistas para seleccionar", - "@downloadedAlbumTapToSelect": { - "description": "Selection hint" - }, - "downloadedAlbumDeleteCount": "¡Eliminar {count} {count, plural, one {}=1{pista} other{pistas}}", - "@downloadedAlbumDeleteCount": { - "description": "Delete button text with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadedAlbumSelectToDelete": "Seleccionar pistas a eliminar", - "@downloadedAlbumSelectToDelete": { - "description": "Placeholder when nothing selected" - }, - "downloadedAlbumDiscHeader": "Disco {discNumber}", - "@downloadedAlbumDiscHeader": { - "description": "Header for disc separator in multi-disc albums", - "placeholders": { - "discNumber": { - "type": "int", - "example": "1" - } - } - }, - "recentTypeArtist": "Artista", - "@recentTypeArtist": { - "description": "Recent access item type - artist" - }, - "recentTypeAlbum": "Álbum", - "@recentTypeAlbum": { - "description": "Recent access item type - album" - }, - "recentTypeSong": "Canción", - "@recentTypeSong": { - "description": "Recent access item type - song/track" - }, - "recentTypePlaylist": "Lista de reproducción", - "@recentTypePlaylist": { - "description": "Recent access item type - playlist" - }, - "recentEmpty": "No recent items yet", - "@recentEmpty": { - "description": "Empty state text for recent access list" - }, - "recentShowAllDownloads": "Show All Downloads", - "@recentShowAllDownloads": { - "description": "Button label to unhide hidden downloads in recent access" - }, - "recentPlaylistInfo": "Lista de reproducción: {name}", - "@recentPlaylistInfo": { - "description": "Snackbar message when tapping playlist in recent access", - "placeholders": { - "name": { - "type": "String", - "description": "Playlist name" - } - } - }, - "discographyDownload": "Descargar Discografía", - "@discographyDownload": { - "description": "Button - download artist discography" - }, - "discographyDownloadAll": "Descargar Todo", - "@discographyDownloadAll": { - "description": "Option - download entire discography" - }, - "discographyDownloadAllSubtitle": "{count} pistas de {albumCount} lanzamientos", - "@discographyDownloadAllSubtitle": { - "description": "Subtitle showing total tracks and albums", - "placeholders": { - "count": { - "type": "int" - }, - "albumCount": { - "type": "int" - } - } - }, - "discographyAlbumsOnly": "Sólo álbumes", - "@discographyAlbumsOnly": { - "description": "Option - download only albums" - }, - "discographyAlbumsOnlySubtitle": "{count} pistas de {albumCount} álbumes", - "@discographyAlbumsOnlySubtitle": { - "description": "Subtitle showing album tracks count", - "placeholders": { - "count": { - "type": "int" - }, - "albumCount": { - "type": "int" - } - } - }, - "discographySinglesOnly": "Solo sencillos & EPs ", - "@discographySinglesOnly": { - "description": "Option - download only singles" - }, - "discographySinglesOnlySubtitle": "{count} tracks from {albumCount} singles", - "@discographySinglesOnlySubtitle": { - "description": "Subtitle showing singles tracks count", - "placeholders": { - "count": { - "type": "int" - }, - "albumCount": { - "type": "int" - } - } - }, - "discographySelectAlbums": "Select Albums...", - "@discographySelectAlbums": { - "description": "Option - manually select albums to download" - }, - "discographySelectAlbumsSubtitle": "Choose specific albums or singles", - "@discographySelectAlbumsSubtitle": { - "description": "Subtitle for select albums option" - }, - "discographyFetchingTracks": "Fetching tracks...", - "@discographyFetchingTracks": { - "description": "Progress - fetching album tracks" - }, - "discographyFetchingAlbum": "Fetching {current} of {total}...", - "@discographyFetchingAlbum": { - "description": "Progress - fetching specific album", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } - }, - "discographySelectedCount": "{count} selected", - "@discographySelectedCount": { - "description": "Selection count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "discographyDownloadSelected": "Download Selected", - "@discographyDownloadSelected": { - "description": "Button - download selected albums" - }, - "discographyAddedToQueue": "Added {count} tracks to queue", - "@discographyAddedToQueue": { - "description": "Snackbar - tracks added from discography", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "discographySkippedDownloaded": "{added} added, {skipped} already downloaded", - "@discographySkippedDownloaded": { - "description": "Snackbar - with skipped tracks count", - "placeholders": { - "added": { - "type": "int" - }, - "skipped": { - "type": "int" - } - } - }, - "discographyNoAlbums": "No albums available", - "@discographyNoAlbums": { - "description": "Error - no albums found for artist" - }, - "discographyFailedToFetch": "Failed to fetch some albums", - "@discographyFailedToFetch": { - "description": "Error - some albums failed to load" - }, - "sectionStorageAccess": "Storage Access", - "@sectionStorageAccess": { - "description": "Section header for storage access settings" - }, - "allFilesAccess": "All Files Access", - "@allFilesAccess": { - "description": "Toggle for MANAGE_EXTERNAL_STORAGE permission" - }, - "allFilesAccessEnabledSubtitle": "Can write to any folder", - "@allFilesAccessEnabledSubtitle": { - "description": "Subtitle when all files access is enabled" - }, - "allFilesAccessDisabledSubtitle": "Limited to media folders only", - "@allFilesAccessDisabledSubtitle": { - "description": "Subtitle when all files access is disabled" - }, - "allFilesAccessDescription": "Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.", - "@allFilesAccessDescription": { - "description": "Description explaining when to enable all files access" - }, - "allFilesAccessDeniedMessage": "Permission was denied. Please enable 'All files access' manually in system settings.", - "@allFilesAccessDeniedMessage": { - "description": "Message when permission is permanently denied" - }, - "allFilesAccessDisabledMessage": "All Files Access disabled. The app will use limited storage access.", - "@allFilesAccessDisabledMessage": { - "description": "Snackbar message when user disables all files access" - }, - "settingsLocalLibrary": "Local Library", - "@settingsLocalLibrary": { - "description": "Settings menu item - local library" - }, - "settingsLocalLibrarySubtitle": "Scan music & detect duplicates", - "@settingsLocalLibrarySubtitle": { - "description": "Subtitle for local library settings" - }, - "settingsCache": "Storage & Cache", - "@settingsCache": { - "description": "Settings menu item - cache management" - }, - "settingsCacheSubtitle": "View size and clear cached data", - "@settingsCacheSubtitle": { - "description": "Subtitle for cache management menu" - }, - "libraryTitle": "Local Library", - "@libraryTitle": { - "description": "Library settings page title" - }, - "libraryScanSettings": "Scan Settings", - "@libraryScanSettings": { - "description": "Section header for scan settings" - }, - "libraryEnableLocalLibrary": "Enable Local Library", - "@libraryEnableLocalLibrary": { - "description": "Toggle to enable library scanning" - }, - "libraryEnableLocalLibrarySubtitle": "Scan and track your existing music", - "@libraryEnableLocalLibrarySubtitle": { - "description": "Subtitle for enable toggle" - }, - "libraryFolder": "Library Folder", - "@libraryFolder": { - "description": "Folder selection setting" - }, - "libraryFolderHint": "Tap to select folder", - "@libraryFolderHint": { - "description": "Placeholder when no folder selected" - }, - "libraryShowDuplicateIndicator": "Show Duplicate Indicator", - "@libraryShowDuplicateIndicator": { - "description": "Toggle for duplicate indicator in search" - }, - "libraryShowDuplicateIndicatorSubtitle": "Show when searching for existing tracks", - "@libraryShowDuplicateIndicatorSubtitle": { - "description": "Subtitle for duplicate indicator toggle" - }, - "libraryActions": "Actions", - "@libraryActions": { - "description": "Section header for library actions" - }, - "libraryScan": "Scan Library", - "@libraryScan": { - "description": "Button to start library scan" - }, - "libraryScanSubtitle": "Scan for audio files", - "@libraryScanSubtitle": { - "description": "Subtitle for scan button" - }, - "libraryScanSelectFolderFirst": "Select a folder first", - "@libraryScanSelectFolderFirst": { - "description": "Message when trying to scan without folder" - }, - "libraryCleanupMissingFiles": "Cleanup Missing Files", - "@libraryCleanupMissingFiles": { - "description": "Button to remove entries for missing files" - }, - "libraryCleanupMissingFilesSubtitle": "Remove entries for files that no longer exist", - "@libraryCleanupMissingFilesSubtitle": { - "description": "Subtitle for cleanup button" - }, - "libraryClear": "Clear Library", - "@libraryClear": { - "description": "Button to clear all library entries" - }, - "libraryClearSubtitle": "Remove all scanned tracks", - "@libraryClearSubtitle": { - "description": "Subtitle for clear button" - }, - "libraryClearConfirmTitle": "Clear Library", - "@libraryClearConfirmTitle": { - "description": "Dialog title for clear confirmation" - }, - "libraryClearConfirmMessage": "This will remove all scanned tracks from your library. Your actual music files will not be deleted.", - "@libraryClearConfirmMessage": { - "description": "Dialog message for clear confirmation" - }, - "libraryAbout": "About Local Library", - "@libraryAbout": { - "description": "Section header for about info" - }, - "libraryAboutDescription": "Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.", - "@libraryAboutDescription": { - "description": "Description of local library feature" - }, - "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", - "@libraryTracksUnit": { - "description": "Unit label for tracks count (without the number itself)", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "libraryLastScanned": "Last scanned: {time}", - "@libraryLastScanned": { - "description": "Last scan time display", - "placeholders": { - "time": { - "type": "String" - } - } - }, - "libraryLastScannedNever": "Never", - "@libraryLastScannedNever": { - "description": "Shown when library has never been scanned" - }, - "libraryScanning": "Scanning...", - "@libraryScanning": { - "description": "Status during scan" - }, - "libraryScanProgress": "{progress}% of {total} files", - "@libraryScanProgress": { - "description": "Scan progress display", - "placeholders": { - "progress": { - "type": "String" - }, - "total": { - "type": "int" - } - } - }, - "libraryInLibrary": "In Library", - "@libraryInLibrary": { - "description": "Badge shown on tracks that exist in local library" - }, - "libraryRemovedMissingFiles": "Removed {count} missing files from library", - "@libraryRemovedMissingFiles": { - "description": "Snackbar after cleanup", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "libraryCleared": "Library cleared", - "@libraryCleared": { - "description": "Snackbar after clearing library" - }, - "libraryStorageAccessRequired": "Storage Access Required", - "@libraryStorageAccessRequired": { - "description": "Dialog title for storage permission" - }, - "libraryStorageAccessMessage": "SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.", - "@libraryStorageAccessMessage": { - "description": "Dialog message for storage permission" - }, - "libraryFolderNotExist": "Selected folder does not exist", - "@libraryFolderNotExist": { - "description": "Error when folder doesn't exist" - }, - "librarySourceDownloaded": "Downloaded", - "@librarySourceDownloaded": { - "description": "Badge for tracks downloaded via SpotiFLAC" - }, - "librarySourceLocal": "Local", - "@librarySourceLocal": { - "description": "Badge for tracks from local library scan" - }, - "libraryFilterAll": "All", - "@libraryFilterAll": { - "description": "Filter chip - show all library items" - }, - "libraryFilterDownloaded": "Downloaded", - "@libraryFilterDownloaded": { - "description": "Filter chip - show only downloaded items" - }, - "libraryFilterLocal": "Local", - "@libraryFilterLocal": { - "description": "Filter chip - show only local library items" - }, - "libraryFilterTitle": "Filters", - "@libraryFilterTitle": { - "description": "Filter bottom sheet title" - }, - "libraryFilterReset": "Reset", - "@libraryFilterReset": { - "description": "Reset all filters button" - }, - "libraryFilterApply": "Apply", - "@libraryFilterApply": { - "description": "Apply filters button" - }, - "libraryFilterSource": "Source", - "@libraryFilterSource": { - "description": "Filter section - source type" - }, - "libraryFilterQuality": "Quality", - "@libraryFilterQuality": { - "description": "Filter section - audio quality" - }, - "libraryFilterQualityHiRes": "Hi-Res (24bit)", - "@libraryFilterQualityHiRes": { - "description": "Filter option - high resolution audio" - }, - "libraryFilterQualityCD": "CD (16bit)", - "@libraryFilterQualityCD": { - "description": "Filter option - CD quality audio" - }, - "libraryFilterQualityLossy": "Lossy", - "@libraryFilterQualityLossy": { - "description": "Filter option - lossy compressed audio" - }, - "libraryFilterFormat": "Format", - "@libraryFilterFormat": { - "description": "Filter section - file format" - }, - "libraryFilterSort": "Sort", - "@libraryFilterSort": { - "description": "Filter section - sort order" - }, - "libraryFilterSortLatest": "Latest", - "@libraryFilterSortLatest": { - "description": "Sort option - newest first" - }, - "libraryFilterSortOldest": "Oldest", - "@libraryFilterSortOldest": { - "description": "Sort option - oldest first" - }, - "timeJustNow": "Just now", - "@timeJustNow": { - "description": "Relative time - less than a minute ago" - }, - "timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}", - "@timeMinutesAgo": { - "description": "Relative time - minutes ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}", - "@timeHoursAgo": { - "description": "Relative time - hours ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", - "@tutorialWelcomeTitle": { - "description": "Tutorial welcome page title" - }, - "tutorialWelcomeDesc": "Let's learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.", - "@tutorialWelcomeDesc": { - "description": "Tutorial welcome page description" - }, - "tutorialWelcomeTip1": "Download music from Spotify, Deezer, or paste any supported URL", - "@tutorialWelcomeTip1": { - "description": "Tutorial welcome tip 1" - }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", - "@tutorialWelcomeTip2": { - "description": "Tutorial welcome tip 2" - }, - "tutorialWelcomeTip3": "Automatic metadata, cover art, and lyrics embedding", - "@tutorialWelcomeTip3": { - "description": "Tutorial welcome tip 3" - }, - "tutorialSearchTitle": "Finding Music", - "@tutorialSearchTitle": { - "description": "Tutorial search page title" - }, - "tutorialSearchDesc": "There are two easy ways to find music you want to download.", - "@tutorialSearchDesc": { - "description": "Tutorial search page description" - }, - "tutorialDownloadTitle": "Downloading Music", - "@tutorialDownloadTitle": { - "description": "Tutorial download page title" - }, - "tutorialDownloadDesc": "Downloading music is simple and fast. Here's how it works.", - "@tutorialDownloadDesc": { - "description": "Tutorial download page description" - }, - "tutorialLibraryTitle": "Your Library", - "@tutorialLibraryTitle": { - "description": "Tutorial library page title" - }, - "tutorialLibraryDesc": "All your downloaded music is organized in the Library tab.", - "@tutorialLibraryDesc": { - "description": "Tutorial library page description" - }, - "tutorialLibraryTip1": "View download progress and queue in the Library tab", - "@tutorialLibraryTip1": { - "description": "Tutorial library tip 1" - }, - "tutorialLibraryTip2": "Tap any track to play it with your music player", - "@tutorialLibraryTip2": { - "description": "Tutorial library tip 2" - }, - "tutorialLibraryTip3": "Switch between list and grid view for better browsing", - "@tutorialLibraryTip3": { - "description": "Tutorial library tip 3" - }, - "tutorialExtensionsTitle": "Extensions", - "@tutorialExtensionsTitle": { - "description": "Tutorial extensions page title" - }, - "tutorialExtensionsDesc": "Extend the app's capabilities with community extensions.", - "@tutorialExtensionsDesc": { - "description": "Tutorial extensions page description" - }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", - "@tutorialExtensionsTip1": { - "description": "Tutorial extensions tip 1" - }, - "tutorialExtensionsTip2": "Add new download providers or search sources", - "@tutorialExtensionsTip2": { - "description": "Tutorial extensions tip 2" - }, - "tutorialExtensionsTip3": "Get lyrics, enhanced metadata, and more features", - "@tutorialExtensionsTip3": { - "description": "Tutorial extensions tip 3" - }, - "tutorialSettingsTitle": "Customize Your Experience", - "@tutorialSettingsTitle": { - "description": "Tutorial settings page title" - }, - "tutorialSettingsDesc": "Personalize the app in Settings to match your preferences.", - "@tutorialSettingsDesc": { - "description": "Tutorial settings page description" - }, - "tutorialSettingsTip1": "Change download location and folder organization", - "@tutorialSettingsTip1": { - "description": "Tutorial settings tip 1" - }, - "tutorialSettingsTip2": "Set default audio quality and format preferences", - "@tutorialSettingsTip2": { - "description": "Tutorial settings tip 2" - }, - "tutorialSettingsTip3": "Customize app theme and appearance", - "@tutorialSettingsTip3": { - "description": "Tutorial settings tip 3" - }, - "tutorialReadyMessage": "You're all set! Start downloading your favorite music now.", - "@tutorialReadyMessage": { - "description": "Tutorial completion message" - }, - "libraryForceFullScan": "Force Full Scan", - "@libraryForceFullScan": { - "description": "Button to force a complete rescan of library" - }, - "libraryForceFullScanSubtitle": "Rescan all files, ignoring cache", - "@libraryForceFullScanSubtitle": { - "description": "Subtitle for force full scan button" - }, - "cleanupOrphanedDownloads": "Cleanup Orphaned Downloads", - "@cleanupOrphanedDownloads": { - "description": "Button to remove history entries for deleted files" - }, - "cleanupOrphanedDownloadsSubtitle": "Remove history entries for files that no longer exist", - "@cleanupOrphanedDownloadsSubtitle": { - "description": "Subtitle for orphaned cleanup button" - }, - "cleanupOrphanedDownloadsResult": "Removed {count} orphaned entries from history", - "@cleanupOrphanedDownloadsResult": { - "description": "Snackbar after orphan cleanup", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "cleanupOrphanedDownloadsNone": "No orphaned entries found", - "@cleanupOrphanedDownloadsNone": { - "description": "Snackbar when no orphans found" - }, - "cacheTitle": "Storage & Cache", - "@cacheTitle": { - "description": "Cache management page title" - }, - "cacheSummaryTitle": "Cache overview", - "@cacheSummaryTitle": { - "description": "Heading for cache summary card" - }, - "cacheSummarySubtitle": "Clearing cache will not remove downloaded music files.", - "@cacheSummarySubtitle": { - "description": "Helper text for cache summary card" - }, - "cacheEstimatedTotal": "Estimated cache usage: {size}", - "@cacheEstimatedTotal": { - "description": "Total cache size shown in summary", - "placeholders": { - "size": { - "type": "String" - } - } - }, - "cacheSectionStorage": "Cached Data", - "@cacheSectionStorage": { - "description": "Section header for cache entries" - }, - "cacheSectionMaintenance": "Maintenance", - "@cacheSectionMaintenance": { - "description": "Section header for cleanup actions" - }, - "cacheAppDirectory": "App cache directory", - "@cacheAppDirectory": { - "description": "Cache item title for app cache directory" - }, - "cacheAppDirectoryDesc": "HTTP responses, WebView data, and other temporary app data.", - "@cacheAppDirectoryDesc": { - "description": "Description of what app cache directory contains" - }, - "cacheTempDirectory": "Temporary directory", - "@cacheTempDirectory": { - "description": "Cache item title for temporary files directory" - }, - "cacheTempDirectoryDesc": "Temporary files from downloads and audio conversion.", - "@cacheTempDirectoryDesc": { - "description": "Description of what temporary directory contains" - }, - "cacheCoverImage": "Cover image cache", - "@cacheCoverImage": { - "description": "Cache item title for persistent cover images" - }, - "cacheCoverImageDesc": "Downloaded album and track cover art. Will re-download when viewed.", - "@cacheCoverImageDesc": { - "description": "Description of what cover image cache contains" - }, - "cacheLibraryCover": "Library cover cache", - "@cacheLibraryCover": { - "description": "Cache item title for local library cover art images" - }, - "cacheLibraryCoverDesc": "Cover art extracted from local music files. Will re-extract on next scan.", - "@cacheLibraryCoverDesc": { - "description": "Description of what library cover cache contains" - }, - "cacheExploreFeed": "Explore feed cache", - "@cacheExploreFeed": { - "description": "Cache item title for explore home feed cache" - }, - "cacheExploreFeedDesc": "Explore tab content (new releases, trending). Will refresh on next visit.", - "@cacheExploreFeedDesc": { - "description": "Description of what explore feed cache contains" - }, - "cacheTrackLookup": "Track lookup cache", - "@cacheTrackLookup": { - "description": "Cache item title for track ID lookup cache" - }, - "cacheTrackLookupDesc": "Spotify/Deezer track ID lookups. Clearing may slow next few searches.", - "@cacheTrackLookupDesc": { - "description": "Description of what track lookup cache contains" - }, - "cacheCleanupUnusedDesc": "Remove orphaned download history and library entries for missing files.", - "@cacheCleanupUnusedDesc": { - "description": "Description of what cleanup unused data does" - }, - "cacheNoData": "No cached data", - "@cacheNoData": { - "description": "Label when cache category has no data" - }, - "cacheSizeWithFiles": "{size} in {count} files", - "@cacheSizeWithFiles": { - "description": "Cache size and file count", - "placeholders": { - "size": { - "type": "String" - }, - "count": { - "type": "int" - } - } - }, - "cacheSizeOnly": "{size}", - "@cacheSizeOnly": { - "description": "Cache size only", - "placeholders": { - "size": { - "type": "String" - } - } - }, - "cacheEntries": "{count} entries", - "@cacheEntries": { - "description": "Track cache entry count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "cacheClearSuccess": "Cleared: {target}", - "@cacheClearSuccess": { - "description": "Snackbar after clearing selected cache", - "placeholders": { - "target": { - "type": "String" - } - } - }, - "cacheClearConfirmTitle": "Clear cache?", - "@cacheClearConfirmTitle": { - "description": "Dialog title before clearing one cache category" - }, - "cacheClearConfirmMessage": "This will clear cached data for {target}. Downloaded music files will not be deleted.", - "@cacheClearConfirmMessage": { - "description": "Dialog message before clearing selected cache", - "placeholders": { - "target": { - "type": "String" - } - } - }, - "cacheClearAllConfirmTitle": "Clear all cache?", - "@cacheClearAllConfirmTitle": { - "description": "Dialog title before clearing all caches" - }, - "cacheClearAllConfirmMessage": "This will clear all cache categories on this page. Downloaded music files will not be deleted.", - "@cacheClearAllConfirmMessage": { - "description": "Dialog message before clearing all caches" - }, - "cacheClearAll": "Clear all cache", - "@cacheClearAll": { - "description": "Button label to clear all caches" - }, - "cacheCleanupUnused": "Cleanup unused data", - "@cacheCleanupUnused": { - "description": "Action title for cleaning unused entries" - }, - "cacheCleanupUnusedSubtitle": "Remove orphaned download history and missing library entries", - "@cacheCleanupUnusedSubtitle": { - "description": "Subtitle for cleanup unused data action" - }, - "cacheCleanupResult": "Cleanup completed: {downloadCount} orphaned downloads, {libraryCount} missing library entries", - "@cacheCleanupResult": { - "description": "Snackbar after unused data cleanup", - "placeholders": { - "downloadCount": { - "type": "int" - }, - "libraryCount": { - "type": "int" - } - } - }, - "cacheRefreshStats": "Refresh stats", - "@cacheRefreshStats": { - "description": "Button label to refresh cache statistics" - }, - "trackSaveCoverArt": "Save Cover Art", - "@trackSaveCoverArt": { - "description": "Menu action - save album cover art as file" - }, - "trackSaveCoverArtSubtitle": "Save album art as .jpg file", - "@trackSaveCoverArtSubtitle": { - "description": "Subtitle for save cover art action" - }, - "trackSaveLyrics": "Save Lyrics (.lrc)", - "@trackSaveLyrics": { - "description": "Menu action - save lyrics as .lrc file" - }, - "trackSaveLyricsSubtitle": "Fetch and save lyrics as .lrc file", - "@trackSaveLyricsSubtitle": { - "description": "Subtitle for save lyrics action" - }, - "trackSaveLyricsProgress": "Saving lyrics...", - "@trackSaveLyricsProgress": { - "description": "Snackbar while saving lyrics to file" - }, - "trackReEnrich": "Re-enrich", - "@trackReEnrich": { - "description": "Menu action - re-embed metadata into audio file" - }, - "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", - "@trackReEnrichOnlineSubtitle": { - "description": "Subtitle for re-enrich metadata action for local items" - }, - "trackEditMetadata": "Edit Metadata", - "@trackEditMetadata": { - "description": "Menu action - edit embedded metadata" - }, - "trackCoverSaved": "Cover art saved to {fileName}", - "@trackCoverSaved": { - "description": "Snackbar after cover art saved", - "placeholders": { - "fileName": { - "type": "String" - } - } - }, - "trackCoverNoSource": "No cover art source available", - "@trackCoverNoSource": { - "description": "Snackbar when no cover art URL or embedded cover" - }, - "trackLyricsSaved": "Lyrics saved to {fileName}", - "@trackLyricsSaved": { - "description": "Snackbar after lyrics saved", - "placeholders": { - "fileName": { - "type": "String" - } - } - }, - "trackReEnrichProgress": "Re-enriching metadata...", - "@trackReEnrichProgress": { - "description": "Snackbar while re-enriching metadata" - }, - "trackReEnrichSearching": "Searching metadata online...", - "@trackReEnrichSearching": { - "description": "Snackbar while searching metadata from internet for local items" - }, - "trackReEnrichSuccess": "Metadata re-enriched successfully", - "@trackReEnrichSuccess": { - "description": "Snackbar after successful re-enrichment" - }, - "trackReEnrichFfmpegFailed": "FFmpeg metadata embed failed", - "@trackReEnrichFfmpegFailed": { - "description": "Snackbar when FFmpeg embed fails for MP3/Opus" - }, - "trackSaveFailed": "Failed: {error}", - "@trackSaveFailed": { - "description": "Snackbar when save operation fails", - "placeholders": { - "error": { - "type": "String" - } - } - }, - "trackConvertFormat": "Convert Format", - "@trackConvertFormat": { - "description": "Menu item - convert audio format" - }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", - "@trackConvertFormatSubtitle": { - "description": "Subtitle for convert format menu item" - }, - "trackConvertTitle": "Convert Audio", - "@trackConvertTitle": { - "description": "Title of convert bottom sheet" - }, - "trackConvertTargetFormat": "Target Format", - "@trackConvertTargetFormat": { - "description": "Label for format selection" - }, - "trackConvertBitrate": "Bitrate", - "@trackConvertBitrate": { - "description": "Label for bitrate selection" - }, - "trackConvertConfirmTitle": "Confirm Conversion", - "@trackConvertConfirmTitle": { - "description": "Confirmation dialog title" - }, - "trackConvertConfirmMessage": "Convert from {sourceFormat} to {targetFormat} at {bitrate}?\n\nThe original file will be deleted after conversion.", - "@trackConvertConfirmMessage": { - "description": "Confirmation dialog message", - "placeholders": { - "sourceFormat": { - "type": "String" - }, - "targetFormat": { - "type": "String" - }, - "bitrate": { - "type": "String" - } - } - }, - "trackConvertConverting": "Converting audio...", - "@trackConvertConverting": { - "description": "Snackbar while converting" - }, - "trackConvertSuccess": "Converted to {format} successfully", - "@trackConvertSuccess": { - "description": "Snackbar after successful conversion", - "placeholders": { - "format": { - "type": "String" - } - } - }, - "trackConvertFailed": "Conversion failed", - "@trackConvertFailed": { - "description": "Snackbar when conversion fails" - }, - "actionCreate": "Create", - "@actionCreate": { - "description": "Generic action button - create" - }, - "collectionFoldersTitle": "My folders", - "@collectionFoldersTitle": { - "description": "Library section title for custom folders" - }, - "collectionWishlist": "Wishlist", - "@collectionWishlist": { - "description": "Custom folder for saved tracks to download later" - }, - "collectionLoved": "Loved", - "@collectionLoved": { - "description": "Custom folder for favorite tracks" - }, - "collectionPlaylists": "Playlists", - "@collectionPlaylists": { - "description": "Custom user playlists folder" - }, - "collectionPlaylist": "Playlist", - "@collectionPlaylist": { - "description": "Single playlist label" - }, - "collectionAddToPlaylist": "Add to playlist", - "@collectionAddToPlaylist": { - "description": "Action to add a track to user playlist" - }, - "collectionCreatePlaylist": "Create playlist", - "@collectionCreatePlaylist": { - "description": "Action to create a new playlist" - }, - "collectionNoPlaylistsYet": "No playlists yet", - "@collectionNoPlaylistsYet": { - "description": "Empty state title when user has no playlists" - }, - "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", - "@collectionNoPlaylistsSubtitle": { - "description": "Empty state subtitle when user has no playlists" - }, - "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", - "@collectionPlaylistTracks": { - "description": "Track count label for custom playlists", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "collectionAddedToPlaylist": "Added to \"{playlistName}\"", - "@collectionAddedToPlaylist": { - "description": "Snackbar after adding track to playlist", - "placeholders": { - "playlistName": { - "type": "String" - } - } - }, - "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", - "@collectionAlreadyInPlaylist": { - "description": "Snackbar when track already exists in playlist", - "placeholders": { - "playlistName": { - "type": "String" - } - } - }, - "collectionPlaylistCreated": "Playlist created", - "@collectionPlaylistCreated": { - "description": "Snackbar after creating playlist" - }, - "collectionPlaylistNameHint": "Playlist name", - "@collectionPlaylistNameHint": { - "description": "Hint text for playlist name input" - }, - "collectionPlaylistNameRequired": "Playlist name is required", - "@collectionPlaylistNameRequired": { - "description": "Validation error for empty playlist name" - }, - "collectionRenamePlaylist": "Rename playlist", - "@collectionRenamePlaylist": { - "description": "Action to rename playlist" - }, - "collectionDeletePlaylist": "Delete playlist", - "@collectionDeletePlaylist": { - "description": "Action to delete playlist" - }, - "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", - "@collectionDeletePlaylistMessage": { - "description": "Confirmation message for deleting playlist", - "placeholders": { - "playlistName": { - "type": "String" - } - } - }, - "collectionPlaylistDeleted": "Playlist deleted", - "@collectionPlaylistDeleted": { - "description": "Snackbar after deleting playlist" - }, - "collectionPlaylistRenamed": "Playlist renamed", - "@collectionPlaylistRenamed": { - "description": "Snackbar after renaming playlist" - }, - "collectionWishlistEmptyTitle": "Wishlist is empty", - "@collectionWishlistEmptyTitle": { - "description": "Wishlist empty state title" - }, - "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", - "@collectionWishlistEmptySubtitle": { - "description": "Wishlist empty state subtitle" - }, - "collectionLovedEmptyTitle": "Loved folder is empty", - "@collectionLovedEmptyTitle": { - "description": "Loved empty state title" - }, - "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", - "@collectionLovedEmptySubtitle": { - "description": "Loved empty state subtitle" - }, - "collectionPlaylistEmptyTitle": "Playlist is empty", - "@collectionPlaylistEmptyTitle": { - "description": "Playlist empty state title" - }, - "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", - "@collectionPlaylistEmptySubtitle": { - "description": "Playlist empty state subtitle" - }, - "collectionRemoveFromPlaylist": "Remove from playlist", - "@collectionRemoveFromPlaylist": { - "description": "Tooltip for removing track from playlist" - }, - "collectionRemoveFromFolder": "Remove from folder", - "@collectionRemoveFromFolder": { - "description": "Tooltip for removing track from wishlist/loved folder" - }, - "collectionRemoved": "\"{trackName}\" removed", - "@collectionRemoved": { - "description": "Snackbar after removing a track from a collection", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "collectionAddedToLoved": "\"{trackName}\" added to Loved", - "@collectionAddedToLoved": { - "description": "Snackbar after adding track to loved folder", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", - "@collectionRemovedFromLoved": { - "description": "Snackbar after removing track from loved folder", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", - "@collectionAddedToWishlist": { - "description": "Snackbar after adding track to wishlist", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", - "@collectionRemovedFromWishlist": { - "description": "Snackbar after removing track from wishlist", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "trackOptionAddToLoved": "Add to Loved", - "@trackOptionAddToLoved": { - "description": "Bottom sheet action label - add track to loved folder" - }, - "trackOptionRemoveFromLoved": "Remove from Loved", - "@trackOptionRemoveFromLoved": { - "description": "Bottom sheet action label - remove track from loved folder" - }, - "trackOptionAddToWishlist": "Add to Wishlist", - "@trackOptionAddToWishlist": { - "description": "Bottom sheet action label - add track to wishlist" - }, - "trackOptionRemoveFromWishlist": "Remove from Wishlist", - "@trackOptionRemoveFromWishlist": { - "description": "Bottom sheet action label - remove track from wishlist" - }, - "collectionPlaylistChangeCover": "Change cover image", - "@collectionPlaylistChangeCover": { - "description": "Bottom sheet action to pick a custom cover image for a playlist" - }, - "collectionPlaylistRemoveCover": "Remove cover image", - "@collectionPlaylistRemoveCover": { - "description": "Bottom sheet action to remove custom cover image from a playlist" - }, - "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", - "@selectionShareCount": { - "description": "Share button text with count in selection mode", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "selectionShareNoFiles": "No shareable files found", - "@selectionShareNoFiles": { - "description": "Snackbar when no selected files exist on disk" - }, - "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", - "@selectionConvertCount": { - "description": "Convert button text with count in selection mode", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "selectionConvertNoConvertible": "No convertible tracks selected", - "@selectionConvertNoConvertible": { - "description": "Snackbar when no selected tracks support conversion" - }, - "selectionBatchConvertConfirmTitle": "Batch Convert", - "@selectionBatchConvertConfirmTitle": { - "description": "Confirmation dialog title for batch conversion" - }, - "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", - "@selectionBatchConvertConfirmMessage": { - "description": "Confirmation dialog message for batch conversion", - "placeholders": { - "count": { - "type": "int" - }, - "format": { - "type": "String" - }, - "bitrate": { - "type": "String" - } - } - }, - "selectionBatchConvertProgress": "Converting {current} of {total}...", - "@selectionBatchConvertProgress": { - "description": "Snackbar during batch conversion progress", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } - }, - "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", - "@selectionBatchConvertSuccess": { - "description": "Snackbar after batch conversion completes", - "placeholders": { - "success": { - "type": "int" - }, - "total": { - "type": "int" - }, - "format": { - "type": "String" - } - } - }, - "downloadedAlbumDownloadedCount": "{count} descargado", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - } -} \ No newline at end of file diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index dcd14626..de9d90d0 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -897,6 +897,18 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, + "errorUrlNotRecognized": "Link tidak dikenali", + "@errorUrlNotRecognized": { + "description": "Error title - URL not handled by any extension or service" + }, + "errorUrlNotRecognizedMessage": "Link ini tidak didukung. Pastikan URL benar dan ekstensi yang kompatibel sudah terpasang.", + "@errorUrlNotRecognizedMessage": { + "description": "Error message - URL not recognized explanation" + }, + "errorUrlFetchFailed": "Gagal memuat konten dari link ini. Silakan coba lagi.", + "@errorUrlFetchFailed": { + "description": "Error message - generic URL fetch failure" + }, "errorMissingExtensionSource": "Tidak dapat memuat {item}: sumber ekstensi tidak ada", "@errorMissingExtensionSource": { "description": "Error - extension source not available", diff --git a/lib/l10n/arb/app_pt-PT.arb b/lib/l10n/arb/app_pt-PT.arb deleted file mode 100644 index 7d06cfa8..00000000 --- a/lib/l10n/arb/app_pt-PT.arb +++ /dev/null @@ -1,3105 +0,0 @@ -{ - "@@locale": "pt-PT", - "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", - "@appName": { - "description": "App name - DO NOT TRANSLATE" - }, - "navHome": "Início", - "@navHome": { - "description": "Bottom navigation - Home tab" - }, - "navLibrary": "Library", - "@navLibrary": { - "description": "Bottom navigation - Library tab" - }, - "navSettings": "Configurações", - "@navSettings": { - "description": "Bottom navigation - Settings tab" - }, - "navStore": "Loja", - "@navStore": { - "description": "Bottom navigation - Extension store tab" - }, - "homeTitle": "Início", - "@homeTitle": { - "description": "Home screen title" - }, - "homeSubtitle": "Cole um link do Spotify ou procure por nome", - "@homeSubtitle": { - "description": "Subtitle shown below search box" - }, - "homeSupports": "Suporte: Faixas, Álbuns, Playlists, URLs de Artista", - "@homeSupports": { - "description": "Info text about supported URL types" - }, - "homeRecent": "Recentes", - "@homeRecent": { - "description": "Section header for recent searches" - }, - "historyFilterAll": "Tudo", - "@historyFilterAll": { - "description": "Filter chip - show all items" - }, - "historyFilterAlbums": "Álbuns", - "@historyFilterAlbums": { - "description": "Filter chip - show albums only" - }, - "historyFilterSingles": "Singles", - "@historyFilterSingles": { - "description": "Filter chip - show singles only" - }, - "historySearchHint": "Pesquisar histórico...", - "@historySearchHint": { - "description": "Search bar placeholder in history" - }, - "settingsTitle": "Configurações", - "@settingsTitle": { - "description": "Settings screen title" - }, - "settingsDownload": "Download", - "@settingsDownload": { - "description": "Settings section - download options" - }, - "settingsAppearance": "Aparência", - "@settingsAppearance": { - "description": "Settings section - visual customization" - }, - "settingsOptions": "Opções", - "@settingsOptions": { - "description": "Settings section - app options" - }, - "settingsExtensions": "Extensões", - "@settingsExtensions": { - "description": "Settings section - extension management" - }, - "settingsAbout": "Sobre", - "@settingsAbout": { - "description": "Settings section - app info" - }, - "downloadTitle": "Download", - "@downloadTitle": { - "description": "Download settings page title" - }, - "downloadAskQualitySubtitle": "Mostrar seletor de qualidade para cada download", - "@downloadAskQualitySubtitle": { - "description": "Subtitle for ask quality toggle" - }, - "downloadFilenameFormat": "Formato do Nome do Arquivo", - "@downloadFilenameFormat": { - "description": "Setting for output filename pattern" - }, - "downloadFolderOrganization": "Organização de Pastas", - "@downloadFolderOrganization": { - "description": "Setting for folder structure" - }, - "appearanceTitle": "Aparência", - "@appearanceTitle": { - "description": "Appearance settings page title" - }, - "appearanceThemeSystem": "Sistema", - "@appearanceThemeSystem": { - "description": "Follow system theme" - }, - "appearanceThemeLight": "Claro", - "@appearanceThemeLight": { - "description": "Light theme" - }, - "appearanceThemeDark": "Escuro", - "@appearanceThemeDark": { - "description": "Dark theme" - }, - "appearanceDynamicColor": "Cores Dinâmicas", - "@appearanceDynamicColor": { - "description": "Material You dynamic colors" - }, - "appearanceDynamicColorSubtitle": "Usar cores do seu papel de parede", - "@appearanceDynamicColorSubtitle": { - "description": "Subtitle for dynamic color" - }, - "appearanceHistoryView": "Visualização do Histórico", - "@appearanceHistoryView": { - "description": "Layout style for history" - }, - "appearanceHistoryViewList": "Lista", - "@appearanceHistoryViewList": { - "description": "List layout option" - }, - "appearanceHistoryViewGrid": "Grade", - "@appearanceHistoryViewGrid": { - "description": "Grid layout option" - }, - "optionsTitle": "Opções", - "@optionsTitle": { - "description": "Options settings page title" - }, - "optionsPrimaryProvider": "Provedor Primário", - "@optionsPrimaryProvider": { - "description": "Main search provider setting" - }, - "optionsPrimaryProviderSubtitle": "Serviço usado ao pesquisar por nome da faixa.", - "@optionsPrimaryProviderSubtitle": { - "description": "Subtitle for primary provider" - }, - "optionsUsingExtension": "Usando a extensão: {extensionName}", - "@optionsUsingExtension": { - "description": "Shows active extension name", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "optionsSwitchBack": "Toque no Deezer ou Spotify para alternar de volta da extensão", - "@optionsSwitchBack": { - "description": "Hint to switch back to built-in providers" - }, - "optionsAutoFallback": "Fallback Automático", - "@optionsAutoFallback": { - "description": "Auto-retry with other services" - }, - "optionsAutoFallbackSubtitle": "Tentar outros serviços se o download falhar", - "@optionsAutoFallbackSubtitle": { - "description": "Subtitle for auto fallback" - }, - "optionsUseExtensionProviders": "Usar Provedores de Extensão", - "@optionsUseExtensionProviders": { - "description": "Enable extension download providers" - }, - "optionsUseExtensionProvidersOn": "Extensões serão tentadas primeiro", - "@optionsUseExtensionProvidersOn": { - "description": "Status when extension providers enabled" - }, - "optionsUseExtensionProvidersOff": "Usando apenas provedores integrados", - "@optionsUseExtensionProvidersOff": { - "description": "Status when extension providers disabled" - }, - "optionsEmbedLyrics": "Incorporar Letras", - "@optionsEmbedLyrics": { - "description": "Embed lyrics in audio files" - }, - "optionsEmbedLyricsSubtitle": "Incorporar letras sincronizadas aos arquivos FLAC", - "@optionsEmbedLyricsSubtitle": { - "description": "Subtitle for embed lyrics" - }, - "optionsMaxQualityCover": "Capa de Qualidade Máxima", - "@optionsMaxQualityCover": { - "description": "Download highest quality album art" - }, - "optionsMaxQualityCoverSubtitle": "Baixar capa do álbum com a mais alta resolução", - "@optionsMaxQualityCoverSubtitle": { - "description": "Subtitle for max quality cover" - }, - "optionsConcurrentDownloads": "Downloads Simultâneos", - "@optionsConcurrentDownloads": { - "description": "Number of parallel downloads" - }, - "optionsConcurrentSequential": "Sequencial (1 por vez)", - "@optionsConcurrentSequential": { - "description": "Download one at a time" - }, - "optionsConcurrentParallel": "{count} downloads paralelos", - "@optionsConcurrentParallel": { - "description": "Multiple parallel downloads", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "optionsConcurrentWarning": "Downloads simultâneos podem causar um limite da taxa (ratelimit)", - "@optionsConcurrentWarning": { - "description": "Warning about rate limits" - }, - "optionsExtensionStore": "Loja de Extensões", - "@optionsExtensionStore": { - "description": "Show/hide store tab" - }, - "optionsExtensionStoreSubtitle": "Mostrar aba da Loja na navegação", - "@optionsExtensionStoreSubtitle": { - "description": "Subtitle for extension store toggle" - }, - "optionsCheckUpdates": "Procurar Atualizações", - "@optionsCheckUpdates": { - "description": "Auto update check toggle" - }, - "optionsCheckUpdatesSubtitle": "Notificar quando uma nova versão estiver disponível", - "@optionsCheckUpdatesSubtitle": { - "description": "Subtitle for update check" - }, - "optionsUpdateChannel": "Canal de Atualização", - "@optionsUpdateChannel": { - "description": "Stable vs preview releases" - }, - "optionsUpdateChannelStable": "Somente versões estáveis", - "@optionsUpdateChannelStable": { - "description": "Only stable updates" - }, - "optionsUpdateChannelPreview": "Obter versões de prévia", - "@optionsUpdateChannelPreview": { - "description": "Include beta/preview updates" - }, - "optionsUpdateChannelWarning": "A prévia pode conter erros ou recursos incompletos", - "@optionsUpdateChannelWarning": { - "description": "Warning about preview channel" - }, - "optionsClearHistory": "Limpar Histórico de Download", - "@optionsClearHistory": { - "description": "Delete all download history" - }, - "optionsClearHistorySubtitle": "Remover todas as faixas baixadas do histórico", - "@optionsClearHistorySubtitle": { - "description": "Subtitle for clear history" - }, - "optionsDetailedLogging": "Registro detalhado", - "@optionsDetailedLogging": { - "description": "Enable verbose logs for debugging" - }, - "optionsDetailedLoggingOn": "Registros detalhados estão sendo gravados", - "@optionsDetailedLoggingOn": { - "description": "Status when logging enabled" - }, - "optionsDetailedLoggingOff": "Habilitar para relatórios de erros", - "@optionsDetailedLoggingOff": { - "description": "Status when logging disabled" - }, - "optionsSpotifyCredentials": "Credenciais do Spotify", - "@optionsSpotifyCredentials": { - "description": "Spotify API credentials setting" - }, - "optionsSpotifyCredentialsConfigured": "Client ID: {clientId}...", - "@optionsSpotifyCredentialsConfigured": { - "description": "Shows configured client ID preview", - "placeholders": { - "clientId": { - "type": "String" - } - } - }, - "optionsSpotifyCredentialsRequired": "Obrigatório - toque para configurar", - "@optionsSpotifyCredentialsRequired": { - "description": "Prompt to set up credentials" - }, - "optionsSpotifyWarning": "O Spotify requer as suas próprias credenciais de API. Consiga gratuitamente em developer.spotify.com", - "@optionsSpotifyWarning": { - "description": "Info about Spotify API requirement" - }, - "optionsSpotifyDeprecationWarning": "Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.", - "@optionsSpotifyDeprecationWarning": { - "description": "Warning about Spotify API deprecation" - }, - "extensionsTitle": "Extensões", - "@extensionsTitle": { - "description": "Extensions page title" - }, - "extensionsDisabled": "Desabilitado", - "@extensionsDisabled": { - "description": "Extension status - inactive" - }, - "extensionsVersion": "Versão {version}", - "@extensionsVersion": { - "description": "Extension version display", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "extensionsAuthor": "por {author}", - "@extensionsAuthor": { - "description": "Extension author credit", - "placeholders": { - "author": { - "type": "String" - } - } - }, - "extensionsUninstall": "Desinstalar", - "@extensionsUninstall": { - "description": "Uninstall extension button" - }, - "storeTitle": "Loja de Extensões", - "@storeTitle": { - "description": "Store screen title" - }, - "storeSearch": "Pesquisar extensões...", - "@storeSearch": { - "description": "Store search placeholder" - }, - "storeInstall": "Instalar", - "@storeInstall": { - "description": "Install extension button" - }, - "storeInstalled": "Instalado", - "@storeInstalled": { - "description": "Already installed badge" - }, - "storeUpdate": "Atualizar", - "@storeUpdate": { - "description": "Update available button" - }, - "aboutTitle": "Sobre", - "@aboutTitle": { - "description": "About page title" - }, - "aboutContributors": "Colaboradores", - "@aboutContributors": { - "description": "Section for contributors" - }, - "aboutMobileDeveloper": "Desenvolvedor da versão móvel", - "@aboutMobileDeveloper": { - "description": "Role description for mobile dev" - }, - "aboutOriginalCreator": "Criador do SpotiFLAC original", - "@aboutOriginalCreator": { - "description": "Role description for original creator" - }, - "aboutLogoArtist": "O artista talentoso que criou o nosso lindo logotipo do aplicativo!", - "@aboutLogoArtist": { - "description": "Role description for logo artist" - }, - "aboutTranslators": "Tradutores", - "@aboutTranslators": { - "description": "Section for translators" - }, - "aboutSpecialThanks": "Agradecimentos Especiais", - "@aboutSpecialThanks": { - "description": "Section for special thanks" - }, - "aboutLinks": "Links", - "@aboutLinks": { - "description": "Section for external links" - }, - "aboutMobileSource": "Código-fonte do app móvel", - "@aboutMobileSource": { - "description": "Link to mobile GitHub repo" - }, - "aboutPCSource": "Código-fonte do app desktop", - "@aboutPCSource": { - "description": "Link to PC GitHub repo" - }, - "aboutReportIssue": "Reportar um problema", - "@aboutReportIssue": { - "description": "Link to report bugs" - }, - "aboutReportIssueSubtitle": "Reporte qualquer problema que encontrar", - "@aboutReportIssueSubtitle": { - "description": "Subtitle for report issue" - }, - "aboutFeatureRequest": "Solicitação de recurso", - "@aboutFeatureRequest": { - "description": "Link to suggest features" - }, - "aboutFeatureRequestSubtitle": "Sugira novos recursos para o aplicativo", - "@aboutFeatureRequestSubtitle": { - "description": "Subtitle for feature request" - }, - "aboutTelegramChannel": "Canal do Telegram", - "@aboutTelegramChannel": { - "description": "Link to Telegram channel" - }, - "aboutTelegramChannelSubtitle": "Anúncios e atualizações", - "@aboutTelegramChannelSubtitle": { - "description": "Subtitle for Telegram channel" - }, - "aboutTelegramChat": "Comunidade do Telegram", - "@aboutTelegramChat": { - "description": "Link to Telegram chat group" - }, - "aboutTelegramChatSubtitle": "Converse com outros usuários", - "@aboutTelegramChatSubtitle": { - "description": "Subtitle for Telegram chat" - }, - "aboutSocial": "Social", - "@aboutSocial": { - "description": "Section for social links" - }, - "aboutApp": "Aplicativo", - "@aboutApp": { - "description": "Section for app info" - }, - "aboutVersion": "Versão", - "@aboutVersion": { - "description": "Version info label" - }, - "aboutBinimumDesc": "O criador da API QQDL e HiFi. Sem esta API, os downloads Tidal não existiriam!", - "@aboutBinimumDesc": { - "description": "Credit description for binimum" - }, - "aboutSachinsenalDesc": "O criador original do projeto HiFi. A base da integração do Tidal!", - "@aboutSachinsenalDesc": { - "description": "Credit description for sachinsenal0x64" - }, - "aboutSjdonadoDesc": "Creator of I Don't Have Spotify (IDHS). The fallback link resolver that saves the day!", - "@aboutSjdonadoDesc": { - "description": "Credit description for sjdonado" - }, - "aboutDabMusic": "DAB Music", - "@aboutDabMusic": { - "description": "Name of Qobuz API service - DO NOT TRANSLATE" - }, - "aboutDabMusicDesc": "A melhor API de streaming do Qobuz. Downloads de alta resolução não seriam possíveis sem isso!", - "@aboutDabMusicDesc": { - "description": "Credit for DAB Music API" - }, - "aboutSpotiSaver": "SpotiSaver", - "@aboutSpotiSaver": { - "description": "Name of SpotiSaver API service - DO NOT TRANSLATE" - }, - "aboutSpotiSaverDesc": "Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!", - "@aboutSpotiSaverDesc": { - "description": "Credit for SpotiSaver API" - }, - "aboutAppDescription": "Baixe faixas do Spotify em qualidade sem perdas do Tidal, Qobuz e Amazon Music.", - "@aboutAppDescription": { - "description": "App description in header card" - }, - "artistAlbums": "Álbuns", - "@artistAlbums": { - "description": "Section header for artist albums" - }, - "artistSingles": "Singles e EPs", - "@artistSingles": { - "description": "Section header for singles/EPs" - }, - "artistCompilations": "Compilações", - "@artistCompilations": { - "description": "Section header for compilations" - }, - "artistPopular": "Populares", - "@artistPopular": { - "description": "Section header for popular/top tracks" - }, - "artistMonthlyListeners": "{count} ouvintes mensais", - "@artistMonthlyListeners": { - "description": "Monthly listener count display", - "placeholders": { - "count": { - "type": "String", - "description": "Formatted listener count" - } - } - }, - "trackMetadataService": "Serviço", - "@trackMetadataService": { - "description": "Metadata field - download service used" - }, - "trackMetadataPlay": "Reproduzir", - "@trackMetadataPlay": { - "description": "Action button - play track" - }, - "trackMetadataShare": "Compartilhar", - "@trackMetadataShare": { - "description": "Action button - share track" - }, - "trackMetadataDelete": "Apagar", - "@trackMetadataDelete": { - "description": "Action button - delete track" - }, - "setupGrantPermission": "Conceder Permissão", - "@setupGrantPermission": { - "description": "Button to request permission" - }, - "setupSkip": "Ignorar por enquanto", - "@setupSkip": { - "description": "Skip current step button" - }, - "setupStorageAccessRequired": "Acesso ao Armazenamento Necessário", - "@setupStorageAccessRequired": { - "description": "Title when storage access needed" - }, - "setupStorageAccessMessageAndroid11": "O Android 11+ requer a permissão \"Acesso a Todos os Arquivos\" para salvar arquivos na pasta de download escolhida.", - "@setupStorageAccessMessageAndroid11": { - "description": "Android 11+ specific explanation" - }, - "setupOpenSettings": "Abrir Configurações", - "@setupOpenSettings": { - "description": "Button to open system settings" - }, - "setupPermissionDeniedMessage": "Permissão negada. Por favor, conceda todas as permissões para continuar.", - "@setupPermissionDeniedMessage": { - "description": "Error when permission denied" - }, - "setupPermissionRequired": "Permissão {permissionType} Necessária", - "@setupPermissionRequired": { - "description": "Generic permission required title", - "placeholders": { - "permissionType": { - "type": "String", - "description": "Type of permission (Storage/Notification)" - } - } - }, - "setupPermissionRequiredMessage": "A permissão {permissionType} é necessária para a melhor experiência. Você pode alterar isso mais tarde em Configurações.", - "@setupPermissionRequiredMessage": { - "description": "Generic permission required message", - "placeholders": { - "permissionType": { - "type": "String" - } - } - }, - "setupUseDefaultFolder": "Usar Pasta Padrão?", - "@setupUseDefaultFolder": { - "description": "Dialog title for default folder" - }, - "setupNoFolderSelected": "Nenhuma pasta selecionada. Você gostaria de usar a pasta padrão de música?", - "@setupNoFolderSelected": { - "description": "Prompt when no folder selected" - }, - "setupUseDefault": "Usar Padrão", - "@setupUseDefault": { - "description": "Button to use default folder" - }, - "setupDownloadLocationTitle": "Local do Download", - "@setupDownloadLocationTitle": { - "description": "Download location dialog title" - }, - "setupDownloadLocationIosMessage": "No iOS, downloads são salvos na pasta Documentos do aplicativo. Você pode acessá-los através do app Arquivos.", - "@setupDownloadLocationIosMessage": { - "description": "iOS-specific folder info" - }, - "setupAppDocumentsFolder": "Pasta Documentos do App", - "@setupAppDocumentsFolder": { - "description": "iOS documents folder option" - }, - "setupAppDocumentsFolderSubtitle": "Recomendado - acessível através do aplicativo Arquivos", - "@setupAppDocumentsFolderSubtitle": { - "description": "Subtitle for documents folder" - }, - "setupChooseFromFiles": "Escolher dos Arquivos", - "@setupChooseFromFiles": { - "description": "iOS file picker option" - }, - "setupChooseFromFilesSubtitle": "Selecione o iCloud ou outro local", - "@setupChooseFromFilesSubtitle": { - "description": "Subtitle for file picker" - }, - "setupIosEmptyFolderWarning": "Limitação do iOS: Pastas vazias não podem ser selecionadas. Escolha uma pasta com pelo menos um arquivo.", - "@setupIosEmptyFolderWarning": { - "description": "iOS folder selection warning" - }, - "setupIcloudNotSupported": "iCloud Drive is not supported. Please use the app Documents folder.", - "@setupIcloudNotSupported": { - "description": "Error when user selects iCloud Drive on iOS" - }, - "setupDownloadInFlac": "Baixar faixas do Spotify em FLAC", - "@setupDownloadInFlac": { - "description": "App tagline in setup" - }, - "setupStorageGranted": "Permissão de Armazenamento Concedida!", - "@setupStorageGranted": { - "description": "Success message for storage permission" - }, - "setupStorageRequired": "Permissão de Armazenamento Necessária", - "@setupStorageRequired": { - "description": "Title when storage permission needed" - }, - "setupStorageDescription": "O SpotiFLAC precisa de permissão de armazenamento para salvar os seus arquivos de música baixados.", - "@setupStorageDescription": { - "description": "Explanation for storage permission" - }, - "setupNotificationGranted": "Permissão de Notificações Concedida!", - "@setupNotificationGranted": { - "description": "Success message for notification permission" - }, - "setupNotificationEnable": "Habilitar Notificações", - "@setupNotificationEnable": { - "description": "Button to enable notifications" - }, - "setupFolderChoose": "Escolher Pasta de Download", - "@setupFolderChoose": { - "description": "Button to choose folder" - }, - "setupFolderDescription": "Selecione uma pasta onde as suas músicas baixadas serão salvas.", - "@setupFolderDescription": { - "description": "Explanation for folder selection" - }, - "setupSelectFolder": "Seleccionar Pasta", - "@setupSelectFolder": { - "description": "Button to select folder" - }, - "setupEnableNotifications": "Habilitar Notificações", - "@setupEnableNotifications": { - "description": "Button to enable notifications" - }, - "setupNotificationBackgroundDescription": "Seja notificado sobre o progresso e conclusão do download. Isso ajuda você a acompanhar os downloads quando o app estiver em segundo plano.", - "@setupNotificationBackgroundDescription": { - "description": "Detailed notification explanation" - }, - "setupSkipForNow": "Ignorar por enquanto", - "@setupSkipForNow": { - "description": "Skip button text" - }, - "setupNext": "Próximo", - "@setupNext": { - "description": "Next button text" - }, - "setupGetStarted": "Começar", - "@setupGetStarted": { - "description": "Final setup button" - }, - "setupAllowAccessToManageFiles": "Por favor, habilite \"Permitir acesso para gerenciar todos os arquivos\" na próxima tela.", - "@setupAllowAccessToManageFiles": { - "description": "Instruction for file access permission" - }, - "dialogCancel": "Cancelar", - "@dialogCancel": { - "description": "Dialog button - cancel action" - }, - "dialogSave": "Salvar", - "@dialogSave": { - "description": "Dialog button - save changes" - }, - "dialogDelete": "Apagar", - "@dialogDelete": { - "description": "Dialog button - delete item" - }, - "dialogRetry": "Tentar novamente", - "@dialogRetry": { - "description": "Dialog button - retry action" - }, - "dialogClear": "Limpar", - "@dialogClear": { - "description": "Dialog button - clear items" - }, - "dialogDone": "Concluído", - "@dialogDone": { - "description": "Dialog button - action completed" - }, - "dialogImport": "Importar", - "@dialogImport": { - "description": "Dialog button - import data" - }, - "dialogDiscard": "Descartar", - "@dialogDiscard": { - "description": "Dialog button - discard changes" - }, - "dialogRemove": "Remover", - "@dialogRemove": { - "description": "Dialog button - remove item" - }, - "dialogUninstall": "Desinstalar", - "@dialogUninstall": { - "description": "Dialog button - uninstall extension" - }, - "dialogDiscardChanges": "Descartar Alterações?", - "@dialogDiscardChanges": { - "description": "Dialog title - unsaved changes warning" - }, - "dialogUnsavedChanges": "Você tem alterações não salvas. Deseja descartá-las?", - "@dialogUnsavedChanges": { - "description": "Dialog message - unsaved changes" - }, - "dialogClearAll": "Limpar Tudo", - "@dialogClearAll": { - "description": "Dialog title - clear all items" - }, - "dialogRemoveExtension": "Remover Extensão", - "@dialogRemoveExtension": { - "description": "Dialog title - uninstall extension" - }, - "dialogRemoveExtensionMessage": "Tem certeza de que deseja remover esta extensão? Isso não pode ser desfeito.", - "@dialogRemoveExtensionMessage": { - "description": "Dialog message - uninstall confirmation" - }, - "dialogUninstallExtension": "Desinstalar Extensão?", - "@dialogUninstallExtension": { - "description": "Dialog title - uninstall extension" - }, - "dialogUninstallExtensionMessage": "Tem certeza que deseja remover {extensionName}?", - "@dialogUninstallExtensionMessage": { - "description": "Dialog message - uninstall specific extension", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "dialogClearHistoryTitle": "Limpar Histórico", - "@dialogClearHistoryTitle": { - "description": "Dialog title - clear download history" - }, - "dialogClearHistoryMessage": "Tem certeza que deseja limpar todo o histórico de downloads? Isso não pode ser desfeito.", - "@dialogClearHistoryMessage": { - "description": "Dialog message - clear history confirmation" - }, - "dialogDeleteSelectedTitle": "Apagar Selecionados", - "@dialogDeleteSelectedTitle": { - "description": "Dialog title - delete selected items" - }, - "dialogDeleteSelectedMessage": "Apagar {count} {count, plural, one {}=1{faixa} other{faixas}} do histórico?\n\nIsso também apagará os arquivos do armazenamento.", - "@dialogDeleteSelectedMessage": { - "description": "Dialog message - delete selected tracks", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "dialogImportPlaylistTitle": "Importar Playlist", - "@dialogImportPlaylistTitle": { - "description": "Dialog title - import CSV playlist" - }, - "dialogImportPlaylistMessage": "{count} Faixas encontradas em CSV. Adicioná-las à lista de downloads?", - "csvImportTracks": "{count} faixas do CSV", - "@csvImportTracks": { - "description": "Label shown in quality picker for CSV import", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "@dialogImportPlaylistMessage": { - "description": "Dialog message - import playlist confirmation", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "snackbarAddedToQueue": "\"{trackName}\" adicionada à fila", - "@snackbarAddedToQueue": { - "description": "Snackbar - track added to download queue", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "snackbarAddedTracksToQueue": "{count} faixas adicionadas à fila", - "@snackbarAddedTracksToQueue": { - "description": "Snackbar - multiple tracks added to queue", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "snackbarAlreadyDownloaded": "\"{trackName}\" já foi baixada", - "@snackbarAlreadyDownloaded": { - "description": "Snackbar - track already exists", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "snackbarAlreadyInLibrary": "\"{trackName}\" already exists in your library", - "@snackbarAlreadyInLibrary": { - "description": "Snackbar - track already exists in local library", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "snackbarHistoryCleared": "Histórico limpo", - "@snackbarHistoryCleared": { - "description": "Snackbar - history deleted" - }, - "snackbarCredentialsSaved": "Credenciais salvas", - "@snackbarCredentialsSaved": { - "description": "Snackbar - Spotify credentials saved" - }, - "snackbarCredentialsCleared": "Credenciais limpas", - "@snackbarCredentialsCleared": { - "description": "Snackbar - Spotify credentials removed" - }, - "snackbarDeletedTracks": "{count} {count, plural, one {}=1{faixa apagada} other{faixas apagadas}}", - "@snackbarDeletedTracks": { - "description": "Snackbar - tracks deleted", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "snackbarCannotOpenFile": "Não foi possível abrir o arquivo: {error}", - "@snackbarCannotOpenFile": { - "description": "Snackbar - file open error", - "placeholders": { - "error": { - "type": "String" - } - } - }, - "snackbarFillAllFields": "Por favor, preencha todos os campos", - "@snackbarFillAllFields": { - "description": "Snackbar - validation error" - }, - "snackbarViewQueue": "Ver Fila", - "@snackbarViewQueue": { - "description": "Snackbar action - view download queue" - }, - "snackbarUrlCopied": "URL do {platform} copiado para a área de transferência", - "@snackbarUrlCopied": { - "description": "Snackbar - URL copied", - "placeholders": { - "platform": { - "type": "String", - "description": "Platform name (Spotify/Deezer)" - } - } - }, - "snackbarFileNotFound": "Arquivo não encontrado", - "@snackbarFileNotFound": { - "description": "Snackbar - file doesn't exist" - }, - "snackbarSelectExtFile": "Por favor, selecione um arquivo .spotiflac-ext", - "@snackbarSelectExtFile": { - "description": "Snackbar - wrong file type selected" - }, - "snackbarProviderPrioritySaved": "Prioridade de provedor salva", - "@snackbarProviderPrioritySaved": { - "description": "Snackbar - provider order saved" - }, - "snackbarMetadataProviderSaved": "Prioridade do provedor de metadados salva", - "@snackbarMetadataProviderSaved": { - "description": "Snackbar - metadata provider order saved" - }, - "snackbarExtensionInstalled": "{extensionName} instalada.", - "@snackbarExtensionInstalled": { - "description": "Snackbar - extension installed successfully", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "snackbarExtensionUpdated": "{extensionName} atualizada.", - "@snackbarExtensionUpdated": { - "description": "Snackbar - extension updated successfully", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "snackbarFailedToInstall": "Falha ao instalar a extensão", - "@snackbarFailedToInstall": { - "description": "Snackbar - extension install error" - }, - "snackbarFailedToUpdate": "Falha ao atualizar a extensão", - "@snackbarFailedToUpdate": { - "description": "Snackbar - extension update error" - }, - "errorRateLimited": "Tráfico Limitado (Rate Limited)", - "@errorRateLimited": { - "description": "Error title - too many requests" - }, - "errorRateLimitedMessage": "Muitas solicitações. Por favor, aguarde um momento antes de pesquisar novamente.", - "@errorRateLimitedMessage": { - "description": "Error message - rate limit explanation" - }, - "errorNoTracksFound": "Nenhuma faixa encontrada", - "@errorNoTracksFound": { - "description": "Error - search returned no results" - }, - "errorMissingExtensionSource": "Não é possível carregar {item}: faltando a fonte da extensão", - "@errorMissingExtensionSource": { - "description": "Error - extension source not available", - "placeholders": { - "item": { - "type": "String" - } - } - }, - "actionPause": "Pausar", - "@actionPause": { - "description": "Action button - pause download" - }, - "actionResume": "Retomar", - "@actionResume": { - "description": "Action button - resume download" - }, - "actionCancel": "Cancelar", - "@actionCancel": { - "description": "Action button - cancel operation" - }, - "actionSelectAll": "Selecionar Tudo", - "@actionSelectAll": { - "description": "Action button - select all items" - }, - "actionDeselect": "Desselecionar", - "@actionDeselect": { - "description": "Action button - deselect all" - }, - "actionRemoveCredentials": "Remover Credenciais", - "@actionRemoveCredentials": { - "description": "Action button - delete Spotify credentials" - }, - "actionSaveCredentials": "Salvar Credenciais", - "@actionSaveCredentials": { - "description": "Action button - save Spotify credentials" - }, - "selectionSelected": "{count} selecionado(s)", - "@selectionSelected": { - "description": "Selection count indicator", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "selectionAllSelected": "Todas as faixas selecionadas", - "@selectionAllSelected": { - "description": "Status - all items selected" - }, - "selectionSelectToDelete": "Selecione as faixas para apagar", - "@selectionSelectToDelete": { - "description": "Placeholder when nothing selected" - }, - "progressFetchingMetadata": "Buscando metadados... {current}/{total}", - "@progressFetchingMetadata": { - "description": "Progress indicator - loading track info", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } - }, - "progressReadingCsv": "Lendo CSV...", - "@progressReadingCsv": { - "description": "Progress indicator - parsing CSV file" - }, - "searchSongs": "Músicas", - "@searchSongs": { - "description": "Search result category - songs" - }, - "searchArtists": "Artistas", - "@searchArtists": { - "description": "Search result category - artists" - }, - "searchAlbums": "Álbuns", - "@searchAlbums": { - "description": "Search result category - albums" - }, - "searchPlaylists": "Playlists", - "@searchPlaylists": { - "description": "Search result category - playlists" - }, - "tooltipPlay": "Reproduzir", - "@tooltipPlay": { - "description": "Tooltip - play button" - }, - "filenameFormat": "Formato do Nome do Arquivo", - "@filenameFormat": { - "description": "Setting title - filename pattern" - }, - "filenameShowAdvancedTags": "Show advanced tags", - "@filenameShowAdvancedTags": { - "description": "Toggle label for showing advanced filename tags" - }, - "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", - "@filenameShowAdvancedTagsDescription": { - "description": "Description for advanced filename tag toggle" - }, - "folderOrganizationNone": "Nenhuma organização", - "@folderOrganizationNone": { - "description": "Folder option - flat structure" - }, - "folderOrganizationByArtist": "Por Artista", - "@folderOrganizationByArtist": { - "description": "Folder option - artist folders" - }, - "folderOrganizationByAlbum": "Por Album", - "@folderOrganizationByAlbum": { - "description": "Folder option - album folders" - }, - "folderOrganizationByArtistAlbum": "Artista/Álbum", - "@folderOrganizationByArtistAlbum": { - "description": "Folder option - nested folders" - }, - "folderOrganizationDescription": "Organizar arquivos baixados em pastas", - "@folderOrganizationDescription": { - "description": "Folder organization sheet description" - }, - "folderOrganizationNoneSubtitle": "Todos os arquivos na pasta de download", - "@folderOrganizationNoneSubtitle": { - "description": "Subtitle for no organization option" - }, - "folderOrganizationByArtistSubtitle": "Pasta separada para cada artista", - "@folderOrganizationByArtistSubtitle": { - "description": "Subtitle for artist folder option" - }, - "folderOrganizationByAlbumSubtitle": "Pasta separada para cada álbum", - "@folderOrganizationByAlbumSubtitle": { - "description": "Subtitle for album folder option" - }, - "folderOrganizationByArtistAlbumSubtitle": "Pastas aninhadas para artista e álbum", - "@folderOrganizationByArtistAlbumSubtitle": { - "description": "Subtitle for nested folder option" - }, - "updateAvailable": "Atualização Disponível", - "@updateAvailable": { - "description": "Update dialog title" - }, - "updateLater": "Depois", - "@updateLater": { - "description": "Update button - dismiss" - }, - "updateStartingDownload": "Iniciando download...", - "@updateStartingDownload": { - "description": "Update status - initializing" - }, - "updateDownloadFailed": "Download falhou", - "@updateDownloadFailed": { - "description": "Update error title" - }, - "updateFailedMessage": "Falha ao baixar a atualização", - "@updateFailedMessage": { - "description": "Update error message" - }, - "updateNewVersionReady": "Uma nova versão está pronta", - "@updateNewVersionReady": { - "description": "Update subtitle" - }, - "updateCurrent": "Atual", - "@updateCurrent": { - "description": "Label for current version" - }, - "updateNew": "Novo", - "@updateNew": { - "description": "Label for new version" - }, - "updateDownloading": "Baixando...", - "@updateDownloading": { - "description": "Update status - downloading" - }, - "updateWhatsNew": "Novidades", - "@updateWhatsNew": { - "description": "Changelog section title" - }, - "updateDownloadInstall": "Baixar e Instalar", - "@updateDownloadInstall": { - "description": "Update button - download and install" - }, - "updateDontRemind": "Não lembrar", - "@updateDontRemind": { - "description": "Update button - skip this version" - }, - "providerPriorityTitle": "Prioridade de Provedor", - "@providerPriorityTitle": { - "description": "Provider priority page title" - }, - "providerPriorityDescription": "Arraste para reordenar provedores de download. O aplicativo irá tentar provedores de cima para baixo ao baixar as faixas.", - "@providerPriorityDescription": { - "description": "Provider priority page description" - }, - "providerPriorityInfo": "Se uma faixa não estiver disponível no primeiro provedor, o aplicativo irá tentar automaticamente a próxima.", - "@providerPriorityInfo": { - "description": "Info tip about fallback behavior" - }, - "providerBuiltIn": "Embutido", - "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" - }, - "providerExtension": "Extensão", - "@providerExtension": { - "description": "Label for extension-provided providers" - }, - "metadataProviderPriorityTitle": "Prioridade de Metadados", - "@metadataProviderPriorityTitle": { - "description": "Metadata priority page title" - }, - "metadataProviderPriorityDescription": "Arraste para reordenar provedores de metadados. O aplicativo tentará provedores de cima para baixo ao procurar por faixas e buscar metadados.", - "@metadataProviderPriorityDescription": { - "description": "Metadata priority page description" - }, - "metadataProviderPriorityInfo": "O Deezer não tem limites de taxa e é recomendado como principal. O Spotify pode limitar a taxa após muitas solicitações.", - "@metadataProviderPriorityInfo": { - "description": "Info tip about rate limits" - }, - "metadataNoRateLimits": "Sem limites de taxa", - "@metadataNoRateLimits": { - "description": "Deezer provider description" - }, - "metadataMayRateLimit": "Pode ter limites de taxa", - "@metadataMayRateLimit": { - "description": "Spotify provider description" - }, - "logTitle": "Registros", - "@logTitle": { - "description": "Logs screen title" - }, - "logCopied": "Registros copiados para área de transferência", - "@logCopied": { - "description": "Snackbar - logs copied" - }, - "logSearchHint": "Pesquisar registros...", - "@logSearchHint": { - "description": "Log search placeholder" - }, - "logFilterLevel": "Nível", - "@logFilterLevel": { - "description": "Filter by log level" - }, - "logFilterSection": "Filtro", - "@logFilterSection": { - "description": "Filter section title" - }, - "logShareLogs": "Compartilhar registros", - "@logShareLogs": { - "description": "Share button tooltip" - }, - "logClearLogs": "Limpar registros", - "@logClearLogs": { - "description": "Clear button tooltip" - }, - "logClearLogsTitle": "Limpar Registros", - "@logClearLogsTitle": { - "description": "Clear logs dialog title" - }, - "logClearLogsMessage": "Tem certeza de que deseja limpar todos os registros?", - "@logClearLogsMessage": { - "description": "Clear logs confirmation message" - }, - "logFilterBySeverity": "Filtrar registros por gravidade", - "@logFilterBySeverity": { - "description": "Filter dialog title" - }, - "logNoLogsYet": "Ainda não há registros", - "@logNoLogsYet": { - "description": "Empty state title" - }, - "logNoLogsYetSubtitle": "Os registros aparecerão aqui enquanto você usa o aplicativo", - "@logNoLogsYetSubtitle": { - "description": "Empty state subtitle" - }, - "logEntriesFiltered": "Entradas ({count} filtradas)", - "@logEntriesFiltered": { - "description": "Log count with filter active", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logEntries": "Entradas ({count})", - "@logEntries": { - "description": "Total log count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "credentialsTitle": "Credenciais do Spotify", - "@credentialsTitle": { - "description": "Credentials dialog title" - }, - "credentialsDescription": "Digite a sua Client ID e Secret para usar a sua própria cota de aplicativo do Spotify.", - "@credentialsDescription": { - "description": "Credentials dialog explanation" - }, - "credentialsClientId": "Client ID", - "@credentialsClientId": { - "description": "Client ID field label - DO NOT TRANSLATE" - }, - "credentialsClientIdHint": "Colar Client ID", - "@credentialsClientIdHint": { - "description": "Client ID placeholder" - }, - "credentialsClientSecret": "Client Secret", - "@credentialsClientSecret": { - "description": "Client Secret field label - DO NOT TRANSLATE" - }, - "credentialsClientSecretHint": "Colar Client Secret", - "@credentialsClientSecretHint": { - "description": "Client Secret placeholder" - }, - "channelStable": "Estável", - "@channelStable": { - "description": "Update channel - stable releases" - }, - "channelPreview": "Prévia", - "@channelPreview": { - "description": "Update channel - beta/preview releases" - }, - "sectionSearchSource": "Origem da Pesquisa", - "@sectionSearchSource": { - "description": "Settings section header" - }, - "sectionDownload": "Download", - "@sectionDownload": { - "description": "Settings section header" - }, - "sectionPerformance": "Desempenho", - "@sectionPerformance": { - "description": "Settings section header" - }, - "sectionApp": "Aplicativo", - "@sectionApp": { - "description": "Settings section header" - }, - "sectionData": "Dados", - "@sectionData": { - "description": "Settings section header" - }, - "sectionDebug": "Depuração", - "@sectionDebug": { - "description": "Settings section header" - }, - "sectionService": "Serviço", - "@sectionService": { - "description": "Settings section header" - }, - "sectionAudioQuality": "Qualidade de Áudio", - "@sectionAudioQuality": { - "description": "Settings section header" - }, - "sectionFileSettings": "Configurações de Arquivo", - "@sectionFileSettings": { - "description": "Settings section header" - }, - "sectionLyrics": "Letras", - "@sectionLyrics": { - "description": "Settings section header" - }, - "lyricsMode": "Modo de Letras", - "@lyricsMode": { - "description": "Setting - how to save lyrics" - }, - "lyricsModeDescription": "Escolha como as letras são salvas com os seus downloads", - "@lyricsModeDescription": { - "description": "Lyrics mode picker description" - }, - "lyricsModeEmbed": "Incorporar no arquivo", - "@lyricsModeEmbed": { - "description": "Lyrics mode option - embed in audio file" - }, - "lyricsModeEmbedSubtitle": "Letra armazenada nos metadados da FLAC", - "@lyricsModeEmbedSubtitle": { - "description": "Subtitle for embed option" - }, - "lyricsModeExternal": "Arquivo .lrc externo", - "@lyricsModeExternal": { - "description": "Lyrics mode option - separate LRC file" - }, - "lyricsModeExternalSubtitle": "Arquivo .lrc separado para reprodutores como o Samsung Music", - "@lyricsModeExternalSubtitle": { - "description": "Subtitle for external option" - }, - "lyricsModeBoth": "Ambos", - "@lyricsModeBoth": { - "description": "Lyrics mode option - embed and external" - }, - "lyricsModeBothSubtitle": "Incorporar e salvar arquivo .lrc", - "@lyricsModeBothSubtitle": { - "description": "Subtitle for both option" - }, - "sectionColor": "Cor", - "@sectionColor": { - "description": "Settings section header" - }, - "sectionTheme": "Tema", - "@sectionTheme": { - "description": "Settings section header" - }, - "sectionLayout": "Layout", - "@sectionLayout": { - "description": "Settings section header" - }, - "sectionLanguage": "Idioma", - "@sectionLanguage": { - "description": "Settings section header for language" - }, - "appearanceLanguage": "Idioma do aplicativo", - "@appearanceLanguage": { - "description": "Language setting title" - }, - "settingsAppearanceSubtitle": "Tema, cores, exibição", - "@settingsAppearanceSubtitle": { - "description": "Appearance settings description" - }, - "settingsDownloadSubtitle": "Serviço, qualidade, formato de nome de arquivo", - "@settingsDownloadSubtitle": { - "description": "Download settings description" - }, - "settingsOptionsSubtitle": "Fallback, letras, arte de capa, atualizações", - "@settingsOptionsSubtitle": { - "description": "Options settings description" - }, - "settingsExtensionsSubtitle": "Gerenciar provedores de download", - "@settingsExtensionsSubtitle": { - "description": "Extensions settings description" - }, - "settingsLogsSubtitle": "Ver logs do app para depuração", - "@settingsLogsSubtitle": { - "description": "Logs settings description" - }, - "loadingSharedLink": "Carregando link compartilhado...", - "@loadingSharedLink": { - "description": "Status when opening shared URL" - }, - "pressBackAgainToExit": "Pressione voltar novamente para sair", - "@pressBackAgainToExit": { - "description": "Exit confirmation message" - }, - "downloadAllCount": "Baixar Todos ({count})", - "@downloadAllCount": { - "description": "Download all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "tracksCount": "{count, plural, one {}=1{1 faixa} other{{count} faixas}}", - "@tracksCount": { - "description": "Track count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "trackCopyFilePath": "Copiar caminho do arquivo", - "@trackCopyFilePath": { - "description": "Action - copy file path" - }, - "trackRemoveFromDevice": "Remover do dispositivo", - "@trackRemoveFromDevice": { - "description": "Action - delete downloaded file" - }, - "trackLoadLyrics": "Carregar Letras", - "@trackLoadLyrics": { - "description": "Action - fetch lyrics" - }, - "trackMetadata": "Metadados", - "@trackMetadata": { - "description": "Tab title - track metadata" - }, - "trackFileInfo": "Informações do Arquivo", - "@trackFileInfo": { - "description": "Tab title - file information" - }, - "trackLyrics": "Letras", - "@trackLyrics": { - "description": "Tab title - lyrics" - }, - "trackFileNotFound": "Arquivo não encontrado", - "@trackFileNotFound": { - "description": "Error - file doesn't exist" - }, - "trackOpenInDeezer": "Abrir no Deezer", - "@trackOpenInDeezer": { - "description": "Action - open track in Deezer app" - }, - "trackOpenInSpotify": "Abrir no Spotify", - "@trackOpenInSpotify": { - "description": "Action - open track in Spotify app" - }, - "trackTrackName": "Nome da faixa", - "@trackTrackName": { - "description": "Metadata label - track title" - }, - "trackArtist": "Artista", - "@trackArtist": { - "description": "Metadata label - artist name" - }, - "trackAlbumArtist": "Artista do álbum", - "@trackAlbumArtist": { - "description": "Metadata label - album artist" - }, - "trackAlbum": "Álbum", - "@trackAlbum": { - "description": "Metadata label - album name" - }, - "trackTrackNumber": "Número da faixa", - "@trackTrackNumber": { - "description": "Metadata label - track number" - }, - "trackDiscNumber": "Número do disco", - "@trackDiscNumber": { - "description": "Metadata label - disc number" - }, - "trackDuration": "Duração", - "@trackDuration": { - "description": "Metadata label - track length" - }, - "trackAudioQuality": "Qualidade de Áudio", - "@trackAudioQuality": { - "description": "Metadata label - audio quality" - }, - "trackReleaseDate": "Data de lançamento", - "@trackReleaseDate": { - "description": "Metadata label - release date" - }, - "trackGenre": "Género", - "@trackGenre": { - "description": "Metadata label - music genre" - }, - "trackLabel": "Gravadora", - "@trackLabel": { - "description": "Metadata label - record label" - }, - "trackCopyright": "Direitos Autorais", - "@trackCopyright": { - "description": "Metadata label - copyright information" - }, - "trackDownloaded": "Baixado", - "@trackDownloaded": { - "description": "Metadata label - download date" - }, - "trackCopyLyrics": "Copiar letra", - "@trackCopyLyrics": { - "description": "Action - copy lyrics to clipboard" - }, - "trackLyricsNotAvailable": "Letra não disponível para esta faixa", - "@trackLyricsNotAvailable": { - "description": "Message when lyrics not found" - }, - "trackLyricsTimeout": "A solicitação expirou. Tente novamente mais tarde.", - "@trackLyricsTimeout": { - "description": "Message when lyrics request times out" - }, - "trackLyricsLoadFailed": "Falha ao carregar a letra", - "@trackLyricsLoadFailed": { - "description": "Message when lyrics loading fails" - }, - "trackEmbedLyrics": "Incorporar Letras", - "@trackEmbedLyrics": { - "description": "Action - embed lyrics into audio file" - }, - "trackLyricsEmbedded": "Letras incorporadas com sucesso", - "@trackLyricsEmbedded": { - "description": "Snackbar - lyrics saved to file" - }, - "trackInstrumental": "Faixa de instrumentais", - "@trackInstrumental": { - "description": "Message when track is instrumental (no lyrics)" - }, - "trackCopiedToClipboard": "Copiado para a área de transferência", - "@trackCopiedToClipboard": { - "description": "Snackbar - content copied" - }, - "trackDeleteConfirmTitle": "Remover do dispositivo?", - "@trackDeleteConfirmTitle": { - "description": "Delete confirmation title" - }, - "trackDeleteConfirmMessage": "Isto irá excluir o arquivo baixado permanentemente e removê-lo do seu histórico.", - "@trackDeleteConfirmMessage": { - "description": "Delete confirmation message" - }, - "dateToday": "Hoje", - "@dateToday": { - "description": "Relative date - today" - }, - "dateYesterday": "Ontem", - "@dateYesterday": { - "description": "Relative date - yesterday" - }, - "dateDaysAgo": "{count} dias atrás", - "@dateDaysAgo": { - "description": "Relative date - days ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "dateWeeksAgo": "{count} semanas atrás", - "@dateWeeksAgo": { - "description": "Relative date - weeks ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "dateMonthsAgo": "{count} meses atrás", - "@dateMonthsAgo": { - "description": "Relative date - months ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storeFilterAll": "Tudo", - "@storeFilterAll": { - "description": "Store filter - all extensions" - }, - "storeFilterMetadata": "Metadados", - "@storeFilterMetadata": { - "description": "Store filter - metadata providers" - }, - "storeFilterDownload": "Download", - "@storeFilterDownload": { - "description": "Store filter - download providers" - }, - "storeFilterUtility": "Utilidade", - "@storeFilterUtility": { - "description": "Store filter - utility extensions" - }, - "storeFilterLyrics": "Letras", - "@storeFilterLyrics": { - "description": "Store filter - lyrics providers" - }, - "storeFilterIntegration": "Integração", - "@storeFilterIntegration": { - "description": "Store filter - integrations" - }, - "storeClearFilters": "Limpar filtros", - "@storeClearFilters": { - "description": "Button to clear all filters" - }, - "extensionDefaultProvider": "Padrão (Deezer/Spotify)", - "@extensionDefaultProvider": { - "description": "Default search provider option" - }, - "extensionDefaultProviderSubtitle": "Usar pesquisa integrada", - "@extensionDefaultProviderSubtitle": { - "description": "Subtitle for default provider" - }, - "extensionAuthor": "Autor", - "@extensionAuthor": { - "description": "Extension detail - author" - }, - "extensionId": "ID", - "@extensionId": { - "description": "Extension detail - unique ID" - }, - "extensionError": "Erro", - "@extensionError": { - "description": "Extension detail - error message" - }, - "extensionCapabilities": "Funcionalidades", - "@extensionCapabilities": { - "description": "Section header - extension features" - }, - "extensionMetadataProvider": "Provedor de Metadados", - "@extensionMetadataProvider": { - "description": "Capability - provides metadata" - }, - "extensionDownloadProvider": "Provedor de Download", - "@extensionDownloadProvider": { - "description": "Capability - provides downloads" - }, - "extensionLyricsProvider": "Provedor de Letras", - "@extensionLyricsProvider": { - "description": "Capability - provides lyrics" - }, - "extensionUrlHandler": "Gerenciador de URL", - "@extensionUrlHandler": { - "description": "Capability - handles URLs" - }, - "extensionQualityOptions": "Opções de Qualidade", - "@extensionQualityOptions": { - "description": "Capability - quality selection" - }, - "extensionPostProcessingHooks": "Ganchos de Pós-Processamento", - "@extensionPostProcessingHooks": { - "description": "Capability - post-processing" - }, - "extensionPermissions": "Permissões", - "@extensionPermissions": { - "description": "Section header - required permissions" - }, - "extensionSettings": "Configurações", - "@extensionSettings": { - "description": "Section header - extension settings" - }, - "extensionRemoveButton": "Remover Extensão", - "@extensionRemoveButton": { - "description": "Button to uninstall extension" - }, - "extensionUpdated": "Atualizado", - "@extensionUpdated": { - "description": "Extension detail - last update" - }, - "extensionMinAppVersion": "Versão Mínima do App", - "@extensionMinAppVersion": { - "description": "Extension detail - minimum app version" - }, - "extensionCustomTrackMatching": "Correspondência de Faixa Personalizada", - "@extensionCustomTrackMatching": { - "description": "Capability - custom track matching algorithm" - }, - "extensionPostProcessing": "Pós-Processamento", - "@extensionPostProcessing": { - "description": "Capability - post-download processing" - }, - "extensionHooksAvailable": "{count} gancho(s) disponíveis", - "@extensionHooksAvailable": { - "description": "Post-processing hooks count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "extensionPatternsCount": "{count} padrão(ões)", - "@extensionPatternsCount": { - "description": "URL patterns count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "extensionStrategy": "Estratégia: {strategy}", - "@extensionStrategy": { - "description": "Track matching strategy name", - "placeholders": { - "strategy": { - "type": "String" - } - } - }, - "extensionsProviderPrioritySection": "Prioridade de Provedor", - "@extensionsProviderPrioritySection": { - "description": "Section header - provider priority" - }, - "extensionsInstalledSection": "Extensões Instaladas", - "@extensionsInstalledSection": { - "description": "Section header - installed extensions" - }, - "extensionsNoExtensions": "Nenhuma extensão instalada", - "@extensionsNoExtensions": { - "description": "Empty state - no extensions" - }, - "extensionsNoExtensionsSubtitle": "Instale arquivos .spotiflac-ext para adicionar novos provedores", - "@extensionsNoExtensionsSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsInstallButton": "Instalar Extensão", - "@extensionsInstallButton": { - "description": "Button to install extension from file" - }, - "extensionsInfoTip": "Extensões podem adicionar novos metadados e baixar provedores. Somente instale extensões a partir de fontes confiáveis.", - "@extensionsInfoTip": { - "description": "Security warning about extensions" - }, - "extensionsInstalledSuccess": "Extensão instalada com sucesso", - "@extensionsInstalledSuccess": { - "description": "Success message after install" - }, - "extensionsDownloadPriority": "Prioridade de Download", - "@extensionsDownloadPriority": { - "description": "Setting - download provider order" - }, - "extensionsDownloadPrioritySubtitle": "Definir ordem do serviço de download", - "@extensionsDownloadPrioritySubtitle": { - "description": "Subtitle for download priority" - }, - "extensionsNoDownloadProvider": "Nenhuma extensão com provedor de download", - "@extensionsNoDownloadProvider": { - "description": "Empty state - no download providers" - }, - "extensionsMetadataPriority": "Prioridade de Metadados", - "@extensionsMetadataPriority": { - "description": "Setting - metadata provider order" - }, - "extensionsMetadataPrioritySubtitle": "Definir ordem de origem de pesquisa e metadados", - "@extensionsMetadataPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, - "extensionsNoMetadataProvider": "Nenhuma extensão com provedor de metadados", - "@extensionsNoMetadataProvider": { - "description": "Empty state - no metadata providers" - }, - "extensionsSearchProvider": "Provedor de Pesquisa", - "@extensionsSearchProvider": { - "description": "Setting - search provider selection" - }, - "extensionsNoCustomSearch": "Nenhuma extensão com pesquisa personalizada", - "@extensionsNoCustomSearch": { - "description": "Empty state - no search providers" - }, - "extensionsSearchProviderDescription": "Escolha qual serviço utilizar para pesquisar faixas", - "@extensionsSearchProviderDescription": { - "description": "Search provider setting description" - }, - "extensionsCustomSearch": "Busca personalizada", - "@extensionsCustomSearch": { - "description": "Label for custom search provider" - }, - "extensionsErrorLoading": "Erro ao carregar extensão", - "@extensionsErrorLoading": { - "description": "Error message when extension fails to load" - }, - "qualityFlacLossless": "FLAC Lossless", - "@qualityFlacLossless": { - "description": "Quality option - CD quality FLAC" - }, - "qualityFlacLosslessSubtitle": "16-bit / 44.1kHz", - "@qualityFlacLosslessSubtitle": { - "description": "Technical spec for lossless" - }, - "qualityHiResFlac": "Hi-Res FLAC", - "@qualityHiResFlac": { - "description": "Quality option - high resolution FLAC" - }, - "qualityHiResFlacSubtitle": "24-bit / até 96kHz", - "@qualityHiResFlacSubtitle": { - "description": "Technical spec for hi-res" - }, - "qualityHiResFlacMax": "Hi-Res FLAC Max", - "@qualityHiResFlacMax": { - "description": "Quality option - maximum resolution FLAC" - }, - "qualityHiResFlacMaxSubtitle": "24-bit / até 192kHz", - "@qualityHiResFlacMaxSubtitle": { - "description": "Technical spec for hi-res max" - }, - "qualityNote": "A qualidade real depende da faixa que estiver disponível no serviço", - "@qualityNote": { - "description": "Note about quality availability" - }, - "youtubeQualityNote": "YouTube provides lossy audio only. Not part of lossless fallback.", - "@youtubeQualityNote": { - "description": "Note for YouTube service explaining lossy-only quality" - }, - "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", - "@youtubeOpusBitrateTitle": { - "description": "Title for YouTube Opus bitrate setting" - }, - "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", - "@youtubeMp3BitrateTitle": { - "description": "Title for YouTube MP3 bitrate setting" - }, - "downloadAskBeforeDownload": "Perguntar qualidade antes de baixar", - "@downloadAskBeforeDownload": { - "description": "Setting - show quality picker" - }, - "downloadDirectory": "Pasta de Download", - "@downloadDirectory": { - "description": "Setting - download folder" - }, - "downloadSeparateSinglesFolder": "Pasta de Singles Separada", - "@downloadSeparateSinglesFolder": { - "description": "Setting - separate folder for singles" - }, - "downloadAlbumFolderStructure": "Estrutura da Pasta de Álbum", - "@downloadAlbumFolderStructure": { - "description": "Setting - album folder organization" - }, - "downloadUseAlbumArtistForFolders": "Use Album Artist for folders", - "@downloadUseAlbumArtistForFolders": { - "description": "Setting - choose whether artist folders use Album Artist or Track Artist" - }, - "downloadUsePrimaryArtistOnly": "Primary artist only for folders", - "@downloadUsePrimaryArtistOnly": { - "description": "Setting - strip featured artists from folder name" - }, - "downloadUsePrimaryArtistOnlyEnabled": "Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)", - "@downloadUsePrimaryArtistOnlyEnabled": { - "description": "Subtitle when primary artist only is enabled" - }, - "downloadUsePrimaryArtistOnlyDisabled": "Full artist string used for folder name", - "@downloadUsePrimaryArtistOnlyDisabled": { - "description": "Subtitle when primary artist only is disabled" - }, - "downloadSelectQuality": "Selecionar Qualidade", - "@downloadSelectQuality": { - "description": "Dialog title - choose audio quality" - }, - "downloadFrom": "Baixar De", - "@downloadFrom": { - "description": "Label - download source" - }, - "appearanceAmoledDark": "Escuro AMOLED", - "@appearanceAmoledDark": { - "description": "Theme option - pure black" - }, - "appearanceAmoledDarkSubtitle": "Fundo preto puro", - "@appearanceAmoledDarkSubtitle": { - "description": "Subtitle for AMOLED dark" - }, - "queueClearAll": "Limpar Tudo", - "@queueClearAll": { - "description": "Button - clear all queue items" - }, - "queueClearAllMessage": "Você tem certeza que deseja limpar todos os downloads?", - "@queueClearAllMessage": { - "description": "Clear queue confirmation" - }, - "settingsAutoExportFailed": "Auto-export failed downloads", - "@settingsAutoExportFailed": { - "description": "Setting toggle for auto-export" - }, - "settingsAutoExportFailedSubtitle": "Save failed downloads to TXT file automatically", - "@settingsAutoExportFailedSubtitle": { - "description": "Subtitle for auto-export setting" - }, - "settingsDownloadNetwork": "Download Network", - "@settingsDownloadNetwork": { - "description": "Setting for network type preference" - }, - "settingsDownloadNetworkAny": "WiFi + Mobile Data", - "@settingsDownloadNetworkAny": { - "description": "Network option - use any connection" - }, - "settingsDownloadNetworkWifiOnly": "WiFi Only", - "@settingsDownloadNetworkWifiOnly": { - "description": "Network option - only use WiFi" - }, - "settingsDownloadNetworkSubtitle": "Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.", - "@settingsDownloadNetworkSubtitle": { - "description": "Subtitle explaining network preference" - }, - "albumFolderArtistAlbum": "Artista / Álbum", - "@albumFolderArtistAlbum": { - "description": "Album folder option" - }, - "albumFolderArtistAlbumSubtitle": "Álbuns/Nome do Artista/Nome do Álbum/", - "@albumFolderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "albumFolderArtistYearAlbum": "Artista / [Ano] Álbum", - "@albumFolderArtistYearAlbum": { - "description": "Album folder option with year" - }, - "albumFolderArtistYearAlbumSubtitle": "Álbuns/Nome do Artista/[2005] Nome do Álbum/", - "@albumFolderArtistYearAlbumSubtitle": { - "description": "Folder structure example" - }, - "albumFolderAlbumOnly": "Somente Álbum", - "@albumFolderAlbumOnly": { - "description": "Album folder option" - }, - "albumFolderAlbumOnlySubtitle": "Albums/Nome do Álbum/", - "@albumFolderAlbumOnlySubtitle": { - "description": "Folder structure example" - }, - "albumFolderYearAlbum": "[Ano] Álbum", - "@albumFolderYearAlbum": { - "description": "Album folder option with year" - }, - "albumFolderYearAlbumSubtitle": "Álbuns/[2005] Nome do Álbum/", - "@albumFolderYearAlbumSubtitle": { - "description": "Folder structure example" - }, - "albumFolderArtistAlbumSingles": "Artista / Álbum + Singles", - "@albumFolderArtistAlbumSingles": { - "description": "Album folder option with singles inside artist" - }, - "albumFolderArtistAlbumSinglesSubtitle": "Artista/Álbum/ e Artista/Singles/", - "@albumFolderArtistAlbumSinglesSubtitle": { - "description": "Folder structure example" - }, - "downloadedAlbumDeleteSelected": "Apagar Selecionados", - "@downloadedAlbumDeleteSelected": { - "description": "Button - delete selected tracks" - }, - "downloadedAlbumDeleteMessage": "Excluir {count} {count, plural, one {}=1{faixa} other{faixas}} deste álbum?\n\nIsso também excluirá os arquivos do armazenamento.", - "@downloadedAlbumDeleteMessage": { - "description": "Delete confirmation with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadedAlbumSelectedCount": "{count} selecionado(s)", - "@downloadedAlbumSelectedCount": { - "description": "Selection count indicator", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadedAlbumAllSelected": "Todas as faixas selecionadas", - "@downloadedAlbumAllSelected": { - "description": "Status - all items selected" - }, - "downloadedAlbumTapToSelect": "Toque nas faixas para selecionar", - "@downloadedAlbumTapToSelect": { - "description": "Selection hint" - }, - "downloadedAlbumDeleteCount": "Apagar {count} {count, plural, one {}=1{faixa} other{faixas}}", - "@downloadedAlbumDeleteCount": { - "description": "Delete button text with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadedAlbumSelectToDelete": "Selecione as faixas para apagar", - "@downloadedAlbumSelectToDelete": { - "description": "Placeholder when nothing selected" - }, - "downloadedAlbumDiscHeader": "Disco {discNumber}", - "@downloadedAlbumDiscHeader": { - "description": "Header for disc separator in multi-disc albums", - "placeholders": { - "discNumber": { - "type": "int", - "example": "1" - } - } - }, - "recentTypeArtist": "Artista", - "@recentTypeArtist": { - "description": "Recent access item type - artist" - }, - "recentTypeAlbum": "Álbum", - "@recentTypeAlbum": { - "description": "Recent access item type - album" - }, - "recentTypeSong": "Música", - "@recentTypeSong": { - "description": "Recent access item type - song/track" - }, - "recentTypePlaylist": "Playlist", - "@recentTypePlaylist": { - "description": "Recent access item type - playlist" - }, - "recentEmpty": "No recent items yet", - "@recentEmpty": { - "description": "Empty state text for recent access list" - }, - "recentShowAllDownloads": "Show All Downloads", - "@recentShowAllDownloads": { - "description": "Button label to unhide hidden downloads in recent access" - }, - "recentPlaylistInfo": "Playlist: {name}", - "@recentPlaylistInfo": { - "description": "Snackbar message when tapping playlist in recent access", - "placeholders": { - "name": { - "type": "String", - "description": "Playlist name" - } - } - }, - "discographyDownload": "Baixar Discografia", - "@discographyDownload": { - "description": "Button - download artist discography" - }, - "discographyDownloadAll": "Baixar Tudo", - "@discographyDownloadAll": { - "description": "Option - download entire discography" - }, - "discographyDownloadAllSubtitle": "{count} faixas de {albumCount} lançamentos", - "@discographyDownloadAllSubtitle": { - "description": "Subtitle showing total tracks and albums", - "placeholders": { - "count": { - "type": "int" - }, - "albumCount": { - "type": "int" - } - } - }, - "discographyAlbumsOnly": "Somente Álbuns", - "@discographyAlbumsOnly": { - "description": "Option - download only albums" - }, - "discographyAlbumsOnlySubtitle": "{count} faixas de {albumCount} álbuns", - "@discographyAlbumsOnlySubtitle": { - "description": "Subtitle showing album tracks count", - "placeholders": { - "count": { - "type": "int" - }, - "albumCount": { - "type": "int" - } - } - }, - "discographySinglesOnly": "Somente Singles e EPs", - "@discographySinglesOnly": { - "description": "Option - download only singles" - }, - "discographySinglesOnlySubtitle": "{count} faixas de {albumCount} singles", - "@discographySinglesOnlySubtitle": { - "description": "Subtitle showing singles tracks count", - "placeholders": { - "count": { - "type": "int" - }, - "albumCount": { - "type": "int" - } - } - }, - "discographySelectAlbums": "Selecione Álbuns...", - "@discographySelectAlbums": { - "description": "Option - manually select albums to download" - }, - "discographySelectAlbumsSubtitle": "Escolher álbuns ou singles específicos", - "@discographySelectAlbumsSubtitle": { - "description": "Subtitle for select albums option" - }, - "discographyFetchingTracks": "Buscando faixas...", - "@discographyFetchingTracks": { - "description": "Progress - fetching album tracks" - }, - "discographyFetchingAlbum": "Buscando {current} de {total}...", - "@discographyFetchingAlbum": { - "description": "Progress - fetching specific album", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } - }, - "discographySelectedCount": "{count} selecionado(s)", - "@discographySelectedCount": { - "description": "Selection count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "discographyDownloadSelected": "Baixar Selecionados", - "@discographyDownloadSelected": { - "description": "Button - download selected albums" - }, - "discographyAddedToQueue": "{count} faixas adicionadas à fila", - "@discographyAddedToQueue": { - "description": "Snackbar - tracks added from discography", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "discographySkippedDownloaded": "{added} adicionada(s), {skipped} já baixada(s)", - "@discographySkippedDownloaded": { - "description": "Snackbar - with skipped tracks count", - "placeholders": { - "added": { - "type": "int" - }, - "skipped": { - "type": "int" - } - } - }, - "discographyNoAlbums": "Nenhum álbum disponível", - "@discographyNoAlbums": { - "description": "Error - no albums found for artist" - }, - "discographyFailedToFetch": "Falha ao obter alguns álbuns", - "@discographyFailedToFetch": { - "description": "Error - some albums failed to load" - }, - "sectionStorageAccess": "Storage Access", - "@sectionStorageAccess": { - "description": "Section header for storage access settings" - }, - "allFilesAccess": "All Files Access", - "@allFilesAccess": { - "description": "Toggle for MANAGE_EXTERNAL_STORAGE permission" - }, - "allFilesAccessEnabledSubtitle": "Can write to any folder", - "@allFilesAccessEnabledSubtitle": { - "description": "Subtitle when all files access is enabled" - }, - "allFilesAccessDisabledSubtitle": "Limited to media folders only", - "@allFilesAccessDisabledSubtitle": { - "description": "Subtitle when all files access is disabled" - }, - "allFilesAccessDescription": "Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.", - "@allFilesAccessDescription": { - "description": "Description explaining when to enable all files access" - }, - "allFilesAccessDeniedMessage": "Permission was denied. Please enable 'All files access' manually in system settings.", - "@allFilesAccessDeniedMessage": { - "description": "Message when permission is permanently denied" - }, - "allFilesAccessDisabledMessage": "All Files Access disabled. The app will use limited storage access.", - "@allFilesAccessDisabledMessage": { - "description": "Snackbar message when user disables all files access" - }, - "settingsLocalLibrary": "Local Library", - "@settingsLocalLibrary": { - "description": "Settings menu item - local library" - }, - "settingsLocalLibrarySubtitle": "Scan music & detect duplicates", - "@settingsLocalLibrarySubtitle": { - "description": "Subtitle for local library settings" - }, - "settingsCache": "Storage & Cache", - "@settingsCache": { - "description": "Settings menu item - cache management" - }, - "settingsCacheSubtitle": "View size and clear cached data", - "@settingsCacheSubtitle": { - "description": "Subtitle for cache management menu" - }, - "libraryTitle": "Local Library", - "@libraryTitle": { - "description": "Library settings page title" - }, - "libraryScanSettings": "Scan Settings", - "@libraryScanSettings": { - "description": "Section header for scan settings" - }, - "libraryEnableLocalLibrary": "Enable Local Library", - "@libraryEnableLocalLibrary": { - "description": "Toggle to enable library scanning" - }, - "libraryEnableLocalLibrarySubtitle": "Scan and track your existing music", - "@libraryEnableLocalLibrarySubtitle": { - "description": "Subtitle for enable toggle" - }, - "libraryFolder": "Library Folder", - "@libraryFolder": { - "description": "Folder selection setting" - }, - "libraryFolderHint": "Tap to select folder", - "@libraryFolderHint": { - "description": "Placeholder when no folder selected" - }, - "libraryShowDuplicateIndicator": "Show Duplicate Indicator", - "@libraryShowDuplicateIndicator": { - "description": "Toggle for duplicate indicator in search" - }, - "libraryShowDuplicateIndicatorSubtitle": "Show when searching for existing tracks", - "@libraryShowDuplicateIndicatorSubtitle": { - "description": "Subtitle for duplicate indicator toggle" - }, - "libraryActions": "Actions", - "@libraryActions": { - "description": "Section header for library actions" - }, - "libraryScan": "Scan Library", - "@libraryScan": { - "description": "Button to start library scan" - }, - "libraryScanSubtitle": "Scan for audio files", - "@libraryScanSubtitle": { - "description": "Subtitle for scan button" - }, - "libraryScanSelectFolderFirst": "Select a folder first", - "@libraryScanSelectFolderFirst": { - "description": "Message when trying to scan without folder" - }, - "libraryCleanupMissingFiles": "Cleanup Missing Files", - "@libraryCleanupMissingFiles": { - "description": "Button to remove entries for missing files" - }, - "libraryCleanupMissingFilesSubtitle": "Remove entries for files that no longer exist", - "@libraryCleanupMissingFilesSubtitle": { - "description": "Subtitle for cleanup button" - }, - "libraryClear": "Clear Library", - "@libraryClear": { - "description": "Button to clear all library entries" - }, - "libraryClearSubtitle": "Remove all scanned tracks", - "@libraryClearSubtitle": { - "description": "Subtitle for clear button" - }, - "libraryClearConfirmTitle": "Clear Library", - "@libraryClearConfirmTitle": { - "description": "Dialog title for clear confirmation" - }, - "libraryClearConfirmMessage": "This will remove all scanned tracks from your library. Your actual music files will not be deleted.", - "@libraryClearConfirmMessage": { - "description": "Dialog message for clear confirmation" - }, - "libraryAbout": "About Local Library", - "@libraryAbout": { - "description": "Section header for about info" - }, - "libraryAboutDescription": "Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.", - "@libraryAboutDescription": { - "description": "Description of local library feature" - }, - "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", - "@libraryTracksUnit": { - "description": "Unit label for tracks count (without the number itself)", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "libraryLastScanned": "Last scanned: {time}", - "@libraryLastScanned": { - "description": "Last scan time display", - "placeholders": { - "time": { - "type": "String" - } - } - }, - "libraryLastScannedNever": "Never", - "@libraryLastScannedNever": { - "description": "Shown when library has never been scanned" - }, - "libraryScanning": "Scanning...", - "@libraryScanning": { - "description": "Status during scan" - }, - "libraryScanProgress": "{progress}% of {total} files", - "@libraryScanProgress": { - "description": "Scan progress display", - "placeholders": { - "progress": { - "type": "String" - }, - "total": { - "type": "int" - } - } - }, - "libraryInLibrary": "In Library", - "@libraryInLibrary": { - "description": "Badge shown on tracks that exist in local library" - }, - "libraryRemovedMissingFiles": "Removed {count} missing files from library", - "@libraryRemovedMissingFiles": { - "description": "Snackbar after cleanup", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "libraryCleared": "Library cleared", - "@libraryCleared": { - "description": "Snackbar after clearing library" - }, - "libraryStorageAccessRequired": "Storage Access Required", - "@libraryStorageAccessRequired": { - "description": "Dialog title for storage permission" - }, - "libraryStorageAccessMessage": "SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.", - "@libraryStorageAccessMessage": { - "description": "Dialog message for storage permission" - }, - "libraryFolderNotExist": "Selected folder does not exist", - "@libraryFolderNotExist": { - "description": "Error when folder doesn't exist" - }, - "librarySourceDownloaded": "Downloaded", - "@librarySourceDownloaded": { - "description": "Badge for tracks downloaded via SpotiFLAC" - }, - "librarySourceLocal": "Local", - "@librarySourceLocal": { - "description": "Badge for tracks from local library scan" - }, - "libraryFilterAll": "All", - "@libraryFilterAll": { - "description": "Filter chip - show all library items" - }, - "libraryFilterDownloaded": "Downloaded", - "@libraryFilterDownloaded": { - "description": "Filter chip - show only downloaded items" - }, - "libraryFilterLocal": "Local", - "@libraryFilterLocal": { - "description": "Filter chip - show only local library items" - }, - "libraryFilterTitle": "Filters", - "@libraryFilterTitle": { - "description": "Filter bottom sheet title" - }, - "libraryFilterReset": "Reset", - "@libraryFilterReset": { - "description": "Reset all filters button" - }, - "libraryFilterApply": "Apply", - "@libraryFilterApply": { - "description": "Apply filters button" - }, - "libraryFilterSource": "Source", - "@libraryFilterSource": { - "description": "Filter section - source type" - }, - "libraryFilterQuality": "Quality", - "@libraryFilterQuality": { - "description": "Filter section - audio quality" - }, - "libraryFilterQualityHiRes": "Hi-Res (24bit)", - "@libraryFilterQualityHiRes": { - "description": "Filter option - high resolution audio" - }, - "libraryFilterQualityCD": "CD (16bit)", - "@libraryFilterQualityCD": { - "description": "Filter option - CD quality audio" - }, - "libraryFilterQualityLossy": "Lossy", - "@libraryFilterQualityLossy": { - "description": "Filter option - lossy compressed audio" - }, - "libraryFilterFormat": "Format", - "@libraryFilterFormat": { - "description": "Filter section - file format" - }, - "libraryFilterSort": "Sort", - "@libraryFilterSort": { - "description": "Filter section - sort order" - }, - "libraryFilterSortLatest": "Latest", - "@libraryFilterSortLatest": { - "description": "Sort option - newest first" - }, - "libraryFilterSortOldest": "Oldest", - "@libraryFilterSortOldest": { - "description": "Sort option - oldest first" - }, - "timeJustNow": "Just now", - "@timeJustNow": { - "description": "Relative time - less than a minute ago" - }, - "timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}", - "@timeMinutesAgo": { - "description": "Relative time - minutes ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}", - "@timeHoursAgo": { - "description": "Relative time - hours ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", - "@tutorialWelcomeTitle": { - "description": "Tutorial welcome page title" - }, - "tutorialWelcomeDesc": "Let's learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.", - "@tutorialWelcomeDesc": { - "description": "Tutorial welcome page description" - }, - "tutorialWelcomeTip1": "Download music from Spotify, Deezer, or paste any supported URL", - "@tutorialWelcomeTip1": { - "description": "Tutorial welcome tip 1" - }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", - "@tutorialWelcomeTip2": { - "description": "Tutorial welcome tip 2" - }, - "tutorialWelcomeTip3": "Automatic metadata, cover art, and lyrics embedding", - "@tutorialWelcomeTip3": { - "description": "Tutorial welcome tip 3" - }, - "tutorialSearchTitle": "Finding Music", - "@tutorialSearchTitle": { - "description": "Tutorial search page title" - }, - "tutorialSearchDesc": "There are two easy ways to find music you want to download.", - "@tutorialSearchDesc": { - "description": "Tutorial search page description" - }, - "tutorialDownloadTitle": "Downloading Music", - "@tutorialDownloadTitle": { - "description": "Tutorial download page title" - }, - "tutorialDownloadDesc": "Downloading music is simple and fast. Here's how it works.", - "@tutorialDownloadDesc": { - "description": "Tutorial download page description" - }, - "tutorialLibraryTitle": "Your Library", - "@tutorialLibraryTitle": { - "description": "Tutorial library page title" - }, - "tutorialLibraryDesc": "All your downloaded music is organized in the Library tab.", - "@tutorialLibraryDesc": { - "description": "Tutorial library page description" - }, - "tutorialLibraryTip1": "View download progress and queue in the Library tab", - "@tutorialLibraryTip1": { - "description": "Tutorial library tip 1" - }, - "tutorialLibraryTip2": "Tap any track to play it with your music player", - "@tutorialLibraryTip2": { - "description": "Tutorial library tip 2" - }, - "tutorialLibraryTip3": "Switch between list and grid view for better browsing", - "@tutorialLibraryTip3": { - "description": "Tutorial library tip 3" - }, - "tutorialExtensionsTitle": "Extensions", - "@tutorialExtensionsTitle": { - "description": "Tutorial extensions page title" - }, - "tutorialExtensionsDesc": "Extend the app's capabilities with community extensions.", - "@tutorialExtensionsDesc": { - "description": "Tutorial extensions page description" - }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", - "@tutorialExtensionsTip1": { - "description": "Tutorial extensions tip 1" - }, - "tutorialExtensionsTip2": "Add new download providers or search sources", - "@tutorialExtensionsTip2": { - "description": "Tutorial extensions tip 2" - }, - "tutorialExtensionsTip3": "Get lyrics, enhanced metadata, and more features", - "@tutorialExtensionsTip3": { - "description": "Tutorial extensions tip 3" - }, - "tutorialSettingsTitle": "Customize Your Experience", - "@tutorialSettingsTitle": { - "description": "Tutorial settings page title" - }, - "tutorialSettingsDesc": "Personalize the app in Settings to match your preferences.", - "@tutorialSettingsDesc": { - "description": "Tutorial settings page description" - }, - "tutorialSettingsTip1": "Change download location and folder organization", - "@tutorialSettingsTip1": { - "description": "Tutorial settings tip 1" - }, - "tutorialSettingsTip2": "Set default audio quality and format preferences", - "@tutorialSettingsTip2": { - "description": "Tutorial settings tip 2" - }, - "tutorialSettingsTip3": "Customize app theme and appearance", - "@tutorialSettingsTip3": { - "description": "Tutorial settings tip 3" - }, - "tutorialReadyMessage": "You're all set! Start downloading your favorite music now.", - "@tutorialReadyMessage": { - "description": "Tutorial completion message" - }, - "libraryForceFullScan": "Force Full Scan", - "@libraryForceFullScan": { - "description": "Button to force a complete rescan of library" - }, - "libraryForceFullScanSubtitle": "Rescan all files, ignoring cache", - "@libraryForceFullScanSubtitle": { - "description": "Subtitle for force full scan button" - }, - "cleanupOrphanedDownloads": "Cleanup Orphaned Downloads", - "@cleanupOrphanedDownloads": { - "description": "Button to remove history entries for deleted files" - }, - "cleanupOrphanedDownloadsSubtitle": "Remove history entries for files that no longer exist", - "@cleanupOrphanedDownloadsSubtitle": { - "description": "Subtitle for orphaned cleanup button" - }, - "cleanupOrphanedDownloadsResult": "Removed {count} orphaned entries from history", - "@cleanupOrphanedDownloadsResult": { - "description": "Snackbar after orphan cleanup", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "cleanupOrphanedDownloadsNone": "No orphaned entries found", - "@cleanupOrphanedDownloadsNone": { - "description": "Snackbar when no orphans found" - }, - "cacheTitle": "Storage & Cache", - "@cacheTitle": { - "description": "Cache management page title" - }, - "cacheSummaryTitle": "Cache overview", - "@cacheSummaryTitle": { - "description": "Heading for cache summary card" - }, - "cacheSummarySubtitle": "Clearing cache will not remove downloaded music files.", - "@cacheSummarySubtitle": { - "description": "Helper text for cache summary card" - }, - "cacheEstimatedTotal": "Estimated cache usage: {size}", - "@cacheEstimatedTotal": { - "description": "Total cache size shown in summary", - "placeholders": { - "size": { - "type": "String" - } - } - }, - "cacheSectionStorage": "Cached Data", - "@cacheSectionStorage": { - "description": "Section header for cache entries" - }, - "cacheSectionMaintenance": "Maintenance", - "@cacheSectionMaintenance": { - "description": "Section header for cleanup actions" - }, - "cacheAppDirectory": "App cache directory", - "@cacheAppDirectory": { - "description": "Cache item title for app cache directory" - }, - "cacheAppDirectoryDesc": "HTTP responses, WebView data, and other temporary app data.", - "@cacheAppDirectoryDesc": { - "description": "Description of what app cache directory contains" - }, - "cacheTempDirectory": "Temporary directory", - "@cacheTempDirectory": { - "description": "Cache item title for temporary files directory" - }, - "cacheTempDirectoryDesc": "Temporary files from downloads and audio conversion.", - "@cacheTempDirectoryDesc": { - "description": "Description of what temporary directory contains" - }, - "cacheCoverImage": "Cover image cache", - "@cacheCoverImage": { - "description": "Cache item title for persistent cover images" - }, - "cacheCoverImageDesc": "Downloaded album and track cover art. Will re-download when viewed.", - "@cacheCoverImageDesc": { - "description": "Description of what cover image cache contains" - }, - "cacheLibraryCover": "Library cover cache", - "@cacheLibraryCover": { - "description": "Cache item title for local library cover art images" - }, - "cacheLibraryCoverDesc": "Cover art extracted from local music files. Will re-extract on next scan.", - "@cacheLibraryCoverDesc": { - "description": "Description of what library cover cache contains" - }, - "cacheExploreFeed": "Explore feed cache", - "@cacheExploreFeed": { - "description": "Cache item title for explore home feed cache" - }, - "cacheExploreFeedDesc": "Explore tab content (new releases, trending). Will refresh on next visit.", - "@cacheExploreFeedDesc": { - "description": "Description of what explore feed cache contains" - }, - "cacheTrackLookup": "Track lookup cache", - "@cacheTrackLookup": { - "description": "Cache item title for track ID lookup cache" - }, - "cacheTrackLookupDesc": "Spotify/Deezer track ID lookups. Clearing may slow next few searches.", - "@cacheTrackLookupDesc": { - "description": "Description of what track lookup cache contains" - }, - "cacheCleanupUnusedDesc": "Remove orphaned download history and library entries for missing files.", - "@cacheCleanupUnusedDesc": { - "description": "Description of what cleanup unused data does" - }, - "cacheNoData": "No cached data", - "@cacheNoData": { - "description": "Label when cache category has no data" - }, - "cacheSizeWithFiles": "{size} in {count} files", - "@cacheSizeWithFiles": { - "description": "Cache size and file count", - "placeholders": { - "size": { - "type": "String" - }, - "count": { - "type": "int" - } - } - }, - "cacheSizeOnly": "{size}", - "@cacheSizeOnly": { - "description": "Cache size only", - "placeholders": { - "size": { - "type": "String" - } - } - }, - "cacheEntries": "{count} entries", - "@cacheEntries": { - "description": "Track cache entry count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "cacheClearSuccess": "Cleared: {target}", - "@cacheClearSuccess": { - "description": "Snackbar after clearing selected cache", - "placeholders": { - "target": { - "type": "String" - } - } - }, - "cacheClearConfirmTitle": "Clear cache?", - "@cacheClearConfirmTitle": { - "description": "Dialog title before clearing one cache category" - }, - "cacheClearConfirmMessage": "This will clear cached data for {target}. Downloaded music files will not be deleted.", - "@cacheClearConfirmMessage": { - "description": "Dialog message before clearing selected cache", - "placeholders": { - "target": { - "type": "String" - } - } - }, - "cacheClearAllConfirmTitle": "Clear all cache?", - "@cacheClearAllConfirmTitle": { - "description": "Dialog title before clearing all caches" - }, - "cacheClearAllConfirmMessage": "This will clear all cache categories on this page. Downloaded music files will not be deleted.", - "@cacheClearAllConfirmMessage": { - "description": "Dialog message before clearing all caches" - }, - "cacheClearAll": "Clear all cache", - "@cacheClearAll": { - "description": "Button label to clear all caches" - }, - "cacheCleanupUnused": "Cleanup unused data", - "@cacheCleanupUnused": { - "description": "Action title for cleaning unused entries" - }, - "cacheCleanupUnusedSubtitle": "Remove orphaned download history and missing library entries", - "@cacheCleanupUnusedSubtitle": { - "description": "Subtitle for cleanup unused data action" - }, - "cacheCleanupResult": "Cleanup completed: {downloadCount} orphaned downloads, {libraryCount} missing library entries", - "@cacheCleanupResult": { - "description": "Snackbar after unused data cleanup", - "placeholders": { - "downloadCount": { - "type": "int" - }, - "libraryCount": { - "type": "int" - } - } - }, - "cacheRefreshStats": "Refresh stats", - "@cacheRefreshStats": { - "description": "Button label to refresh cache statistics" - }, - "trackSaveCoverArt": "Save Cover Art", - "@trackSaveCoverArt": { - "description": "Menu action - save album cover art as file" - }, - "trackSaveCoverArtSubtitle": "Save album art as .jpg file", - "@trackSaveCoverArtSubtitle": { - "description": "Subtitle for save cover art action" - }, - "trackSaveLyrics": "Save Lyrics (.lrc)", - "@trackSaveLyrics": { - "description": "Menu action - save lyrics as .lrc file" - }, - "trackSaveLyricsSubtitle": "Fetch and save lyrics as .lrc file", - "@trackSaveLyricsSubtitle": { - "description": "Subtitle for save lyrics action" - }, - "trackSaveLyricsProgress": "Saving lyrics...", - "@trackSaveLyricsProgress": { - "description": "Snackbar while saving lyrics to file" - }, - "trackReEnrich": "Re-enrich", - "@trackReEnrich": { - "description": "Menu action - re-embed metadata into audio file" - }, - "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", - "@trackReEnrichOnlineSubtitle": { - "description": "Subtitle for re-enrich metadata action for local items" - }, - "trackEditMetadata": "Edit Metadata", - "@trackEditMetadata": { - "description": "Menu action - edit embedded metadata" - }, - "trackCoverSaved": "Cover art saved to {fileName}", - "@trackCoverSaved": { - "description": "Snackbar after cover art saved", - "placeholders": { - "fileName": { - "type": "String" - } - } - }, - "trackCoverNoSource": "No cover art source available", - "@trackCoverNoSource": { - "description": "Snackbar when no cover art URL or embedded cover" - }, - "trackLyricsSaved": "Lyrics saved to {fileName}", - "@trackLyricsSaved": { - "description": "Snackbar after lyrics saved", - "placeholders": { - "fileName": { - "type": "String" - } - } - }, - "trackReEnrichProgress": "Re-enriching metadata...", - "@trackReEnrichProgress": { - "description": "Snackbar while re-enriching metadata" - }, - "trackReEnrichSearching": "Searching metadata online...", - "@trackReEnrichSearching": { - "description": "Snackbar while searching metadata from internet for local items" - }, - "trackReEnrichSuccess": "Metadata re-enriched successfully", - "@trackReEnrichSuccess": { - "description": "Snackbar after successful re-enrichment" - }, - "trackReEnrichFfmpegFailed": "FFmpeg metadata embed failed", - "@trackReEnrichFfmpegFailed": { - "description": "Snackbar when FFmpeg embed fails for MP3/Opus" - }, - "trackSaveFailed": "Failed: {error}", - "@trackSaveFailed": { - "description": "Snackbar when save operation fails", - "placeholders": { - "error": { - "type": "String" - } - } - }, - "trackConvertFormat": "Convert Format", - "@trackConvertFormat": { - "description": "Menu item - convert audio format" - }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", - "@trackConvertFormatSubtitle": { - "description": "Subtitle for convert format menu item" - }, - "trackConvertTitle": "Convert Audio", - "@trackConvertTitle": { - "description": "Title of convert bottom sheet" - }, - "trackConvertTargetFormat": "Target Format", - "@trackConvertTargetFormat": { - "description": "Label for format selection" - }, - "trackConvertBitrate": "Bitrate", - "@trackConvertBitrate": { - "description": "Label for bitrate selection" - }, - "trackConvertConfirmTitle": "Confirm Conversion", - "@trackConvertConfirmTitle": { - "description": "Confirmation dialog title" - }, - "trackConvertConfirmMessage": "Convert from {sourceFormat} to {targetFormat} at {bitrate}?\n\nThe original file will be deleted after conversion.", - "@trackConvertConfirmMessage": { - "description": "Confirmation dialog message", - "placeholders": { - "sourceFormat": { - "type": "String" - }, - "targetFormat": { - "type": "String" - }, - "bitrate": { - "type": "String" - } - } - }, - "trackConvertConverting": "Converting audio...", - "@trackConvertConverting": { - "description": "Snackbar while converting" - }, - "trackConvertSuccess": "Converted to {format} successfully", - "@trackConvertSuccess": { - "description": "Snackbar after successful conversion", - "placeholders": { - "format": { - "type": "String" - } - } - }, - "trackConvertFailed": "Conversion failed", - "@trackConvertFailed": { - "description": "Snackbar when conversion fails" - }, - "actionCreate": "Create", - "@actionCreate": { - "description": "Generic action button - create" - }, - "collectionFoldersTitle": "My folders", - "@collectionFoldersTitle": { - "description": "Library section title for custom folders" - }, - "collectionWishlist": "Wishlist", - "@collectionWishlist": { - "description": "Custom folder for saved tracks to download later" - }, - "collectionLoved": "Loved", - "@collectionLoved": { - "description": "Custom folder for favorite tracks" - }, - "collectionPlaylists": "Playlists", - "@collectionPlaylists": { - "description": "Custom user playlists folder" - }, - "collectionPlaylist": "Playlist", - "@collectionPlaylist": { - "description": "Single playlist label" - }, - "collectionAddToPlaylist": "Add to playlist", - "@collectionAddToPlaylist": { - "description": "Action to add a track to user playlist" - }, - "collectionCreatePlaylist": "Create playlist", - "@collectionCreatePlaylist": { - "description": "Action to create a new playlist" - }, - "collectionNoPlaylistsYet": "No playlists yet", - "@collectionNoPlaylistsYet": { - "description": "Empty state title when user has no playlists" - }, - "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", - "@collectionNoPlaylistsSubtitle": { - "description": "Empty state subtitle when user has no playlists" - }, - "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", - "@collectionPlaylistTracks": { - "description": "Track count label for custom playlists", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "collectionAddedToPlaylist": "Added to \"{playlistName}\"", - "@collectionAddedToPlaylist": { - "description": "Snackbar after adding track to playlist", - "placeholders": { - "playlistName": { - "type": "String" - } - } - }, - "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", - "@collectionAlreadyInPlaylist": { - "description": "Snackbar when track already exists in playlist", - "placeholders": { - "playlistName": { - "type": "String" - } - } - }, - "collectionPlaylistCreated": "Playlist created", - "@collectionPlaylistCreated": { - "description": "Snackbar after creating playlist" - }, - "collectionPlaylistNameHint": "Playlist name", - "@collectionPlaylistNameHint": { - "description": "Hint text for playlist name input" - }, - "collectionPlaylistNameRequired": "Playlist name is required", - "@collectionPlaylistNameRequired": { - "description": "Validation error for empty playlist name" - }, - "collectionRenamePlaylist": "Rename playlist", - "@collectionRenamePlaylist": { - "description": "Action to rename playlist" - }, - "collectionDeletePlaylist": "Delete playlist", - "@collectionDeletePlaylist": { - "description": "Action to delete playlist" - }, - "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", - "@collectionDeletePlaylistMessage": { - "description": "Confirmation message for deleting playlist", - "placeholders": { - "playlistName": { - "type": "String" - } - } - }, - "collectionPlaylistDeleted": "Playlist deleted", - "@collectionPlaylistDeleted": { - "description": "Snackbar after deleting playlist" - }, - "collectionPlaylistRenamed": "Playlist renamed", - "@collectionPlaylistRenamed": { - "description": "Snackbar after renaming playlist" - }, - "collectionWishlistEmptyTitle": "Wishlist is empty", - "@collectionWishlistEmptyTitle": { - "description": "Wishlist empty state title" - }, - "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", - "@collectionWishlistEmptySubtitle": { - "description": "Wishlist empty state subtitle" - }, - "collectionLovedEmptyTitle": "Loved folder is empty", - "@collectionLovedEmptyTitle": { - "description": "Loved empty state title" - }, - "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", - "@collectionLovedEmptySubtitle": { - "description": "Loved empty state subtitle" - }, - "collectionPlaylistEmptyTitle": "Playlist is empty", - "@collectionPlaylistEmptyTitle": { - "description": "Playlist empty state title" - }, - "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", - "@collectionPlaylistEmptySubtitle": { - "description": "Playlist empty state subtitle" - }, - "collectionRemoveFromPlaylist": "Remove from playlist", - "@collectionRemoveFromPlaylist": { - "description": "Tooltip for removing track from playlist" - }, - "collectionRemoveFromFolder": "Remove from folder", - "@collectionRemoveFromFolder": { - "description": "Tooltip for removing track from wishlist/loved folder" - }, - "collectionRemoved": "\"{trackName}\" removed", - "@collectionRemoved": { - "description": "Snackbar after removing a track from a collection", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "collectionAddedToLoved": "\"{trackName}\" added to Loved", - "@collectionAddedToLoved": { - "description": "Snackbar after adding track to loved folder", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", - "@collectionRemovedFromLoved": { - "description": "Snackbar after removing track from loved folder", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", - "@collectionAddedToWishlist": { - "description": "Snackbar after adding track to wishlist", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", - "@collectionRemovedFromWishlist": { - "description": "Snackbar after removing track from wishlist", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "trackOptionAddToLoved": "Add to Loved", - "@trackOptionAddToLoved": { - "description": "Bottom sheet action label - add track to loved folder" - }, - "trackOptionRemoveFromLoved": "Remove from Loved", - "@trackOptionRemoveFromLoved": { - "description": "Bottom sheet action label - remove track from loved folder" - }, - "trackOptionAddToWishlist": "Add to Wishlist", - "@trackOptionAddToWishlist": { - "description": "Bottom sheet action label - add track to wishlist" - }, - "trackOptionRemoveFromWishlist": "Remove from Wishlist", - "@trackOptionRemoveFromWishlist": { - "description": "Bottom sheet action label - remove track from wishlist" - }, - "collectionPlaylistChangeCover": "Change cover image", - "@collectionPlaylistChangeCover": { - "description": "Bottom sheet action to pick a custom cover image for a playlist" - }, - "collectionPlaylistRemoveCover": "Remove cover image", - "@collectionPlaylistRemoveCover": { - "description": "Bottom sheet action to remove custom cover image from a playlist" - }, - "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", - "@selectionShareCount": { - "description": "Share button text with count in selection mode", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "selectionShareNoFiles": "No shareable files found", - "@selectionShareNoFiles": { - "description": "Snackbar when no selected files exist on disk" - }, - "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", - "@selectionConvertCount": { - "description": "Convert button text with count in selection mode", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "selectionConvertNoConvertible": "No convertible tracks selected", - "@selectionConvertNoConvertible": { - "description": "Snackbar when no selected tracks support conversion" - }, - "selectionBatchConvertConfirmTitle": "Batch Convert", - "@selectionBatchConvertConfirmTitle": { - "description": "Confirmation dialog title for batch conversion" - }, - "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", - "@selectionBatchConvertConfirmMessage": { - "description": "Confirmation dialog message for batch conversion", - "placeholders": { - "count": { - "type": "int" - }, - "format": { - "type": "String" - }, - "bitrate": { - "type": "String" - } - } - }, - "selectionBatchConvertProgress": "Converting {current} of {total}...", - "@selectionBatchConvertProgress": { - "description": "Snackbar during batch conversion progress", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } - }, - "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", - "@selectionBatchConvertSuccess": { - "description": "Snackbar after batch conversion completes", - "placeholders": { - "success": { - "type": "int" - }, - "total": { - "type": "int" - }, - "format": { - "type": "String" - } - } - }, - "downloadedAlbumDownloadedCount": "{count} baixado(s)", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - } -} \ No newline at end of file diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 877c925e..10f1fce3 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -728,7 +728,7 @@ "@dialogDeleteSelectedTitle": { "description": "Dialog title - delete selected items" }, - "dialogDeleteSelectedMessage": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}} из истории?\n\nЭто также удалит файлы из хранилища.", + "dialogDeleteSelectedMessage": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} other{треков}} из истории?\n\nЭто также удалит файлы из хранилища.", "@dialogDeleteSelectedMessage": { "description": "Dialog message - delete selected tracks", "placeholders": { @@ -807,7 +807,7 @@ "@snackbarCredentialsCleared": { "description": "Snackbar - Spotify credentials removed" }, - "snackbarDeletedTracks": "Удалено {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", + "snackbarDeletedTracks": "Удалено {count} {count, plural, one {трек} few {трека} many {треков} other{треков}}", "@snackbarDeletedTracks": { "description": "Snackbar - tracks deleted", "placeholders": { @@ -1350,7 +1350,7 @@ } } }, - "tracksCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}", + "tracksCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} other {{count} треков}}", "@tracksCount": { "description": "Track count display", "placeholders": { @@ -1889,7 +1889,7 @@ "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" }, - "downloadedAlbumDeleteMessage": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}} из этого альбома?\n\nЭто также удалит файлы из хранилища.", + "downloadedAlbumDeleteMessage": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} other{треков}} из этого альбома?\n\nЭто также удалит файлы из хранилища.", "@downloadedAlbumDeleteMessage": { "description": "Delete confirmation with count", "placeholders": { @@ -1915,7 +1915,7 @@ "@downloadedAlbumTapToSelect": { "description": "Selection hint" }, - "downloadedAlbumDeleteCount": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", + "downloadedAlbumDeleteCount": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} other{треков}}", "@downloadedAlbumDeleteCount": { "description": "Delete button text with count", "placeholders": { @@ -2214,7 +2214,7 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksUnit": "{count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", + "libraryTracksUnit": "{count, plural, one {трек} few {трека} many {треков} other{треков}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", "placeholders": { @@ -2353,7 +2353,7 @@ "@timeJustNow": { "description": "Relative time - less than a minute ago" }, - "timeMinutesAgo": "{count, plural, one {{count} минуту} few {{count} минуты} many {{count} минут} =1 {1 минуту} other {{count} минут}} назад", + "timeMinutesAgo": "{count, plural, one {{count} минуту} few {{count} минуты} many {{count} минут} other {{count} минут}} назад", "@timeMinutesAgo": { "description": "Relative time - minutes ago", "placeholders": { @@ -2362,7 +2362,7 @@ } } }, - "timeHoursAgo": "{count, plural, one {{count} час} few {{count} часа} many {{count} часов} =1 {1 час} other {{count} часов}} назад", + "timeHoursAgo": "{count, plural, one {{count} час} few {{count} часа} many {{count} часов} other {{count} часов}} назад", "@timeHoursAgo": { "description": "Relative time - hours ago", "placeholders": { @@ -2848,7 +2848,7 @@ "@collectionNoPlaylistsSubtitle": { "description": "Empty state subtitle when user has no playlists" }, - "collectionPlaylistTracks": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}", + "collectionPlaylistTracks": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} other {{count} треков}}", "@collectionPlaylistTracks": { "description": "Track count label for custom playlists", "placeholders": { @@ -3013,7 +3013,7 @@ "@collectionPlaylistRemoveCover": { "description": "Bottom sheet action to remove custom cover image from a playlist" }, - "selectionShareCount": "Отправить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", + "selectionShareCount": "Отправить {count} {count, plural, one {трек} few {трека} many {треков} other{треков}}", "@selectionShareCount": { "description": "Share button text with count in selection mode", "placeholders": { @@ -3026,7 +3026,7 @@ "@selectionShareNoFiles": { "description": "Snackbar when no selected files exist on disk" }, - "selectionConvertCount": "Конвертировать {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", + "selectionConvertCount": "Конвертировать {count} {count, plural, one {трек} few {трека} many {треков} other{треков}}", "@selectionConvertCount": { "description": "Convert button text with count in selection mode", "placeholders": { diff --git a/lib/l10n/arb/app_tr-TR.arb b/lib/l10n/arb/app_tr-TR.arb deleted file mode 100644 index 3e87884e..00000000 --- a/lib/l10n/arb/app_tr-TR.arb +++ /dev/null @@ -1,3105 +0,0 @@ -{ - "@@locale": "tr", - "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", - "@appName": { - "description": "App name - DO NOT TRANSLATE" - }, - "navHome": "Ara", - "@navHome": { - "description": "Bottom navigation - Home tab" - }, - "navLibrary": "Library", - "@navLibrary": { - "description": "Bottom navigation - Library tab" - }, - "navSettings": "Ayarlar", - "@navSettings": { - "description": "Bottom navigation - Settings tab" - }, - "navStore": "Dükkan", - "@navStore": { - "description": "Bottom navigation - Extension store tab" - }, - "homeTitle": "Ara", - "@homeTitle": { - "description": "Home screen title" - }, - "homeSubtitle": "Spotify linki yapıştır veya isimle arat", - "@homeSubtitle": { - "description": "Subtitle shown below search box" - }, - "homeSupports": "Desteklenen linkler: Şarkı, Albüm, Çalma Listesi, Sanatçı linkleri", - "@homeSupports": { - "description": "Info text about supported URL types" - }, - "homeRecent": "En son", - "@homeRecent": { - "description": "Section header for recent searches" - }, - "historyFilterAll": "Tümü", - "@historyFilterAll": { - "description": "Filter chip - show all items" - }, - "historyFilterAlbums": "Albümler", - "@historyFilterAlbums": { - "description": "Filter chip - show albums only" - }, - "historyFilterSingles": "Single'lar", - "@historyFilterSingles": { - "description": "Filter chip - show singles only" - }, - "historySearchHint": "Arama geçmişi...", - "@historySearchHint": { - "description": "Search bar placeholder in history" - }, - "settingsTitle": "Ayarlar", - "@settingsTitle": { - "description": "Settings screen title" - }, - "settingsDownload": "İndirme", - "@settingsDownload": { - "description": "Settings section - download options" - }, - "settingsAppearance": "Görünüm", - "@settingsAppearance": { - "description": "Settings section - visual customization" - }, - "settingsOptions": "Seçenekler", - "@settingsOptions": { - "description": "Settings section - app options" - }, - "settingsExtensions": "Eklentiler", - "@settingsExtensions": { - "description": "Settings section - extension management" - }, - "settingsAbout": "Hakkında", - "@settingsAbout": { - "description": "Settings section - app info" - }, - "downloadTitle": "İndirme", - "@downloadTitle": { - "description": "Download settings page title" - }, - "downloadAskQualitySubtitle": "Her indirmeden önce kalite seçim ekranını göster", - "@downloadAskQualitySubtitle": { - "description": "Subtitle for ask quality toggle" - }, - "downloadFilenameFormat": "Dosya adı formatı", - "@downloadFilenameFormat": { - "description": "Setting for output filename pattern" - }, - "downloadFolderOrganization": "Dosya Organizasyonu", - "@downloadFolderOrganization": { - "description": "Setting for folder structure" - }, - "appearanceTitle": "Görünüm", - "@appearanceTitle": { - "description": "Appearance settings page title" - }, - "appearanceThemeSystem": "Sistem", - "@appearanceThemeSystem": { - "description": "Follow system theme" - }, - "appearanceThemeLight": "Açık", - "@appearanceThemeLight": { - "description": "Light theme" - }, - "appearanceThemeDark": "Koyu", - "@appearanceThemeDark": { - "description": "Dark theme" - }, - "appearanceDynamicColor": "Dinamik Renk", - "@appearanceDynamicColor": { - "description": "Material You dynamic colors" - }, - "appearanceDynamicColorSubtitle": "Duvar kağıdının renklerini kullan", - "@appearanceDynamicColorSubtitle": { - "description": "Subtitle for dynamic color" - }, - "appearanceHistoryView": "Geçmiş Düzeni", - "@appearanceHistoryView": { - "description": "Layout style for history" - }, - "appearanceHistoryViewList": "Liste", - "@appearanceHistoryViewList": { - "description": "List layout option" - }, - "appearanceHistoryViewGrid": "Izgara", - "@appearanceHistoryViewGrid": { - "description": "Grid layout option" - }, - "optionsTitle": "Seçenekler", - "@optionsTitle": { - "description": "Options settings page title" - }, - "optionsPrimaryProvider": "Ana Kaynek", - "@optionsPrimaryProvider": { - "description": "Main search provider setting" - }, - "optionsPrimaryProviderSubtitle": "Şarkı ismi aratılırken kullanılan kaynak.", - "@optionsPrimaryProviderSubtitle": { - "description": "Subtitle for primary provider" - }, - "optionsUsingExtension": "Kullanılan eklenti: {extensionName}", - "@optionsUsingExtension": { - "description": "Shows active extension name", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "optionsSwitchBack": "Dahili kaynaklara dönmek için Deezer veya Spotify'a tıkla", - "@optionsSwitchBack": { - "description": "Hint to switch back to built-in providers" - }, - "optionsAutoFallback": "Diğerlerini dene", - "@optionsAutoFallback": { - "description": "Auto-retry with other services" - }, - "optionsAutoFallbackSubtitle": "İndirme başarısız olursa diğer hizmetleri dene", - "@optionsAutoFallbackSubtitle": { - "description": "Subtitle for auto fallback" - }, - "optionsUseExtensionProviders": "Eklenti sağlayıcılarını kullan", - "@optionsUseExtensionProviders": { - "description": "Enable extension download providers" - }, - "optionsUseExtensionProvidersOn": "Eklentiler ilk denenecek", - "@optionsUseExtensionProvidersOn": { - "description": "Status when extension providers enabled" - }, - "optionsUseExtensionProvidersOff": "Sadece dahili sağlayıcıları kullan", - "@optionsUseExtensionProvidersOff": { - "description": "Status when extension providers disabled" - }, - "optionsEmbedLyrics": "Şarkı Sözlerini Göm", - "@optionsEmbedLyrics": { - "description": "Embed lyrics in audio files" - }, - "optionsEmbedLyricsSubtitle": "Senkronize şarkı sözlerini FLAC dosyalarına göm", - "@optionsEmbedLyricsSubtitle": { - "description": "Subtitle for embed lyrics" - }, - "optionsMaxQualityCover": "En Yüksek Kapak Kalitesi", - "@optionsMaxQualityCover": { - "description": "Download highest quality album art" - }, - "optionsMaxQualityCoverSubtitle": "En yüksek kalitedeki albüm kapaklarını indir", - "@optionsMaxQualityCoverSubtitle": { - "description": "Subtitle for max quality cover" - }, - "optionsConcurrentDownloads": "Eş Zamanlı İndirmeler", - "@optionsConcurrentDownloads": { - "description": "Number of parallel downloads" - }, - "optionsConcurrentSequential": "Sıralı (Birer birer)", - "@optionsConcurrentSequential": { - "description": "Download one at a time" - }, - "optionsConcurrentParallel": "Aynı anda {count} indirme", - "@optionsConcurrentParallel": { - "description": "Multiple parallel downloads", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "optionsConcurrentWarning": "Aynı anda birden fazla indirme sınırlamaya takılabilir", - "@optionsConcurrentWarning": { - "description": "Warning about rate limits" - }, - "optionsExtensionStore": "Eklenti Dükkanı", - "@optionsExtensionStore": { - "description": "Show/hide store tab" - }, - "optionsExtensionStoreSubtitle": "Dükkan sekmesini altta göster", - "@optionsExtensionStoreSubtitle": { - "description": "Subtitle for extension store toggle" - }, - "optionsCheckUpdates": "Güncelleştirmeleri Denetle", - "@optionsCheckUpdates": { - "description": "Auto update check toggle" - }, - "optionsCheckUpdatesSubtitle": "Yeni sürüm çıktığında bildir", - "@optionsCheckUpdatesSubtitle": { - "description": "Subtitle for update check" - }, - "optionsUpdateChannel": "Güncelleme Kanalı", - "@optionsUpdateChannel": { - "description": "Stable vs preview releases" - }, - "optionsUpdateChannelStable": "Sadece stabil sürümler", - "@optionsUpdateChannelStable": { - "description": "Only stable updates" - }, - "optionsUpdateChannelPreview": "Önizleme sürümlerini al", - "@optionsUpdateChannelPreview": { - "description": "Include beta/preview updates" - }, - "optionsUpdateChannelWarning": "Önizleme sürümleri hatalar veya tamamlanmamış özellikler içerebilir", - "@optionsUpdateChannelWarning": { - "description": "Warning about preview channel" - }, - "optionsClearHistory": "İndirme Geçmişini Temizle", - "@optionsClearHistory": { - "description": "Delete all download history" - }, - "optionsClearHistorySubtitle": "İndirilen bütün şarkıları geçmişten temizle", - "@optionsClearHistorySubtitle": { - "description": "Subtitle for clear history" - }, - "optionsDetailedLogging": "Detaylı Günlükleme", - "@optionsDetailedLogging": { - "description": "Enable verbose logs for debugging" - }, - "optionsDetailedLoggingOn": "Detaylı günlük kayıt ediliyor", - "@optionsDetailedLoggingOn": { - "description": "Status when logging enabled" - }, - "optionsDetailedLoggingOff": "Hata bildirmek için aç", - "@optionsDetailedLoggingOff": { - "description": "Status when logging disabled" - }, - "optionsSpotifyCredentials": "Spotify Kimlik Bilgileri", - "@optionsSpotifyCredentials": { - "description": "Spotify API credentials setting" - }, - "optionsSpotifyCredentialsConfigured": "Client ID: {clientId}...", - "@optionsSpotifyCredentialsConfigured": { - "description": "Shows configured client ID preview", - "placeholders": { - "clientId": { - "type": "String" - } - } - }, - "optionsSpotifyCredentialsRequired": "Zorunlu - değiştirmek için tıkla", - "@optionsSpotifyCredentialsRequired": { - "description": "Prompt to set up credentials" - }, - "optionsSpotifyWarning": "Spotify'ın senin API kimlik bilgilerine ihtiyacı var. Onları developer.spotify.com'dan alabilirsin", - "@optionsSpotifyWarning": { - "description": "Info about Spotify API requirement" - }, - "optionsSpotifyDeprecationWarning": "Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.", - "@optionsSpotifyDeprecationWarning": { - "description": "Warning about Spotify API deprecation" - }, - "extensionsTitle": "Eklentiler", - "@extensionsTitle": { - "description": "Extensions page title" - }, - "extensionsDisabled": "Devre Dışı", - "@extensionsDisabled": { - "description": "Extension status - inactive" - }, - "extensionsVersion": "Versiyon {version}", - "@extensionsVersion": { - "description": "Extension version display", - "placeholders": { - "version": { - "type": "String" - } - } - }, - "extensionsAuthor": "{author} tarafından", - "@extensionsAuthor": { - "description": "Extension author credit", - "placeholders": { - "author": { - "type": "String" - } - } - }, - "extensionsUninstall": "Kaldır", - "@extensionsUninstall": { - "description": "Uninstall extension button" - }, - "storeTitle": "Eklenti Dükkanı", - "@storeTitle": { - "description": "Store screen title" - }, - "storeSearch": "Eklenti ara...", - "@storeSearch": { - "description": "Store search placeholder" - }, - "storeInstall": "Kur", - "@storeInstall": { - "description": "Install extension button" - }, - "storeInstalled": "Kuruldu", - "@storeInstalled": { - "description": "Already installed badge" - }, - "storeUpdate": "Güncelle", - "@storeUpdate": { - "description": "Update available button" - }, - "aboutTitle": "Hakkında", - "@aboutTitle": { - "description": "About page title" - }, - "aboutContributors": "Katkıda Bulunanlar", - "@aboutContributors": { - "description": "Section for contributors" - }, - "aboutMobileDeveloper": "Mobil versiyon geliştiricisi", - "@aboutMobileDeveloper": { - "description": "Role description for mobile dev" - }, - "aboutOriginalCreator": "Orijinal SpotiFLAC'ın kurucusu", - "@aboutOriginalCreator": { - "description": "Role description for original creator" - }, - "aboutLogoArtist": "Uygulama logomuzu yaratmış yetenekli sanatçımız!", - "@aboutLogoArtist": { - "description": "Role description for logo artist" - }, - "aboutTranslators": "Çevirmenler", - "@aboutTranslators": { - "description": "Section for translators" - }, - "aboutSpecialThanks": "Özel teşekkür", - "@aboutSpecialThanks": { - "description": "Section for special thanks" - }, - "aboutLinks": "Linkler", - "@aboutLinks": { - "description": "Section for external links" - }, - "aboutMobileSource": "Mobil kaynak kodu", - "@aboutMobileSource": { - "description": "Link to mobile GitHub repo" - }, - "aboutPCSource": "PC kaynak kodu", - "@aboutPCSource": { - "description": "Link to PC GitHub repo" - }, - "aboutReportIssue": "Sorun bildir", - "@aboutReportIssue": { - "description": "Link to report bugs" - }, - "aboutReportIssueSubtitle": "Karşılaştığın herhangi bir problemi bildir", - "@aboutReportIssueSubtitle": { - "description": "Subtitle for report issue" - }, - "aboutFeatureRequest": "Özellik isteği", - "@aboutFeatureRequest": { - "description": "Link to suggest features" - }, - "aboutFeatureRequestSubtitle": "Uygulama için yeni özellikler isteyin", - "@aboutFeatureRequestSubtitle": { - "description": "Subtitle for feature request" - }, - "aboutTelegramChannel": "Telegram Kanalı", - "@aboutTelegramChannel": { - "description": "Link to Telegram channel" - }, - "aboutTelegramChannelSubtitle": "Duyurular ve güncellemeler", - "@aboutTelegramChannelSubtitle": { - "description": "Subtitle for Telegram channel" - }, - "aboutTelegramChat": "Telegram Grubu", - "@aboutTelegramChat": { - "description": "Link to Telegram chat group" - }, - "aboutTelegramChatSubtitle": "Diğer kullanıcılarla sohbet et", - "@aboutTelegramChatSubtitle": { - "description": "Subtitle for Telegram chat" - }, - "aboutSocial": "Sosyal ağlar", - "@aboutSocial": { - "description": "Section for social links" - }, - "aboutApp": "Uygulama", - "@aboutApp": { - "description": "Section for app info" - }, - "aboutVersion": "Versiyon", - "@aboutVersion": { - "description": "Version info label" - }, - "aboutBinimumDesc": "QQDL ve HiFi API'ın kurucusu. Bu API olmadan, Tidal indirmeleri olmazdı!", - "@aboutBinimumDesc": { - "description": "Credit description for binimum" - }, - "aboutSachinsenalDesc": "Orijinal HiFi projesi kurucusu. Tidal entegrasyonun temeli!", - "@aboutSachinsenalDesc": { - "description": "Credit description for sachinsenal0x64" - }, - "aboutSjdonadoDesc": "Creator of I Don't Have Spotify (IDHS). The fallback link resolver that saves the day!", - "@aboutSjdonadoDesc": { - "description": "Credit description for sjdonado" - }, - "aboutDabMusic": "DAB Music", - "@aboutDabMusic": { - "description": "Name of Qobuz API service - DO NOT TRANSLATE" - }, - "aboutDabMusicDesc": "En iyi Qobuz streaming API'ı. Yüksek kalite indirmeler bunun sayesinde!", - "@aboutDabMusicDesc": { - "description": "Credit for DAB Music API" - }, - "aboutSpotiSaver": "SpotiSaver", - "@aboutSpotiSaver": { - "description": "Name of SpotiSaver API service - DO NOT TRANSLATE" - }, - "aboutSpotiSaverDesc": "Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!", - "@aboutSpotiSaverDesc": { - "description": "Credit for SpotiSaver API" - }, - "aboutAppDescription": "Spotify şarkılarını Tidal, Qobuz ve Amazon Music'den yüksek kalitede indir.", - "@aboutAppDescription": { - "description": "App description in header card" - }, - "artistAlbums": "Albümler", - "@artistAlbums": { - "description": "Section header for artist albums" - }, - "artistSingles": "Single'lar ve EP'ler", - "@artistSingles": { - "description": "Section header for singles/EPs" - }, - "artistCompilations": "Derlemeler", - "@artistCompilations": { - "description": "Section header for compilations" - }, - "artistPopular": "Popüler", - "@artistPopular": { - "description": "Section header for popular/top tracks" - }, - "artistMonthlyListeners": "Aylık {count} dinleyici", - "@artistMonthlyListeners": { - "description": "Monthly listener count display", - "placeholders": { - "count": { - "type": "String", - "description": "Formatted listener count" - } - } - }, - "trackMetadataService": "Hizmet", - "@trackMetadataService": { - "description": "Metadata field - download service used" - }, - "trackMetadataPlay": "Oynat", - "@trackMetadataPlay": { - "description": "Action button - play track" - }, - "trackMetadataShare": "Paylaş", - "@trackMetadataShare": { - "description": "Action button - share track" - }, - "trackMetadataDelete": "Sil", - "@trackMetadataDelete": { - "description": "Action button - delete track" - }, - "setupGrantPermission": "İzin Ver", - "@setupGrantPermission": { - "description": "Button to request permission" - }, - "setupSkip": "Şimdilik atla", - "@setupSkip": { - "description": "Skip current step button" - }, - "setupStorageAccessRequired": "Depolama Erişimi Gerekli", - "@setupStorageAccessRequired": { - "description": "Title when storage access needed" - }, - "setupStorageAccessMessageAndroid11": "Android 11 ve sonrasında şarkıların seçili klasörünüze kaydedilebilmesi için \"Bütün dosyalara eriş\" iznine ihtiyaç var.", - "@setupStorageAccessMessageAndroid11": { - "description": "Android 11+ specific explanation" - }, - "setupOpenSettings": "Ayarları Aç", - "@setupOpenSettings": { - "description": "Button to open system settings" - }, - "setupPermissionDeniedMessage": "İzin reddedildi. Devam etmek için lütfen bütün izinleri verin.", - "@setupPermissionDeniedMessage": { - "description": "Error when permission denied" - }, - "setupPermissionRequired": "{permissionType} İzni Zorunlu", - "@setupPermissionRequired": { - "description": "Generic permission required title", - "placeholders": { - "permissionType": { - "type": "String", - "description": "Type of permission (Storage/Notification)" - } - } - }, - "setupPermissionRequiredMessage": "En iyi deneyim için {permissionType} izni zorunludur. Bunu ayarlardan daha sonra değiştirebilirsiniz.", - "@setupPermissionRequiredMessage": { - "description": "Generic permission required message", - "placeholders": { - "permissionType": { - "type": "String" - } - } - }, - "setupUseDefaultFolder": "Varsayılan Klasörü Kullan?", - "@setupUseDefaultFolder": { - "description": "Dialog title for default folder" - }, - "setupNoFolderSelected": "Klasör seçilmedi. Varsayılan \"Music\" klasörünü kullanmak ister misiniz?", - "@setupNoFolderSelected": { - "description": "Prompt when no folder selected" - }, - "setupUseDefault": "Varsayılanı Kullan", - "@setupUseDefault": { - "description": "Button to use default folder" - }, - "setupDownloadLocationTitle": "İndirme Konumu", - "@setupDownloadLocationTitle": { - "description": "Download location dialog title" - }, - "setupDownloadLocationIosMessage": "iOS'ta indirilenler uygulamanın \"Documents\" dosyasına kaydedilir. Onlara Dosyalar uygulamasından erişebilirsiniz.", - "@setupDownloadLocationIosMessage": { - "description": "iOS-specific folder info" - }, - "setupAppDocumentsFolder": "App Documents Folder", - "@setupAppDocumentsFolder": { - "description": "iOS documents folder option" - }, - "setupAppDocumentsFolderSubtitle": "Tavsiye edilen - Dosyalar uygulamasından erişilebilir", - "@setupAppDocumentsFolderSubtitle": { - "description": "Subtitle for documents folder" - }, - "setupChooseFromFiles": "Dosyalar'dan Seç", - "@setupChooseFromFiles": { - "description": "iOS file picker option" - }, - "setupChooseFromFilesSubtitle": "iCloud veya başka konum seç", - "@setupChooseFromFilesSubtitle": { - "description": "Subtitle for file picker" - }, - "setupIosEmptyFolderWarning": "iOS'un sınırlaması: Boş klasörler seçilemiyor. İçinde en az bir dosya bulunan bir klasör seçin.", - "@setupIosEmptyFolderWarning": { - "description": "iOS folder selection warning" - }, - "setupIcloudNotSupported": "iCloud Drive is not supported. Please use the app Documents folder.", - "@setupIcloudNotSupported": { - "description": "Error when user selects iCloud Drive on iOS" - }, - "setupDownloadInFlac": "Spotify şarkılarını FLAC olarak indirin", - "@setupDownloadInFlac": { - "description": "App tagline in setup" - }, - "setupStorageGranted": "Depolama İzni Verildi!", - "@setupStorageGranted": { - "description": "Success message for storage permission" - }, - "setupStorageRequired": "Depolama İzni Gerekli", - "@setupStorageRequired": { - "description": "Title when storage permission needed" - }, - "setupStorageDescription": "SpotiFLAC'ın şarkılarınızı kaydetmek için depolama iznine ihtiyacı var.", - "@setupStorageDescription": { - "description": "Explanation for storage permission" - }, - "setupNotificationGranted": "Bildirim İzni Verildi!", - "@setupNotificationGranted": { - "description": "Success message for notification permission" - }, - "setupNotificationEnable": "Bildirimleri Etkinleştir", - "@setupNotificationEnable": { - "description": "Button to enable notifications" - }, - "setupFolderChoose": "İndirilecek Klasörü Seç", - "@setupFolderChoose": { - "description": "Button to choose folder" - }, - "setupFolderDescription": "İndirdiğin şarkıların kaydedileceği klasörü seç.", - "@setupFolderDescription": { - "description": "Explanation for folder selection" - }, - "setupSelectFolder": "Klasör Seç", - "@setupSelectFolder": { - "description": "Button to select folder" - }, - "setupEnableNotifications": "Bildirimleri Etkinleştir", - "@setupEnableNotifications": { - "description": "Button to enable notifications" - }, - "setupNotificationBackgroundDescription": "İndirmelerin durumu hakkında bildirim al. Bunu açmak uygulama arka plandayken indirmelerinizi takip etmenizi sağlar.", - "@setupNotificationBackgroundDescription": { - "description": "Detailed notification explanation" - }, - "setupSkipForNow": "Şimdilik atla", - "@setupSkipForNow": { - "description": "Skip button text" - }, - "setupNext": "Sıradaki", - "@setupNext": { - "description": "Next button text" - }, - "setupGetStarted": "Başla", - "@setupGetStarted": { - "description": "Final setup button" - }, - "setupAllowAccessToManageFiles": "Lütfen bir sonraki ekranda \"Bütün dosyalara eriş\" iznini sağlayın.", - "@setupAllowAccessToManageFiles": { - "description": "Instruction for file access permission" - }, - "dialogCancel": "İptal", - "@dialogCancel": { - "description": "Dialog button - cancel action" - }, - "dialogSave": "Kaydet", - "@dialogSave": { - "description": "Dialog button - save changes" - }, - "dialogDelete": "Sil", - "@dialogDelete": { - "description": "Dialog button - delete item" - }, - "dialogRetry": "Yeniden dene", - "@dialogRetry": { - "description": "Dialog button - retry action" - }, - "dialogClear": "Temizle", - "@dialogClear": { - "description": "Dialog button - clear items" - }, - "dialogDone": "Tamamlandı", - "@dialogDone": { - "description": "Dialog button - action completed" - }, - "dialogImport": "İçe aktar", - "@dialogImport": { - "description": "Dialog button - import data" - }, - "dialogDiscard": "Vazgeç", - "@dialogDiscard": { - "description": "Dialog button - discard changes" - }, - "dialogRemove": "Kaldır", - "@dialogRemove": { - "description": "Dialog button - remove item" - }, - "dialogUninstall": "Kaldır", - "@dialogUninstall": { - "description": "Dialog button - uninstall extension" - }, - "dialogDiscardChanges": "Değişiklikleri İptal Et?", - "@dialogDiscardChanges": { - "description": "Dialog title - unsaved changes warning" - }, - "dialogUnsavedChanges": "Kaydedilmeyen değişiklikler mevcut. Bu değişiklikleri iptal etmek istiyor musunuz?", - "@dialogUnsavedChanges": { - "description": "Dialog message - unsaved changes" - }, - "dialogClearAll": "Tümünü Temizle", - "@dialogClearAll": { - "description": "Dialog title - clear all items" - }, - "dialogRemoveExtension": "Eklentiyi Kaldır", - "@dialogRemoveExtension": { - "description": "Dialog title - uninstall extension" - }, - "dialogRemoveExtensionMessage": "Bu eklentiyi kaldırmak istediğine emin misin? Bu işlem geri alınamaz.", - "@dialogRemoveExtensionMessage": { - "description": "Dialog message - uninstall confirmation" - }, - "dialogUninstallExtension": "Eklentiyi Kaldır?", - "@dialogUninstallExtension": { - "description": "Dialog title - uninstall extension" - }, - "dialogUninstallExtensionMessage": "{extensionName} eklentisini kaldırmak istediğine emin misin?", - "@dialogUninstallExtensionMessage": { - "description": "Dialog message - uninstall specific extension", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "dialogClearHistoryTitle": "Geçmişi Temizle", - "@dialogClearHistoryTitle": { - "description": "Dialog title - clear download history" - }, - "dialogClearHistoryMessage": "Tüm indirme geçmişini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.", - "@dialogClearHistoryMessage": { - "description": "Dialog message - clear history confirmation" - }, - "dialogDeleteSelectedTitle": "Seçileni Sil", - "@dialogDeleteSelectedTitle": { - "description": "Dialog title - delete selected items" - }, - "dialogDeleteSelectedMessage": "{count} {count, plural, one {}=1{şarkıyı} other{şarkıyı}} geçmişten silmeye emin misiniz?\n\nBu işlem seçilenleri cihazınızdan da silecektir.", - "@dialogDeleteSelectedMessage": { - "description": "Dialog message - delete selected tracks", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "dialogImportPlaylistTitle": "Çalma listesini içe aktar", - "@dialogImportPlaylistTitle": { - "description": "Dialog title - import CSV playlist" - }, - "dialogImportPlaylistMessage": "CSV'de {count} şarkı bulundu. İndirme kuyruğuna ekle?", - "csvImportTracks": "CSV'den {count} şarkı", - "@csvImportTracks": { - "description": "Label shown in quality picker for CSV import", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "@dialogImportPlaylistMessage": { - "description": "Dialog message - import playlist confirmation", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "snackbarAddedToQueue": "\"{trackName}\" kuyruğa eklendi", - "@snackbarAddedToQueue": { - "description": "Snackbar - track added to download queue", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "snackbarAddedTracksToQueue": "{count} şarkı kuyruğa eklendi", - "@snackbarAddedTracksToQueue": { - "description": "Snackbar - multiple tracks added to queue", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "snackbarAlreadyDownloaded": "\"{trackName}\" zaten indirilmiş", - "@snackbarAlreadyDownloaded": { - "description": "Snackbar - track already exists", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "snackbarAlreadyInLibrary": "\"{trackName}\" already exists in your library", - "@snackbarAlreadyInLibrary": { - "description": "Snackbar - track already exists in local library", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "snackbarHistoryCleared": "Geçmiş temizlendi", - "@snackbarHistoryCleared": { - "description": "Snackbar - history deleted" - }, - "snackbarCredentialsSaved": "Kimlik bilgileri kaydedildi", - "@snackbarCredentialsSaved": { - "description": "Snackbar - Spotify credentials saved" - }, - "snackbarCredentialsCleared": "Kimlik bilgileri temizlendi", - "@snackbarCredentialsCleared": { - "description": "Snackbar - Spotify credentials removed" - }, - "snackbarDeletedTracks": "{count} {count, plural, one {}=1{şarkı} other{şarkı}} silindi", - "@snackbarDeletedTracks": { - "description": "Snackbar - tracks deleted", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "snackbarCannotOpenFile": "Dosya açılamadı: {error}", - "@snackbarCannotOpenFile": { - "description": "Snackbar - file open error", - "placeholders": { - "error": { - "type": "String" - } - } - }, - "snackbarFillAllFields": "Lütfen tüm alanları doldurun", - "@snackbarFillAllFields": { - "description": "Snackbar - validation error" - }, - "snackbarViewQueue": "Kuyruğu Görüntüle", - "@snackbarViewQueue": { - "description": "Snackbar action - view download queue" - }, - "snackbarUrlCopied": "{platform} Bağlantı panoya kopyalandı", - "@snackbarUrlCopied": { - "description": "Snackbar - URL copied", - "placeholders": { - "platform": { - "type": "String", - "description": "Platform name (Spotify/Deezer)" - } - } - }, - "snackbarFileNotFound": "Dosya bulunamadı", - "@snackbarFileNotFound": { - "description": "Snackbar - file doesn't exist" - }, - "snackbarSelectExtFile": "Lütfen .spotiflac-ext dosyasını seçin", - "@snackbarSelectExtFile": { - "description": "Snackbar - wrong file type selected" - }, - "snackbarProviderPrioritySaved": "Provider priority saved", - "@snackbarProviderPrioritySaved": { - "description": "Snackbar - provider order saved" - }, - "snackbarMetadataProviderSaved": "Metadata provider priority saved", - "@snackbarMetadataProviderSaved": { - "description": "Snackbar - metadata provider order saved" - }, - "snackbarExtensionInstalled": "{extensionName} yüklendi.", - "@snackbarExtensionInstalled": { - "description": "Snackbar - extension installed successfully", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "snackbarExtensionUpdated": "{extensionName} güncellendi.", - "@snackbarExtensionUpdated": { - "description": "Snackbar - extension updated successfully", - "placeholders": { - "extensionName": { - "type": "String" - } - } - }, - "snackbarFailedToInstall": "Eklenti yüklenirken hata oluştu", - "@snackbarFailedToInstall": { - "description": "Snackbar - extension install error" - }, - "snackbarFailedToUpdate": "Eklenti güncellenirken hata oluştu", - "@snackbarFailedToUpdate": { - "description": "Snackbar - extension update error" - }, - "errorRateLimited": "Aşırı istek gönderildi", - "@errorRateLimited": { - "description": "Error title - too many requests" - }, - "errorRateLimitedMessage": "Çok fazla istek. Lütfen arama yapmadan önce biraz bekleyin.", - "@errorRateLimitedMessage": { - "description": "Error message - rate limit explanation" - }, - "errorNoTracksFound": "Parça bulunamadı", - "@errorNoTracksFound": { - "description": "Error - search returned no results" - }, - "errorMissingExtensionSource": "{item} yüklenemedi: Eksik eklenti kaynağı", - "@errorMissingExtensionSource": { - "description": "Error - extension source not available", - "placeholders": { - "item": { - "type": "String" - } - } - }, - "actionPause": "Duraklat", - "@actionPause": { - "description": "Action button - pause download" - }, - "actionResume": "Devam et", - "@actionResume": { - "description": "Action button - resume download" - }, - "actionCancel": "Vazgeç", - "@actionCancel": { - "description": "Action button - cancel operation" - }, - "actionSelectAll": "Tümünü Seç", - "@actionSelectAll": { - "description": "Action button - select all items" - }, - "actionDeselect": "Seçimi kaldır", - "@actionDeselect": { - "description": "Action button - deselect all" - }, - "actionRemoveCredentials": "Özellikleri kaldır", - "@actionRemoveCredentials": { - "description": "Action button - delete Spotify credentials" - }, - "actionSaveCredentials": "Özellikleri kaydet", - "@actionSaveCredentials": { - "description": "Action button - save Spotify credentials" - }, - "selectionSelected": "{count} seçildi", - "@selectionSelected": { - "description": "Selection count indicator", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "selectionAllSelected": "Tüm parçalar seçildi", - "@selectionAllSelected": { - "description": "Status - all items selected" - }, - "selectionSelectToDelete": "Silinecek parçaları seçin", - "@selectionSelectToDelete": { - "description": "Placeholder when nothing selected" - }, - "progressFetchingMetadata": "Meta verileri alınıyor... {current}/{total}", - "@progressFetchingMetadata": { - "description": "Progress indicator - loading track info", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } - }, - "progressReadingCsv": "CSV okunuyor...", - "@progressReadingCsv": { - "description": "Progress indicator - parsing CSV file" - }, - "searchSongs": "Şarkılar", - "@searchSongs": { - "description": "Search result category - songs" - }, - "searchArtists": "Sanatçılar", - "@searchArtists": { - "description": "Search result category - artists" - }, - "searchAlbums": "Albümler", - "@searchAlbums": { - "description": "Search result category - albums" - }, - "searchPlaylists": "Çalma Listeleri", - "@searchPlaylists": { - "description": "Search result category - playlists" - }, - "tooltipPlay": "Oynat", - "@tooltipPlay": { - "description": "Tooltip - play button" - }, - "filenameFormat": "Dosya adı formatı", - "@filenameFormat": { - "description": "Setting title - filename pattern" - }, - "filenameShowAdvancedTags": "Show advanced tags", - "@filenameShowAdvancedTags": { - "description": "Toggle label for showing advanced filename tags" - }, - "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", - "@filenameShowAdvancedTagsDescription": { - "description": "Description for advanced filename tag toggle" - }, - "folderOrganizationNone": "Organizasyon yok", - "@folderOrganizationNone": { - "description": "Folder option - flat structure" - }, - "folderOrganizationByArtist": "Sanatçıya Göre", - "@folderOrganizationByArtist": { - "description": "Folder option - artist folders" - }, - "folderOrganizationByAlbum": "Albüme Göre", - "@folderOrganizationByAlbum": { - "description": "Folder option - album folders" - }, - "folderOrganizationByArtistAlbum": "Sanatçı/Albüm", - "@folderOrganizationByArtistAlbum": { - "description": "Folder option - nested folders" - }, - "folderOrganizationDescription": "İndirilenleri klasörlerle organize et", - "@folderOrganizationDescription": { - "description": "Folder organization sheet description" - }, - "folderOrganizationNoneSubtitle": "Her şey indirilen dosyasına kaydedilecek", - "@folderOrganizationNoneSubtitle": { - "description": "Subtitle for no organization option" - }, - "folderOrganizationByArtistSubtitle": "Her sanatçı için ayrı klasör", - "@folderOrganizationByArtistSubtitle": { - "description": "Subtitle for artist folder option" - }, - "folderOrganizationByAlbumSubtitle": "Her albüm için ayrı klasör", - "@folderOrganizationByAlbumSubtitle": { - "description": "Subtitle for album folder option" - }, - "folderOrganizationByArtistAlbumSubtitle": "Sanatçı klasörlerinin içinde Albüm klasörleri", - "@folderOrganizationByArtistAlbumSubtitle": { - "description": "Subtitle for nested folder option" - }, - "updateAvailable": "Güncelleme Mevcut", - "@updateAvailable": { - "description": "Update dialog title" - }, - "updateLater": "Daha Sonra", - "@updateLater": { - "description": "Update button - dismiss" - }, - "updateStartingDownload": "İndirme başlıyor...", - "@updateStartingDownload": { - "description": "Update status - initializing" - }, - "updateDownloadFailed": "İndirme başarısız", - "@updateDownloadFailed": { - "description": "Update error title" - }, - "updateFailedMessage": "Güncelleme indirilemedi", - "@updateFailedMessage": { - "description": "Update error message" - }, - "updateNewVersionReady": "Yeni bir sürüm hazır", - "@updateNewVersionReady": { - "description": "Update subtitle" - }, - "updateCurrent": "Şimdiki", - "@updateCurrent": { - "description": "Label for current version" - }, - "updateNew": "Yeni", - "@updateNew": { - "description": "Label for new version" - }, - "updateDownloading": "İndiriliyor...", - "@updateDownloading": { - "description": "Update status - downloading" - }, - "updateWhatsNew": "Yenilikler", - "@updateWhatsNew": { - "description": "Changelog section title" - }, - "updateDownloadInstall": "İndir & Yükle", - "@updateDownloadInstall": { - "description": "Update button - download and install" - }, - "updateDontRemind": "Bir daha sorma", - "@updateDontRemind": { - "description": "Update button - skip this version" - }, - "providerPriorityTitle": "İndirme hizmetleri öncelik sırası", - "@providerPriorityTitle": { - "description": "Provider priority page title" - }, - "providerPriorityDescription": "İndirme hizmetlerini sıralamak için kaydır. Uygulama şarkı indirirken hizmetleri yukarıdan aşağıya doğru deneyecektir.", - "@providerPriorityDescription": { - "description": "Provider priority page description" - }, - "providerPriorityInfo": "Eğer bir şarkı ilk hizmette mevcut değilse uygulama otomatik olarak bir sonrakini deneyecektir.", - "@providerPriorityInfo": { - "description": "Info tip about fallback behavior" - }, - "providerBuiltIn": "Dahili", - "@providerBuiltIn": { - "description": "Label for built-in providers (Tidal/Qobuz/Amazon)" - }, - "providerExtension": "Eklenti", - "@providerExtension": { - "description": "Label for extension-provided providers" - }, - "metadataProviderPriorityTitle": "Metadata Priority", - "@metadataProviderPriorityTitle": { - "description": "Metadata priority page title" - }, - "metadataProviderPriorityDescription": "Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.", - "@metadataProviderPriorityDescription": { - "description": "Metadata priority page description" - }, - "metadataProviderPriorityInfo": "Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.", - "@metadataProviderPriorityInfo": { - "description": "Info tip about rate limits" - }, - "metadataNoRateLimits": "No rate limits", - "@metadataNoRateLimits": { - "description": "Deezer provider description" - }, - "metadataMayRateLimit": "May rate limit", - "@metadataMayRateLimit": { - "description": "Spotify provider description" - }, - "logTitle": "Kayıtlar", - "@logTitle": { - "description": "Logs screen title" - }, - "logCopied": "Kayıtlar panoya kopyalandı", - "@logCopied": { - "description": "Snackbar - logs copied" - }, - "logSearchHint": "Kayıtları Ara...", - "@logSearchHint": { - "description": "Log search placeholder" - }, - "logFilterLevel": "Seviye", - "@logFilterLevel": { - "description": "Filter by log level" - }, - "logFilterSection": "Filtre", - "@logFilterSection": { - "description": "Filter section title" - }, - "logShareLogs": "Kayıtları paylaş", - "@logShareLogs": { - "description": "Share button tooltip" - }, - "logClearLogs": "Kayıtları temizle", - "@logClearLogs": { - "description": "Clear button tooltip" - }, - "logClearLogsTitle": "Kayıtları temizle", - "@logClearLogsTitle": { - "description": "Clear logs dialog title" - }, - "logClearLogsMessage": "Tüm kayıtları temizlemek istediğinize emin misiniz?", - "@logClearLogsMessage": { - "description": "Clear logs confirmation message" - }, - "logFilterBySeverity": "Filter logs by severity", - "@logFilterBySeverity": { - "description": "Filter dialog title" - }, - "logNoLogsYet": "No logs yet", - "@logNoLogsYet": { - "description": "Empty state title" - }, - "logNoLogsYetSubtitle": "Logs will appear here as you use the app", - "@logNoLogsYetSubtitle": { - "description": "Empty state subtitle" - }, - "logEntriesFiltered": "Entries ({count} filtered)", - "@logEntriesFiltered": { - "description": "Log count with filter active", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "logEntries": "Entries ({count})", - "@logEntries": { - "description": "Total log count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "credentialsTitle": "Spotify Credentials", - "@credentialsTitle": { - "description": "Credentials dialog title" - }, - "credentialsDescription": "Enter your Client ID and Secret to use your own Spotify application quota.", - "@credentialsDescription": { - "description": "Credentials dialog explanation" - }, - "credentialsClientId": "Client ID", - "@credentialsClientId": { - "description": "Client ID field label - DO NOT TRANSLATE" - }, - "credentialsClientIdHint": "Paste Client ID", - "@credentialsClientIdHint": { - "description": "Client ID placeholder" - }, - "credentialsClientSecret": "Client Secret", - "@credentialsClientSecret": { - "description": "Client Secret field label - DO NOT TRANSLATE" - }, - "credentialsClientSecretHint": "Paste Client Secret", - "@credentialsClientSecretHint": { - "description": "Client Secret placeholder" - }, - "channelStable": "Stable", - "@channelStable": { - "description": "Update channel - stable releases" - }, - "channelPreview": "Preview", - "@channelPreview": { - "description": "Update channel - beta/preview releases" - }, - "sectionSearchSource": "Search Source", - "@sectionSearchSource": { - "description": "Settings section header" - }, - "sectionDownload": "Download", - "@sectionDownload": { - "description": "Settings section header" - }, - "sectionPerformance": "Performance", - "@sectionPerformance": { - "description": "Settings section header" - }, - "sectionApp": "App", - "@sectionApp": { - "description": "Settings section header" - }, - "sectionData": "Data", - "@sectionData": { - "description": "Settings section header" - }, - "sectionDebug": "Debug", - "@sectionDebug": { - "description": "Settings section header" - }, - "sectionService": "Service", - "@sectionService": { - "description": "Settings section header" - }, - "sectionAudioQuality": "Audio Quality", - "@sectionAudioQuality": { - "description": "Settings section header" - }, - "sectionFileSettings": "File Settings", - "@sectionFileSettings": { - "description": "Settings section header" - }, - "sectionLyrics": "Lyrics", - "@sectionLyrics": { - "description": "Settings section header" - }, - "lyricsMode": "Lyrics Mode", - "@lyricsMode": { - "description": "Setting - how to save lyrics" - }, - "lyricsModeDescription": "Choose how lyrics are saved with your downloads", - "@lyricsModeDescription": { - "description": "Lyrics mode picker description" - }, - "lyricsModeEmbed": "Embed in file", - "@lyricsModeEmbed": { - "description": "Lyrics mode option - embed in audio file" - }, - "lyricsModeEmbedSubtitle": "Lyrics stored inside FLAC metadata", - "@lyricsModeEmbedSubtitle": { - "description": "Subtitle for embed option" - }, - "lyricsModeExternal": "External .lrc file", - "@lyricsModeExternal": { - "description": "Lyrics mode option - separate LRC file" - }, - "lyricsModeExternalSubtitle": "Separate .lrc file for players like Samsung Music", - "@lyricsModeExternalSubtitle": { - "description": "Subtitle for external option" - }, - "lyricsModeBoth": "Both", - "@lyricsModeBoth": { - "description": "Lyrics mode option - embed and external" - }, - "lyricsModeBothSubtitle": "Embed and save .lrc file", - "@lyricsModeBothSubtitle": { - "description": "Subtitle for both option" - }, - "sectionColor": "Color", - "@sectionColor": { - "description": "Settings section header" - }, - "sectionTheme": "Theme", - "@sectionTheme": { - "description": "Settings section header" - }, - "sectionLayout": "Layout", - "@sectionLayout": { - "description": "Settings section header" - }, - "sectionLanguage": "Language", - "@sectionLanguage": { - "description": "Settings section header for language" - }, - "appearanceLanguage": "App Language", - "@appearanceLanguage": { - "description": "Language setting title" - }, - "settingsAppearanceSubtitle": "Theme, colors, display", - "@settingsAppearanceSubtitle": { - "description": "Appearance settings description" - }, - "settingsDownloadSubtitle": "Service, quality, filename format", - "@settingsDownloadSubtitle": { - "description": "Download settings description" - }, - "settingsOptionsSubtitle": "Fallback, lyrics, cover art, updates", - "@settingsOptionsSubtitle": { - "description": "Options settings description" - }, - "settingsExtensionsSubtitle": "Manage download providers", - "@settingsExtensionsSubtitle": { - "description": "Extensions settings description" - }, - "settingsLogsSubtitle": "View app logs for debugging", - "@settingsLogsSubtitle": { - "description": "Logs settings description" - }, - "loadingSharedLink": "Loading shared link...", - "@loadingSharedLink": { - "description": "Status when opening shared URL" - }, - "pressBackAgainToExit": "Press back again to exit", - "@pressBackAgainToExit": { - "description": "Exit confirmation message" - }, - "downloadAllCount": "Download All ({count})", - "@downloadAllCount": { - "description": "Download all button with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}", - "@tracksCount": { - "description": "Track count display", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "trackCopyFilePath": "Copy file path", - "@trackCopyFilePath": { - "description": "Action - copy file path" - }, - "trackRemoveFromDevice": "Remove from device", - "@trackRemoveFromDevice": { - "description": "Action - delete downloaded file" - }, - "trackLoadLyrics": "Load Lyrics", - "@trackLoadLyrics": { - "description": "Action - fetch lyrics" - }, - "trackMetadata": "Metadata", - "@trackMetadata": { - "description": "Tab title - track metadata" - }, - "trackFileInfo": "File Info", - "@trackFileInfo": { - "description": "Tab title - file information" - }, - "trackLyrics": "Lyrics", - "@trackLyrics": { - "description": "Tab title - lyrics" - }, - "trackFileNotFound": "File not found", - "@trackFileNotFound": { - "description": "Error - file doesn't exist" - }, - "trackOpenInDeezer": "Open in Deezer", - "@trackOpenInDeezer": { - "description": "Action - open track in Deezer app" - }, - "trackOpenInSpotify": "Open in Spotify", - "@trackOpenInSpotify": { - "description": "Action - open track in Spotify app" - }, - "trackTrackName": "Track name", - "@trackTrackName": { - "description": "Metadata label - track title" - }, - "trackArtist": "Artist", - "@trackArtist": { - "description": "Metadata label - artist name" - }, - "trackAlbumArtist": "Album artist", - "@trackAlbumArtist": { - "description": "Metadata label - album artist" - }, - "trackAlbum": "Album", - "@trackAlbum": { - "description": "Metadata label - album name" - }, - "trackTrackNumber": "Track number", - "@trackTrackNumber": { - "description": "Metadata label - track number" - }, - "trackDiscNumber": "Disc number", - "@trackDiscNumber": { - "description": "Metadata label - disc number" - }, - "trackDuration": "Duration", - "@trackDuration": { - "description": "Metadata label - track length" - }, - "trackAudioQuality": "Audio quality", - "@trackAudioQuality": { - "description": "Metadata label - audio quality" - }, - "trackReleaseDate": "Release date", - "@trackReleaseDate": { - "description": "Metadata label - release date" - }, - "trackGenre": "Genre", - "@trackGenre": { - "description": "Metadata label - music genre" - }, - "trackLabel": "Label", - "@trackLabel": { - "description": "Metadata label - record label" - }, - "trackCopyright": "Copyright", - "@trackCopyright": { - "description": "Metadata label - copyright information" - }, - "trackDownloaded": "Downloaded", - "@trackDownloaded": { - "description": "Metadata label - download date" - }, - "trackCopyLyrics": "Copy lyrics", - "@trackCopyLyrics": { - "description": "Action - copy lyrics to clipboard" - }, - "trackLyricsNotAvailable": "Lyrics not available for this track", - "@trackLyricsNotAvailable": { - "description": "Message when lyrics not found" - }, - "trackLyricsTimeout": "Request timed out. Try again later.", - "@trackLyricsTimeout": { - "description": "Message when lyrics request times out" - }, - "trackLyricsLoadFailed": "Failed to load lyrics", - "@trackLyricsLoadFailed": { - "description": "Message when lyrics loading fails" - }, - "trackEmbedLyrics": "Embed Lyrics", - "@trackEmbedLyrics": { - "description": "Action - embed lyrics into audio file" - }, - "trackLyricsEmbedded": "Lyrics embedded successfully", - "@trackLyricsEmbedded": { - "description": "Snackbar - lyrics saved to file" - }, - "trackInstrumental": "Instrumental track", - "@trackInstrumental": { - "description": "Message when track is instrumental (no lyrics)" - }, - "trackCopiedToClipboard": "Copied to clipboard", - "@trackCopiedToClipboard": { - "description": "Snackbar - content copied" - }, - "trackDeleteConfirmTitle": "Remove from device?", - "@trackDeleteConfirmTitle": { - "description": "Delete confirmation title" - }, - "trackDeleteConfirmMessage": "This will permanently delete the downloaded file and remove it from your history.", - "@trackDeleteConfirmMessage": { - "description": "Delete confirmation message" - }, - "dateToday": "Today", - "@dateToday": { - "description": "Relative date - today" - }, - "dateYesterday": "Yesterday", - "@dateYesterday": { - "description": "Relative date - yesterday" - }, - "dateDaysAgo": "{count} days ago", - "@dateDaysAgo": { - "description": "Relative date - days ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "dateWeeksAgo": "{count} weeks ago", - "@dateWeeksAgo": { - "description": "Relative date - weeks ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "dateMonthsAgo": "{count} months ago", - "@dateMonthsAgo": { - "description": "Relative date - months ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "storeFilterAll": "All", - "@storeFilterAll": { - "description": "Store filter - all extensions" - }, - "storeFilterMetadata": "Metadata", - "@storeFilterMetadata": { - "description": "Store filter - metadata providers" - }, - "storeFilterDownload": "Download", - "@storeFilterDownload": { - "description": "Store filter - download providers" - }, - "storeFilterUtility": "Utility", - "@storeFilterUtility": { - "description": "Store filter - utility extensions" - }, - "storeFilterLyrics": "Lyrics", - "@storeFilterLyrics": { - "description": "Store filter - lyrics providers" - }, - "storeFilterIntegration": "Integration", - "@storeFilterIntegration": { - "description": "Store filter - integrations" - }, - "storeClearFilters": "Clear filters", - "@storeClearFilters": { - "description": "Button to clear all filters" - }, - "extensionDefaultProvider": "Default (Deezer/Spotify)", - "@extensionDefaultProvider": { - "description": "Default search provider option" - }, - "extensionDefaultProviderSubtitle": "Use built-in search", - "@extensionDefaultProviderSubtitle": { - "description": "Subtitle for default provider" - }, - "extensionAuthor": "Author", - "@extensionAuthor": { - "description": "Extension detail - author" - }, - "extensionId": "ID", - "@extensionId": { - "description": "Extension detail - unique ID" - }, - "extensionError": "Error", - "@extensionError": { - "description": "Extension detail - error message" - }, - "extensionCapabilities": "Capabilities", - "@extensionCapabilities": { - "description": "Section header - extension features" - }, - "extensionMetadataProvider": "Metadata Provider", - "@extensionMetadataProvider": { - "description": "Capability - provides metadata" - }, - "extensionDownloadProvider": "Download Provider", - "@extensionDownloadProvider": { - "description": "Capability - provides downloads" - }, - "extensionLyricsProvider": "Lyrics Provider", - "@extensionLyricsProvider": { - "description": "Capability - provides lyrics" - }, - "extensionUrlHandler": "URL Handler", - "@extensionUrlHandler": { - "description": "Capability - handles URLs" - }, - "extensionQualityOptions": "Quality Options", - "@extensionQualityOptions": { - "description": "Capability - quality selection" - }, - "extensionPostProcessingHooks": "Post-Processing Hooks", - "@extensionPostProcessingHooks": { - "description": "Capability - post-processing" - }, - "extensionPermissions": "Permissions", - "@extensionPermissions": { - "description": "Section header - required permissions" - }, - "extensionSettings": "Settings", - "@extensionSettings": { - "description": "Section header - extension settings" - }, - "extensionRemoveButton": "Remove Extension", - "@extensionRemoveButton": { - "description": "Button to uninstall extension" - }, - "extensionUpdated": "Updated", - "@extensionUpdated": { - "description": "Extension detail - last update" - }, - "extensionMinAppVersion": "Min App Version", - "@extensionMinAppVersion": { - "description": "Extension detail - minimum app version" - }, - "extensionCustomTrackMatching": "Custom Track Matching", - "@extensionCustomTrackMatching": { - "description": "Capability - custom track matching algorithm" - }, - "extensionPostProcessing": "Post-Processing", - "@extensionPostProcessing": { - "description": "Capability - post-download processing" - }, - "extensionHooksAvailable": "{count} hook(s) available", - "@extensionHooksAvailable": { - "description": "Post-processing hooks count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "extensionPatternsCount": "{count} pattern(s)", - "@extensionPatternsCount": { - "description": "URL patterns count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "extensionStrategy": "Strategy: {strategy}", - "@extensionStrategy": { - "description": "Track matching strategy name", - "placeholders": { - "strategy": { - "type": "String" - } - } - }, - "extensionsProviderPrioritySection": "Provider Priority", - "@extensionsProviderPrioritySection": { - "description": "Section header - provider priority" - }, - "extensionsInstalledSection": "Installed Extensions", - "@extensionsInstalledSection": { - "description": "Section header - installed extensions" - }, - "extensionsNoExtensions": "No extensions installed", - "@extensionsNoExtensions": { - "description": "Empty state - no extensions" - }, - "extensionsNoExtensionsSubtitle": "Install .spotiflac-ext files to add new providers", - "@extensionsNoExtensionsSubtitle": { - "description": "Empty state subtitle" - }, - "extensionsInstallButton": "Install Extension", - "@extensionsInstallButton": { - "description": "Button to install extension from file" - }, - "extensionsInfoTip": "Extensions can add new metadata and download providers. Only install extensions from trusted sources.", - "@extensionsInfoTip": { - "description": "Security warning about extensions" - }, - "extensionsInstalledSuccess": "Extension installed successfully", - "@extensionsInstalledSuccess": { - "description": "Success message after install" - }, - "extensionsDownloadPriority": "Download Priority", - "@extensionsDownloadPriority": { - "description": "Setting - download provider order" - }, - "extensionsDownloadPrioritySubtitle": "Set download service order", - "@extensionsDownloadPrioritySubtitle": { - "description": "Subtitle for download priority" - }, - "extensionsNoDownloadProvider": "No extensions with download provider", - "@extensionsNoDownloadProvider": { - "description": "Empty state - no download providers" - }, - "extensionsMetadataPriority": "Metadata Priority", - "@extensionsMetadataPriority": { - "description": "Setting - metadata provider order" - }, - "extensionsMetadataPrioritySubtitle": "Set search & metadata source order", - "@extensionsMetadataPrioritySubtitle": { - "description": "Subtitle for metadata priority" - }, - "extensionsNoMetadataProvider": "No extensions with metadata provider", - "@extensionsNoMetadataProvider": { - "description": "Empty state - no metadata providers" - }, - "extensionsSearchProvider": "Search Provider", - "@extensionsSearchProvider": { - "description": "Setting - search provider selection" - }, - "extensionsNoCustomSearch": "No extensions with custom search", - "@extensionsNoCustomSearch": { - "description": "Empty state - no search providers" - }, - "extensionsSearchProviderDescription": "Choose which service to use for searching tracks", - "@extensionsSearchProviderDescription": { - "description": "Search provider setting description" - }, - "extensionsCustomSearch": "Custom search", - "@extensionsCustomSearch": { - "description": "Label for custom search provider" - }, - "extensionsErrorLoading": "Error loading extension", - "@extensionsErrorLoading": { - "description": "Error message when extension fails to load" - }, - "qualityFlacLossless": "FLAC Lossless", - "@qualityFlacLossless": { - "description": "Quality option - CD quality FLAC" - }, - "qualityFlacLosslessSubtitle": "16-bit / 44.1kHz", - "@qualityFlacLosslessSubtitle": { - "description": "Technical spec for lossless" - }, - "qualityHiResFlac": "Hi-Res FLAC", - "@qualityHiResFlac": { - "description": "Quality option - high resolution FLAC" - }, - "qualityHiResFlacSubtitle": "24-bit / up to 96kHz", - "@qualityHiResFlacSubtitle": { - "description": "Technical spec for hi-res" - }, - "qualityHiResFlacMax": "Hi-Res FLAC Max", - "@qualityHiResFlacMax": { - "description": "Quality option - maximum resolution FLAC" - }, - "qualityHiResFlacMaxSubtitle": "24-bit / up to 192kHz", - "@qualityHiResFlacMaxSubtitle": { - "description": "Technical spec for hi-res max" - }, - "qualityNote": "Actual quality depends on track availability from the service", - "@qualityNote": { - "description": "Note about quality availability" - }, - "youtubeQualityNote": "YouTube provides lossy audio only. Not part of lossless fallback.", - "@youtubeQualityNote": { - "description": "Note for YouTube service explaining lossy-only quality" - }, - "youtubeOpusBitrateTitle": "YouTube Opus Bitrate", - "@youtubeOpusBitrateTitle": { - "description": "Title for YouTube Opus bitrate setting" - }, - "youtubeMp3BitrateTitle": "YouTube MP3 Bitrate", - "@youtubeMp3BitrateTitle": { - "description": "Title for YouTube MP3 bitrate setting" - }, - "downloadAskBeforeDownload": "Ask Before Download", - "@downloadAskBeforeDownload": { - "description": "Setting - show quality picker" - }, - "downloadDirectory": "Download Directory", - "@downloadDirectory": { - "description": "Setting - download folder" - }, - "downloadSeparateSinglesFolder": "Separate Singles Folder", - "@downloadSeparateSinglesFolder": { - "description": "Setting - separate folder for singles" - }, - "downloadAlbumFolderStructure": "Album Folder Structure", - "@downloadAlbumFolderStructure": { - "description": "Setting - album folder organization" - }, - "downloadUseAlbumArtistForFolders": "Use Album Artist for folders", - "@downloadUseAlbumArtistForFolders": { - "description": "Setting - choose whether artist folders use Album Artist or Track Artist" - }, - "downloadUsePrimaryArtistOnly": "Primary artist only for folders", - "@downloadUsePrimaryArtistOnly": { - "description": "Setting - strip featured artists from folder name" - }, - "downloadUsePrimaryArtistOnlyEnabled": "Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)", - "@downloadUsePrimaryArtistOnlyEnabled": { - "description": "Subtitle when primary artist only is enabled" - }, - "downloadUsePrimaryArtistOnlyDisabled": "Full artist string used for folder name", - "@downloadUsePrimaryArtistOnlyDisabled": { - "description": "Subtitle when primary artist only is disabled" - }, - "downloadSelectQuality": "Select Quality", - "@downloadSelectQuality": { - "description": "Dialog title - choose audio quality" - }, - "downloadFrom": "Download From", - "@downloadFrom": { - "description": "Label - download source" - }, - "appearanceAmoledDark": "AMOLED Dark", - "@appearanceAmoledDark": { - "description": "Theme option - pure black" - }, - "appearanceAmoledDarkSubtitle": "Pure black background", - "@appearanceAmoledDarkSubtitle": { - "description": "Subtitle for AMOLED dark" - }, - "queueClearAll": "Clear All", - "@queueClearAll": { - "description": "Button - clear all queue items" - }, - "queueClearAllMessage": "Are you sure you want to clear all downloads?", - "@queueClearAllMessage": { - "description": "Clear queue confirmation" - }, - "settingsAutoExportFailed": "Auto-export failed downloads", - "@settingsAutoExportFailed": { - "description": "Setting toggle for auto-export" - }, - "settingsAutoExportFailedSubtitle": "Save failed downloads to TXT file automatically", - "@settingsAutoExportFailedSubtitle": { - "description": "Subtitle for auto-export setting" - }, - "settingsDownloadNetwork": "Download Network", - "@settingsDownloadNetwork": { - "description": "Setting for network type preference" - }, - "settingsDownloadNetworkAny": "WiFi + Mobile Data", - "@settingsDownloadNetworkAny": { - "description": "Network option - use any connection" - }, - "settingsDownloadNetworkWifiOnly": "WiFi Only", - "@settingsDownloadNetworkWifiOnly": { - "description": "Network option - only use WiFi" - }, - "settingsDownloadNetworkSubtitle": "Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.", - "@settingsDownloadNetworkSubtitle": { - "description": "Subtitle explaining network preference" - }, - "albumFolderArtistAlbum": "Artist / Album", - "@albumFolderArtistAlbum": { - "description": "Album folder option" - }, - "albumFolderArtistAlbumSubtitle": "Albums/Artist Name/Album Name/", - "@albumFolderArtistAlbumSubtitle": { - "description": "Folder structure example" - }, - "albumFolderArtistYearAlbum": "Artist / [Year] Album", - "@albumFolderArtistYearAlbum": { - "description": "Album folder option with year" - }, - "albumFolderArtistYearAlbumSubtitle": "Albums/Artist Name/[2005] Album Name/", - "@albumFolderArtistYearAlbumSubtitle": { - "description": "Folder structure example" - }, - "albumFolderAlbumOnly": "Album Only", - "@albumFolderAlbumOnly": { - "description": "Album folder option" - }, - "albumFolderAlbumOnlySubtitle": "Albums/Album Name/", - "@albumFolderAlbumOnlySubtitle": { - "description": "Folder structure example" - }, - "albumFolderYearAlbum": "[Year] Album", - "@albumFolderYearAlbum": { - "description": "Album folder option with year" - }, - "albumFolderYearAlbumSubtitle": "Albums/[2005] Album Name/", - "@albumFolderYearAlbumSubtitle": { - "description": "Folder structure example" - }, - "albumFolderArtistAlbumSingles": "Artist / Album + Singles", - "@albumFolderArtistAlbumSingles": { - "description": "Album folder option with singles inside artist" - }, - "albumFolderArtistAlbumSinglesSubtitle": "Artist/Album/ and Artist/Singles/", - "@albumFolderArtistAlbumSinglesSubtitle": { - "description": "Folder structure example" - }, - "downloadedAlbumDeleteSelected": "Delete Selected", - "@downloadedAlbumDeleteSelected": { - "description": "Button - delete selected tracks" - }, - "downloadedAlbumDeleteMessage": "Delete {count} {count, plural, =1{track} other{tracks}} from this album?\n\nThis will also delete the files from storage.", - "@downloadedAlbumDeleteMessage": { - "description": "Delete confirmation with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadedAlbumSelectedCount": "{count} selected", - "@downloadedAlbumSelectedCount": { - "description": "Selection count indicator", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadedAlbumAllSelected": "All tracks selected", - "@downloadedAlbumAllSelected": { - "description": "Status - all items selected" - }, - "downloadedAlbumTapToSelect": "Tap tracks to select", - "@downloadedAlbumTapToSelect": { - "description": "Selection hint" - }, - "downloadedAlbumDeleteCount": "Delete {count} {count, plural, =1{track} other{tracks}}", - "@downloadedAlbumDeleteCount": { - "description": "Delete button text with count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadedAlbumSelectToDelete": "Select tracks to delete", - "@downloadedAlbumSelectToDelete": { - "description": "Placeholder when nothing selected" - }, - "downloadedAlbumDiscHeader": "Disc {discNumber}", - "@downloadedAlbumDiscHeader": { - "description": "Header for disc separator in multi-disc albums", - "placeholders": { - "discNumber": { - "type": "int", - "example": "1" - } - } - }, - "recentTypeArtist": "Artist", - "@recentTypeArtist": { - "description": "Recent access item type - artist" - }, - "recentTypeAlbum": "Album", - "@recentTypeAlbum": { - "description": "Recent access item type - album" - }, - "recentTypeSong": "Song", - "@recentTypeSong": { - "description": "Recent access item type - song/track" - }, - "recentTypePlaylist": "Playlist", - "@recentTypePlaylist": { - "description": "Recent access item type - playlist" - }, - "recentEmpty": "No recent items yet", - "@recentEmpty": { - "description": "Empty state text for recent access list" - }, - "recentShowAllDownloads": "Show All Downloads", - "@recentShowAllDownloads": { - "description": "Button label to unhide hidden downloads in recent access" - }, - "recentPlaylistInfo": "Playlist: {name}", - "@recentPlaylistInfo": { - "description": "Snackbar message when tapping playlist in recent access", - "placeholders": { - "name": { - "type": "String", - "description": "Playlist name" - } - } - }, - "discographyDownload": "Download Discography", - "@discographyDownload": { - "description": "Button - download artist discography" - }, - "discographyDownloadAll": "Download All", - "@discographyDownloadAll": { - "description": "Option - download entire discography" - }, - "discographyDownloadAllSubtitle": "{count} tracks from {albumCount} releases", - "@discographyDownloadAllSubtitle": { - "description": "Subtitle showing total tracks and albums", - "placeholders": { - "count": { - "type": "int" - }, - "albumCount": { - "type": "int" - } - } - }, - "discographyAlbumsOnly": "Albums Only", - "@discographyAlbumsOnly": { - "description": "Option - download only albums" - }, - "discographyAlbumsOnlySubtitle": "{count} tracks from {albumCount} albums", - "@discographyAlbumsOnlySubtitle": { - "description": "Subtitle showing album tracks count", - "placeholders": { - "count": { - "type": "int" - }, - "albumCount": { - "type": "int" - } - } - }, - "discographySinglesOnly": "Singles & EPs Only", - "@discographySinglesOnly": { - "description": "Option - download only singles" - }, - "discographySinglesOnlySubtitle": "{count} tracks from {albumCount} singles", - "@discographySinglesOnlySubtitle": { - "description": "Subtitle showing singles tracks count", - "placeholders": { - "count": { - "type": "int" - }, - "albumCount": { - "type": "int" - } - } - }, - "discographySelectAlbums": "Select Albums...", - "@discographySelectAlbums": { - "description": "Option - manually select albums to download" - }, - "discographySelectAlbumsSubtitle": "Choose specific albums or singles", - "@discographySelectAlbumsSubtitle": { - "description": "Subtitle for select albums option" - }, - "discographyFetchingTracks": "Fetching tracks...", - "@discographyFetchingTracks": { - "description": "Progress - fetching album tracks" - }, - "discographyFetchingAlbum": "Fetching {current} of {total}...", - "@discographyFetchingAlbum": { - "description": "Progress - fetching specific album", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } - }, - "discographySelectedCount": "{count} selected", - "@discographySelectedCount": { - "description": "Selection count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "discographyDownloadSelected": "Download Selected", - "@discographyDownloadSelected": { - "description": "Button - download selected albums" - }, - "discographyAddedToQueue": "Added {count} tracks to queue", - "@discographyAddedToQueue": { - "description": "Snackbar - tracks added from discography", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "discographySkippedDownloaded": "{added} added, {skipped} already downloaded", - "@discographySkippedDownloaded": { - "description": "Snackbar - with skipped tracks count", - "placeholders": { - "added": { - "type": "int" - }, - "skipped": { - "type": "int" - } - } - }, - "discographyNoAlbums": "No albums available", - "@discographyNoAlbums": { - "description": "Error - no albums found for artist" - }, - "discographyFailedToFetch": "Failed to fetch some albums", - "@discographyFailedToFetch": { - "description": "Error - some albums failed to load" - }, - "sectionStorageAccess": "Storage Access", - "@sectionStorageAccess": { - "description": "Section header for storage access settings" - }, - "allFilesAccess": "All Files Access", - "@allFilesAccess": { - "description": "Toggle for MANAGE_EXTERNAL_STORAGE permission" - }, - "allFilesAccessEnabledSubtitle": "Can write to any folder", - "@allFilesAccessEnabledSubtitle": { - "description": "Subtitle when all files access is enabled" - }, - "allFilesAccessDisabledSubtitle": "Limited to media folders only", - "@allFilesAccessDisabledSubtitle": { - "description": "Subtitle when all files access is disabled" - }, - "allFilesAccessDescription": "Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.", - "@allFilesAccessDescription": { - "description": "Description explaining when to enable all files access" - }, - "allFilesAccessDeniedMessage": "Permission was denied. Please enable 'All files access' manually in system settings.", - "@allFilesAccessDeniedMessage": { - "description": "Message when permission is permanently denied" - }, - "allFilesAccessDisabledMessage": "All Files Access disabled. The app will use limited storage access.", - "@allFilesAccessDisabledMessage": { - "description": "Snackbar message when user disables all files access" - }, - "settingsLocalLibrary": "Local Library", - "@settingsLocalLibrary": { - "description": "Settings menu item - local library" - }, - "settingsLocalLibrarySubtitle": "Scan music & detect duplicates", - "@settingsLocalLibrarySubtitle": { - "description": "Subtitle for local library settings" - }, - "settingsCache": "Storage & Cache", - "@settingsCache": { - "description": "Settings menu item - cache management" - }, - "settingsCacheSubtitle": "View size and clear cached data", - "@settingsCacheSubtitle": { - "description": "Subtitle for cache management menu" - }, - "libraryTitle": "Local Library", - "@libraryTitle": { - "description": "Library settings page title" - }, - "libraryScanSettings": "Scan Settings", - "@libraryScanSettings": { - "description": "Section header for scan settings" - }, - "libraryEnableLocalLibrary": "Enable Local Library", - "@libraryEnableLocalLibrary": { - "description": "Toggle to enable library scanning" - }, - "libraryEnableLocalLibrarySubtitle": "Scan and track your existing music", - "@libraryEnableLocalLibrarySubtitle": { - "description": "Subtitle for enable toggle" - }, - "libraryFolder": "Library Folder", - "@libraryFolder": { - "description": "Folder selection setting" - }, - "libraryFolderHint": "Tap to select folder", - "@libraryFolderHint": { - "description": "Placeholder when no folder selected" - }, - "libraryShowDuplicateIndicator": "Show Duplicate Indicator", - "@libraryShowDuplicateIndicator": { - "description": "Toggle for duplicate indicator in search" - }, - "libraryShowDuplicateIndicatorSubtitle": "Show when searching for existing tracks", - "@libraryShowDuplicateIndicatorSubtitle": { - "description": "Subtitle for duplicate indicator toggle" - }, - "libraryActions": "Actions", - "@libraryActions": { - "description": "Section header for library actions" - }, - "libraryScan": "Scan Library", - "@libraryScan": { - "description": "Button to start library scan" - }, - "libraryScanSubtitle": "Scan for audio files", - "@libraryScanSubtitle": { - "description": "Subtitle for scan button" - }, - "libraryScanSelectFolderFirst": "Select a folder first", - "@libraryScanSelectFolderFirst": { - "description": "Message when trying to scan without folder" - }, - "libraryCleanupMissingFiles": "Cleanup Missing Files", - "@libraryCleanupMissingFiles": { - "description": "Button to remove entries for missing files" - }, - "libraryCleanupMissingFilesSubtitle": "Remove entries for files that no longer exist", - "@libraryCleanupMissingFilesSubtitle": { - "description": "Subtitle for cleanup button" - }, - "libraryClear": "Clear Library", - "@libraryClear": { - "description": "Button to clear all library entries" - }, - "libraryClearSubtitle": "Remove all scanned tracks", - "@libraryClearSubtitle": { - "description": "Subtitle for clear button" - }, - "libraryClearConfirmTitle": "Clear Library", - "@libraryClearConfirmTitle": { - "description": "Dialog title for clear confirmation" - }, - "libraryClearConfirmMessage": "This will remove all scanned tracks from your library. Your actual music files will not be deleted.", - "@libraryClearConfirmMessage": { - "description": "Dialog message for clear confirmation" - }, - "libraryAbout": "About Local Library", - "@libraryAbout": { - "description": "Section header for about info" - }, - "libraryAboutDescription": "Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.", - "@libraryAboutDescription": { - "description": "Description of local library feature" - }, - "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", - "@libraryTracksUnit": { - "description": "Unit label for tracks count (without the number itself)", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "libraryLastScanned": "Last scanned: {time}", - "@libraryLastScanned": { - "description": "Last scan time display", - "placeholders": { - "time": { - "type": "String" - } - } - }, - "libraryLastScannedNever": "Never", - "@libraryLastScannedNever": { - "description": "Shown when library has never been scanned" - }, - "libraryScanning": "Scanning...", - "@libraryScanning": { - "description": "Status during scan" - }, - "libraryScanProgress": "{progress}% of {total} files", - "@libraryScanProgress": { - "description": "Scan progress display", - "placeholders": { - "progress": { - "type": "String" - }, - "total": { - "type": "int" - } - } - }, - "libraryInLibrary": "In Library", - "@libraryInLibrary": { - "description": "Badge shown on tracks that exist in local library" - }, - "libraryRemovedMissingFiles": "Removed {count} missing files from library", - "@libraryRemovedMissingFiles": { - "description": "Snackbar after cleanup", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "libraryCleared": "Library cleared", - "@libraryCleared": { - "description": "Snackbar after clearing library" - }, - "libraryStorageAccessRequired": "Storage Access Required", - "@libraryStorageAccessRequired": { - "description": "Dialog title for storage permission" - }, - "libraryStorageAccessMessage": "SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.", - "@libraryStorageAccessMessage": { - "description": "Dialog message for storage permission" - }, - "libraryFolderNotExist": "Selected folder does not exist", - "@libraryFolderNotExist": { - "description": "Error when folder doesn't exist" - }, - "librarySourceDownloaded": "Downloaded", - "@librarySourceDownloaded": { - "description": "Badge for tracks downloaded via SpotiFLAC" - }, - "librarySourceLocal": "Local", - "@librarySourceLocal": { - "description": "Badge for tracks from local library scan" - }, - "libraryFilterAll": "All", - "@libraryFilterAll": { - "description": "Filter chip - show all library items" - }, - "libraryFilterDownloaded": "Downloaded", - "@libraryFilterDownloaded": { - "description": "Filter chip - show only downloaded items" - }, - "libraryFilterLocal": "Local", - "@libraryFilterLocal": { - "description": "Filter chip - show only local library items" - }, - "libraryFilterTitle": "Filters", - "@libraryFilterTitle": { - "description": "Filter bottom sheet title" - }, - "libraryFilterReset": "Reset", - "@libraryFilterReset": { - "description": "Reset all filters button" - }, - "libraryFilterApply": "Apply", - "@libraryFilterApply": { - "description": "Apply filters button" - }, - "libraryFilterSource": "Source", - "@libraryFilterSource": { - "description": "Filter section - source type" - }, - "libraryFilterQuality": "Quality", - "@libraryFilterQuality": { - "description": "Filter section - audio quality" - }, - "libraryFilterQualityHiRes": "Hi-Res (24bit)", - "@libraryFilterQualityHiRes": { - "description": "Filter option - high resolution audio" - }, - "libraryFilterQualityCD": "CD (16bit)", - "@libraryFilterQualityCD": { - "description": "Filter option - CD quality audio" - }, - "libraryFilterQualityLossy": "Lossy", - "@libraryFilterQualityLossy": { - "description": "Filter option - lossy compressed audio" - }, - "libraryFilterFormat": "Format", - "@libraryFilterFormat": { - "description": "Filter section - file format" - }, - "libraryFilterSort": "Sort", - "@libraryFilterSort": { - "description": "Filter section - sort order" - }, - "libraryFilterSortLatest": "Latest", - "@libraryFilterSortLatest": { - "description": "Sort option - newest first" - }, - "libraryFilterSortOldest": "Oldest", - "@libraryFilterSortOldest": { - "description": "Sort option - oldest first" - }, - "timeJustNow": "Just now", - "@timeJustNow": { - "description": "Relative time - less than a minute ago" - }, - "timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}", - "@timeMinutesAgo": { - "description": "Relative time - minutes ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}", - "@timeHoursAgo": { - "description": "Relative time - hours ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", - "@tutorialWelcomeTitle": { - "description": "Tutorial welcome page title" - }, - "tutorialWelcomeDesc": "Let's learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.", - "@tutorialWelcomeDesc": { - "description": "Tutorial welcome page description" - }, - "tutorialWelcomeTip1": "Download music from Spotify, Deezer, or paste any supported URL", - "@tutorialWelcomeTip1": { - "description": "Tutorial welcome tip 1" - }, - "tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music", - "@tutorialWelcomeTip2": { - "description": "Tutorial welcome tip 2" - }, - "tutorialWelcomeTip3": "Automatic metadata, cover art, and lyrics embedding", - "@tutorialWelcomeTip3": { - "description": "Tutorial welcome tip 3" - }, - "tutorialSearchTitle": "Finding Music", - "@tutorialSearchTitle": { - "description": "Tutorial search page title" - }, - "tutorialSearchDesc": "There are two easy ways to find music you want to download.", - "@tutorialSearchDesc": { - "description": "Tutorial search page description" - }, - "tutorialDownloadTitle": "Downloading Music", - "@tutorialDownloadTitle": { - "description": "Tutorial download page title" - }, - "tutorialDownloadDesc": "Downloading music is simple and fast. Here's how it works.", - "@tutorialDownloadDesc": { - "description": "Tutorial download page description" - }, - "tutorialLibraryTitle": "Your Library", - "@tutorialLibraryTitle": { - "description": "Tutorial library page title" - }, - "tutorialLibraryDesc": "All your downloaded music is organized in the Library tab.", - "@tutorialLibraryDesc": { - "description": "Tutorial library page description" - }, - "tutorialLibraryTip1": "View download progress and queue in the Library tab", - "@tutorialLibraryTip1": { - "description": "Tutorial library tip 1" - }, - "tutorialLibraryTip2": "Tap any track to play it with your music player", - "@tutorialLibraryTip2": { - "description": "Tutorial library tip 2" - }, - "tutorialLibraryTip3": "Switch between list and grid view for better browsing", - "@tutorialLibraryTip3": { - "description": "Tutorial library tip 3" - }, - "tutorialExtensionsTitle": "Extensions", - "@tutorialExtensionsTitle": { - "description": "Tutorial extensions page title" - }, - "tutorialExtensionsDesc": "Extend the app's capabilities with community extensions.", - "@tutorialExtensionsDesc": { - "description": "Tutorial extensions page description" - }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", - "@tutorialExtensionsTip1": { - "description": "Tutorial extensions tip 1" - }, - "tutorialExtensionsTip2": "Add new download providers or search sources", - "@tutorialExtensionsTip2": { - "description": "Tutorial extensions tip 2" - }, - "tutorialExtensionsTip3": "Get lyrics, enhanced metadata, and more features", - "@tutorialExtensionsTip3": { - "description": "Tutorial extensions tip 3" - }, - "tutorialSettingsTitle": "Customize Your Experience", - "@tutorialSettingsTitle": { - "description": "Tutorial settings page title" - }, - "tutorialSettingsDesc": "Personalize the app in Settings to match your preferences.", - "@tutorialSettingsDesc": { - "description": "Tutorial settings page description" - }, - "tutorialSettingsTip1": "Change download location and folder organization", - "@tutorialSettingsTip1": { - "description": "Tutorial settings tip 1" - }, - "tutorialSettingsTip2": "Set default audio quality and format preferences", - "@tutorialSettingsTip2": { - "description": "Tutorial settings tip 2" - }, - "tutorialSettingsTip3": "Customize app theme and appearance", - "@tutorialSettingsTip3": { - "description": "Tutorial settings tip 3" - }, - "tutorialReadyMessage": "You're all set! Start downloading your favorite music now.", - "@tutorialReadyMessage": { - "description": "Tutorial completion message" - }, - "libraryForceFullScan": "Force Full Scan", - "@libraryForceFullScan": { - "description": "Button to force a complete rescan of library" - }, - "libraryForceFullScanSubtitle": "Rescan all files, ignoring cache", - "@libraryForceFullScanSubtitle": { - "description": "Subtitle for force full scan button" - }, - "cleanupOrphanedDownloads": "Cleanup Orphaned Downloads", - "@cleanupOrphanedDownloads": { - "description": "Button to remove history entries for deleted files" - }, - "cleanupOrphanedDownloadsSubtitle": "Remove history entries for files that no longer exist", - "@cleanupOrphanedDownloadsSubtitle": { - "description": "Subtitle for orphaned cleanup button" - }, - "cleanupOrphanedDownloadsResult": "Removed {count} orphaned entries from history", - "@cleanupOrphanedDownloadsResult": { - "description": "Snackbar after orphan cleanup", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "cleanupOrphanedDownloadsNone": "No orphaned entries found", - "@cleanupOrphanedDownloadsNone": { - "description": "Snackbar when no orphans found" - }, - "cacheTitle": "Storage & Cache", - "@cacheTitle": { - "description": "Cache management page title" - }, - "cacheSummaryTitle": "Cache overview", - "@cacheSummaryTitle": { - "description": "Heading for cache summary card" - }, - "cacheSummarySubtitle": "Clearing cache will not remove downloaded music files.", - "@cacheSummarySubtitle": { - "description": "Helper text for cache summary card" - }, - "cacheEstimatedTotal": "Estimated cache usage: {size}", - "@cacheEstimatedTotal": { - "description": "Total cache size shown in summary", - "placeholders": { - "size": { - "type": "String" - } - } - }, - "cacheSectionStorage": "Cached Data", - "@cacheSectionStorage": { - "description": "Section header for cache entries" - }, - "cacheSectionMaintenance": "Maintenance", - "@cacheSectionMaintenance": { - "description": "Section header for cleanup actions" - }, - "cacheAppDirectory": "App cache directory", - "@cacheAppDirectory": { - "description": "Cache item title for app cache directory" - }, - "cacheAppDirectoryDesc": "HTTP responses, WebView data, and other temporary app data.", - "@cacheAppDirectoryDesc": { - "description": "Description of what app cache directory contains" - }, - "cacheTempDirectory": "Temporary directory", - "@cacheTempDirectory": { - "description": "Cache item title for temporary files directory" - }, - "cacheTempDirectoryDesc": "Temporary files from downloads and audio conversion.", - "@cacheTempDirectoryDesc": { - "description": "Description of what temporary directory contains" - }, - "cacheCoverImage": "Cover image cache", - "@cacheCoverImage": { - "description": "Cache item title for persistent cover images" - }, - "cacheCoverImageDesc": "Downloaded album and track cover art. Will re-download when viewed.", - "@cacheCoverImageDesc": { - "description": "Description of what cover image cache contains" - }, - "cacheLibraryCover": "Library cover cache", - "@cacheLibraryCover": { - "description": "Cache item title for local library cover art images" - }, - "cacheLibraryCoverDesc": "Cover art extracted from local music files. Will re-extract on next scan.", - "@cacheLibraryCoverDesc": { - "description": "Description of what library cover cache contains" - }, - "cacheExploreFeed": "Explore feed cache", - "@cacheExploreFeed": { - "description": "Cache item title for explore home feed cache" - }, - "cacheExploreFeedDesc": "Explore tab content (new releases, trending). Will refresh on next visit.", - "@cacheExploreFeedDesc": { - "description": "Description of what explore feed cache contains" - }, - "cacheTrackLookup": "Track lookup cache", - "@cacheTrackLookup": { - "description": "Cache item title for track ID lookup cache" - }, - "cacheTrackLookupDesc": "Spotify/Deezer track ID lookups. Clearing may slow next few searches.", - "@cacheTrackLookupDesc": { - "description": "Description of what track lookup cache contains" - }, - "cacheCleanupUnusedDesc": "Remove orphaned download history and library entries for missing files.", - "@cacheCleanupUnusedDesc": { - "description": "Description of what cleanup unused data does" - }, - "cacheNoData": "No cached data", - "@cacheNoData": { - "description": "Label when cache category has no data" - }, - "cacheSizeWithFiles": "{size} in {count} files", - "@cacheSizeWithFiles": { - "description": "Cache size and file count", - "placeholders": { - "size": { - "type": "String" - }, - "count": { - "type": "int" - } - } - }, - "cacheSizeOnly": "{size}", - "@cacheSizeOnly": { - "description": "Cache size only", - "placeholders": { - "size": { - "type": "String" - } - } - }, - "cacheEntries": "{count} entries", - "@cacheEntries": { - "description": "Track cache entry count", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "cacheClearSuccess": "Cleared: {target}", - "@cacheClearSuccess": { - "description": "Snackbar after clearing selected cache", - "placeholders": { - "target": { - "type": "String" - } - } - }, - "cacheClearConfirmTitle": "Clear cache?", - "@cacheClearConfirmTitle": { - "description": "Dialog title before clearing one cache category" - }, - "cacheClearConfirmMessage": "This will clear cached data for {target}. Downloaded music files will not be deleted.", - "@cacheClearConfirmMessage": { - "description": "Dialog message before clearing selected cache", - "placeholders": { - "target": { - "type": "String" - } - } - }, - "cacheClearAllConfirmTitle": "Clear all cache?", - "@cacheClearAllConfirmTitle": { - "description": "Dialog title before clearing all caches" - }, - "cacheClearAllConfirmMessage": "This will clear all cache categories on this page. Downloaded music files will not be deleted.", - "@cacheClearAllConfirmMessage": { - "description": "Dialog message before clearing all caches" - }, - "cacheClearAll": "Clear all cache", - "@cacheClearAll": { - "description": "Button label to clear all caches" - }, - "cacheCleanupUnused": "Cleanup unused data", - "@cacheCleanupUnused": { - "description": "Action title for cleaning unused entries" - }, - "cacheCleanupUnusedSubtitle": "Remove orphaned download history and missing library entries", - "@cacheCleanupUnusedSubtitle": { - "description": "Subtitle for cleanup unused data action" - }, - "cacheCleanupResult": "Cleanup completed: {downloadCount} orphaned downloads, {libraryCount} missing library entries", - "@cacheCleanupResult": { - "description": "Snackbar after unused data cleanup", - "placeholders": { - "downloadCount": { - "type": "int" - }, - "libraryCount": { - "type": "int" - } - } - }, - "cacheRefreshStats": "Refresh stats", - "@cacheRefreshStats": { - "description": "Button label to refresh cache statistics" - }, - "trackSaveCoverArt": "Save Cover Art", - "@trackSaveCoverArt": { - "description": "Menu action - save album cover art as file" - }, - "trackSaveCoverArtSubtitle": "Save album art as .jpg file", - "@trackSaveCoverArtSubtitle": { - "description": "Subtitle for save cover art action" - }, - "trackSaveLyrics": "Save Lyrics (.lrc)", - "@trackSaveLyrics": { - "description": "Menu action - save lyrics as .lrc file" - }, - "trackSaveLyricsSubtitle": "Fetch and save lyrics as .lrc file", - "@trackSaveLyricsSubtitle": { - "description": "Subtitle for save lyrics action" - }, - "trackSaveLyricsProgress": "Saving lyrics...", - "@trackSaveLyricsProgress": { - "description": "Snackbar while saving lyrics to file" - }, - "trackReEnrich": "Re-enrich", - "@trackReEnrich": { - "description": "Menu action - re-embed metadata into audio file" - }, - "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", - "@trackReEnrichOnlineSubtitle": { - "description": "Subtitle for re-enrich metadata action for local items" - }, - "trackEditMetadata": "Edit Metadata", - "@trackEditMetadata": { - "description": "Menu action - edit embedded metadata" - }, - "trackCoverSaved": "Cover art saved to {fileName}", - "@trackCoverSaved": { - "description": "Snackbar after cover art saved", - "placeholders": { - "fileName": { - "type": "String" - } - } - }, - "trackCoverNoSource": "No cover art source available", - "@trackCoverNoSource": { - "description": "Snackbar when no cover art URL or embedded cover" - }, - "trackLyricsSaved": "Lyrics saved to {fileName}", - "@trackLyricsSaved": { - "description": "Snackbar after lyrics saved", - "placeholders": { - "fileName": { - "type": "String" - } - } - }, - "trackReEnrichProgress": "Re-enriching metadata...", - "@trackReEnrichProgress": { - "description": "Snackbar while re-enriching metadata" - }, - "trackReEnrichSearching": "Searching metadata online...", - "@trackReEnrichSearching": { - "description": "Snackbar while searching metadata from internet for local items" - }, - "trackReEnrichSuccess": "Metadata re-enriched successfully", - "@trackReEnrichSuccess": { - "description": "Snackbar after successful re-enrichment" - }, - "trackReEnrichFfmpegFailed": "FFmpeg metadata embed failed", - "@trackReEnrichFfmpegFailed": { - "description": "Snackbar when FFmpeg embed fails for MP3/Opus" - }, - "trackSaveFailed": "Failed: {error}", - "@trackSaveFailed": { - "description": "Snackbar when save operation fails", - "placeholders": { - "error": { - "type": "String" - } - } - }, - "trackConvertFormat": "Convert Format", - "@trackConvertFormat": { - "description": "Menu item - convert audio format" - }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", - "@trackConvertFormatSubtitle": { - "description": "Subtitle for convert format menu item" - }, - "trackConvertTitle": "Convert Audio", - "@trackConvertTitle": { - "description": "Title of convert bottom sheet" - }, - "trackConvertTargetFormat": "Target Format", - "@trackConvertTargetFormat": { - "description": "Label for format selection" - }, - "trackConvertBitrate": "Bitrate", - "@trackConvertBitrate": { - "description": "Label for bitrate selection" - }, - "trackConvertConfirmTitle": "Confirm Conversion", - "@trackConvertConfirmTitle": { - "description": "Confirmation dialog title" - }, - "trackConvertConfirmMessage": "Convert from {sourceFormat} to {targetFormat} at {bitrate}?\n\nThe original file will be deleted after conversion.", - "@trackConvertConfirmMessage": { - "description": "Confirmation dialog message", - "placeholders": { - "sourceFormat": { - "type": "String" - }, - "targetFormat": { - "type": "String" - }, - "bitrate": { - "type": "String" - } - } - }, - "trackConvertConverting": "Converting audio...", - "@trackConvertConverting": { - "description": "Snackbar while converting" - }, - "trackConvertSuccess": "Converted to {format} successfully", - "@trackConvertSuccess": { - "description": "Snackbar after successful conversion", - "placeholders": { - "format": { - "type": "String" - } - } - }, - "trackConvertFailed": "Conversion failed", - "@trackConvertFailed": { - "description": "Snackbar when conversion fails" - }, - "actionCreate": "Create", - "@actionCreate": { - "description": "Generic action button - create" - }, - "collectionFoldersTitle": "My folders", - "@collectionFoldersTitle": { - "description": "Library section title for custom folders" - }, - "collectionWishlist": "Wishlist", - "@collectionWishlist": { - "description": "Custom folder for saved tracks to download later" - }, - "collectionLoved": "Loved", - "@collectionLoved": { - "description": "Custom folder for favorite tracks" - }, - "collectionPlaylists": "Playlists", - "@collectionPlaylists": { - "description": "Custom user playlists folder" - }, - "collectionPlaylist": "Playlist", - "@collectionPlaylist": { - "description": "Single playlist label" - }, - "collectionAddToPlaylist": "Add to playlist", - "@collectionAddToPlaylist": { - "description": "Action to add a track to user playlist" - }, - "collectionCreatePlaylist": "Create playlist", - "@collectionCreatePlaylist": { - "description": "Action to create a new playlist" - }, - "collectionNoPlaylistsYet": "No playlists yet", - "@collectionNoPlaylistsYet": { - "description": "Empty state title when user has no playlists" - }, - "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", - "@collectionNoPlaylistsSubtitle": { - "description": "Empty state subtitle when user has no playlists" - }, - "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", - "@collectionPlaylistTracks": { - "description": "Track count label for custom playlists", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "collectionAddedToPlaylist": "Added to \"{playlistName}\"", - "@collectionAddedToPlaylist": { - "description": "Snackbar after adding track to playlist", - "placeholders": { - "playlistName": { - "type": "String" - } - } - }, - "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", - "@collectionAlreadyInPlaylist": { - "description": "Snackbar when track already exists in playlist", - "placeholders": { - "playlistName": { - "type": "String" - } - } - }, - "collectionPlaylistCreated": "Playlist created", - "@collectionPlaylistCreated": { - "description": "Snackbar after creating playlist" - }, - "collectionPlaylistNameHint": "Playlist name", - "@collectionPlaylistNameHint": { - "description": "Hint text for playlist name input" - }, - "collectionPlaylistNameRequired": "Playlist name is required", - "@collectionPlaylistNameRequired": { - "description": "Validation error for empty playlist name" - }, - "collectionRenamePlaylist": "Rename playlist", - "@collectionRenamePlaylist": { - "description": "Action to rename playlist" - }, - "collectionDeletePlaylist": "Delete playlist", - "@collectionDeletePlaylist": { - "description": "Action to delete playlist" - }, - "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", - "@collectionDeletePlaylistMessage": { - "description": "Confirmation message for deleting playlist", - "placeholders": { - "playlistName": { - "type": "String" - } - } - }, - "collectionPlaylistDeleted": "Playlist deleted", - "@collectionPlaylistDeleted": { - "description": "Snackbar after deleting playlist" - }, - "collectionPlaylistRenamed": "Playlist renamed", - "@collectionPlaylistRenamed": { - "description": "Snackbar after renaming playlist" - }, - "collectionWishlistEmptyTitle": "Wishlist is empty", - "@collectionWishlistEmptyTitle": { - "description": "Wishlist empty state title" - }, - "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", - "@collectionWishlistEmptySubtitle": { - "description": "Wishlist empty state subtitle" - }, - "collectionLovedEmptyTitle": "Loved folder is empty", - "@collectionLovedEmptyTitle": { - "description": "Loved empty state title" - }, - "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", - "@collectionLovedEmptySubtitle": { - "description": "Loved empty state subtitle" - }, - "collectionPlaylistEmptyTitle": "Playlist is empty", - "@collectionPlaylistEmptyTitle": { - "description": "Playlist empty state title" - }, - "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", - "@collectionPlaylistEmptySubtitle": { - "description": "Playlist empty state subtitle" - }, - "collectionRemoveFromPlaylist": "Remove from playlist", - "@collectionRemoveFromPlaylist": { - "description": "Tooltip for removing track from playlist" - }, - "collectionRemoveFromFolder": "Remove from folder", - "@collectionRemoveFromFolder": { - "description": "Tooltip for removing track from wishlist/loved folder" - }, - "collectionRemoved": "\"{trackName}\" removed", - "@collectionRemoved": { - "description": "Snackbar after removing a track from a collection", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "collectionAddedToLoved": "\"{trackName}\" added to Loved", - "@collectionAddedToLoved": { - "description": "Snackbar after adding track to loved folder", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", - "@collectionRemovedFromLoved": { - "description": "Snackbar after removing track from loved folder", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", - "@collectionAddedToWishlist": { - "description": "Snackbar after adding track to wishlist", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", - "@collectionRemovedFromWishlist": { - "description": "Snackbar after removing track from wishlist", - "placeholders": { - "trackName": { - "type": "String" - } - } - }, - "trackOptionAddToLoved": "Add to Loved", - "@trackOptionAddToLoved": { - "description": "Bottom sheet action label - add track to loved folder" - }, - "trackOptionRemoveFromLoved": "Remove from Loved", - "@trackOptionRemoveFromLoved": { - "description": "Bottom sheet action label - remove track from loved folder" - }, - "trackOptionAddToWishlist": "Add to Wishlist", - "@trackOptionAddToWishlist": { - "description": "Bottom sheet action label - add track to wishlist" - }, - "trackOptionRemoveFromWishlist": "Remove from Wishlist", - "@trackOptionRemoveFromWishlist": { - "description": "Bottom sheet action label - remove track from wishlist" - }, - "collectionPlaylistChangeCover": "Change cover image", - "@collectionPlaylistChangeCover": { - "description": "Bottom sheet action to pick a custom cover image for a playlist" - }, - "collectionPlaylistRemoveCover": "Remove cover image", - "@collectionPlaylistRemoveCover": { - "description": "Bottom sheet action to remove custom cover image from a playlist" - }, - "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", - "@selectionShareCount": { - "description": "Share button text with count in selection mode", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "selectionShareNoFiles": "No shareable files found", - "@selectionShareNoFiles": { - "description": "Snackbar when no selected files exist on disk" - }, - "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", - "@selectionConvertCount": { - "description": "Convert button text with count in selection mode", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "selectionConvertNoConvertible": "No convertible tracks selected", - "@selectionConvertNoConvertible": { - "description": "Snackbar when no selected tracks support conversion" - }, - "selectionBatchConvertConfirmTitle": "Batch Convert", - "@selectionBatchConvertConfirmTitle": { - "description": "Confirmation dialog title for batch conversion" - }, - "selectionBatchConvertConfirmMessage": "Convert {count} {count, plural, =1{track} other{tracks}} to {format} at {bitrate}?\n\nOriginal files will be deleted after conversion.", - "@selectionBatchConvertConfirmMessage": { - "description": "Confirmation dialog message for batch conversion", - "placeholders": { - "count": { - "type": "int" - }, - "format": { - "type": "String" - }, - "bitrate": { - "type": "String" - } - } - }, - "selectionBatchConvertProgress": "Converting {current} of {total}...", - "@selectionBatchConvertProgress": { - "description": "Snackbar during batch conversion progress", - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } - }, - "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", - "@selectionBatchConvertSuccess": { - "description": "Snackbar after batch conversion completes", - "placeholders": { - "success": { - "type": "int" - }, - "total": { - "type": "int" - }, - "format": { - "type": "String" - } - } - }, - "downloadedAlbumDownloadedCount": "{count} downloaded", - "@downloadedAlbumDownloadedCount": { - "description": "Downloaded tracks count badge", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", - "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" - }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", - "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" - } -} \ No newline at end of file diff --git a/lib/l10n/arb/app_zh_CN.arb b/lib/l10n/arb/app_zh_CN.arb index 5b4dd487..d7a9c1aa 100644 --- a/lib/l10n/arb/app_zh_CN.arb +++ b/lib/l10n/arb/app_zh_CN.arb @@ -1,5 +1,5 @@ { - "@@locale": "zh-CN", + "@@locale": "zh_CN", "@@last_modified": "2026-01-16", "appName": "SpotiFLAC", "@appName": { diff --git a/lib/l10n/arb/app_zh_TW.arb b/lib/l10n/arb/app_zh_TW.arb index d33658af..b51686c0 100644 --- a/lib/l10n/arb/app_zh_TW.arb +++ b/lib/l10n/arb/app_zh_TW.arb @@ -1,5 +1,5 @@ { - "@@locale": "zh-TW", + "@@locale": "zh_TW", "@@last_modified": "2026-01-16", "appName": "SpotiFLAC", "@appName": { diff --git a/lib/providers/track_provider.dart b/lib/providers/track_provider.dart index 271f22f7..8ba3f33b 100644 --- a/lib/providers/track_provider.dart +++ b/lib/providers/track_provider.dart @@ -457,6 +457,19 @@ class TrackNotifier extends Notifier { return; } + // If URL doesn't match any known service, it's unrecognized + final isSpotifyUrl = url.contains('open.spotify.com') || + url.contains('spotify.link') || + url.startsWith('spotify:'); + if (!isSpotifyUrl) { + state = TrackState( + isLoading: false, + error: 'url_not_recognized', + hasSearchText: state.hasSearchText, + ); + return; + } + final parsed = await PlatformBridge.parseSpotifyUrl(url); if (!_isRequestValid(requestId)) return; diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index f1c3f807..65e8fcec 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -583,7 +583,25 @@ class _HomeTabState extends ConsumerState if (url.isEmpty) return; if (url.startsWith('http') || url.startsWith('spotify:')) { await ref.read(trackProvider.notifier).fetchFromUrl(url); - _navigateToDetailIfNeeded(); + final trackState = ref.read(trackProvider); + if (trackState.error != null && mounted) { + final l10n = context.l10n; + final errorMsg = trackState.error!; + final isRateLimit = errorMsg.contains('429') || + errorMsg.toLowerCase().contains('rate limit') || + errorMsg.toLowerCase().contains('too many requests'); + final displayMessage = errorMsg == 'url_not_recognized' + ? l10n.errorUrlNotRecognizedMessage + : isRateLimit + ? l10n.errorRateLimitedMessage + : l10n.errorUrlFetchFailed; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(displayMessage)), + ); + ref.read(trackProvider.notifier).clear(); + } else { + _navigateToDetailIfNeeded(); + } } else { final settings = ref.read(settingsProvider); await ref @@ -1262,7 +1280,8 @@ class _HomeTabState extends ConsumerState (searchArtists != null && searchArtists.isNotEmpty) || (searchAlbums != null && searchAlbums.isNotEmpty) || (searchPlaylists != null && searchPlaylists.isNotEmpty) || - isLoading; + isLoading || + error != null; return SliverMainAxisGroup( slivers: _buildSearchResults( @@ -2224,11 +2243,14 @@ class _HomeTabState extends ConsumerState } Widget _buildErrorWidget(String error, ColorScheme colorScheme) { + final l10n = context.l10n; final isRateLimit = error.contains('429') || error.toLowerCase().contains('rate limit') || error.toLowerCase().contains('too many requests'); + final isUrlNotRecognized = error == 'url_not_recognized'; + if (isRateLimit) { return Card( elevation: 0, @@ -2245,7 +2267,7 @@ class _HomeTabState extends ConsumerState crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Rate Limited', + l10n.errorRateLimited, style: TextStyle( color: colorScheme.onErrorContainer, fontWeight: FontWeight.bold, @@ -2253,7 +2275,7 @@ class _HomeTabState extends ConsumerState ), const SizedBox(height: 4), Text( - 'Too many requests. Please wait a moment before searching again.', + l10n.errorRateLimitedMessage, style: TextStyle( color: colorScheme.onErrorContainer, fontSize: 12, @@ -2268,6 +2290,45 @@ class _HomeTabState extends ConsumerState ); } + if (isUrlNotRecognized) { + return Card( + elevation: 0, + color: colorScheme.errorContainer.withValues(alpha: 0.5), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Icon(Icons.link_off, color: colorScheme.error), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.errorUrlNotRecognized, + style: TextStyle( + color: colorScheme.error, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + l10n.errorUrlNotRecognizedMessage, + style: TextStyle( + color: colorScheme.error, + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + return Card( elevation: 0, color: colorScheme.errorContainer.withValues(alpha: 0.5), @@ -2279,7 +2340,10 @@ class _HomeTabState extends ConsumerState Icon(Icons.error_outline, color: colorScheme.error), const SizedBox(width: 12), Expanded( - child: Text(error, style: TextStyle(color: colorScheme.error)), + child: Text( + l10n.errorUrlFetchFailed, + style: TextStyle(color: colorScheme.error), + ), ), ], ), diff --git a/lib/screens/main_shell.dart b/lib/screens/main_shell.dart index e103c6a1..b77fee99 100644 --- a/lib/screens/main_shell.dart +++ b/lib/screens/main_shell.dart @@ -102,13 +102,29 @@ class _MainShellState extends ConsumerState { if (_currentIndex != 0) { _onNavTap(0); } - ref.read(trackProvider.notifier).fetchFromUrl(url); ref.read(settingsProvider.notifier).setHasSearchedBefore(); if (mounted) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(context.l10n.loadingSharedLink))); } + await ref.read(trackProvider.notifier).fetchFromUrl(url); + final trackState = ref.read(trackProvider); + if (trackState.error != null && mounted) { + final l10n = context.l10n; + final errorMsg = trackState.error!; + final isRateLimit = errorMsg.contains('429') || + errorMsg.toLowerCase().contains('rate limit') || + errorMsg.toLowerCase().contains('too many requests'); + final displayMessage = errorMsg == 'url_not_recognized' + ? l10n.errorUrlNotRecognizedMessage + : isRateLimit + ? l10n.errorRateLimitedMessage + : l10n.errorUrlFetchFailed; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(displayMessage)), + ); + } } Future _checkForUpdates() async { From 9c6856b584430291040ba3c560d8e7e3b6d46e10 Mon Sep 17 00:00:00 2001 From: zarzet Date: Tue, 10 Mar 2026 15:26:19 +0700 Subject: [PATCH 094/104] refactor: migrate to git-cliff for changelog and deduplicate library items - Replace manual CHANGELOG.md parsing with git-cliff action in release workflow - Add cliff.toml config for conventional commit grouping and GitHub integration - Extract buildPathMatchKeys into shared utility with Android storage alias support - Deduplicate local library items overlapping with downloaded items in queue tab --- .github/workflows/release.yml | 106 +++++++++------------- cliff.toml | 105 +++++++++++++++++++++ lib/providers/local_library_provider.dart | 78 ++-------------- lib/screens/queue_tab.dart | 35 ++++++- lib/utils/path_match_keys.dart | 104 +++++++++++++++++++++ 5 files changed, 292 insertions(+), 136 deletions(-) create mode 100644 cliff.toml create mode 100644 lib/utils/path_match_keys.dart diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 04ade01e..1129349f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -309,32 +309,22 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v6 + with: + fetch-depth: 0 # Full history needed for git-cliff - - name: Extract changelog for version + - name: Generate changelog with git-cliff id: changelog + uses: orhun/git-cliff-action@v4 + with: + config: cliff.toml + args: --latest --strip header + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OUTPUT: /tmp/changelog.txt + + - name: Show generated changelog run: | - VERSION=${{ needs.get-version.outputs.version }} - VERSION_NUM=${VERSION#v} # Remove 'v' prefix - - echo "Looking for version: $VERSION_NUM" - - # Extract changelog section for this version using sed - # Find the line with version, then print until next version header or end - CHANGELOG=$(sed -n "/^## \[$VERSION_NUM\]/,/^## \[/{ /^## \[$VERSION_NUM\]/d; /^## \[/d; p; }" CHANGELOG.md) - - # If no changelog found, use default message - if [ -z "$CHANGELOG" ]; then - echo "No changelog found for version $VERSION_NUM" - CHANGELOG="See CHANGELOG.md for details." - else - echo "Found changelog content" - # Remove trailing --- separator if present (CHANGELOG uses --- between versions) - CHANGELOG=$(echo "$CHANGELOG" | sed '/^---$/d') - fi - - # Save to file for multiline support - echo "$CHANGELOG" > /tmp/changelog.txt - echo "Extracted changelog:" + echo "Generated changelog:" cat /tmp/changelog.txt - name: Download Android APK @@ -352,15 +342,13 @@ jobs: - name: Prepare release body run: | VERSION=${{ needs.get-version.outputs.version }} - cat > /tmp/release_body.txt << 'HEADER' - ### What's New - HEADER - - cat /tmp/changelog.txt >> /tmp/release_body.txt - REPO_OWNER="${{ github.repository_owner }}" REPO_NAME="${{ github.event.repository.name }}" - + + # Start with git-cliff changelog + cp /tmp/changelog.txt /tmp/release_body.txt + + # Append download section cat >> /tmp/release_body.txt << FOOTER --- @@ -404,6 +392,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Download Android APK uses: actions/download-artifact@v7 @@ -417,52 +407,40 @@ jobs: name: ios-ipa path: ./release - - name: Extract changelog for version + - name: Generate changelog with git-cliff for Telegram + uses: orhun/git-cliff-action@v4 + with: + config: cliff.toml + args: --latest --strip all + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OUTPUT: /tmp/cliff_tg.txt + + - name: Convert changelog for Telegram id: changelog run: | - VERSION=${{ needs.get-version.outputs.version }} - VERSION_NUM=${VERSION#v} - - # Extract changelog, limit to ~2500 chars for Telegram (4096 limit minus message overhead) - # Use tr -d '\r' to handle CRLF line endings from Windows - FULL_CHANGELOG=$(cat CHANGELOG.md | tr -d '\r' | sed -n "/^## \[$VERSION_NUM\]/,/^## \[/{ /^## \[$VERSION_NUM\]/d; /^## \[/d; p; }" | sed '/^---$/d') - - echo "DEBUG: Extracted changelog length: ${#FULL_CHANGELOG}" - echo "DEBUG: First 200 chars: ${FULL_CHANGELOG:0:200}" - - if [ -z "$FULL_CHANGELOG" ]; then - CHANGELOG="See release notes on GitHub for details." + if [ ! -s /tmp/cliff_tg.txt ]; then + echo "See release notes on GitHub for details." > /tmp/changelog.txt else - # Convert GitHub Markdown to Telegram HTML: - # - **text** → text - # - `code` → code - # - ### Header → Header - # - Escape HTML special chars first - # - Remove > blockquote prefix - CHANGELOG=$(echo "$FULL_CHANGELOG" | \ - sed 's/^> //' | \ + # Convert Markdown to Telegram HTML + CHANGELOG=$(cat /tmp/cliff_tg.txt | \ + sed '/^\*\*Full Changelog\*\*/d' | \ + sed 's/\[#\([0-9]*\)\]([^)]*)/#\1/g' | \ + sed 's/\[@\([^]]*\)\]([^)]*)/@\1/g' | \ sed 's/&/\&/g' | \ sed 's//\>/g' | \ - sed 's/`\([^`]*\)`/\1<\/code>/g' | \ sed 's/\*\*\([^*]*\)\*\*/\1<\/b>/g' | \ sed 's/^### \(.*\)$/\1<\/b>/g' | \ sed 's/^## \(.*\)$/\1<\/b>/g' | \ - sed 's/^- /• /g' | \ - sed 's/^ - / ◦ /g') - - # Take first 2500 characters, then cut at last complete line + sed 's/^- /• /g') + + # Truncate for Telegram 4096 char limit CHANGELOG=$(echo "$CHANGELOG" | head -c 2500 | sed '$d') - - # Check if truncated - FULL_LEN=${#FULL_CHANGELOG} - if [ $FULL_LEN -gt 2500 ]; then - CHANGELOG="${CHANGELOG}"$'\n\n... (see full changelog on GitHub)' - fi + echo "$CHANGELOG" > /tmp/changelog.txt fi - echo "$CHANGELOG" > /tmp/changelog.txt - echo "DEBUG: Final changelog:" + echo "Telegram changelog:" cat /tmp/changelog.txt - name: Send to Telegram Channel diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 00000000..0509818b --- /dev/null +++ b/cliff.toml @@ -0,0 +1,105 @@ +# git-cliff configuration for SpotiFLAC Mobile +# https://git-cliff.org/docs/configuration + +[changelog] +# Template for the changelog body +body = """ +{%- macro remote_url() -%} + https://github.com/zarzet/SpotiFLAC-Mobile +{%- endmacro -%} + +{% if version %}\ + ## {{ version | trim_start_matches(pat="v") }} +{% else %}\ + ## Unreleased +{% endif %}\ + +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | striptags | trim | upper_first }} + {% for commit in commits %} + - {% if commit.scope %}**{{ commit.scope }}**: {% endif %}\ + {{ commit.message | upper_first }}\ + {% if commit.github.pr_number %} \ + ([#{{ commit.github.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.github.pr_number }}))\ + {% endif %}\ + {%- if commit.github.username %} by [@{{ commit.github.username }}](https://github.com/{{ commit.github.username }}){%- endif %} + {%- endfor %} +{% endfor %} + +{%- if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} + + ### New Contributors +{%- for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} + * @{{ contributor.username }} made their first contribution + {%- if contributor.pr_number %} in \ + [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \ + {%- endif %} +{%- endfor %} +{%- endif -%} + +{% if version %} + {% if previous.version %} + **Full Changelog**: [{{ previous.version }}...{{ version }}]({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}) + {% endif %} +{% else -%} + {% raw %}\n{% endraw %} +{% endif %} +""" +# Remove leading and trailing whitespace +trim = true + +[git] +# Parse conventional commits +conventional_commits = true +filter_unconventional = true + +# Process each line of a commit as an individual commit +split_commits = false + +# Regex for preprocessing the commit messages +commit_preprocessors = [ + # Remove PR number from message (we add it back via GitHub integration) + { pattern = '\(#(\d+)\)', replace = '' }, + # Strip conventional commit prefix for cleaner messages + # (group header already shows the type) +] + +# Regex for parsing and grouping commits +commit_parsers = [ + # Skip noise: translation commits from Crowdin + { message = "^New translations", skip = true }, + { message = "^Update source file", skip = true }, + # Skip merge commits + { message = "^Merge", skip = true }, + # Skip version bump commits + { message = "^v\\d+", skip = true }, + { message = "^chore: update VirusTotal", skip = true }, + + # Group by conventional commit type + { message = "^feat", group = "New Features" }, + { message = "^fix", group = "Bug Fixes" }, + { message = "^perf", group = "Performance" }, + { message = "^refactor", group = "Refactoring" }, + { message = "^doc", group = "Documentation" }, + { message = "^style", group = "Styling" }, + { message = "^test", group = "Testing" }, + { message = "^chore\\(deps\\)", group = "Dependencies" }, + { message = "^chore\\(l10n\\)", skip = true }, + { message = "^chore|^ci", group = "Chores" }, +] + +# Protect breaking changes from being skipped +protect_breaking_commits = true + +# Filter out commits by matching patterns +filter_commits = false + +# Tag pattern for version detection +tag_pattern = "v[0-9].*" + +# Sort commits by newest first +sort_commits = "newest" + +[remote.github] +owner = "zarzet" +repo = "SpotiFLAC-Mobile" diff --git a/lib/providers/local_library_provider.dart b/lib/providers/local_library_provider.dart index fe9f21ad..1dd10dd7 100644 --- a/lib/providers/local_library_provider.dart +++ b/lib/providers/local_library_provider.dart @@ -9,6 +9,7 @@ import 'package:spotiflac_android/services/library_database.dart'; import 'package:spotiflac_android/services/notification_service.dart'; import 'package:spotiflac_android/services/platform_bridge.dart'; import 'package:spotiflac_android/utils/logger.dart'; +import 'package:spotiflac_android/utils/path_match_keys.dart'; final _log = AppLogger('LocalLibrary'); @@ -193,74 +194,11 @@ class LocalLibraryNotifier extends Notifier { await _loadFromDatabase(); } - Set _buildPathMatchKeys(String? filePath) { - final raw = filePath?.trim() ?? ''; - if (raw.isEmpty) return const {}; - - final cleaned = raw.startsWith('EXISTS:') ? raw.substring(7) : raw; - final keys = {}; - - void addNormalized(String value) { - final trimmed = value.trim(); - if (trimmed.isEmpty) return; - keys.add(trimmed); - keys.add(trimmed.toLowerCase()); - if (trimmed.contains('\\')) { - final slash = trimmed.replaceAll('\\', '/'); - keys.add(slash); - keys.add(slash.toLowerCase()); - } - if (trimmed.contains('%')) { - try { - final decoded = Uri.decodeFull(trimmed); - keys.add(decoded); - keys.add(decoded.toLowerCase()); - } catch (_) {} - } - - Uri? parsed; - try { - parsed = Uri.parse(trimmed); - } catch (_) {} - - if (parsed != null && parsed.hasScheme) { - final noQueryOrFragment = parsed.replace(query: null, fragment: null); - keys.add(noQueryOrFragment.toString()); - keys.add(noQueryOrFragment.toString().toLowerCase()); - - if (parsed.scheme == 'file') { - try { - final fileOnly = parsed.toFilePath(); - if (fileOnly.isNotEmpty) { - keys.add(fileOnly); - keys.add(fileOnly.toLowerCase()); - if (fileOnly.contains('\\')) { - final slash = fileOnly.replaceAll('\\', '/'); - keys.add(slash); - keys.add(slash.toLowerCase()); - } - } - } catch (_) {} - } - } else if (trimmed.startsWith('/')) { - try { - final asFileUri = Uri.file(trimmed).toString(); - keys.add(asFileUri); - keys.add(asFileUri.toLowerCase()); - } catch (_) {} - } - } - - addNormalized(cleaned); - - return keys; - } - bool _isDownloadedPath(String? filePath, Set downloadedPathKeys) { if (filePath == null || filePath.isEmpty || downloadedPathKeys.isEmpty) { return false; } - final candidateKeys = _buildPathMatchKeys(filePath); + final candidateKeys = buildPathMatchKeys(filePath); for (final key in candidateKeys) { if (downloadedPathKeys.contains(key)) { return true; @@ -322,8 +260,9 @@ class LocalLibraryNotifier extends Notifier { String? resolvedPath; bool didStartSecurityAccess = false; if (Platform.isIOS && iosBookmark != null && iosBookmark.isNotEmpty) { - resolvedPath = - await PlatformBridge.startAccessingIosBookmark(iosBookmark); + resolvedPath = await PlatformBridge.startAccessingIosBookmark( + iosBookmark, + ); if (resolvedPath != null) { didStartSecurityAccess = true; _log.i('Started iOS security-scoped access: $resolvedPath'); @@ -354,7 +293,7 @@ class LocalLibraryNotifier extends Notifier { }; final downloadedPathKeys = {}; for (final path in allHistoryPaths) { - downloadedPathKeys.addAll(_buildPathMatchKeys(path)); + downloadedPathKeys.addAll(buildPathMatchKeys(path)); } _log.i( 'Excluding ${allHistoryPaths.length} downloaded files from library scan ' @@ -834,8 +773,9 @@ class LocalLibraryNotifier extends Notifier { Future cleanupMissingFiles({String? iosBookmark}) async { bool didStartSecurityAccess = false; if (Platform.isIOS && iosBookmark != null && iosBookmark.isNotEmpty) { - final resolved = - await PlatformBridge.startAccessingIosBookmark(iosBookmark); + final resolved = await PlatformBridge.startAccessingIosBookmark( + iosBookmark, + ); if (resolved != null) { didStartSecurityAccess = true; } diff --git a/lib/screens/queue_tab.dart b/lib/screens/queue_tab.dart index 1aa3bad3..e05429c9 100644 --- a/lib/screens/queue_tab.dart +++ b/lib/screens/queue_tab.dart @@ -29,6 +29,7 @@ import 'package:spotiflac_android/screens/downloaded_album_screen.dart'; import 'package:spotiflac_android/screens/library_tracks_folder_screen.dart'; import 'package:spotiflac_android/screens/local_album_screen.dart'; import 'package:spotiflac_android/utils/clickable_metadata.dart'; +import 'package:spotiflac_android/utils/path_match_keys.dart'; import 'package:spotiflac_android/utils/string_utils.dart'; enum LibraryItemSource { downloaded, local } @@ -2112,10 +2113,22 @@ class _QueueTabState extends ConsumerState { if (count > 1) albumCount++; } + final downloadedPathKeys = {}; + for (final item in items) { + downloadedPathKeys.addAll(buildPathMatchKeys(item.filePath)); + } + + final dedupedLocalItems = localItems + .where((item) { + final localPathKeys = buildPathMatchKeys(item.filePath); + return !localPathKeys.any(downloadedPathKeys.contains); + }) + .toList(growable: false); + // Calculate local library stats final localAlbumCounts = {}; final localAlbumMap = >{}; - for (final item in localItems) { + for (final item in dedupedLocalItems) { final key = '${item.albumName.toLowerCase()}|${(item.albumArtist ?? item.artistName).toLowerCase()}'; localAlbumCounts[key] = (localAlbumCounts[key] ?? 0) + 1; @@ -2841,8 +2854,24 @@ class _QueueTabState extends ConsumerState { .map((item) => UnifiedLibraryItem.fromLocalLibrary(item)) .toList(growable: false); - final merged = [...unifiedDownloaded, ...unifiedLocal] - ..sort((a, b) => b.addedAt.compareTo(a.addedAt)); + final downloadedPathKeys = {}; + for (final item in unifiedDownloaded) { + downloadedPathKeys.addAll(buildPathMatchKeys(item.filePath)); + } + + final dedupedUnifiedLocal = []; + for (final item in unifiedLocal) { + final localPathKeys = buildPathMatchKeys(item.filePath); + final overlapsDownloaded = localPathKeys.any(downloadedPathKeys.contains); + if (!overlapsDownloaded) { + dedupedUnifiedLocal.add(item); + } + } + + final merged = [ + ...unifiedDownloaded, + ...dedupedUnifiedLocal, + ]..sort((a, b) => b.addedAt.compareTo(a.addedAt)); _unifiedItemsCache[filterMode] = _UnifiedCacheEntry( historyItems: historyItems, diff --git a/lib/utils/path_match_keys.dart b/lib/utils/path_match_keys.dart new file mode 100644 index 00000000..ace140dd --- /dev/null +++ b/lib/utils/path_match_keys.dart @@ -0,0 +1,104 @@ +import 'dart:io'; + +const _androidStoragePathAliases = [ + '/storage/emulated/0', + '/storage/emulated/legacy', + '/storage/self/primary', + '/sdcard', + '/mnt/sdcard', +]; + +Set buildPathMatchKeys(String? filePath) { + final raw = filePath?.trim() ?? ''; + if (raw.isEmpty) return const {}; + + final cleaned = raw.startsWith('EXISTS:') ? raw.substring(7).trim() : raw; + if (cleaned.isEmpty) return const {}; + + final keys = {}; + final visited = {}; + + void addNormalized(String value) { + final trimmed = value.trim(); + if (trimmed.isEmpty) return; + if (!visited.add(trimmed)) return; + + keys.add(trimmed); + keys.add(trimmed.toLowerCase()); + + if (trimmed.contains('\\')) { + final slash = trimmed.replaceAll('\\', '/'); + if (slash != trimmed) { + addNormalized(slash); + } + } + + if (trimmed.contains('%')) { + try { + final decoded = Uri.decodeFull(trimmed); + if (decoded != trimmed) { + addNormalized(decoded); + } + } catch (_) {} + } + + Uri? parsed; + try { + parsed = Uri.parse(trimmed); + } catch (_) {} + + if (parsed != null && parsed.hasScheme) { + final withoutQueryOrFragment = parsed.replace( + query: null, + fragment: null, + ); + final uriString = withoutQueryOrFragment.toString(); + keys.add(uriString); + keys.add(uriString.toLowerCase()); + + if (parsed.scheme == 'file') { + try { + addNormalized(parsed.toFilePath()); + } catch (_) {} + } + } else if (trimmed.startsWith('/')) { + try { + final asFileUri = Uri.file(trimmed).toString(); + keys.add(asFileUri); + keys.add(asFileUri.toLowerCase()); + } catch (_) {} + } + + if (Platform.isAndroid) { + for (final alias in _androidEquivalentPaths(trimmed)) { + if (alias != trimmed) { + addNormalized(alias); + } + } + } + } + + addNormalized(cleaned); + return keys; +} + +Iterable _androidEquivalentPaths(String path) { + final normalized = path.replaceAll('\\', '/'); + final lower = normalized.toLowerCase(); + String? suffix; + + for (final prefix in _androidStoragePathAliases) { + if (lower == prefix) { + suffix = ''; + break; + } + final withSlash = '$prefix/'; + if (lower.startsWith(withSlash)) { + suffix = normalized.substring(prefix.length); + break; + } + } + + if (suffix == null) return const []; + return _androidStoragePathAliases.map((prefix) => '$prefix$suffix'); +} From db55bb4693f88e7f96c4cb7564a155948bcbcba7 Mon Sep 17 00:00:00 2001 From: zarzet Date: Tue, 10 Mar 2026 23:12:44 +0700 Subject: [PATCH 095/104] fix(deezer): update quality label to FLAC Best Quality since API may deliver above CD quality --- lib/widgets/download_service_picker.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/widgets/download_service_picker.dart b/lib/widgets/download_service_picker.dart index 69eb4f1d..41c8c330 100644 --- a/lib/widgets/download_service_picker.dart +++ b/lib/widgets/download_service_picker.dart @@ -73,8 +73,8 @@ const _builtInServices = [ qualityOptions: [ QualityOption( id: 'FLAC', - label: 'FLAC Lossless', - description: '16-bit / 44.1kHz (CD Quality)', + label: 'FLAC Best Quality', + description: 'Up to 24-bit / 48kHz+', ), ], ), From 64408c8d8b32258d9a9ac51f172de2a90b8e7898 Mon Sep 17 00:00:00 2001 From: zarzet Date: Tue, 10 Mar 2026 23:25:45 +0700 Subject: [PATCH 096/104] feat: add Single and EP badge on artist page and fix EP bucket filtering (#203) --- lib/screens/artist_screen.dart | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/lib/screens/artist_screen.dart b/lib/screens/artist_screen.dart index a1c37333..8ad6bf03 100644 --- a/lib/screens/artist_screen.dart +++ b/lib/screens/artist_screen.dart @@ -343,7 +343,7 @@ class _ArtistScreenState extends ConsumerState { .where((a) => a.albumType == 'album') .toList(growable: false); _singlesBucket = albums - .where((a) => a.albumType == 'single') + .where((a) => a.albumType == 'single' || a.albumType == 'ep') .toList(growable: false); _compilationsBucket = albums .where((a) => a.albumType == 'compilation') @@ -414,6 +414,7 @@ class _ArtistScreenState extends ConsumerState { context.l10n.artistSingles, singles, colorScheme, + showTypeBadge: true, ), ), if (compilations.isNotEmpty) @@ -1523,8 +1524,9 @@ class _ArtistScreenState extends ConsumerState { Widget _buildAlbumSection( String title, List albums, - ColorScheme colorScheme, - ) { + ColorScheme colorScheme, { + bool showTypeBadge = false, + }) { final sectionHeight = _artistAlbumSectionHeight(); final tileSize = _artistAlbumTileSize(); @@ -1555,6 +1557,7 @@ class _ArtistScreenState extends ConsumerState { colorScheme, tileSize: tileSize, sectionHeight: sectionHeight, + showTypeBadge: showTypeBadge, ), ); }, @@ -1569,6 +1572,7 @@ class _ArtistScreenState extends ConsumerState { ColorScheme colorScheme, { required double tileSize, required double sectionHeight, + bool showTypeBadge = false, }) { final isSelected = _selectedAlbumIds.contains(album.id); @@ -1681,6 +1685,29 @@ class _ArtistScreenState extends ConsumerState { : null, ), ), + if (showTypeBadge) + Positioned( + left: 6, + bottom: 6, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.7), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + album.albumType == 'ep' ? 'EP' : 'Single', + style: const TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.w600, + ), + ), + ), + ), ], ), const SizedBox(height: 8), From 76fe8dbc69cc5618b346373953dd1d56a5092da8 Mon Sep 17 00:00:00 2001 From: zarzet Date: Wed, 11 Mar 2026 00:31:20 +0700 Subject: [PATCH 097/104] feat: add CUE sheet support for local library scanning and splitting (#201) Parse .cue files in library scanner (Go + SAF) to display individual tracks instead of one large audio file. Add FFmpeg-based CUE splitting to extract tracks into separate FLAC files with embedded metadata and cover art. - Go: CUE parser, two-pass scan (CUE first, skip referenced audio), virtual paths (cue#trackNN) for DB UNIQUE constraint, audioDir override for SAF temp-file scenarios - Android: SAF scanner recognizes .cue in both full and incremental scan, copies .cue+audio to temp for Go parsing, unchanged-CUE audio sibling dedup, parseCueSheet handler resolves SAF audio siblings - Dart: FFmpegService.splitCueToTracks, CUE split UI in track metadata screen, persistent output dir for SAF splits with write-back - CUE virtual path normalization across fileExists/fileStat/deleteFile/ openFile; play/share/open blocked for virtual tracks with guidance to split first; delete only removes DB entry, not shared .cue file - iOS: parseCueSheet handler - Localization: 12 new CUE-related strings Requested by @Seerafimm Closes #201 --- .../kotlin/com/zarz/spotiflac/MainActivity.kt | 527 +++++++++++++++- go_backend/cue_parser.go | 577 +++++++++++++++++ go_backend/exports.go | 26 + go_backend/library_scan.go | 120 +++- ios/Runner/AppDelegate.swift | 9 + lib/l10n/app_localizations.dart | 72 +++ lib/l10n/app_localizations_de.dart | 48 ++ lib/l10n/app_localizations_en.dart | 48 ++ lib/l10n/app_localizations_es.dart | 48 ++ lib/l10n/app_localizations_fr.dart | 48 ++ lib/l10n/app_localizations_hi.dart | 48 ++ lib/l10n/app_localizations_id.dart | 48 ++ lib/l10n/app_localizations_ja.dart | 48 ++ lib/l10n/app_localizations_ko.dart | 48 ++ lib/l10n/app_localizations_nl.dart | 48 ++ lib/l10n/app_localizations_pt.dart | 48 ++ lib/l10n/app_localizations_ru.dart | 48 ++ lib/l10n/app_localizations_tr.dart | 48 ++ lib/l10n/app_localizations_zh.dart | 48 ++ lib/l10n/arb/app_en.arb | 84 +++ lib/providers/playback_provider.dart | 12 + lib/screens/local_album_screen.dart | 20 +- lib/screens/track_metadata_screen.dart | 593 +++++++++++++++++- lib/services/ffmpeg_service.dart | 154 +++++ lib/services/platform_bridge.dart | 16 + lib/utils/file_access.dart | 50 +- 26 files changed, 2844 insertions(+), 40 deletions(-) create mode 100644 go_backend/cue_parser.go diff --git a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt index da56220d..8fe38bfc 100644 --- a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt +++ b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt @@ -807,6 +807,72 @@ class MainActivity: FlutterFragmentActivity() { } } + /** + * Get the parent DocumentFile directory for a SAF document URI. + * The child URI must be a tree-based document URI (e.g. from SAF tree scan). + * Returns a DocumentFile that supports findFile() for sibling lookup. + */ + private fun safParentDir(childUri: Uri): DocumentFile? { + try { + val docId = android.provider.DocumentsContract.getDocumentId(childUri) + if (docId.isNullOrEmpty()) return null + + // Document IDs typically look like "primary:Music/Album/file.cue" + // Parent would be "primary:Music/Album" + val lastSlash = docId.lastIndexOf('/') + if (lastSlash <= 0) return null + + val parentDocId = docId.substring(0, lastSlash) + + // Build a tree document URI for the parent so it supports listing/findFile + val treeDocId = android.provider.DocumentsContract.getTreeDocumentId(childUri) + if (treeDocId.isNullOrEmpty()) return null + + val parentUri = android.provider.DocumentsContract.buildDocumentUriUsingTree( + childUri, parentDocId + ) + return DocumentFile.fromTreeUri(this, parentUri) + ?: DocumentFile.fromSingleUri(this, parentUri) + } catch (e: Exception) { + android.util.Log.w("SpotiFLAC", "Failed to get SAF parent dir: ${e.message}") + return null + } + } + + /** + * Extract the audio filename referenced by a CUE sheet file. + * Reads the FILE "name" TYPE line from the .cue text. + * Returns just the filename (no path), or null if not found. + */ + private fun extractCueAudioFileName(cueTempPath: String): String? { + try { + val lines = File(cueTempPath).readLines() + for (line in lines) { + val trimmed = line.trim().let { l -> + // Strip BOM + if (l.startsWith("\uFEFF")) l.removePrefix("\uFEFF").trim() else l + } + if (trimmed.uppercase(Locale.ROOT).startsWith("FILE ")) { + val rest = trimmed.substring(5).trim() + // Parse: "filename" TYPE or filename TYPE + val filename = if (rest.startsWith("\"")) { + val endQuote = rest.indexOf('"', 1) + if (endQuote > 0) rest.substring(1, endQuote) else rest + } else { + // Last word is the type, everything else is the filename + val parts = rest.split("\\s+".toRegex()) + if (parts.size >= 2) parts.dropLast(1).joinToString(" ") else rest + } + // Return just the filename (strip any path separators) + return filename.substringAfterLast("/").substringAfterLast("\\") + } + } + } catch (e: Exception) { + android.util.Log.w("SpotiFLAC", "Failed to extract audio filename from CUE: ${e.message}") + } + return null + } + private fun scanSafTree(treeUriStr: String): String { if (treeUriStr.isBlank()) return "[]" @@ -820,8 +886,10 @@ class MainActivity: FlutterFragmentActivity() { it.currentFile = "Scanning folders..." } - val supportedExt = setOf(".flac", ".m4a", ".mp3", ".opus", ".ogg") + val supportedAudioExt = setOf(".flac", ".m4a", ".mp3", ".opus", ".ogg") val audioFiles = mutableListOf>() + // CUE files: (cueDoc, parentDir) — we need the parent to find sibling audio + val cueFiles = mutableListOf>() val visitedDirUris = mutableSetOf() var traversalErrors = 0 @@ -870,7 +938,9 @@ class MainActivity: FlutterFragmentActivity() { } else if (child.isFile) { val name = child.name ?: continue val ext = name.substringAfterLast('.', "").lowercase(Locale.ROOT) - if (ext.isNotBlank() && supportedExt.contains(".$ext")) { + if (ext == "cue") { + cueFiles.add(child to dir) + } else if (ext.isNotBlank() && supportedAudioExt.contains(".$ext")) { audioFiles.add(child to path) } } @@ -885,11 +955,12 @@ class MainActivity: FlutterFragmentActivity() { } } + val totalItems = audioFiles.size + cueFiles.size updateSafScanProgress { - it.totalFiles = audioFiles.size + it.totalFiles = totalItems } - if (audioFiles.isEmpty()) { + if (audioFiles.isEmpty() && cueFiles.isEmpty()) { updateSafScanProgress { it.isComplete = true it.progressPct = 100.0 @@ -901,12 +972,138 @@ class MainActivity: FlutterFragmentActivity() { var scanned = 0 var errors = traversalErrors + // --- CUE first pass: parse CUE sheets, expand to tracks, track referenced audio --- + val cueReferencedAudioUris = mutableSetOf() + + for ((cueDoc, parentDir) in cueFiles) { + if (safScanCancel) { + updateSafScanProgress { it.isComplete = true } + return "[]" + } + + val cueName = try { cueDoc.name ?: "" } catch (_: Exception) { "" } + updateSafScanProgress { it.currentFile = cueName } + + var tempCuePath: String? = null + var tempAudioPath: String? = null + try { + // Copy CUE to temp + tempCuePath = copyUriToTemp(cueDoc.uri, ".cue") + if (tempCuePath == null) { + errors++ + android.util.Log.w("SpotiFLAC", "SAF scan: failed to copy CUE ${cueDoc.uri}") + scanned++ + continue + } + + // Extract the audio filename from the CUE sheet text + val audioFileName = extractCueAudioFileName(tempCuePath) + + // Find the referenced audio file as a sibling in the same SAF directory + var audioDoc: DocumentFile? = null + if (!audioFileName.isNullOrBlank()) { + audioDoc = try { parentDir.findFile(audioFileName) } catch (_: Exception) { null } + } + + // Fallback: try common audio extensions with the CUE base name + if (audioDoc == null) { + val cueBaseName = cueName.substringBeforeLast('.') + val commonExts = listOf(".flac", ".wav", ".ape", ".mp3", ".ogg", ".wv", ".m4a") + for (ext in commonExts) { + audioDoc = try { parentDir.findFile(cueBaseName + ext) } catch (_: Exception) { null } + if (audioDoc != null) break + // Try uppercase + audioDoc = try { parentDir.findFile(cueBaseName + ext.uppercase(Locale.ROOT)) } catch (_: Exception) { null } + if (audioDoc != null) break + } + } + + if (audioDoc == null) { + android.util.Log.w("SpotiFLAC", "SAF scan: no audio file found for CUE $cueName") + errors++ + scanned++ + continue + } + + // Mark this audio file so we skip it in the regular audio pass + cueReferencedAudioUris.add(audioDoc.uri.toString()) + + // Copy audio to same temp dir so Go can resolve it + val tempDir = File(tempCuePath).parent ?: cacheDir.absolutePath + val audioName = try { audioDoc.name ?: "audio.flac" } catch (_: Exception) { "audio.flac" } + val audioExt = audioName.substringAfterLast('.', "").lowercase(Locale.ROOT) + val fallbackAudioExt = if (audioExt.isNotBlank()) ".$audioExt" else null + + tempAudioPath = copyUriToTemp(audioDoc.uri, fallbackAudioExt) + if (tempAudioPath == null) { + android.util.Log.w("SpotiFLAC", "SAF scan: failed to copy audio for CUE $cueName") + errors++ + scanned++ + continue + } + + // Rename temp audio to its original name so Go can find it by name + val renamedAudio = File(tempDir, audioName) + val tempAudioFile = File(tempAudioPath) + if (renamedAudio.absolutePath != tempAudioFile.absolutePath) { + tempAudioFile.renameTo(renamedAudio) + tempAudioPath = renamedAudio.absolutePath + } + + val cueLastModified = try { cueDoc.lastModified() } catch (_: Exception) { 0L } + + // Call Go to produce library scan entries for each CUE track + val cueResultsJson = Gobackend.scanCueSheetForLibrary( + tempCuePath, + tempDir, + cueDoc.uri.toString(), + cueLastModified + ) + + val cueArray = JSONArray(cueResultsJson) + for (j in 0 until cueArray.length()) { + results.put(cueArray.getJSONObject(j)) + } + + android.util.Log.d( + "SpotiFLAC", + "SAF scan: CUE $cueName -> ${cueArray.length()} tracks" + ) + } catch (e: Exception) { + errors++ + android.util.Log.w("SpotiFLAC", "SAF scan: error processing CUE $cueName: ${e.message}") + } finally { + try { tempCuePath?.let { File(it).delete() } } catch (_: Exception) {} + try { tempAudioPath?.let { File(it).delete() } } catch (_: Exception) {} + } + + scanned++ + val pct = scanned.toDouble() / totalItems.toDouble() * 100.0 + updateSafScanProgress { + it.scannedFiles = scanned + it.errorCount = errors + it.progressPct = pct + } + } + + // --- Regular audio file pass: skip files referenced by CUE sheets --- for ((doc, _) in audioFiles) { if (safScanCancel) { updateSafScanProgress { it.isComplete = true } return "[]" } + // Skip audio files that are represented by CUE track entries + if (cueReferencedAudioUris.contains(doc.uri.toString())) { + scanned++ + val pct = scanned.toDouble() / totalItems.toDouble() * 100.0 + updateSafScanProgress { + it.scannedFiles = scanned + it.progressPct = pct + } + continue + } + val name = try { doc.name ?: "" } catch (_: Exception) { "" } updateSafScanProgress { it.currentFile = name @@ -947,7 +1144,7 @@ class MainActivity: FlutterFragmentActivity() { } scanned++ - val pct = scanned.toDouble() / audioFiles.size.toDouble() * 100.0 + val pct = scanned.toDouble() / totalItems.toDouble() * 100.0 updateSafScanProgress { it.scannedFiles = scanned it.errorCount = errors @@ -965,6 +1162,8 @@ class MainActivity: FlutterFragmentActivity() { /** * Incremental SAF tree scan - only scans new or modified files. + * Supports .cue sheets: expands them into virtual track entries and + * deduplicates audio files referenced by CUE sheets. * @param treeUriStr The SAF tree URI to scan * @param existingFilesJson JSON object mapping file URI -> lastModified timestamp * @return JSON object with new/changed files and removed URIs @@ -1007,13 +1206,29 @@ class MainActivity: FlutterFragmentActivity() { it.currentFile = "Scanning folders..." } - val supportedExt = setOf(".flac", ".m4a", ".mp3", ".opus", ".ogg") + val supportedAudioExt = setOf(".flac", ".m4a", ".mp3", ".opus", ".ogg") val audioFiles = mutableListOf>() // doc, path, lastModified + // CUE files to scan: (cueDoc, parentDir, lastModified) + val cueFilesToScan = mutableListOf>() + // Unchanged CUE files: (cueDoc, parentDir) — need to discover audio siblings for skip set + val unchangedCueFiles = mutableListOf>() val currentUris = mutableSetOf() val visitedDirUris = mutableSetOf() var traversalErrors = 0 - // Collect all audio files with lastModified + // Build a map of CUE base URIs -> existing virtual track URIs from the database. + // Virtual paths look like "content://...album.cue#track01". + // We need this to preserve virtual paths for unchanged CUE files. + val existingCueVirtualPaths = mutableMapOf>() // cueUri -> [virtualPaths] + for (key in existingFiles.keys) { + val hashIdx = key.indexOf("#track") + if (hashIdx > 0) { + val baseCueUri = key.substring(0, hashIdx) + existingCueVirtualPaths.getOrPut(baseCueUri) { mutableListOf() }.add(key) + } + } + + // Collect all files with lastModified val queue: ArrayDeque> = ArrayDeque() queue.add(root to "") @@ -1076,7 +1291,27 @@ class MainActivity: FlutterFragmentActivity() { val name = child.name ?: continue val ext = name.substringAfterLast('.', "").lowercase(Locale.ROOT) - if (ext.isNotBlank() && supportedExt.contains(".$ext")) { + + if (ext == "cue") { + val lastModified = try { + child.lastModified() + } catch (_: Exception) { 0L } + + // Check if any virtual track from this CUE exists with matching modTime + val virtualPaths = existingCueVirtualPaths[uriStr] + val existingModified = virtualPaths?.firstOrNull()?.let { existingFiles[it] } + + if (existingModified != null && existingModified == lastModified) { + // CUE is unchanged — mark virtual paths as current so they aren't removed + unchangedCueFiles.add(child to dir) + for (vp in virtualPaths) { + currentUris.add(vp) + } + } else { + // CUE is new or modified — needs scanning + cueFilesToScan.add(Triple(child, dir, lastModified)) + } + } else if (ext.isNotBlank() && supportedAudioExt.contains(".$ext")) { val existingModified = existingFiles[uriStr] val lastModified = try { child.lastModified() @@ -1104,13 +1339,14 @@ class MainActivity: FlutterFragmentActivity() { // Find removed files (in existing but not in current) val removedUris = existingFiles.keys.filter { !currentUris.contains(it) } val totalFiles = currentUris.size - val skippedCount = (totalFiles - audioFiles.size).coerceAtLeast(0) + val filesToProcess = audioFiles.size + cueFilesToScan.size + val skippedCount = (totalFiles - filesToProcess).coerceAtLeast(0) updateSafScanProgress { it.totalFiles = totalFiles } - if (audioFiles.isEmpty()) { + if (audioFiles.isEmpty() && cueFilesToScan.isEmpty()) { updateSafScanProgress { it.isComplete = true it.scannedFiles = totalFiles @@ -1128,6 +1364,173 @@ class MainActivity: FlutterFragmentActivity() { var scanned = 0 var errors = traversalErrors + // --- CUE first pass: parse new/modified CUE sheets --- + val cueReferencedAudioUris = mutableSetOf() + + for ((cueDoc, parentDir, cueLastModified) in cueFilesToScan) { + if (safScanCancel) { + updateSafScanProgress { it.isComplete = true } + val result = JSONObject() + result.put("files", JSONArray()) + result.put("removedUris", JSONArray()) + result.put("skippedCount", skippedCount) + result.put("totalFiles", totalFiles) + result.put("cancelled", true) + return result.toString() + } + + val cueName = try { cueDoc.name ?: "" } catch (_: Exception) { "" } + updateSafScanProgress { it.currentFile = cueName } + + var tempCuePath: String? = null + var tempAudioPath: String? = null + try { + // Copy CUE to temp + tempCuePath = copyUriToTemp(cueDoc.uri, ".cue") + if (tempCuePath == null) { + errors++ + android.util.Log.w("SpotiFLAC", "SAF incremental scan: failed to copy CUE ${cueDoc.uri}") + scanned++ + continue + } + + // Extract the audio filename from the CUE sheet text + val audioFileName = extractCueAudioFileName(tempCuePath) + + // Find the referenced audio file as a sibling in the same SAF directory + var audioDoc: DocumentFile? = null + if (!audioFileName.isNullOrBlank()) { + audioDoc = try { parentDir.findFile(audioFileName) } catch (_: Exception) { null } + } + + // Fallback: try common audio extensions with the CUE base name + if (audioDoc == null) { + val cueBaseName = cueName.substringBeforeLast('.') + val commonExts = listOf(".flac", ".wav", ".ape", ".mp3", ".ogg", ".wv", ".m4a") + for (ext in commonExts) { + audioDoc = try { parentDir.findFile(cueBaseName + ext) } catch (_: Exception) { null } + if (audioDoc != null) break + audioDoc = try { parentDir.findFile(cueBaseName + ext.uppercase(Locale.ROOT)) } catch (_: Exception) { null } + if (audioDoc != null) break + } + } + + if (audioDoc == null) { + android.util.Log.w("SpotiFLAC", "SAF incremental scan: no audio file found for CUE $cueName") + errors++ + scanned++ + continue + } + + // Mark this audio file so we skip it in the regular audio pass + cueReferencedAudioUris.add(audioDoc.uri.toString()) + + // Copy audio to same temp dir so Go can resolve it + val tempDir = File(tempCuePath).parent ?: cacheDir.absolutePath + val audioName = try { audioDoc.name ?: "audio.flac" } catch (_: Exception) { "audio.flac" } + val audioExt = audioName.substringAfterLast('.', "").lowercase(Locale.ROOT) + val fallbackAudioExt = if (audioExt.isNotBlank()) ".$audioExt" else null + + tempAudioPath = copyUriToTemp(audioDoc.uri, fallbackAudioExt) + if (tempAudioPath == null) { + android.util.Log.w("SpotiFLAC", "SAF incremental scan: failed to copy audio for CUE $cueName") + errors++ + scanned++ + continue + } + + // Rename temp audio to its original name so Go can find it by name + val renamedAudio = File(tempDir, audioName) + val tempAudioFile = File(tempAudioPath) + if (renamedAudio.absolutePath != tempAudioFile.absolutePath) { + tempAudioFile.renameTo(renamedAudio) + tempAudioPath = renamedAudio.absolutePath + } + + // Call Go to produce library scan entries for each CUE track + val cueResultsJson = Gobackend.scanCueSheetForLibrary( + tempCuePath, + tempDir, + cueDoc.uri.toString(), + cueLastModified + ) + + val cueArray = JSONArray(cueResultsJson) + for (j in 0 until cueArray.length()) { + val trackObj = cueArray.getJSONObject(j) + results.put(trackObj) + // Register each virtual path as current so deletion detection works + val virtualPath = trackObj.optString("filePath", "") + if (virtualPath.isNotBlank()) { + currentUris.add(virtualPath) + } + } + + android.util.Log.d( + "SpotiFLAC", + "SAF incremental scan: CUE $cueName -> ${cueArray.length()} tracks" + ) + } catch (e: Exception) { + errors++ + android.util.Log.w("SpotiFLAC", "SAF incremental scan: error processing CUE $cueName: ${e.message}") + } finally { + try { tempCuePath?.let { File(it).delete() } } catch (_: Exception) {} + try { tempAudioPath?.let { File(it).delete() } } catch (_: Exception) {} + } + + scanned++ + val processed = skippedCount + scanned + val pct = if (totalFiles > 0) { + processed.toDouble() / totalFiles.toDouble() * 100.0 + } else { + 100.0 + } + updateSafScanProgress { + it.scannedFiles = processed + it.errorCount = errors + it.progressPct = pct + } + } + + // Discover audio siblings for unchanged CUE files so we skip them + // in the regular audio pass. Copy the .cue to temp (tiny file) to extract + // the audio filename, then find the sibling by name. + for ((cueDoc, parentDir) in unchangedCueFiles) { + var tempCue: String? = null + try { + tempCue = copyUriToTemp(cueDoc.uri, ".cue") + if (tempCue != null) { + val audioFileName = extractCueAudioFileName(tempCue) + var audioDoc: DocumentFile? = null + if (!audioFileName.isNullOrBlank()) { + audioDoc = try { parentDir.findFile(audioFileName) } catch (_: Exception) { null } + } + // Fallback: try common extensions with CUE base name + if (audioDoc == null) { + val cueName = try { cueDoc.name ?: "" } catch (_: Exception) { "" } + val cueBaseName = cueName.substringBeforeLast('.') + if (cueBaseName.isNotBlank()) { + val commonExts = listOf(".flac", ".wav", ".ape", ".mp3", ".ogg", ".wv", ".m4a") + for (ext in commonExts) { + audioDoc = try { parentDir.findFile(cueBaseName + ext) } catch (_: Exception) { null } + if (audioDoc != null) break + audioDoc = try { parentDir.findFile(cueBaseName + ext.uppercase(Locale.ROOT)) } catch (_: Exception) { null } + if (audioDoc != null) break + } + } + } + if (audioDoc != null) { + cueReferencedAudioUris.add(audioDoc.uri.toString()) + } + } + } catch (e: Exception) { + android.util.Log.w("SpotiFLAC", "SAF incremental scan: failed to resolve audio for unchanged CUE: ${e.message}") + } finally { + try { tempCue?.let { File(it).delete() } } catch (_: Exception) {} + } + } + + // --- Regular audio file pass: skip files referenced by CUE sheets --- for ((doc, _, lastModified) in audioFiles) { if (safScanCancel) { updateSafScanProgress { it.isComplete = true } @@ -1140,6 +1543,22 @@ class MainActivity: FlutterFragmentActivity() { return result.toString() } + // Skip audio files that are represented by CUE track entries + if (cueReferencedAudioUris.contains(doc.uri.toString())) { + scanned++ + val processed = skippedCount + scanned + val pct = if (totalFiles > 0) { + processed.toDouble() / totalFiles.toDouble() * 100.0 + } else { + 100.0 + } + updateSafScanProgress { + it.scannedFiles = processed + it.progressPct = pct + } + continue + } + val name = try { doc.name ?: "" } catch (_: Exception) { "" } updateSafScanProgress { it.currentFile = name @@ -1194,6 +1613,9 @@ class MainActivity: FlutterFragmentActivity() { } } + // Recalculate removedUris now that CUE virtual paths have been registered + val finalRemovedUris = existingFiles.keys.filter { !currentUris.contains(it) } + updateSafScanProgress { it.isComplete = true it.progressPct = 100.0 @@ -1201,7 +1623,7 @@ class MainActivity: FlutterFragmentActivity() { val result = JSONObject() result.put("files", results) - result.put("removedUris", JSONArray(removedUris)) + result.put("removedUris", JSONArray(finalRemovedUris)) result.put("skippedCount", skippedCount) result.put("totalFiles", totalFiles) return result.toString() @@ -2756,6 +3178,89 @@ class MainActivity: FlutterFragmentActivity() { } result.success(response) } + // CUE Sheet Parsing + "parseCueSheet" -> { + val cuePath = call.argument("cue_path") ?: "" + val audioDir = call.argument("audio_dir") ?: "" + val response = withContext(Dispatchers.IO) { + try { + if (cuePath.startsWith("content://")) { + val uri = Uri.parse(cuePath) + val tempCuePath = copyUriToTemp(uri, ".cue") + ?: return@withContext """{"error":"Failed to copy CUE file to temp"}""" + var tempAudioPath: String? = null + try { + // Extract audio filename from CUE text + val audioFileName = extractCueAudioFileName(tempCuePath) + + // Try to find the audio sibling in SAF + var audioDoc: DocumentFile? = null + val parentDir = safParentDir(uri) + if (parentDir != null && !audioFileName.isNullOrBlank()) { + audioDoc = try { parentDir.findFile(audioFileName) } catch (_: Exception) { null } + } + + // Fallback: try common extensions with the CUE base name + if (audioDoc == null && parentDir != null) { + val cueName = try { + DocumentFile.fromSingleUri(this@MainActivity, uri)?.name ?: "" + } catch (_: Exception) { "" } + val cueBaseName = cueName.substringBeforeLast('.') + if (cueBaseName.isNotBlank()) { + val commonExts = listOf(".flac", ".wav", ".ape", ".mp3", ".ogg", ".wv", ".m4a") + for (ext in commonExts) { + audioDoc = try { parentDir.findFile(cueBaseName + ext) } catch (_: Exception) { null } + if (audioDoc != null) break + audioDoc = try { parentDir.findFile(cueBaseName + ext.uppercase(Locale.ROOT)) } catch (_: Exception) { null } + if (audioDoc != null) break + } + } + } + + val tempDir = File(tempCuePath).parent ?: cacheDir.absolutePath + if (audioDoc != null) { + // Copy audio to same temp dir with original name + val audioName = try { audioDoc.name ?: "audio.flac" } catch (_: Exception) { "audio.flac" } + val audioExt = audioName.substringAfterLast('.', "").lowercase(Locale.ROOT) + val fallbackExt = if (audioExt.isNotBlank()) ".$audioExt" else null + val copiedAudio = copyUriToTemp(audioDoc.uri, fallbackExt) + if (copiedAudio != null) { + val renamedAudio = File(tempDir, audioName) + val copiedFile = File(copiedAudio) + if (renamedAudio.absolutePath != copiedFile.absolutePath) { + copiedFile.renameTo(renamedAudio) + } + tempAudioPath = renamedAudio.absolutePath + } + } + + // Parse with audio in temp dir; Go will resolve there + val resultJson = Gobackend.parseCueSheet(tempCuePath, tempDir) + + // Replace the temp audio_path with the SAF content:// URI + // so Dart knows it's a SAF file and handles it accordingly + if (audioDoc != null) { + val resultObj = JSONObject(resultJson) + resultObj.put("audio_path", audioDoc.uri.toString()) + // Also pass the original CUE URI for reference + resultObj.put("cue_path", cuePath) + resultObj.toString() + } else { + resultJson + } + } finally { + try { File(tempCuePath).delete() } catch (_: Exception) {} + try { tempAudioPath?.let { File(it).delete() } } catch (_: Exception) {} + } + } else { + Gobackend.parseCueSheet(cuePath, audioDir) + } + } catch (e: Exception) { + """{"error":"${e.message?.replace("\"", "'")}"}""" + } + } + result.success(response) + } else -> result.notImplemented() } } catch (e: Exception) { diff --git a/go_backend/cue_parser.go b/go_backend/cue_parser.go new file mode 100644 index 00000000..a938be2a --- /dev/null +++ b/go_backend/cue_parser.go @@ -0,0 +1,577 @@ +package gobackend + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" +) + +// CueSheet represents a parsed .cue file +type CueSheet struct { + // Album-level metadata + Performer string `json:"performer"` + Title string `json:"title"` + FileName string `json:"file_name"` + FileType string `json:"file_type"` // WAVE, FLAC, MP3, AIFF, etc. + Genre string `json:"genre,omitempty"` + Date string `json:"date,omitempty"` + Comment string `json:"comment,omitempty"` + Composer string `json:"composer,omitempty"` + Tracks []CueTrack `json:"tracks"` +} + +// CueTrack represents a single track in a cue sheet +type CueTrack struct { + Number int `json:"number"` + Title string `json:"title"` + Performer string `json:"performer"` + ISRC string `json:"isrc,omitempty"` + Composer string `json:"composer,omitempty"` + // Index positions in seconds (fractional) + StartTime float64 `json:"start_time"` // INDEX 01 in seconds + PreGap float64 `json:"pre_gap"` // INDEX 00 in seconds (or -1 if not present) +} + +// CueSplitInfo represents the information needed to split a CUE+audio file +type CueSplitInfo struct { + CuePath string `json:"cue_path"` + AudioPath string `json:"audio_path"` + Album string `json:"album"` + Artist string `json:"artist"` + Genre string `json:"genre,omitempty"` + Date string `json:"date,omitempty"` + Tracks []CueSplitTrack `json:"tracks"` +} + +// CueSplitTrack has the FFmpeg split parameters for a single track +type CueSplitTrack struct { + Number int `json:"number"` + Title string `json:"title"` + Artist string `json:"artist"` + ISRC string `json:"isrc,omitempty"` + Composer string `json:"composer,omitempty"` + StartSec float64 `json:"start_sec"` + EndSec float64 `json:"end_sec"` // -1 means until end of file +} + +var ( + reRemCommand = regexp.MustCompile(`^REM\s+(\S+)\s+(.+)$`) + reQuoted = regexp.MustCompile(`"([^"]*)"`) +) + +// ParseCueFile parses a .cue file and returns a CueSheet +func ParseCueFile(cuePath string) (*CueSheet, error) { + f, err := os.Open(cuePath) + if err != nil { + return nil, fmt.Errorf("failed to open cue file: %w", err) + } + defer f.Close() + + sheet := &CueSheet{} + var currentTrack *CueTrack + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + + // Handle BOM at start of file + if strings.HasPrefix(line, "\xef\xbb\xbf") { + line = strings.TrimPrefix(line, "\xef\xbb\xbf") + line = strings.TrimSpace(line) + } + + upper := strings.ToUpper(line) + + // REM commands (album-level metadata) + if strings.HasPrefix(upper, "REM ") { + matches := reRemCommand.FindStringSubmatch(line) + if len(matches) == 3 { + key := strings.ToUpper(matches[1]) + value := unquoteCue(matches[2]) + switch key { + case "GENRE": + sheet.Genre = value + case "DATE": + sheet.Date = value + case "COMMENT": + sheet.Comment = value + case "COMPOSER": + if currentTrack != nil { + currentTrack.Composer = value + } else { + sheet.Composer = value + } + } + } + continue + } + + // PERFORMER + if strings.HasPrefix(upper, "PERFORMER ") { + value := unquoteCue(line[len("PERFORMER "):]) + if currentTrack != nil { + currentTrack.Performer = value + } else { + sheet.Performer = value + } + continue + } + + // TITLE + if strings.HasPrefix(upper, "TITLE ") { + value := unquoteCue(line[len("TITLE "):]) + if currentTrack != nil { + currentTrack.Title = value + } else { + sheet.Title = value + } + continue + } + + // FILE + if strings.HasPrefix(upper, "FILE ") { + rest := line[len("FILE "):] + // Extract filename and type + // Format: FILE "filename.flac" WAVE + // or: FILE filename.flac WAVE + fname, ftype := parseCueFileLine(rest) + sheet.FileName = fname + sheet.FileType = ftype + continue + } + + // TRACK + if strings.HasPrefix(upper, "TRACK ") { + // Save previous track + if currentTrack != nil { + sheet.Tracks = append(sheet.Tracks, *currentTrack) + } + + parts := strings.Fields(line) + trackNum := 0 + if len(parts) >= 2 { + trackNum, _ = strconv.Atoi(parts[1]) + } + + currentTrack = &CueTrack{ + Number: trackNum, + PreGap: -1, + } + continue + } + + // INDEX + if strings.HasPrefix(upper, "INDEX ") && currentTrack != nil { + parts := strings.Fields(line) + if len(parts) >= 3 { + indexNum, _ := strconv.Atoi(parts[1]) + timeSec := parseCueTimestamp(parts[2]) + switch indexNum { + case 0: + currentTrack.PreGap = timeSec + case 1: + currentTrack.StartTime = timeSec + } + } + continue + } + + // ISRC + if strings.HasPrefix(upper, "ISRC ") && currentTrack != nil { + currentTrack.ISRC = strings.TrimSpace(line[len("ISRC "):]) + continue + } + + // SONGWRITER (used as composer sometimes) + if strings.HasPrefix(upper, "SONGWRITER ") { + value := unquoteCue(line[len("SONGWRITER "):]) + if currentTrack != nil { + currentTrack.Composer = value + } else { + sheet.Composer = value + } + continue + } + } + + // Don't forget the last track + if currentTrack != nil { + sheet.Tracks = append(sheet.Tracks, *currentTrack) + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading cue file: %w", err) + } + + if len(sheet.Tracks) == 0 { + return nil, fmt.Errorf("no tracks found in cue file") + } + + return sheet, nil +} + +// parseCueTimestamp converts MM:SS:FF (frames at 75fps) to seconds +func parseCueTimestamp(ts string) float64 { + parts := strings.Split(ts, ":") + if len(parts) != 3 { + return 0 + } + + minutes, _ := strconv.Atoi(parts[0]) + seconds, _ := strconv.Atoi(parts[1]) + frames, _ := strconv.Atoi(parts[2]) + + return float64(minutes)*60 + float64(seconds) + float64(frames)/75.0 +} + +// formatCueTimestamp converts seconds to HH:MM:SS.mmm format for FFmpeg +func formatCueTimestamp(seconds float64) string { + if seconds < 0 { + return "0" + } + hours := int(seconds) / 3600 + mins := (int(seconds) % 3600) / 60 + secs := seconds - float64(hours*3600) - float64(mins*60) + return fmt.Sprintf("%02d:%02d:%06.3f", hours, mins, secs) +} + +// unquoteCue removes surrounding quotes from a CUE value +func unquoteCue(s string) string { + s = strings.TrimSpace(s) + if matches := reQuoted.FindStringSubmatch(s); len(matches) == 2 { + return matches[1] + } + return s +} + +// parseCueFileLine parses the FILE command's filename and type +func parseCueFileLine(rest string) (string, string) { + rest = strings.TrimSpace(rest) + + var filename, ftype string + + if strings.HasPrefix(rest, "\"") { + // Quoted filename + endQuote := strings.Index(rest[1:], "\"") + if endQuote >= 0 { + filename = rest[1 : endQuote+1] + remaining := strings.TrimSpace(rest[endQuote+2:]) + ftype = remaining + } else { + filename = rest + } + } else { + // Unquoted filename - last word is the type + parts := strings.Fields(rest) + if len(parts) >= 2 { + ftype = parts[len(parts)-1] + filename = strings.Join(parts[:len(parts)-1], " ") + } else if len(parts) == 1 { + filename = parts[0] + } + } + + return filename, strings.TrimSpace(ftype) +} + +// ResolveCueAudioPath finds the actual audio file referenced by a .cue sheet. +// It checks relative to the cue file's directory. +func ResolveCueAudioPath(cuePath string, cueFileName string) string { + cueDir := filepath.Dir(cuePath) + + // 1. Try the exact filename from the .cue + candidate := filepath.Join(cueDir, cueFileName) + if _, err := os.Stat(candidate); err == nil { + return candidate + } + + // 2. Try common case variations + baseName := strings.TrimSuffix(cueFileName, filepath.Ext(cueFileName)) + commonExts := []string{".flac", ".wav", ".ape", ".mp3", ".ogg", ".wv", ".m4a"} + for _, ext := range commonExts { + candidate = filepath.Join(cueDir, baseName+ext) + if _, err := os.Stat(candidate); err == nil { + return candidate + } + // Try uppercase ext + candidate = filepath.Join(cueDir, baseName+strings.ToUpper(ext)) + if _, err := os.Stat(candidate); err == nil { + return candidate + } + } + + // 3. Try to find any audio file with the same base name as the .cue file + cueBase := strings.TrimSuffix(filepath.Base(cuePath), filepath.Ext(cuePath)) + for _, ext := range commonExts { + candidate = filepath.Join(cueDir, cueBase+ext) + if _, err := os.Stat(candidate); err == nil { + return candidate + } + } + + // 4. If there's only one audio file in the directory, use that + entries, err := os.ReadDir(cueDir) + if err == nil { + audioExts := map[string]bool{ + ".flac": true, ".wav": true, ".ape": true, ".mp3": true, + ".ogg": true, ".wv": true, ".m4a": true, ".aiff": true, + } + var audioFiles []string + for _, entry := range entries { + if entry.IsDir() { + continue + } + ext := strings.ToLower(filepath.Ext(entry.Name())) + if audioExts[ext] { + audioFiles = append(audioFiles, filepath.Join(cueDir, entry.Name())) + } + } + if len(audioFiles) == 1 { + return audioFiles[0] + } + } + + return "" +} + +// BuildCueSplitInfo creates the split information from a parsed CUE sheet. +// This is returned to the Dart side so FFmpeg can perform the splitting. +// audioDir, if non-empty, overrides the directory for audio file resolution. +func BuildCueSplitInfo(cuePath string, sheet *CueSheet, audioDir string) (*CueSplitInfo, error) { + resolveDir := cuePath + if audioDir != "" { + // Create a virtual path in audioDir so ResolveCueAudioPath looks there + resolveDir = filepath.Join(audioDir, filepath.Base(cuePath)) + } + audioPath := ResolveCueAudioPath(resolveDir, sheet.FileName) + if audioPath == "" { + return nil, fmt.Errorf("audio file not found for cue sheet: %s (referenced: %s)", cuePath, sheet.FileName) + } + + info := &CueSplitInfo{ + CuePath: cuePath, + AudioPath: audioPath, + Album: sheet.Title, + Artist: sheet.Performer, + Genre: sheet.Genre, + Date: sheet.Date, + } + + for i, track := range sheet.Tracks { + performer := track.Performer + if performer == "" { + performer = sheet.Performer + } + + composer := track.Composer + if composer == "" { + composer = sheet.Composer + } + + // End time is the start of the next track, or -1 for the last track + endSec := float64(-1) + if i+1 < len(sheet.Tracks) { + nextTrack := sheet.Tracks[i+1] + // Use pre-gap of next track if available, otherwise its start time + if nextTrack.PreGap >= 0 { + endSec = nextTrack.PreGap + } else { + endSec = nextTrack.StartTime + } + } + + info.Tracks = append(info.Tracks, CueSplitTrack{ + Number: track.Number, + Title: track.Title, + Artist: performer, + ISRC: track.ISRC, + Composer: composer, + StartSec: track.StartTime, + EndSec: endSec, + }) + } + + return info, nil +} + +// ParseCueFileJSON parses a .cue file and returns JSON with split info. +// This is the main entry point called from Dart via the platform bridge. +// audioDir, if non-empty, overrides the directory used for resolving the +// referenced audio file (useful when the .cue was copied to a temp dir +// but the audio still lives in the original location, e.g. SAF). +func ParseCueFileJSON(cuePath string, audioDir string) (string, error) { + sheet, err := ParseCueFile(cuePath) + if err != nil { + return "", fmt.Errorf("failed to parse cue file: %w", err) + } + + info, err := BuildCueSplitInfo(cuePath, sheet, audioDir) + if err != nil { + return "", err + } + + jsonBytes, err := json.Marshal(info) + if err != nil { + return "", fmt.Errorf("failed to marshal cue split info: %w", err) + } + + return string(jsonBytes), nil +} + +// ScanCueFileForLibrary parses a .cue file and returns multiple LibraryScanResult +// entries, one per track. This is used by the library scanner to populate the +// library with individual track entries from a single CUE+FLAC album. +func ScanCueFileForLibrary(cuePath string, scanTime string) ([]LibraryScanResult, error) { + return scanCueFileForLibraryInternal(cuePath, "", "", 0, scanTime) +} + +// ScanCueFileForLibraryExt is like ScanCueFileForLibrary but with extra parameters +// for SAF (Storage Access Framework) scenarios: +// - audioDir: if non-empty, overrides the directory used to find the audio file +// - virtualPathPrefix: if non-empty, used instead of cuePath as the base for +// virtual file paths (e.g. a content:// URI). IDs are also based on this. +// - fileModTime: if > 0, used as the FileModTime for all results instead of +// stat-ing the cuePath on disk (useful when the real file lives behind SAF) +func ScanCueFileForLibraryExt(cuePath, audioDir, virtualPathPrefix string, fileModTime int64, scanTime string) ([]LibraryScanResult, error) { + return scanCueFileForLibraryInternal(cuePath, audioDir, virtualPathPrefix, fileModTime, scanTime) +} + +func scanCueFileForLibraryInternal(cuePath, audioDir, virtualPathPrefix string, fileModTime int64, scanTime string) ([]LibraryScanResult, error) { + sheet, err := ParseCueFile(cuePath) + if err != nil { + return nil, err + } + + // Resolve audio file — optionally in an overridden directory + resolveBase := cuePath + if audioDir != "" { + resolveBase = filepath.Join(audioDir, filepath.Base(cuePath)) + } + audioPath := ResolveCueAudioPath(resolveBase, sheet.FileName) + if audioPath == "" { + return nil, fmt.Errorf("audio file not found for cue: %s (referenced: %s)", cuePath, sheet.FileName) + } + + // Try to get quality info from the audio file + var bitDepth, sampleRate int + var totalDurationSec float64 + audioExt := strings.ToLower(filepath.Ext(audioPath)) + switch audioExt { + case ".flac": + quality, qErr := GetAudioQuality(audioPath) + if qErr == nil { + bitDepth = quality.BitDepth + sampleRate = quality.SampleRate + if quality.SampleRate > 0 && quality.TotalSamples > 0 { + totalDurationSec = float64(quality.TotalSamples) / float64(quality.SampleRate) + } + } + case ".mp3": + quality, qErr := GetMP3Quality(audioPath) + if qErr == nil { + sampleRate = quality.SampleRate + totalDurationSec = float64(quality.Duration) + } + } + + // Extract cover from audio file for all tracks + var coverPath string + libraryCoverCacheMu.RLock() + coverCacheDir := libraryCoverCacheDir + libraryCoverCacheMu.RUnlock() + if coverCacheDir != "" { + cp, err := SaveCoverToCache(audioPath, coverCacheDir) + if err == nil && cp != "" { + coverPath = cp + } + } + + // Determine the base path for virtual paths and IDs + pathBase := cuePath + if virtualPathPrefix != "" { + pathBase = virtualPathPrefix + } + + // Determine fileModTime + modTime := fileModTime + if modTime <= 0 { + if info, err := os.Stat(cuePath); err == nil { + modTime = info.ModTime().UnixMilli() + } + } + + var results []LibraryScanResult + for i, track := range sheet.Tracks { + performer := track.Performer + if performer == "" { + performer = sheet.Performer + } + if performer == "" { + performer = "Unknown Artist" + } + + title := track.Title + if title == "" { + title = fmt.Sprintf("Track %02d", track.Number) + } + + album := sheet.Title + if album == "" { + album = "Unknown Album" + } + + // Calculate duration for this track + var duration int + if i+1 < len(sheet.Tracks) { + nextStart := sheet.Tracks[i+1].StartTime + if sheet.Tracks[i+1].PreGap >= 0 { + nextStart = sheet.Tracks[i+1].PreGap + } + duration = int(nextStart - track.StartTime) + } else if totalDurationSec > 0 { + duration = int(totalDurationSec - track.StartTime) + } + + // Use a unique ID based on pathBase + track number + id := generateLibraryID(fmt.Sprintf("%s#track%d", pathBase, track.Number)) + + // Use a virtual file path that includes the track number to ensure + // uniqueness in the database (file_path has a UNIQUE constraint). + // Format: /path/to/album.cue#track01 or content://...album.cue#track01 + virtualFilePath := fmt.Sprintf("%s#track%02d", pathBase, track.Number) + + result := LibraryScanResult{ + ID: id, + TrackName: title, + ArtistName: performer, + AlbumName: album, + AlbumArtist: sheet.Performer, + FilePath: virtualFilePath, + CoverPath: coverPath, + ScannedAt: scanTime, + ISRC: track.ISRC, + TrackNumber: track.Number, + DiscNumber: 1, + Duration: duration, + ReleaseDate: sheet.Date, + BitDepth: bitDepth, + SampleRate: sampleRate, + Genre: sheet.Genre, + Format: "cue+" + strings.TrimPrefix(audioExt, "."), + } + + result.FileModTime = modTime + + results = append(results, result) + } + + return results, nil +} diff --git a/go_backend/exports.go b/go_backend/exports.go index 08efb1fb..5f83330a 100644 --- a/go_backend/exports.go +++ b/go_backend/exports.go @@ -901,6 +901,32 @@ func ReadFileMetadata(filePath string) (string, error) { return string(jsonBytes), nil } +// ParseCueSheet parses a .cue file and returns JSON with split information. +// This is called from Dart to get track listing and timing data for CUE splitting. +// audioDir, if non-empty, overrides the directory used for resolving the +// referenced audio file (useful for SAF temp file scenarios). +func ParseCueSheet(cuePath string, audioDir string) (string, error) { + return ParseCueFileJSON(cuePath, audioDir) +} + +// ScanCueSheetForLibrary parses a .cue file and returns a JSON array of +// LibraryScanResult entries (one per track). This is the SAF-friendly variant: +// - audioDir overrides where the referenced audio file is resolved +// - virtualPathPrefix replaces cuePath in filePath / id fields (e.g. a content:// URI) +// - fileModTime is stamped on every result (pass 0 to stat cuePath instead) +func ScanCueSheetForLibrary(cuePath, audioDir, virtualPathPrefix string, fileModTime int64) (string, error) { + scanTime := time.Now().UTC().Format(time.RFC3339) + results, err := ScanCueFileForLibraryExt(cuePath, audioDir, virtualPathPrefix, fileModTime, scanTime) + if err != nil { + return "[]", err + } + jsonBytes, err := json.Marshal(results) + if err != nil { + return "[]", fmt.Errorf("failed to marshal cue scan results: %w", err) + } + return string(jsonBytes), nil +} + // EditFileMetadata writes metadata to an audio file. // For FLAC files, uses native Go FLAC library. // For MP3/Opus, returns the metadata map so Dart can use FFmpeg. diff --git a/go_backend/library_scan.go b/go_backend/library_scan.go index d9e53663..34d5c718 100644 --- a/go_backend/library_scan.go +++ b/go_backend/library_scan.go @@ -63,6 +63,7 @@ var supportedAudioFormats = map[string]bool{ ".mp3": true, ".opus": true, ".ogg": true, + ".cue": true, } type libraryAudioFileInfo struct { @@ -166,6 +167,23 @@ func ScanLibraryFolder(folderPath string) (string, error) { scanTime := time.Now().UTC().Format(time.RFC3339) errorCount := 0 + // Track audio files referenced by .cue sheets to avoid duplicates + cueReferencedAudioFiles := make(map[string]bool) + + // First pass: scan .cue files to collect referenced audio paths + for _, filePath := range audioFiles { + ext := strings.ToLower(filepath.Ext(filePath)) + if ext == ".cue" { + sheet, err := ParseCueFile(filePath) + if err == nil && sheet.FileName != "" { + audioPath := ResolveCueAudioPath(filePath, sheet.FileName) + if audioPath != "" { + cueReferencedAudioFiles[audioPath] = true + } + } + } + } + for i, filePath := range audioFiles { select { case <-cancelCh: @@ -179,6 +197,28 @@ func ScanLibraryFolder(folderPath string) (string, error) { libraryScanProgress.ProgressPct = float64(i+1) / float64(totalFiles) * 100 libraryScanProgressMu.Unlock() + ext := strings.ToLower(filepath.Ext(filePath)) + + // Handle .cue files: produce multiple track results + if ext == ".cue" { + cueResults, err := ScanCueFileForLibrary(filePath, scanTime) + if err != nil { + errorCount++ + GoLog("[LibraryScan] Error scanning cue %s: %v\n", filePath, err) + continue + } + results = append(results, cueResults...) + GoLog("[LibraryScan] CUE sheet %s: %d tracks\n", filepath.Base(filePath), len(cueResults)) + continue + } + + // Skip audio files that are referenced by a .cue sheet + // (they will be represented by the cue sheet's track entries instead) + if cueReferencedAudioFiles[filePath] { + GoLog("[LibraryScan] Skipping %s (referenced by .cue sheet)\n", filepath.Base(filePath)) + continue + } + result, err := scanAudioFile(filePath, scanTime) if err != nil { errorCount++ @@ -502,9 +542,44 @@ func ScanLibraryFolderIncremental(folderPath, existingFilesJSON string) (string, var filesToScan []libraryAudioFileInfo skippedCount := 0 + // Build a set of existing CUE virtual path base files for incremental matching. + // CUE tracks are stored with virtual paths like "/path/album.cue#track01". + // We need to match these against the actual .cue file's modTime. + cueBaseModTimes := make(map[string]int64) // base cue path -> modTime from disk + for _, f := range currentFiles { + if strings.ToLower(filepath.Ext(f.path)) == ".cue" { + cueBaseModTimes[f.path] = f.modTime + } + } + for _, f := range currentFiles { existingModTime, exists := existingFiles[f.path] if !exists { + // For .cue files, also check if any virtual path entries exist + if strings.ToLower(filepath.Ext(f.path)) == ".cue" { + hasCueTracks := false + for existingPath := range existingFiles { + if strings.HasPrefix(existingPath, f.path+"#track") { + hasCueTracks = true + break + } + } + if hasCueTracks { + // CUE file exists in DB via virtual paths; check if modTime changed + // Use modTime from any virtual path (they all share the same .cue modTime) + for existingPath, modTime := range existingFiles { + if strings.HasPrefix(existingPath, f.path+"#track") { + if f.modTime == modTime { + skippedCount++ + } else { + filesToScan = append(filesToScan, f) + } + break + } + } + continue + } + } filesToScan = append(filesToScan, f) } else if f.modTime != existingModTime { filesToScan = append(filesToScan, f) @@ -515,7 +590,16 @@ func ScanLibraryFolderIncremental(folderPath, existingFilesJSON string) (string, var deletedPaths []string for existingPath := range existingFiles { - if !currentPathSet[existingPath] { + // For CUE virtual paths (e.g. "/path/album.cue#track01"), + // check if the base .cue file still exists on disk + if idx := strings.LastIndex(existingPath, "#track"); idx > 0 { + baseCuePath := existingPath[:idx] + if currentPathSet[baseCuePath] { + continue // Base .cue file still exists, not deleted + } + // Base CUE file is gone, mark virtual path as deleted + deletedPaths = append(deletedPaths, existingPath) + } else if !currentPathSet[existingPath] { deletedPaths = append(deletedPaths, existingPath) } } @@ -544,6 +628,21 @@ func ScanLibraryFolderIncremental(folderPath, existingFilesJSON string) (string, scanTime := time.Now().UTC().Format(time.RFC3339) errorCount := 0 + // Track audio files referenced by .cue sheets to avoid duplicates (incremental) + cueReferencedAudioFilesInc := make(map[string]bool) + for _, f := range filesToScan { + ext := strings.ToLower(filepath.Ext(f.path)) + if ext == ".cue" { + sheet, err := ParseCueFile(f.path) + if err == nil && sheet.FileName != "" { + audioPath := ResolveCueAudioPath(f.path, sheet.FileName) + if audioPath != "" { + cueReferencedAudioFilesInc[audioPath] = true + } + } + } + } + for i, f := range filesToScan { select { case <-cancelCh: @@ -557,6 +656,25 @@ func ScanLibraryFolderIncremental(folderPath, existingFilesJSON string) (string, libraryScanProgress.ProgressPct = float64(skippedCount+i+1) / float64(totalFiles) * 100 libraryScanProgressMu.Unlock() + ext := strings.ToLower(filepath.Ext(f.path)) + + // Handle .cue files: produce multiple track results + if ext == ".cue" { + cueResults, err := ScanCueFileForLibrary(f.path, scanTime) + if err != nil { + errorCount++ + GoLog("[LibraryScan] Error scanning cue %s: %v\n", f.path, err) + continue + } + results = append(results, cueResults...) + continue + } + + // Skip audio files referenced by .cue sheets + if cueReferencedAudioFilesInc[f.path] { + continue + } + result, err := scanAudioFile(f.path, scanTime) if err != nil { errorCount++ diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 4cb53238..b85020da 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -969,6 +969,15 @@ import Gobackend // Import Go framework if let error = error { throw error } return response + // CUE Sheet Parsing + case "parseCueSheet": + let args = call.arguments as! [String: Any] + let cuePath = args["cue_path"] as! String + let audioDir = args["audio_dir"] as? String ?? "" + let response = GobackendParseCueSheet(cuePath, audioDir, &error) + if let error = error { throw error } + return response + default: throw NSError( domain: "SpotiFLAC", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index c059621b..d722f825 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3812,6 +3812,78 @@ abstract class AppLocalizations { /// **'Conversion failed'** String get trackConvertFailed; + /// Title for CUE split bottom sheet + /// + /// In en, this message translates to: + /// **'Split CUE Sheet'** + String get cueSplitTitle; + + /// Subtitle for CUE split menu item + /// + /// In en, this message translates to: + /// **'Split CUE+FLAC into individual tracks'** + String get cueSplitSubtitle; + + /// Album name in CUE split sheet + /// + /// In en, this message translates to: + /// **'Album: {album}'** + String cueSplitAlbum(String album); + + /// Artist name in CUE split sheet + /// + /// In en, this message translates to: + /// **'Artist: {artist}'** + String cueSplitArtist(String artist); + + /// Number of tracks in CUE sheet + /// + /// In en, this message translates to: + /// **'{count} tracks'** + String cueSplitTrackCount(int count); + + /// CUE split confirmation dialog title + /// + /// In en, this message translates to: + /// **'Split CUE Album'** + String get cueSplitConfirmTitle; + + /// CUE split confirmation dialog message + /// + /// In en, this message translates to: + /// **'Split \"{album}\" into {count} individual FLAC files?\n\nFiles will be saved to the same directory.'** + String cueSplitConfirmMessage(String album, int count); + + /// Snackbar while splitting CUE + /// + /// In en, this message translates to: + /// **'Splitting CUE sheet... ({current}/{total})'** + String cueSplitSplitting(int current, int total); + + /// Snackbar after successful CUE split + /// + /// In en, this message translates to: + /// **'Split into {count} tracks successfully'** + String cueSplitSuccess(int count); + + /// Snackbar when CUE split fails + /// + /// In en, this message translates to: + /// **'CUE split failed'** + String get cueSplitFailed; + + /// Error when CUE audio file is missing + /// + /// In en, this message translates to: + /// **'Audio file not found for this CUE sheet'** + String get cueSplitNoAudioFile; + + /// Button text to start CUE splitting + /// + /// In en, this message translates to: + /// **'Split into Tracks'** + String get cueSplitButton; + /// Generic action button - create /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 246f5314..1bb33ef5 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -2164,6 +2164,54 @@ class AppLocalizationsDe extends AppLocalizations { @override String get trackConvertFailed => 'Konvertierung fehlgeschlagen'; + @override + String get cueSplitTitle => 'Split CUE Sheet'; + + @override + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; + + @override + String cueSplitAlbum(String album) { + return 'Album: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Artist: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count tracks'; + } + + @override + String get cueSplitConfirmTitle => 'Split CUE Album'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Split \"$album\" into $count individual FLAC files?\n\nFiles will be saved to the same directory.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Splitting CUE sheet... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Split into $count tracks successfully'; + } + + @override + String get cueSplitFailed => 'CUE split failed'; + + @override + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; + + @override + String get cueSplitButton => 'Split into Tracks'; + @override String get actionCreate => 'Erstellen'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index c583ab11..bd9d4589 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2137,6 +2137,54 @@ class AppLocalizationsEn extends AppLocalizations { @override String get trackConvertFailed => 'Conversion failed'; + @override + String get cueSplitTitle => 'Split CUE Sheet'; + + @override + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; + + @override + String cueSplitAlbum(String album) { + return 'Album: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Artist: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count tracks'; + } + + @override + String get cueSplitConfirmTitle => 'Split CUE Album'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Split \"$album\" into $count individual FLAC files?\n\nFiles will be saved to the same directory.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Splitting CUE sheet... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Split into $count tracks successfully'; + } + + @override + String get cueSplitFailed => 'CUE split failed'; + + @override + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; + + @override + String get cueSplitButton => 'Split into Tracks'; + @override String get actionCreate => 'Create'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index f4758a27..ed9236ad 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -2137,6 +2137,54 @@ class AppLocalizationsEs extends AppLocalizations { @override String get trackConvertFailed => 'Conversion failed'; + @override + String get cueSplitTitle => 'Split CUE Sheet'; + + @override + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; + + @override + String cueSplitAlbum(String album) { + return 'Album: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Artist: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count tracks'; + } + + @override + String get cueSplitConfirmTitle => 'Split CUE Album'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Split \"$album\" into $count individual FLAC files?\n\nFiles will be saved to the same directory.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Splitting CUE sheet... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Split into $count tracks successfully'; + } + + @override + String get cueSplitFailed => 'CUE split failed'; + + @override + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; + + @override + String get cueSplitButton => 'Split into Tracks'; + @override String get actionCreate => 'Create'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index c93b863f..d6a3b948 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -2139,6 +2139,54 @@ class AppLocalizationsFr extends AppLocalizations { @override String get trackConvertFailed => 'Conversion failed'; + @override + String get cueSplitTitle => 'Split CUE Sheet'; + + @override + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; + + @override + String cueSplitAlbum(String album) { + return 'Album: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Artist: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count tracks'; + } + + @override + String get cueSplitConfirmTitle => 'Split CUE Album'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Split \"$album\" into $count individual FLAC files?\n\nFiles will be saved to the same directory.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Splitting CUE sheet... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Split into $count tracks successfully'; + } + + @override + String get cueSplitFailed => 'CUE split failed'; + + @override + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; + + @override + String get cueSplitButton => 'Split into Tracks'; + @override String get actionCreate => 'Create'; diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index a12620b5..a461bd5b 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -2137,6 +2137,54 @@ class AppLocalizationsHi extends AppLocalizations { @override String get trackConvertFailed => 'Conversion failed'; + @override + String get cueSplitTitle => 'Split CUE Sheet'; + + @override + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; + + @override + String cueSplitAlbum(String album) { + return 'Album: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Artist: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count tracks'; + } + + @override + String get cueSplitConfirmTitle => 'Split CUE Album'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Split \"$album\" into $count individual FLAC files?\n\nFiles will be saved to the same directory.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Splitting CUE sheet... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Split into $count tracks successfully'; + } + + @override + String get cueSplitFailed => 'CUE split failed'; + + @override + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; + + @override + String get cueSplitButton => 'Split into Tracks'; + @override String get actionCreate => 'Create'; diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index e408e8ae..b668481b 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -2144,6 +2144,54 @@ class AppLocalizationsId extends AppLocalizations { @override String get trackConvertFailed => 'Conversion failed'; + @override + String get cueSplitTitle => 'Split CUE Sheet'; + + @override + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; + + @override + String cueSplitAlbum(String album) { + return 'Album: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Artist: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count tracks'; + } + + @override + String get cueSplitConfirmTitle => 'Split CUE Album'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Split \"$album\" into $count individual FLAC files?\n\nFiles will be saved to the same directory.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Splitting CUE sheet... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Split into $count tracks successfully'; + } + + @override + String get cueSplitFailed => 'CUE split failed'; + + @override + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; + + @override + String get cueSplitButton => 'Split into Tracks'; + @override String get actionCreate => 'Create'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 9e898bdf..71d10e1a 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -2124,6 +2124,54 @@ class AppLocalizationsJa extends AppLocalizations { @override String get trackConvertFailed => '変換に失敗しました'; + @override + String get cueSplitTitle => 'Split CUE Sheet'; + + @override + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; + + @override + String cueSplitAlbum(String album) { + return 'Album: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Artist: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count tracks'; + } + + @override + String get cueSplitConfirmTitle => 'Split CUE Album'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Split \"$album\" into $count individual FLAC files?\n\nFiles will be saved to the same directory.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Splitting CUE sheet... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Split into $count tracks successfully'; + } + + @override + String get cueSplitFailed => 'CUE split failed'; + + @override + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; + + @override + String get cueSplitButton => 'Split into Tracks'; + @override String get actionCreate => 'Create'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 66c66e99..43bfbce3 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -2117,6 +2117,54 @@ class AppLocalizationsKo extends AppLocalizations { @override String get trackConvertFailed => 'Conversion failed'; + @override + String get cueSplitTitle => 'Split CUE Sheet'; + + @override + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; + + @override + String cueSplitAlbum(String album) { + return 'Album: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Artist: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count tracks'; + } + + @override + String get cueSplitConfirmTitle => 'Split CUE Album'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Split \"$album\" into $count individual FLAC files?\n\nFiles will be saved to the same directory.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Splitting CUE sheet... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Split into $count tracks successfully'; + } + + @override + String get cueSplitFailed => 'CUE split failed'; + + @override + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; + + @override + String get cueSplitButton => 'Split into Tracks'; + @override String get actionCreate => 'Create'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 6144f9b6..2eeb00cc 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -2137,6 +2137,54 @@ class AppLocalizationsNl extends AppLocalizations { @override String get trackConvertFailed => 'Conversion failed'; + @override + String get cueSplitTitle => 'Split CUE Sheet'; + + @override + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; + + @override + String cueSplitAlbum(String album) { + return 'Album: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Artist: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count tracks'; + } + + @override + String get cueSplitConfirmTitle => 'Split CUE Album'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Split \"$album\" into $count individual FLAC files?\n\nFiles will be saved to the same directory.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Splitting CUE sheet... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Split into $count tracks successfully'; + } + + @override + String get cueSplitFailed => 'CUE split failed'; + + @override + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; + + @override + String get cueSplitButton => 'Split into Tracks'; + @override String get actionCreate => 'Create'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index d5c8d821..9436f6eb 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -2137,6 +2137,54 @@ class AppLocalizationsPt extends AppLocalizations { @override String get trackConvertFailed => 'Conversion failed'; + @override + String get cueSplitTitle => 'Split CUE Sheet'; + + @override + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; + + @override + String cueSplitAlbum(String album) { + return 'Album: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Artist: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count tracks'; + } + + @override + String get cueSplitConfirmTitle => 'Split CUE Album'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Split \"$album\" into $count individual FLAC files?\n\nFiles will be saved to the same directory.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Splitting CUE sheet... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Split into $count tracks successfully'; + } + + @override + String get cueSplitFailed => 'CUE split failed'; + + @override + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; + + @override + String get cueSplitButton => 'Split into Tracks'; + @override String get actionCreate => 'Create'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 909b5842..943b75f9 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -2190,6 +2190,54 @@ class AppLocalizationsRu extends AppLocalizations { @override String get trackConvertFailed => 'Ошибка конвертации'; + @override + String get cueSplitTitle => 'Split CUE Sheet'; + + @override + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; + + @override + String cueSplitAlbum(String album) { + return 'Album: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Artist: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count tracks'; + } + + @override + String get cueSplitConfirmTitle => 'Split CUE Album'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Split \"$album\" into $count individual FLAC files?\n\nFiles will be saved to the same directory.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Splitting CUE sheet... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Split into $count tracks successfully'; + } + + @override + String get cueSplitFailed => 'CUE split failed'; + + @override + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; + + @override + String get cueSplitButton => 'Split into Tracks'; + @override String get actionCreate => 'Создать'; diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index b608cff7..223fecd3 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -2149,6 +2149,54 @@ class AppLocalizationsTr extends AppLocalizations { @override String get trackConvertFailed => 'Conversion failed'; + @override + String get cueSplitTitle => 'Split CUE Sheet'; + + @override + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; + + @override + String cueSplitAlbum(String album) { + return 'Album: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Artist: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count tracks'; + } + + @override + String get cueSplitConfirmTitle => 'Split CUE Album'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Split \"$album\" into $count individual FLAC files?\n\nFiles will be saved to the same directory.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Splitting CUE sheet... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Split into $count tracks successfully'; + } + + @override + String get cueSplitFailed => 'CUE split failed'; + + @override + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; + + @override + String get cueSplitButton => 'Split into Tracks'; + @override String get actionCreate => 'Create'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 4104845c..57f92319 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -2137,6 +2137,54 @@ class AppLocalizationsZh extends AppLocalizations { @override String get trackConvertFailed => 'Conversion failed'; + @override + String get cueSplitTitle => 'Split CUE Sheet'; + + @override + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; + + @override + String cueSplitAlbum(String album) { + return 'Album: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Artist: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count tracks'; + } + + @override + String get cueSplitConfirmTitle => 'Split CUE Album'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Split \"$album\" into $count individual FLAC files?\n\nFiles will be saved to the same directory.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Splitting CUE sheet... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Split into $count tracks successfully'; + } + + @override + String get cueSplitFailed => 'CUE split failed'; + + @override + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; + + @override + String get cueSplitButton => 'Split into Tracks'; + @override String get actionCreate => 'Create'; diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index bb8bac99..bb3036c9 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2820,6 +2820,90 @@ "@trackConvertFailed": { "description": "Snackbar when conversion fails" }, + "cueSplitTitle": "Split CUE Sheet", + "@cueSplitTitle": { + "description": "Title for CUE split bottom sheet" + }, + "cueSplitSubtitle": "Split CUE+FLAC into individual tracks", + "@cueSplitSubtitle": { + "description": "Subtitle for CUE split menu item" + }, + "cueSplitAlbum": "Album: {album}", + "@cueSplitAlbum": { + "description": "Album name in CUE split sheet", + "placeholders": { + "album": { + "type": "String" + } + } + }, + "cueSplitArtist": "Artist: {artist}", + "@cueSplitArtist": { + "description": "Artist name in CUE split sheet", + "placeholders": { + "artist": { + "type": "String" + } + } + }, + "cueSplitTrackCount": "{count} tracks", + "@cueSplitTrackCount": { + "description": "Number of tracks in CUE sheet", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cueSplitConfirmTitle": "Split CUE Album", + "@cueSplitConfirmTitle": { + "description": "CUE split confirmation dialog title" + }, + "cueSplitConfirmMessage": "Split \"{album}\" into {count} individual FLAC files?\n\nFiles will be saved to the same directory.", + "@cueSplitConfirmMessage": { + "description": "CUE split confirmation dialog message", + "placeholders": { + "album": { + "type": "String" + }, + "count": { + "type": "int" + } + } + }, + "cueSplitSplitting": "Splitting CUE sheet... ({current}/{total})", + "@cueSplitSplitting": { + "description": "Snackbar while splitting CUE", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "cueSplitSuccess": "Split into {count} tracks successfully", + "@cueSplitSuccess": { + "description": "Snackbar after successful CUE split", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cueSplitFailed": "CUE split failed", + "@cueSplitFailed": { + "description": "Snackbar when CUE split fails" + }, + "cueSplitNoAudioFile": "Audio file not found for this CUE sheet", + "@cueSplitNoAudioFile": { + "description": "Error when CUE audio file is missing" + }, + "cueSplitButton": "Split into Tracks", + "@cueSplitButton": { + "description": "Button text to start CUE splitting" + }, "actionCreate": "Create", "@actionCreate": { "description": "Generic action button - create" diff --git a/lib/providers/playback_provider.dart b/lib/providers/playback_provider.dart index c35f6a00..bcaa14c0 100644 --- a/lib/providers/playback_provider.dart +++ b/lib/providers/playback_provider.dart @@ -24,6 +24,9 @@ class PlaybackController extends Notifier { String coverUrl = '', Track? track, }) async { + if (isCueVirtualPath(path)) { + throw Exception(cueVirtualTrackRequiresSplitMessage); + } _log.d('Opening external player for "$title" by $artist: $path'); await openFile(path); } @@ -32,11 +35,16 @@ class PlaybackController extends Notifier { if (tracks.isEmpty) return; final orderedTracks = _orderedTracksFromStartIndex(tracks, startIndex); + var skippedCueVirtualTrack = false; for (final track in orderedTracks) { final resolvedPath = await _resolveTrackPath(track); if (resolvedPath == null) { continue; } + if (isCueVirtualPath(resolvedPath)) { + skippedCueVirtualTrack = true; + continue; + } _log.d( 'Opening first available external track for list playback: ' @@ -46,6 +54,10 @@ class PlaybackController extends Notifier { return; } + if (skippedCueVirtualTrack) { + throw Exception(cueVirtualTrackRequiresSplitMessage); + } + throw Exception( 'No local audio file is available to open. Download the track first.', ); diff --git a/lib/screens/local_album_screen.dart b/lib/screens/local_album_screen.dart index 79eca05b..9c560546 100644 --- a/lib/screens/local_album_screen.dart +++ b/lib/screens/local_album_screen.dart @@ -38,6 +38,14 @@ class _LocalAlbumScreenState extends ConsumerState { final ScrollController _scrollController = ScrollController(); late List _sortedTracksCache; late Map> _discGroupsCache; + + void _showCueVirtualTrackSnackBar() { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text(cueVirtualTrackRequiresSplitMessage), + ), + ); + } late List _sortedDiscNumbersCache; late bool _hasMultipleDiscsCache; String? _commonQualityCache; @@ -178,9 +186,11 @@ class _LocalAlbumScreenState extends ConsumerState { for (final id in idsToDelete) { final item = tracksById[id]; if (item != null) { - try { - await deleteFile(item.filePath); - } catch (_) {} + if (!isCueVirtualPath(item.filePath)) { + try { + await deleteFile(item.filePath); + } catch (_) {} + } await libraryNotifier.removeItem(id); deletedCount++; } @@ -203,6 +213,10 @@ class _LocalAlbumScreenState extends ConsumerState { } Future _openFile(LocalLibraryItem track) async { + if (isCueVirtualPath(track.filePath)) { + _showCueVirtualTrackSnackBar(); + return; + } try { await ref .read(playbackProvider.notifier) diff --git a/lib/screens/track_metadata_screen.dart b/lib/screens/track_metadata_screen.dart index fdcd3eef..d8693612 100644 --- a/lib/screens/track_metadata_screen.dart +++ b/lib/screens/track_metadata_screen.dart @@ -13,11 +13,14 @@ import 'package:path_provider/path_provider.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:share_plus/share_plus.dart'; import 'package:spotiflac_android/providers/download_queue_provider.dart'; +import 'package:spotiflac_android/providers/local_library_provider.dart'; import 'package:spotiflac_android/providers/playback_provider.dart'; +import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/services/platform_bridge.dart'; import 'package:spotiflac_android/services/ffmpeg_service.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/utils/logger.dart'; +import 'package:spotiflac_android/utils/mime_utils.dart'; import 'package:spotiflac_android/utils/string_utils.dart'; final _log = AppLogger('TrackMetadata'); @@ -214,10 +217,7 @@ class _TrackMetadataScreenState extends ConsumerState { } Future _checkFile() async { - var filePath = _filePath; - if (filePath.startsWith('EXISTS:')) { - filePath = filePath.substring(7); - } + final filePath = cleanFilePath; bool exists = false; int? size; @@ -544,11 +544,65 @@ class _TrackMetadataScreenState extends ConsumerState { ); } - String get cleanFilePath { + /// The raw file path, with EXISTS: prefix stripped but #trackNN preserved. + /// Use this when you need the full virtual path (e.g. for display or DB lookups). + String get rawFilePath { final path = _filePath; return path.startsWith('EXISTS:') ? path.substring(7) : path; } + /// The clean file path with both EXISTS: prefix and #trackNN suffix stripped. + /// Use this for actual filesystem/SAF operations. + String get cleanFilePath { + var path = _filePath; + if (path.startsWith('EXISTS:')) path = path.substring(7); + // Strip CUE virtual path suffix for filesystem operations + if (isCueVirtualPath(path)) path = stripCueTrackSuffix(path); + return path; + } + + bool get _isCueVirtualTrack => isCueVirtualPath(rawFilePath); + + String _cueVirtualTrackGuidance(BuildContext context) { + return 'This CUE track is virtual. Use ${context.l10n.cueSplitButton} first.'; + } + + void _showCueVirtualTrackSnackBar(BuildContext context) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(_cueVirtualTrackGuidance(context))), + ); + } + + void _hideCurrentSnackBar() { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + } + + String get _l10nCueSplitFailed => context.l10n.cueSplitFailed; + String get _l10nCueSplitNoAudioFile => context.l10n.cueSplitNoAudioFile; + + String _l10nCueSplitSplitting(int current, int total) { + return context.l10n.cueSplitSplitting(current, total); + } + + String _l10nCueSplitSuccess(int count) { + return context.l10n.cueSplitSuccess(count); + } + + void _showSnackBarMessage(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message)), + ); + } + + void _showLongSnackBarMessage(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + duration: const Duration(seconds: 60), + ), + ); + } + String _formatPathForDisplay(String pathOrUri) { if (pathOrUri.isEmpty || !pathOrUri.startsWith('content://')) { return pathOrUri; @@ -1153,8 +1207,8 @@ class _TrackMetadataScreenState extends ConsumerState { bool fileExists, int? fileSize, ) { - final displayFilePath = _formatPathForDisplay(cleanFilePath); - final fileName = _extractFileNameFromPathOrUri(cleanFilePath); + final displayFilePath = _formatPathForDisplay(rawFilePath); + final fileName = _extractFileNameFromPathOrUri(rawFilePath); final fileExtension = fileName.contains('.') ? fileName.split('.').last.toUpperCase() : 'Unknown'; @@ -2365,7 +2419,7 @@ class _TrackMetadataScreenState extends ConsumerState { flex: 2, child: FilledButton.icon( onPressed: fileExists - ? () => _openFile(context, cleanFilePath) + ? () => _openFile(context, rawFilePath) : null, icon: const Icon(Icons.play_arrow), label: Text(context.l10n.trackMetadataPlay), @@ -2487,6 +2541,16 @@ class _TrackMetadataScreenState extends ConsumerState { _showConvertSheet(context); }, ), + if (_fileExists && _isCueFile) + ListTile( + leading: const Icon(Icons.call_split), + title: Text(context.l10n.cueSplitTitle), + subtitle: Text(context.l10n.cueSplitSubtitle), + onTap: () { + Navigator.pop(context); + _showCueSplitSheet(context); + }, + ), const Divider(height: 1), ListTile( leading: const Icon(Icons.share), @@ -2524,11 +2588,34 @@ class _TrackMetadataScreenState extends ConsumerState { lower.endsWith('.ogg'); } + /// Whether the current file is a CUE sheet (or CUE-referenced) + bool get _isCueFile { + // Check if the raw path has a CUE virtual path suffix + if (isCueVirtualPath(rawFilePath)) return true; + final lower = cleanFilePath.toLowerCase(); + if (lower.endsWith('.cue')) return true; + // Check if local library item has cue+ format + if (_isLocalItem && _localLibraryItem != null) { + final format = _localLibraryItem!.format ?? ''; + if (format.startsWith('cue+')) return true; + } + return false; + } + String get _currentFileFormat { + // For CUE tracks, use the format from the library item (e.g. "cue+flac") + if (_isCueFile && _isLocalItem && _localLibraryItem != null) { + final format = _localLibraryItem!.format ?? ''; + if (format.startsWith('cue+')) { + final audioFmt = format.substring(4).toUpperCase(); + return 'CUE+$audioFmt'; + } + } final lower = cleanFilePath.toLowerCase(); if (lower.endsWith('.flac')) return 'FLAC'; if (lower.endsWith('.mp3')) return 'MP3'; if (lower.endsWith('.opus') || lower.endsWith('.ogg')) return 'Opus'; + if (lower.endsWith('.cue')) return 'CUE'; return 'Unknown'; } @@ -2766,6 +2853,470 @@ class _TrackMetadataScreenState extends ConsumerState { ); } + void _showCueSplitSheet(BuildContext context) async { + // Strip the #trackNN suffix from virtual CUE paths to get the real .cue path + var cuePath = cleanFilePath; + final trackSuffix = RegExp(r'#track\d+$'); + if (trackSuffix.hasMatch(cuePath)) { + cuePath = cuePath.replaceFirst(trackSuffix, ''); + } + + // Show loading indicator + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Loading CUE sheet...')), + ); + + try { + final cueInfo = await PlatformBridge.parseCueSheet(cuePath); + + if (!mounted) return; + _hideCurrentSnackBar(); + + if (cueInfo.containsKey('error')) { + _showSnackBarMessage(_l10nCueSplitNoAudioFile); + return; + } + + final album = cueInfo['album'] as String? ?? 'Unknown Album'; + final artist = cueInfo['artist'] as String? ?? 'Unknown Artist'; + final audioPath = cueInfo['audio_path'] as String? ?? ''; + final genre = cueInfo['genre'] as String? ?? ''; + final date = cueInfo['date'] as String? ?? ''; + final tracksRaw = cueInfo['tracks'] as List? ?? []; + + if (audioPath.isEmpty) { + _showSnackBarMessage(_l10nCueSplitNoAudioFile); + return; + } + + final tracks = tracksRaw + .map((t) => CueSplitTrackInfo.fromJson(t as Map)) + .toList(); + + if (tracks.isEmpty) { + _showSnackBarMessage(_l10nCueSplitFailed); + return; + } + + if (!mounted) return; + + showModalBottomSheet( + context: this.context, + useRootNavigator: true, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (sheetContext) { + final colorScheme = Theme.of(sheetContext).colorScheme; + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: colorScheme.onSurfaceVariant.withValues( + alpha: 0.4, + ), + borderRadius: BorderRadius.circular(2), + ), + ), + ), + const SizedBox(height: 16), + Text( + sheetContext.l10n.cueSplitTitle, + style: Theme.of(sheetContext).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 12), + Text( + sheetContext.l10n.cueSplitAlbum(album), + style: Theme.of(sheetContext).textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: 4), + Text( + sheetContext.l10n.cueSplitArtist(artist), + style: Theme.of(sheetContext).textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: 4), + Text( + sheetContext.l10n.cueSplitTrackCount(tracks.length), + style: Theme.of(sheetContext).textTheme.bodyMedium?.copyWith( + color: colorScheme.primary, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 16), + // Track list preview (scrollable, max 200px) + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200), + child: ListView.builder( + shrinkWrap: true, + itemCount: tracks.length, + itemBuilder: (context, index) { + final track = tracks[index]; + final duration = track.endSec > 0 + ? track.endSec - track.startSec + : 0.0; + final durationStr = duration > 0 + ? '${(duration ~/ 60).toString().padLeft(2, '0')}:${(duration.toInt() % 60).toString().padLeft(2, '0')}' + : ''; + return ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + leading: CircleAvatar( + radius: 14, + backgroundColor: colorScheme.primaryContainer, + child: Text( + '${track.number}', + style: TextStyle( + fontSize: 11, + color: colorScheme.onPrimaryContainer, + ), + ), + ), + title: Text( + track.title, + style: const TextStyle(fontSize: 13), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + subtitle: track.artist.isNotEmpty + ? Text( + track.artist, + style: TextStyle( + fontSize: 11, + color: colorScheme.onSurfaceVariant, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ) + : null, + trailing: durationStr.isNotEmpty + ? Text( + durationStr, + style: TextStyle( + fontSize: 12, + color: colorScheme.onSurfaceVariant, + ), + ) + : null, + ); + }, + ), + ), + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: FilledButton.icon( + onPressed: () { + Navigator.pop(sheetContext); + _confirmAndSplitCue( + context: this.context, + audioPath: audioPath, + album: album, + artist: artist, + genre: genre, + date: date, + tracks: tracks, + ); + }, + icon: const Icon(Icons.call_split), + label: Text(sheetContext.l10n.cueSplitButton), + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ), + const SizedBox(height: 8), + ], + ), + ), + ); + }, + ); + } catch (e) { + if (!mounted) return; + _hideCurrentSnackBar(); + _showSnackBarMessage(_l10nCueSplitFailed); + _log.e('Failed to parse CUE sheet: $e'); + } + } + + void _confirmAndSplitCue({ + required BuildContext context, + required String audioPath, + required String album, + required String artist, + required String genre, + required String date, + required List tracks, + }) { + showDialog( + context: context, + builder: (dialogContext) { + return AlertDialog( + title: Text(dialogContext.l10n.cueSplitConfirmTitle), + content: Text( + dialogContext.l10n.cueSplitConfirmMessage(album, tracks.length), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(dialogContext), + child: Text(dialogContext.l10n.dialogCancel), + ), + FilledButton( + onPressed: () { + Navigator.pop(dialogContext); + _performCueSplit( + audioPath: audioPath, + album: album, + artist: artist, + genre: genre, + date: date, + tracks: tracks, + ); + }, + child: Text(dialogContext.l10n.cueSplitButton), + ), + ], + ); + }, + ); + } + + Future _resolvePersistentCueSplitOutputDir() async { + final settings = ref.read(settingsProvider); + final queueState = ref.read(downloadQueueProvider); + final configuredOutputDir = queueState.outputDir.trim(); + if (settings.storageMode != 'saf' && + configuredOutputDir.isNotEmpty && + !isContentUri(configuredOutputDir)) { + final dir = Directory(configuredOutputDir); + await dir.create(recursive: true); + return dir; + } + + if (Platform.isAndroid) { + final externalDir = await getExternalStorageDirectory(); + if (externalDir != null) { + final musicDir = Directory( + '${externalDir.parent.parent.parent.parent.path}' + '${Platform.pathSeparator}Music' + '${Platform.pathSeparator}SpotiFLAC', + ); + await musicDir.create(recursive: true); + return musicDir; + } + } + + final docsDir = await getApplicationDocumentsDirectory(); + final fallbackDir = Directory( + '${docsDir.path}${Platform.pathSeparator}SpotiFLAC', + ); + await fallbackDir.create(recursive: true); + return fallbackDir; + } + + Future?> _exportCueSplitOutputsToSaf({ + required List outputPaths, + required String treeUri, + required String relativeDir, + }) async { + final exportedUris = []; + for (final path in outputPaths) { + final fileName = path.split(Platform.pathSeparator).last; + final safUri = await PlatformBridge.createSafFileFromPath( + treeUri: treeUri, + relativeDir: relativeDir, + fileName: fileName, + mimeType: audioMimeTypeForPath(path), + srcPath: path, + ); + if (safUri != null && safUri.isNotEmpty) { + exportedUris.add(safUri); + } + } + return exportedUris.isEmpty ? null : exportedUris; + } + + Future _performCueSplit({ + required String audioPath, + required String album, + required String artist, + required String genre, + required String date, + required List tracks, + }) async { + if (_isConverting) return; + setState(() => _isConverting = true); + + String? safTempAudioPath; + Directory? tempSplitDir; + try { + // For SAF content:// audio paths, copy to temp for FFmpeg processing + String workingAudioPath = audioPath; + final isSafSource = isContentUri(audioPath); + if (isSafSource) { + final tempPath = await PlatformBridge.copyContentUriToTemp(audioPath); + if (tempPath == null || tempPath.isEmpty) { + throw Exception('Failed to copy SAF audio file to temp'); + } + safTempAudioPath = tempPath; + workingAudioPath = tempPath; + } + + // Determine output directory + final String outputDir; + final treeUri = !_isLocalItem ? (_downloadItem?.downloadTreeUri ?? '') : ''; + final relativeDir = !_isLocalItem ? (_downloadItem?.safRelativeDir ?? '') : ''; + final writeBackToSaf = isSafSource && treeUri.isNotEmpty; + if (writeBackToSaf) { + final tempDir = await getTemporaryDirectory(); + tempSplitDir = Directory( + '${tempDir.path}${Platform.pathSeparator}' + 'cue_split_${DateTime.now().millisecondsSinceEpoch}', + ); + await tempSplitDir.create(recursive: true); + outputDir = tempSplitDir.path; + } else if (isSafSource) { + final persistentDir = await _resolvePersistentCueSplitOutputDir(); + outputDir = persistentDir.path; + } else { + outputDir = File(audioPath).parent.path; + } + + if (!mounted) return; + _showLongSnackBarMessage(_l10nCueSplitSplitting(1, tracks.length)); + + // Extract cover from audio file for embedding + String? coverPath; + try { + final tempDir = await getTemporaryDirectory(); + final coverOutput = + '${tempDir.path}${Platform.pathSeparator}cue_cover_${DateTime.now().millisecondsSinceEpoch}.jpg'; + final coverResult = await PlatformBridge.extractCoverToFile( + workingAudioPath, + coverOutput, + ); + if (coverResult['error'] == null) { + coverPath = coverOutput; + } + } catch (_) {} + + final albumMetadata = { + 'artist': artist, + 'album': album, + 'genre': genre, + 'date': date, + }; + + final outputPaths = await FFmpegService.splitCueToTracks( + audioPath: workingAudioPath, + outputDir: outputDir, + tracks: tracks, + albumMetadata: albumMetadata, + coverPath: coverPath, + onProgress: (current, total) { + if (mounted) { + _hideCurrentSnackBar(); + _showLongSnackBarMessage(_l10nCueSplitSplitting(current, total)); + } + }, + ); + + var finalOutputPaths = outputPaths; + + // Embed cover art into split FLAC files using Go backend + if (coverPath != null && finalOutputPaths != null) { + for (final path in finalOutputPaths) { + if (path.toLowerCase().endsWith('.flac')) { + try { + // Read existing metadata first + final metadata = await PlatformBridge.readFileMetadata(path); + if (metadata['error'] == null) { + final fields = { + 'cover_path': coverPath, + }; + // Preserve existing fields + for (final entry in metadata.entries) { + if (entry.key == 'error' || entry.value == null) continue; + final v = entry.value.toString().trim(); + if (v.isNotEmpty) { + fields[entry.key] = v; + } + } + await PlatformBridge.editFileMetadata(path, fields); + } + } catch (e) { + _log.w('Failed to embed cover to split track: $e'); + } + } + } + } + + if (writeBackToSaf && finalOutputPaths != null) { + final exportedUris = await _exportCueSplitOutputsToSaf( + outputPaths: finalOutputPaths, + treeUri: treeUri, + relativeDir: relativeDir, + ); + finalOutputPaths = exportedUris; + } + + // Cleanup cover temp + if (coverPath != null) { + try { + await File(coverPath).delete(); + } catch (_) {} + } + + if (mounted) { + _hideCurrentSnackBar(); + if (finalOutputPaths != null && finalOutputPaths.isNotEmpty) { + _showSnackBarMessage(_l10nCueSplitSuccess(finalOutputPaths.length)); + } else { + _showSnackBarMessage(_l10nCueSplitFailed); + } + } + } catch (e) { + _log.e('CUE split failed: $e'); + if (mounted) { + _hideCurrentSnackBar(); + _showSnackBarMessage(_l10nCueSplitFailed); + } + } finally { + // Cleanup SAF temp audio copy + if (safTempAudioPath != null) { + try { + await File(safTempAudioPath).delete(); + } catch (_) {} + } + if (tempSplitDir != null) { + try { + await tempSplitDir.delete(recursive: true); + } catch (_) {} + } + if (mounted) { + setState(() => _isConverting = false); + } + } + } + void _confirmAndConvert({ required BuildContext context, required String sourceFormat, @@ -3117,14 +3668,17 @@ class _TrackMetadataScreenState extends ConsumerState { TextButton( onPressed: () async { if (_isLocalItem) { - // For local items, just delete the file - try { - await deleteFile(cleanFilePath); - } catch (e) { - debugPrint('Failed to delete file: $e'); + if (_isCueVirtualTrack && _localLibraryItem != null) { + await ref + .read(localLibraryProvider.notifier) + .removeItem(_localLibraryItem!.id); + } else { + try { + await deleteFile(cleanFilePath); + } catch (e) { + debugPrint('Failed to delete file: $e'); + } } - // Also remove from local library database - // ref.read(localLibraryProvider.notifier).removeItem(_localLibraryItem!.id); } else { try { await deleteFile(cleanFilePath); @@ -3153,6 +3707,10 @@ class _TrackMetadataScreenState extends ConsumerState { } Future _openFile(BuildContext context, String filePath) async { + if (isCueVirtualPath(filePath)) { + _showCueVirtualTrackSnackBar(context); + return; + } try { await ref .read(playbackProvider.notifier) @@ -3185,6 +3743,11 @@ class _TrackMetadataScreenState extends ConsumerState { } Future _shareFile(BuildContext context) async { + if (_isCueVirtualTrack) { + _showCueVirtualTrackSnackBar(context); + return; + } + String sharePath = cleanFilePath; if (!await fileExists(sharePath)) { if (context.mounted) { diff --git a/lib/services/ffmpeg_service.dart b/lib/services/ffmpeg_service.dart index 2b28ca9d..8154f03c 100644 --- a/lib/services/ffmpeg_service.dart +++ b/lib/services/ffmpeg_service.dart @@ -1353,6 +1353,160 @@ class FFmpegService { return id3Map; } + + /// Split a CUE+audio file into individual track files using FFmpeg. + /// Each track is extracted with `-c copy` (no re-encoding) and metadata is embedded. + /// [audioPath] is the source audio file (FLAC, WAV, etc.) + /// [outputDir] is where individual track files will be saved + /// [tracks] is the list of track split info from the Go CUE parser + /// [albumMetadata] contains album-level metadata (artist, album, genre, date) + /// Returns list of output file paths on success, null on failure. + static Future?> splitCueToTracks({ + required String audioPath, + required String outputDir, + required List tracks, + required Map albumMetadata, + String? coverPath, + void Function(int current, int total)? onProgress, + }) async { + if (tracks.isEmpty) { + _log.e('No tracks to split'); + return null; + } + + final outputPaths = []; + final inputExt = audioPath.toLowerCase().split('.').last; + // For lossless formats, keep as FLAC; for others, keep original format + final outputExt = (inputExt == 'flac' || inputExt == 'wav' || inputExt == 'ape' || inputExt == 'wv') + ? 'flac' + : inputExt; + + for (var i = 0; i < tracks.length; i++) { + final track = tracks[i]; + onProgress?.call(i + 1, tracks.length); + + // Sanitize filename + final sanitizedTitle = track.title + .replaceAll(RegExp(r'[<>:"/\\|?*]'), '_') + .replaceAll(RegExp(r'\s+'), ' ') + .trim(); + final trackNumStr = track.number.toString().padLeft(2, '0'); + final outputFileName = '$trackNumStr - $sanitizedTitle.$outputExt'; + final outputPath = '$outputDir${Platform.pathSeparator}$outputFileName'; + + // Build FFmpeg command for this track + final StringBuffer cmdBuffer = StringBuffer(); + cmdBuffer.write('-i "$audioPath" '); + + // Time range + final startTime = _formatSecondsForFFmpeg(track.startSec); + cmdBuffer.write('-ss $startTime '); + + if (track.endSec > 0) { + final endTime = _formatSecondsForFFmpeg(track.endSec); + cmdBuffer.write('-to $endTime '); + } + + if (outputExt == 'flac') { + cmdBuffer.write('-c:a flac -compression_level 8 '); + } else { + cmdBuffer.write('-c:a copy '); + } + + // Metadata + final artist = track.artist.isNotEmpty ? track.artist : (albumMetadata['artist'] ?? ''); + final album = albumMetadata['album'] ?? ''; + final genre = albumMetadata['genre'] ?? ''; + final date = albumMetadata['date'] ?? ''; + + void addMeta(String key, String value) { + if (value.isNotEmpty) { + final sanitized = value.replaceAll('"', '\\"'); + cmdBuffer.write('-metadata $key="$sanitized" '); + } + } + + addMeta('TITLE', track.title); + addMeta('ARTIST', artist); + addMeta('ALBUM', album); + addMeta('ALBUMARTIST', albumMetadata['artist'] ?? ''); + addMeta('TRACKNUMBER', track.number.toString()); + addMeta('GENRE', genre); + addMeta('DATE', date); + if (track.isrc.isNotEmpty) addMeta('ISRC', track.isrc); + if (track.composer.isNotEmpty) addMeta('COMPOSER', track.composer); + + cmdBuffer.write('"$outputPath" -y'); + + final command = cmdBuffer.toString(); + _log.d('CUE split track ${track.number}: ${_previewCommandForLog(command)}'); + + final result = await _execute(command); + if (!result.success) { + _log.e('CUE split failed for track ${track.number}: ${result.output}'); + // Continue with remaining tracks instead of failing completely + continue; + } + + // Embed cover art if available (for FLAC output) + if (coverPath != null && coverPath.isNotEmpty && outputExt == 'flac') { + // Use the Go backend for FLAC cover embedding via PlatformBridge + // (handled by the caller) + } + + outputPaths.add(outputPath); + _log.i('CUE split: track ${track.number} -> $outputFileName'); + } + + if (outputPaths.isEmpty) { + _log.e('CUE split: no tracks were successfully extracted'); + return null; + } + + _log.i('CUE split complete: ${outputPaths.length}/${tracks.length} tracks'); + return outputPaths; + } + + static String _formatSecondsForFFmpeg(double seconds) { + if (seconds < 0) return '0'; + final hours = seconds ~/ 3600; + final mins = (seconds % 3600) ~/ 60; + final secs = seconds - (hours * 3600) - (mins * 60); + return '${hours.toString().padLeft(2, '0')}:${mins.toInt().toString().padLeft(2, '0')}:${secs.toStringAsFixed(3).padLeft(6, '0')}'; + } +} + +/// Track info for CUE splitting, passed from the CUE parser +class CueSplitTrackInfo { + final int number; + final String title; + final String artist; + final String isrc; + final String composer; + final double startSec; + final double endSec; + + CueSplitTrackInfo({ + required this.number, + required this.title, + required this.artist, + this.isrc = '', + this.composer = '', + required this.startSec, + required this.endSec, + }); + + factory CueSplitTrackInfo.fromJson(Map json) { + return CueSplitTrackInfo( + number: json['number'] as int? ?? 0, + title: json['title'] as String? ?? '', + artist: json['artist'] as String? ?? '', + isrc: json['isrc'] as String? ?? '', + composer: json['composer'] as String? ?? '', + startSec: (json['start_sec'] as num?)?.toDouble() ?? 0.0, + endSec: (json['end_sec'] as num?)?.toDouble() ?? -1.0, + ); + } } class FFmpegResult { diff --git a/lib/services/platform_bridge.dart b/lib/services/platform_bridge.dart index 86ae6d0d..bf20d621 100644 --- a/lib/services/platform_bridge.dart +++ b/lib/services/platform_bridge.dart @@ -1298,4 +1298,20 @@ class PlatformBridge { await _channel.invokeMethod('clearStoreCache'); } + /// Parse a .cue file and return split information (track listing, timing, metadata). + /// Returns a map with: cue_path, audio_path, album, artist, genre, date, tracks[] + /// Each track has: number, title, artist, isrc, composer, start_sec, end_sec + /// [audioDir] optionally overrides the directory for audio file resolution (used for SAF). + static Future> parseCueSheet( + String cuePath, { + String audioDir = '', + }) async { + _log.i('parseCueSheet: $cuePath (audioDir: $audioDir)'); + final result = await _channel.invokeMethod('parseCueSheet', { + 'cue_path': cuePath, + 'audio_dir': audioDir, + }); + return jsonDecode(result as String) as Map; + } + } diff --git a/lib/utils/file_access.dart b/lib/utils/file_access.dart index 1dd4f1fc..026fa57e 100644 --- a/lib/utils/file_access.dart +++ b/lib/utils/file_access.dart @@ -212,16 +212,39 @@ bool isContentUri(String? path) { return path != null && path.startsWith('content://'); } +/// Pattern matching CUE virtual path suffixes like #track01, #track12, etc. +final _cueTrackSuffix = RegExp(r'#track\d+$'); + +const cueVirtualTrackRequiresSplitMessage = + 'This CUE track is virtual. Use Split into Tracks first.'; + +/// Whether the path is a CUE virtual path (contains #trackNN suffix). +bool isCueVirtualPath(String? path) { + return path != null && _cueTrackSuffix.hasMatch(path); +} + +/// Strip the #trackNN suffix from a CUE virtual path to get the base .cue path. +/// Returns the path unchanged if it's not a CUE virtual path. +String stripCueTrackSuffix(String path) { + return path.replaceFirst(_cueTrackSuffix, ''); +} + Future fileExists(String? path) async { if (path == null || path.isEmpty) return false; - if (isContentUri(path)) { - return PlatformBridge.safExists(path); + // For CUE virtual paths, check if the base .cue file exists + final realPath = isCueVirtualPath(path) ? stripCueTrackSuffix(path) : path; + if (isContentUri(realPath)) { + return PlatformBridge.safExists(realPath); } - return File(path).exists(); + return File(realPath).exists(); } Future deleteFile(String? path) async { if (path == null || path.isEmpty) return; + // CUE virtual paths should NOT be deleted through this function — + // deleting album.cue would remove ALL tracks. Callers should handle + // CUE deletion specially (e.g. only delete when all tracks are removed). + if (isCueVirtualPath(path)) return; if (isContentUri(path)) { await PlatformBridge.safDelete(path); return; @@ -233,8 +256,10 @@ Future deleteFile(String? path) async { Future fileStat(String? path) async { if (path == null || path.isEmpty) return null; - if (isContentUri(path)) { - final stat = await PlatformBridge.safStat(path); + // For CUE virtual paths, stat the base .cue file + final realPath = isCueVirtualPath(path) ? stripCueTrackSuffix(path) : path; + if (isContentUri(realPath)) { + final stat = await PlatformBridge.safStat(realPath); final exists = stat['exists'] as bool? ?? true; if (!exists) return null; return FileAccessStat( @@ -245,18 +270,23 @@ Future fileStat(String? path) async { ); } - final stat = await FileStat.stat(path); + final stat = await FileStat.stat(realPath); if (stat.type == FileSystemEntityType.notFound) return null; return FileAccessStat(size: stat.size, modified: stat.modified); } Future openFile(String path) async { - if (isContentUri(path)) { - await PlatformBridge.openContentUri(path, mimeType: ''); + if (isCueVirtualPath(path)) { + throw Exception(cueVirtualTrackRequiresSplitMessage); + } + + final realPath = path; + if (isContentUri(realPath)) { + await PlatformBridge.openContentUri(realPath, mimeType: ''); return; } - final mimeType = audioMimeTypeForPath(path); - final result = await OpenFilex.open(path, type: mimeType); + final mimeType = audioMimeTypeForPath(realPath); + final result = await OpenFilex.open(realPath, type: mimeType); if (result.type != ResultType.done) { throw Exception(result.message); } From c2736a61fb7596bb8aafc353470c14f2d95403ca Mon Sep 17 00:00:00 2001 From: zarzet Date: Wed, 11 Mar 2026 00:58:07 +0700 Subject: [PATCH 098/104] refactor: remove built-in Spotify API provider, use Deezer as sole default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove all Spotify credential management (client ID/secret, secure storage) - Remove Spotify platform channel handlers from MainActivity - Remove exported Go functions: GetSpotifyMetadata, SearchSpotify, SearchSpotifyAll, GetSpotifyRelatedArtists, SetSpotifyAPICredentials - Simplify GetSpotifyMetadataWithDeezerFallback to SpotFetch-only path - Remove Spotify built-in fallback in ReEnrichFile search pipeline - Always return false from HasSpotifyCredentials; getCredentials always errors - Default metadataProviderPriority is now ['deezer'] only - Sanitize provider priority list to strip 'spotify' entries on load/save - Add migration v5 to clear saved Spotify credentials from existing installs - Remove Spotify source chip and credentials UI from options settings page - Remove metadataSource param from search() — always uses Deezer - spotify-web extension remains supported via the extension provider system --- .../kotlin/com/zarz/spotiflac/MainActivity.kt | 46 --- go_backend/exports.go | 188 +---------- go_backend/extension_providers.go | 24 +- go_backend/spotify.go | 32 +- lib/models/settings.dart | 4 +- lib/models/settings.g.dart | 2 +- lib/providers/extension_provider.dart | 43 ++- lib/providers/settings_provider.dart | 108 ++---- lib/providers/track_provider.dart | 45 +-- lib/screens/home_tab.dart | 34 +- lib/screens/search_screen.dart | 10 +- .../metadata_provider_priority_page.dart | 7 - .../settings/options_settings_page.dart | 310 +----------------- lib/services/platform_bridge.dart | 61 ---- 14 files changed, 127 insertions(+), 787 deletions(-) diff --git a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt index 8fe38bfc..5d5650da 100644 --- a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt +++ b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt @@ -1877,38 +1877,6 @@ class MainActivity: FlutterFragmentActivity() { } result.success(response) } - "getSpotifyMetadata" -> { - val url = call.argument("url") ?: "" - val response = withContext(Dispatchers.IO) { - Gobackend.getSpotifyMetadata(url) - } - result.success(response) - } - "searchSpotify" -> { - val query = call.argument("query") ?: "" - val limit = call.argument("limit") ?: 10 - val response = withContext(Dispatchers.IO) { - Gobackend.searchSpotify(query, limit.toLong()) - } - result.success(response) - } - "searchSpotifyAll" -> { - val query = call.argument("query") ?: "" - val trackLimit = call.argument("track_limit") ?: 15 - val artistLimit = call.argument("artist_limit") ?: 3 - val response = withContext(Dispatchers.IO) { - Gobackend.searchSpotifyAll(query, trackLimit.toLong(), artistLimit.toLong()) - } - result.success(response) - } - "getSpotifyRelatedArtists" -> { - val artistId = call.argument("artist_id") ?: "" - val limit = call.argument("limit") ?: 12 - val response = withContext(Dispatchers.IO) { - Gobackend.getSpotifyRelatedArtists(artistId, limit.toLong()) - } - result.success(response) - } "checkAvailability" -> { val spotifyId = call.argument("spotify_id") ?: "" val isrc = call.argument("isrc") ?: "" @@ -2542,20 +2510,6 @@ class MainActivity: FlutterFragmentActivity() { "isDownloadServiceRunning" -> { result.success(DownloadService.isServiceRunning()) } - "setSpotifyCredentials" -> { - val clientId = call.argument("client_id") ?: "" - val clientSecret = call.argument("client_secret") ?: "" - withContext(Dispatchers.IO) { - Gobackend.setSpotifyAPICredentials(clientId, clientSecret) - } - result.success(null) - } - "hasSpotifyCredentials" -> { - val hasCredentials = withContext(Dispatchers.IO) { - Gobackend.checkSpotifyCredentials() - } - result.success(hasCredentials) - } "preWarmTrackCache" -> { val tracksJson = call.argument("tracks") ?: "[]" withContext(Dispatchers.IO) { diff --git a/go_backend/exports.go b/go_backend/exports.go index 5f83330a..00791180 100644 --- a/go_backend/exports.go +++ b/go_backend/exports.go @@ -32,126 +32,6 @@ func ParseSpotifyURL(url string) (string, error) { return string(jsonBytes), nil } -func SetSpotifyAPICredentials(clientID, clientSecret string) { - SetSpotifyCredentials(clientID, clientSecret) -} - -func CheckSpotifyCredentials() bool { - return HasSpotifyCredentials() -} - -func GetSpotifyMetadata(spotifyURL string) (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - client, err := NewSpotifyMetadataClient() - if err != nil { - if shouldTrySpotFetchFallback(err) { - data, apiErr := GetSpotifyDataWithAPI(ctx, spotifyURL, DefaultSpotFetchAPIBaseURL) - if apiErr == nil { - jsonBytes, marshalErr := json.Marshal(data) - if marshalErr != nil { - return "", marshalErr - } - return string(jsonBytes), nil - } - } - return "", err - } - data, err := client.GetFilteredData(ctx, spotifyURL, false, 0) - if err != nil { - if shouldTrySpotFetchFallback(err) { - fallbackData, apiErr := GetSpotifyDataWithAPI(ctx, spotifyURL, DefaultSpotFetchAPIBaseURL) - if apiErr == nil { - jsonBytes, marshalErr := json.Marshal(fallbackData) - if marshalErr != nil { - return "", marshalErr - } - return string(jsonBytes), nil - } - } - return "", err - } - - jsonBytes, err := json.Marshal(data) - if err != nil { - return "", err - } - - return string(jsonBytes), nil -} - -func SearchSpotify(query string, limit int) (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() - - client, err := NewSpotifyMetadataClient() - if err != nil { - return "", err - } - results, err := client.SearchTracks(ctx, query, limit) - if err != nil { - return "", err - } - - jsonBytes, err := json.Marshal(results) - if err != nil { - return "", err - } - - return string(jsonBytes), nil -} - -func SearchSpotifyAll(query string, trackLimit, artistLimit int) (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() - - client, err := NewSpotifyMetadataClient() - if err != nil { - return "", err - } - results, err := client.SearchAll(ctx, query, trackLimit, artistLimit) - if err != nil { - return "", err - } - - jsonBytes, err := json.Marshal(results) - if err != nil { - return "", err - } - - return string(jsonBytes), nil -} - -func GetSpotifyRelatedArtists(artistID string, limit int) (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - - client, err := NewSpotifyMetadataClient() - if err != nil { - return "", err - } - - normalizedArtistID := strings.TrimSpace(strings.TrimPrefix(artistID, "spotify:")) - if normalizedArtistID == "" { - return "", fmt.Errorf("invalid Spotify artist ID") - } - - artists, err := client.GetRelatedArtists(ctx, normalizedArtistID, limit) - if err != nil { - return "", err - } - - resp := map[string]interface{}{ - "artists": artists, - } - jsonBytes, err := json.Marshal(resp) - if err != nil { - return "", err - } - return string(jsonBytes), nil -} - func CheckAvailability(spotifyID, isrc string) (string, error) { client := NewSongLinkClient() availability, err := client.CheckTrackAvailability(spotifyID, isrc) @@ -1439,28 +1319,6 @@ func GetSpotifyMetadataWithDeezerFallback(spotifyURL string) (string, error) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - var spotifyErr error - - client, err := NewSpotifyMetadataClient() - if err != nil { - LogWarn("Spotify", "Credentials not configured, falling back to Deezer") - spotifyErr = err - } else { - data, err := client.GetFilteredData(ctx, spotifyURL, false, 0) - if err == nil { - jsonBytes, err := json.Marshal(data) - if err != nil { - return "", err - } - return string(jsonBytes), nil - } - - spotifyErr = err - if !shouldTrySpotFetchFallback(err) { - return "", err - } - } - spotFetchData, apiErr := GetSpotifyDataWithAPI(ctx, spotifyURL, DefaultSpotFetchAPIBaseURL) if apiErr == nil { GoLog("[Fallback] Spotify metadata fetched via SpotFetch API\n") @@ -1474,9 +1332,6 @@ func GetSpotifyMetadataWithDeezerFallback(spotifyURL string) (string, error) { parsed, parseErr := parseSpotifyURI(spotifyURL) if parseErr != nil { - if spotifyErr != nil { - return "", fmt.Errorf("spotify failed (%v), SpotFetch fallback failed (%v), and URL parsing failed: %w", spotifyErr, apiErr, parseErr) - } return "", fmt.Errorf("SpotFetch fallback failed (%v) and URL parsing failed: %w", apiErr, parseErr) } @@ -1487,15 +1342,9 @@ func GetSpotifyMetadataWithDeezerFallback(spotifyURL string) (string, error) { } if parsed.Type == "artist" { - if spotifyErr != nil { - return "", fmt.Errorf("spotify metadata unavailable (%v) and SpotFetch fallback failed (%v). Artist pages require Spotify/SpotFetch API", spotifyErr, apiErr) - } - return "", fmt.Errorf("SpotFetch fallback failed (%v). Artist pages require Spotify/SpotFetch API", apiErr) + return "", fmt.Errorf("SpotFetch fallback failed (%v). Artist pages now require SpotFetch or a metadata extension such as spotify-web", apiErr) } - if spotifyErr != nil { - return "", fmt.Errorf("spotify metadata unavailable (%v), SpotFetch fallback failed (%v), and Deezer conversion is unavailable for playlists", spotifyErr, apiErr) - } return "", fmt.Errorf("SpotFetch fallback failed (%v), and Deezer conversion is unavailable for playlists", apiErr) } @@ -1826,8 +1675,8 @@ func ReEnrichFile(requestJSON string) (string, error) { GoLog("[ReEnrich] Starting re-enrichment for: %s\n", req.FilePath) - // When search_online is true, search for metadata from internet - // Priority: 1) Deezer (reliable, no credentials) 2) Extension providers (spotify-web etc) 3) Spotify built-in API (last resort, deprecated) + // When search_online is true, search for metadata from internet. + // Priority: 1) Deezer (reliable, no credentials) 2) Extension providers (spotify-web etc) if req.SearchOnline && req.TrackName != "" && req.ArtistName != "" { GoLog("[ReEnrich] Searching online metadata for: %s - %s\n", req.TrackName, req.ArtistName) searchQuery := req.TrackName + " " + req.ArtistName @@ -1901,37 +1750,6 @@ func ReEnrichFile(requestJSON string) (string, error) { } } - // 3) Try Spotify built-in API as last resort (will be deprecated) - if !found { - GoLog("[ReEnrich] Trying Spotify API (fallback)...\n") - spotifyClient, spotifyErr := NewSpotifyMetadataClient() - if spotifyErr == nil { - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - results, err := spotifyClient.SearchTracks(ctx, searchQuery, 5) - cancel() - if err == nil && len(results.Tracks) > 0 { - track := results.Tracks[0] - GoLog("[ReEnrich] Spotify match: %s - %s (album: %s)\n", track.Name, track.Artists, track.AlbumName) - req.SpotifyID = track.SpotifyID - req.AlbumName = track.AlbumName - req.AlbumArtist = track.AlbumArtist - req.TrackNumber = track.TrackNumber - req.DiscNumber = track.DiscNumber - req.ReleaseDate = track.ReleaseDate - req.ISRC = track.ISRC - if track.Images != "" { - req.CoverURL = track.Images - } - req.DurationMs = int64(track.DurationMS) - found = true - } else if err != nil { - GoLog("[ReEnrich] Spotify search failed: %v\n", err) - } - } else { - GoLog("[ReEnrich] Spotify client unavailable: %v\n", spotifyErr) - } - } - // Try to get extended metadata (genre, label) from Deezer if not already set if found && req.ISRC != "" && (req.Genre == "" || req.Label == "") { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) diff --git a/go_backend/extension_providers.go b/go_backend/extension_providers.go index a1a419b5..89cf02a2 100644 --- a/go_backend/extension_providers.go +++ b/go_backend/extension_providers.go @@ -644,8 +644,26 @@ func GetProviderPriority() []string { func SetMetadataProviderPriority(providerIDs []string) { metadataProviderPriorityMu.Lock() defer metadataProviderPriorityMu.Unlock() - metadataProviderPriority = providerIDs - GoLog("[Extension] Metadata provider priority set: %v\n", providerIDs) + + sanitized := make([]string, 0, len(providerIDs)+1) + seen := map[string]struct{}{} + for _, providerID := range providerIDs { + providerID = strings.TrimSpace(providerID) + if providerID == "" || providerID == "spotify" { + continue + } + if _, exists := seen[providerID]; exists { + continue + } + seen[providerID] = struct{}{} + sanitized = append(sanitized, providerID) + } + if _, exists := seen["deezer"]; !exists { + sanitized = append([]string{"deezer"}, sanitized...) + } + + metadataProviderPriority = sanitized + GoLog("[Extension] Metadata provider priority set: %v\n", sanitized) } func GetMetadataProviderPriority() []string { @@ -653,7 +671,7 @@ func GetMetadataProviderPriority() []string { defer metadataProviderPriorityMu.RUnlock() if len(metadataProviderPriority) == 0 { - return []string{"deezer", "spotify"} + return []string{"deezer"} } result := make([]string, len(metadataProviderPriority)) diff --git a/go_backend/spotify.go b/go_backend/spotify.go index 9728a72f..570c4e24 100644 --- a/go_backend/spotify.go +++ b/go_backend/spotify.go @@ -9,7 +9,6 @@ import ( "math/rand" "net/http" "net/url" - "os" "strings" "sync" "time" @@ -64,45 +63,20 @@ var ( credentialsMu sync.RWMutex ) -var ErrNoSpotifyCredentials = errors.New("Spotify credentials not configured. Please set your own Client ID and Secret in Settings, or use Deezer as metadata source (free, no credentials required)") +var ErrNoSpotifyCredentials = errors.New("built-in Spotify API metadata provider has been removed; use Deezer or the spotify-web extension instead") func SetSpotifyCredentials(clientID, clientSecret string) { credentialsMu.Lock() defer credentialsMu.Unlock() - customClientID = clientID - customClientSecret = clientSecret + customClientID = "" + customClientSecret = "" } func HasSpotifyCredentials() bool { - credentialsMu.RLock() - defer credentialsMu.RUnlock() - - if customClientID != "" && customClientSecret != "" { - return true - } - - if os.Getenv("SPOTIFY_CLIENT_ID") != "" && os.Getenv("SPOTIFY_CLIENT_SECRET") != "" { - return true - } - return false } func getCredentials() (string, string, error) { - credentialsMu.RLock() - defer credentialsMu.RUnlock() - - if customClientID != "" && customClientSecret != "" { - return customClientID, customClientSecret, nil - } - - clientID := os.Getenv("SPOTIFY_CLIENT_ID") - clientSecret := os.Getenv("SPOTIFY_CLIENT_SECRET") - - if clientID != "" && clientSecret != "" { - return clientID, clientSecret, nil - } - return "", "", ErrNoSpotifyCredentials } diff --git a/lib/models/settings.dart b/lib/models/settings.dart index 8501a4e3..67e3e9ac 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -104,7 +104,7 @@ class AppSettings { this.askQualityBeforeDownload = true, this.spotifyClientId = '', this.spotifyClientSecret = '', - this.useCustomSpotifyCredentials = true, + this.useCustomSpotifyCredentials = false, this.metadataSource = 'deezer', this.enableLogging = false, this.useExtensionProviders = true, @@ -149,7 +149,7 @@ class AppSettings { String? downloadDirectory, String? storageMode, String? downloadTreeUri, - bool? autoFallback, + bool? autoFallback, bool? embedMetadata, bool? embedLyrics, bool? maxQualityCover, diff --git a/lib/models/settings.g.dart b/lib/models/settings.g.dart index cc47ca8e..c3eecb50 100644 --- a/lib/models/settings.g.dart +++ b/lib/models/settings.g.dart @@ -33,7 +33,7 @@ AppSettings _$AppSettingsFromJson(Map json) => AppSettings( spotifyClientId: json['spotifyClientId'] as String? ?? '', spotifyClientSecret: json['spotifyClientSecret'] as String? ?? '', useCustomSpotifyCredentials: - json['useCustomSpotifyCredentials'] as bool? ?? true, + json['useCustomSpotifyCredentials'] as bool? ?? false, metadataSource: json['metadataSource'] as String? ?? 'deezer', enableLogging: json['enableLogging'] as bool? ?? false, useExtensionProviders: json['useExtensionProviders'] as bool? ?? true, diff --git a/lib/providers/extension_provider.dart b/lib/providers/extension_provider.dart index f53aca0c..58d14ee5 100644 --- a/lib/providers/extension_provider.dart +++ b/lib/providers/extension_provider.dart @@ -823,11 +823,19 @@ class ExtensionNotifier extends Notifier { List priority; if (savedJson != null) { final saved = jsonDecode(savedJson) as List; - priority = saved.map((e) => e as String).toList(); + priority = _sanitizeMetadataProviderPriority( + saved.map((e) => e as String).toList(), + ); _log.d('Loaded metadata provider priority from prefs: $priority'); + await prefs.setString( + _metadataProviderPriorityKey, + jsonEncode(priority), + ); await PlatformBridge.setMetadataProviderPriority(priority); } else { - priority = await PlatformBridge.getMetadataProviderPriority(); + priority = _sanitizeMetadataProviderPriority( + await PlatformBridge.getMetadataProviderPriority(), + ); _log.d('Using default metadata provider priority: $priority'); } @@ -840,11 +848,15 @@ class ExtensionNotifier extends Notifier { Future setMetadataProviderPriority(List priority) async { try { final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_metadataProviderPriorityKey, jsonEncode(priority)); + final sanitized = _sanitizeMetadataProviderPriority(priority); + await prefs.setString( + _metadataProviderPriorityKey, + jsonEncode(sanitized), + ); - await PlatformBridge.setMetadataProviderPriority(priority); - state = state.copyWith(metadataProviderPriority: priority); - _log.d('Saved metadata provider priority: $priority'); + await PlatformBridge.setMetadataProviderPriority(sanitized); + state = state.copyWith(metadataProviderPriority: sanitized); + _log.d('Saved metadata provider priority: $sanitized'); } catch (e) { _log.e('Failed to set metadata provider priority: $e'); state = state.copyWith(error: e.toString()); @@ -880,7 +892,7 @@ class ExtensionNotifier extends Notifier { } List getAllMetadataProviders() { - final providers = ['deezer', 'spotify']; + final providers = ['deezer']; for (final ext in state.extensions) { if (ext.enabled && ext.hasMetadataProvider) { providers.add(ext.id); @@ -889,6 +901,23 @@ class ExtensionNotifier extends Notifier { return providers; } + List _sanitizeMetadataProviderPriority(List input) { + final allowed = getAllMetadataProviders().toSet(); + final result = []; + + for (final provider in input) { + if (allowed.contains(provider) && !result.contains(provider)) { + result.add(provider); + } + } + + if (!result.contains('deezer')) { + result.insert(0, 'deezer'); + } + + return result; + } + List get searchProviders { return state.extensions .where((ext) => ext.enabled && ext.hasCustomSearch) diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index a7858319..ed44fea4 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -9,7 +9,7 @@ import 'package:spotiflac_android/utils/logger.dart'; const _settingsKey = 'app_settings'; const _migrationVersionKey = 'settings_migration_version'; -const _currentMigrationVersion = 4; +const _currentMigrationVersion = 5; const _spotifyClientSecretKey = 'spotify_client_secret'; final _log = AppLogger('SettingsProvider'); @@ -41,9 +41,7 @@ class SettingsNotifier extends Notifier { await _normalizeSongLinkRegionIfNeeded(); } - await _loadSpotifyClientSecret(prefs); - - _applySpotifyCredentials(); + await _retireBuiltInSpotifyProvider(); LogBuffer.loggingEnabled = state.enableLogging; @@ -105,6 +103,17 @@ class SettingsNotifier extends Notifier { } state = state.copyWith(lyricsProviders: updatedProviders); } + if (state.metadataSource != 'deezer' || + state.spotifyClientId.isNotEmpty || + state.spotifyClientSecret.isNotEmpty || + state.useCustomSpotifyCredentials) { + state = state.copyWith( + metadataSource: 'deezer', + spotifyClientId: '', + spotifyClientSecret: '', + useCustomSpotifyCredentials: false, + ); + } state = state.copyWith(lastSeenVersion: AppInfo.version); await prefs.setInt(_migrationVersionKey, _currentMigrationVersion); await _saveSettings(); @@ -193,49 +202,28 @@ class SettingsNotifier extends Notifier { await _saveSettings(); } - Future _loadSpotifyClientSecret(SharedPreferences prefs) async { + Future _retireBuiltInSpotifyProvider() async { final storedSecret = await _secureStorage.read( key: _spotifyClientSecretKey, ); - final prefsSecret = state.spotifyClientSecret; - - if ((storedSecret == null || storedSecret.isEmpty) && - prefsSecret.isNotEmpty) { - await _secureStorage.write( - key: _spotifyClientSecretKey, - value: prefsSecret, - ); - } - - final effectiveSecret = (storedSecret != null && storedSecret.isNotEmpty) - ? storedSecret - : (prefsSecret.isNotEmpty ? prefsSecret : ''); - - if (effectiveSecret != state.spotifyClientSecret) { - state = state.copyWith(spotifyClientSecret: effectiveSecret); - } - - if (prefsSecret.isNotEmpty) { - await _saveSettings(); - } - } - - Future _storeSpotifyClientSecret(String secret) async { - if (secret.isEmpty) { + if (storedSecret != null && storedSecret.isNotEmpty) { await _secureStorage.delete(key: _spotifyClientSecretKey); - } else { - await _secureStorage.write(key: _spotifyClientSecretKey, value: secret); } - } - Future _applySpotifyCredentials() async { - if (state.spotifyClientId.isNotEmpty && - state.spotifyClientSecret.isNotEmpty) { - await PlatformBridge.setSpotifyCredentials( - state.spotifyClientId, - state.spotifyClientSecret, - ); + if (state.metadataSource == 'deezer' && + state.spotifyClientId.isEmpty && + state.spotifyClientSecret.isEmpty && + !state.useCustomSpotifyCredentials) { + return; } + + state = state.copyWith( + metadataSource: 'deezer', + spotifyClientId: '', + spotifyClientSecret: '', + useCustomSpotifyCredentials: false, + ); + await _saveSettings(); } void setDefaultService(String service) { @@ -396,45 +384,9 @@ class SettingsNotifier extends Notifier { _saveSettings(); } - void setSpotifyClientId(String clientId) { - state = state.copyWith(spotifyClientId: clientId); - _saveSettings(); - } - - Future setSpotifyClientSecret(String clientSecret) async { - state = state.copyWith(spotifyClientSecret: clientSecret); - await _storeSpotifyClientSecret(clientSecret); - _saveSettings(); - } - - Future setSpotifyCredentials( - String clientId, - String clientSecret, - ) async { - state = state.copyWith( - spotifyClientId: clientId, - spotifyClientSecret: clientSecret, - ); - await _storeSpotifyClientSecret(clientSecret); - _saveSettings(); - _applySpotifyCredentials(); - } - - Future clearSpotifyCredentials() async { - state = state.copyWith(spotifyClientId: '', spotifyClientSecret: ''); - await _storeSpotifyClientSecret(''); - _saveSettings(); - _applySpotifyCredentials(); - } - - void setUseCustomSpotifyCredentials(bool enabled) { - state = state.copyWith(useCustomSpotifyCredentials: enabled); - _saveSettings(); - _applySpotifyCredentials(); - } - void setMetadataSource(String source) { - state = state.copyWith(metadataSource: source); + final normalized = source == 'deezer' ? 'deezer' : 'deezer'; + state = state.copyWith(metadataSource: normalized); _saveSettings(); } diff --git a/lib/providers/track_provider.dart b/lib/providers/track_provider.dart index 8ba3f33b..5adae7d0 100644 --- a/lib/providers/track_provider.dart +++ b/lib/providers/track_provider.dart @@ -458,7 +458,8 @@ class TrackNotifier extends Notifier { } // If URL doesn't match any known service, it's unrecognized - final isSpotifyUrl = url.contains('open.spotify.com') || + final isSpotifyUrl = + url.contains('open.spotify.com') || url.contains('spotify.link') || url.startsWith('spotify:'); if (!isSpotifyUrl) { @@ -546,11 +547,7 @@ class TrackNotifier extends Notifier { } } - Future search( - String query, { - String? metadataSource, - String? filterOverride, - }) async { + Future search(String query, {String? filterOverride}) async { final requestId = ++_currentRequestId; // Preserve selected filter during loading @@ -576,7 +573,7 @@ class TrackNotifier extends Notifier { searchProvider != null && searchProvider.isNotEmpty; - final source = metadataSource ?? 'deezer'; + const source = 'deezer'; _log.i( 'Search started: source=$source, query="$query", useExtensions=$useExtensions, filter=$currentFilter', @@ -602,32 +599,20 @@ class TrackNotifier extends Notifier { } } } catch (e) { - _log.w('Extension search failed, falling back to built-in: $e'); + _log.w('Extension search failed, falling back to Deezer: $e'); } } - if (source == 'deezer') { - _log.d('Calling Deezer search API...'); - results = await PlatformBridge.searchDeezerAll( - query, - trackLimit: 20, - artistLimit: 2, - filter: currentFilter, - ); - _log.i( - 'Deezer returned ${(results['tracks'] as List?)?.length ?? 0} tracks, ${(results['artists'] as List?)?.length ?? 0} artists, ${(results['albums'] as List?)?.length ?? 0} albums', - ); - } else { - _log.d('Calling Spotify search API...'); - results = await PlatformBridge.searchSpotifyAll( - query, - trackLimit: 20, - artistLimit: 2, - ); - _log.i( - 'Spotify returned ${(results['tracks'] as List?)?.length ?? 0} tracks, ${(results['artists'] as List?)?.length ?? 0} artists', - ); - } + _log.d('Calling Deezer search API...'); + results = await PlatformBridge.searchDeezerAll( + query, + trackLimit: 20, + artistLimit: 2, + filter: currentFilter, + ); + _log.i( + 'Deezer returned ${(results['tracks'] as List?)?.length ?? 0} tracks, ${(results['artists'] as List?)?.length ?? 0} artists, ${(results['albums'] as List?)?.length ?? 0} albums', + ); if (!_isRequestValid(requestId)) { _log.w('Search request cancelled (requestId=$requestId)'); diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index 65e8fcec..5663caa3 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -549,11 +549,7 @@ class _HomeTabState extends ConsumerState } await ref .read(trackProvider.notifier) - .search( - query, - metadataSource: settings.metadataSource, - filterOverride: selectedFilter, - ); + .search(query, filterOverride: selectedFilter); } ref.read(settingsProvider.notifier).setHasSearchedBefore(); } @@ -587,26 +583,24 @@ class _HomeTabState extends ConsumerState if (trackState.error != null && mounted) { final l10n = context.l10n; final errorMsg = trackState.error!; - final isRateLimit = errorMsg.contains('429') || + final isRateLimit = + errorMsg.contains('429') || errorMsg.toLowerCase().contains('rate limit') || errorMsg.toLowerCase().contains('too many requests'); final displayMessage = errorMsg == 'url_not_recognized' ? l10n.errorUrlNotRecognizedMessage : isRateLimit - ? l10n.errorRateLimitedMessage - : l10n.errorUrlFetchFailed; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(displayMessage)), - ); + ? l10n.errorRateLimitedMessage + : l10n.errorUrlFetchFailed; + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(displayMessage))); ref.read(trackProvider.notifier).clear(); } else { _navigateToDetailIfNeeded(); } } else { - final settings = ref.read(settingsProvider); - await ref - .read(trackProvider.notifier) - .search(url, metadataSource: settings.metadataSource); + await ref.read(trackProvider.notifier).search(url); } ref.read(settingsProvider.notifier).setHasSearchedBefore(); } @@ -2315,10 +2309,7 @@ class _HomeTabState extends ConsumerState const SizedBox(height: 4), Text( l10n.errorUrlNotRecognizedMessage, - style: TextStyle( - color: colorScheme.error, - fontSize: 12, - ), + style: TextStyle(color: colorScheme.error, fontSize: 12), ), ], ), @@ -2971,9 +2962,6 @@ class _SearchProviderDropdown extends ConsumerWidget { final currentProvider = ref.watch( settingsProvider.select((s) => s.searchProvider), ); - final metadataSource = ref.watch( - settingsProvider.select((s) => s.metadataSource), - ); final extensions = ref.watch(extensionProvider.select((s) => s.extensions)); final colorScheme = Theme.of(context).colorScheme; @@ -3051,7 +3039,7 @@ class _SearchProviderDropdown extends ConsumerWidget { const SizedBox(width: 12), Expanded( child: Text( - metadataSource == 'spotify' ? 'Spotify' : 'Deezer', + 'Deezer', style: TextStyle( fontWeight: currentProvider == null || currentProvider.isEmpty diff --git a/lib/screens/search_screen.dart b/lib/screens/search_screen.dart index 4523fbee..09b2f656 100644 --- a/lib/screens/search_screen.dart +++ b/lib/screens/search_screen.dart @@ -27,10 +27,7 @@ class _SearchScreenState extends ConsumerState { _searchController = TextEditingController(text: widget.query); if (widget.query.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { - final settings = ref.read(settingsProvider); - ref - .read(trackProvider.notifier) - .search(widget.query, metadataSource: settings.metadataSource); + ref.read(trackProvider.notifier).search(widget.query); }); } } @@ -44,10 +41,7 @@ class _SearchScreenState extends ConsumerState { void _search() { final query = _searchController.text.trim(); if (query.isNotEmpty) { - final settings = ref.read(settingsProvider); - ref - .read(trackProvider.notifier) - .search(query, metadataSource: settings.metadataSource); + ref.read(trackProvider.notifier).search(query); } } diff --git a/lib/screens/settings/metadata_provider_priority_page.dart b/lib/screens/settings/metadata_provider_priority_page.dart index 7631e27d..3c614784 100644 --- a/lib/screens/settings/metadata_provider_priority_page.dart +++ b/lib/screens/settings/metadata_provider_priority_page.dart @@ -228,13 +228,6 @@ class _MetadataProviderItem extends StatelessWidget { description: context.l10n.metadataNoRateLimits, isBuiltIn: true, ); - case 'spotify': - return _MetadataProviderInfo( - name: 'Spotify', - icon: Icons.music_note, - description: context.l10n.metadataMayRateLimit, - isBuiltIn: true, - ); default: return _MetadataProviderInfo( name: provider, diff --git a/lib/screens/settings/options_settings_page.dart b/lib/screens/settings/options_settings_page.dart index d57558fa..4a7618d0 100644 --- a/lib/screens/settings/options_settings_page.dart +++ b/lib/screens/settings/options_settings_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; -import 'package:spotiflac_android/models/settings.dart'; import 'package:spotiflac_android/providers/download_queue_provider.dart'; import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/providers/extension_provider.dart'; @@ -73,68 +72,10 @@ class OptionsSettingsPage extends ConsumerWidget { child: SettingsGroup( children: [ _MetadataSourceSelector( - currentSource: settings.metadataSource, onChanged: (v) => ref .read(settingsProvider.notifier) .setMetadataSource(v), ), - if (settings.metadataSource == 'spotify') ...[ - if (settings.spotifyClientId.isEmpty) - Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), - child: Card( - color: Theme.of(context).colorScheme.errorContainer, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Icon( - Icons.warning_amber_rounded, - color: Theme.of( - context, - ).colorScheme.onErrorContainer, - ), - const SizedBox(width: 12), - Expanded( - child: Text( - context.l10n.optionsSpotifyWarning, - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.onErrorContainer, - fontSize: 12, - ), - ), - ), - ], - ), - ), - ), - ), - SettingsItem( - icon: Icons.key, - title: context.l10n.optionsSpotifyCredentials, - subtitle: settings.spotifyClientId.isNotEmpty - ? context.l10n.optionsSpotifyCredentialsConfigured( - settings.spotifyClientId.length > 8 - ? settings.spotifyClientId.substring(0, 8) - : settings.spotifyClientId, - ) - : context.l10n.optionsSpotifyCredentialsRequired, - onTap: () => - _showSpotifyCredentialsDialog(context, ref, settings), - trailing: Icon( - settings.spotifyClientId.isNotEmpty - ? Icons.check_circle - : Icons.error_outline, - color: settings.spotifyClientId.isNotEmpty - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.error, - size: 20, - ), - showDivider: false, - ), - ], ], ), ), @@ -372,214 +313,6 @@ class OptionsSettingsPage extends ConsumerWidget { } } } - - void _showSpotifyCredentialsDialog( - BuildContext context, - WidgetRef ref, - AppSettings settings, - ) { - final clientIdController = TextEditingController( - text: settings.spotifyClientId, - ); - final clientSecretController = TextEditingController( - text: settings.spotifyClientSecret, - ); - final colorScheme = Theme.of(context).colorScheme; - - showModalBottomSheet( - context: context, - useRootNavigator: true, - isScrollControlled: true, - backgroundColor: colorScheme.surface, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(28)), - ), - builder: (context) => Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: SingleChildScrollView( - child: SafeArea( - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Center( - child: Container( - width: 32, - height: 4, - margin: const EdgeInsets.only(bottom: 24), - decoration: BoxDecoration( - color: colorScheme.outlineVariant, - borderRadius: BorderRadius.circular(2), - ), - ), - ), - Text( - context.l10n.credentialsTitle, - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - context.l10n.credentialsDescription, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 32), - - TextField( - controller: clientIdController, - decoration: InputDecoration( - labelText: context.l10n.credentialsClientId, - hintText: context.l10n.credentialsClientIdHint, - filled: true, - fillColor: colorScheme.surfaceContainerHighest.withValues( - alpha: 0.3, - ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: BorderSide( - color: colorScheme.outlineVariant, - ), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: BorderSide( - color: colorScheme.outlineVariant, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: BorderSide( - color: colorScheme.primary, - width: 2, - ), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 16, - ), - prefixIcon: const Icon(Icons.person_outline), - ), - ), - const SizedBox(height: 16), - - TextField( - controller: clientSecretController, - obscureText: true, - decoration: InputDecoration( - labelText: context.l10n.credentialsClientSecret, - hintText: context.l10n.credentialsClientSecretHint, - filled: true, - fillColor: colorScheme.surfaceContainerHighest.withValues( - alpha: 0.3, - ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: BorderSide( - color: colorScheme.outlineVariant, - ), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: BorderSide( - color: colorScheme.outlineVariant, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: BorderSide( - color: colorScheme.primary, - width: 2, - ), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 16, - ), - prefixIcon: const Icon(Icons.lock_outline), - ), - ), - - const SizedBox(height: 32), - - FilledButton( - onPressed: () { - final clientId = clientIdController.text.trim(); - final clientSecret = clientSecretController.text.trim(); - - if (clientId.isNotEmpty && clientSecret.isNotEmpty) { - ref - .read(settingsProvider.notifier) - .setSpotifyCredentials(clientId, clientSecret); - Navigator.pop(context); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.snackbarCredentialsSaved, - ), - ), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.snackbarFillAllFields), - ), - ); - } - }, - style: FilledButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - ), - child: Text( - context.l10n.actionSaveCredentials, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ), - - if (settings.spotifyClientId.isNotEmpty) ...[ - const SizedBox(height: 12), - TextButton( - onPressed: () { - ref - .read(settingsProvider.notifier) - .clearSpotifyCredentials(); - Navigator.pop(context); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.snackbarCredentialsCleared, - ), - ), - ); - }, - style: TextButton.styleFrom( - foregroundColor: colorScheme.error, - padding: const EdgeInsets.symmetric(vertical: 16), - ), - child: Text(context.l10n.actionRemoveCredentials), - ), - ], - - const SizedBox(height: 16), - ], - ), - ), - ), - ), - ), - ); - } } class _ConcurrentDownloadsItem extends StatelessWidget { @@ -875,12 +608,8 @@ class _ChannelChip extends StatelessWidget { } class _MetadataSourceSelector extends ConsumerWidget { - final String currentSource; final ValueChanged onChanged; - const _MetadataSourceSelector({ - required this.currentSource, - required this.onChanged, - }); + const _MetadataSourceSelector({required this.onChanged}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -930,7 +659,7 @@ class _MetadataSourceSelector extends ConsumerWidget { _SourceChip( icon: Icons.graphic_eq, label: 'Deezer', - isSelected: currentSource == 'deezer' && !hasExtensionSearch, + isSelected: !hasExtensionSearch, onTap: () { if (hasExtensionSearch) { ref.read(settingsProvider.notifier).setSearchProvider(null); @@ -938,18 +667,6 @@ class _MetadataSourceSelector extends ConsumerWidget { onChanged('deezer'); }, ), - const SizedBox(width: 12), - _SourceChip( - icon: Icons.music_note, - label: 'Spotify', - isSelected: currentSource == 'spotify' && !hasExtensionSearch, - onTap: () { - if (hasExtensionSearch) { - ref.read(settingsProvider.notifier).setSearchProvider(null); - } - onChanged('spotify'); - }, - ), ], ), if (hasExtensionSearch) ...[ @@ -964,7 +681,7 @@ class _MetadataSourceSelector extends ConsumerWidget { const SizedBox(width: 8), Expanded( child: Text( - context.l10n.optionsSwitchBack, + 'Tap Deezer to switch back from extension', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: colorScheme.onSurfaceVariant, ), @@ -973,27 +690,6 @@ class _MetadataSourceSelector extends ConsumerWidget { ], ), ], - if (currentSource == 'spotify' && !hasExtensionSearch) ...[ - const SizedBox(height: 12), - Row( - children: [ - Icon( - Icons.warning_amber_rounded, - size: 16, - color: colorScheme.error, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - context.l10n.optionsSpotifyDeprecationWarning, - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith(color: colorScheme.error), - ), - ), - ], - ), - ], ], ), ); diff --git a/lib/services/platform_bridge.dart b/lib/services/platform_bridge.dart index bf20d621..c41207b9 100644 --- a/lib/services/platform_bridge.dart +++ b/lib/services/platform_bridge.dart @@ -20,51 +20,6 @@ class PlatformBridge { return jsonDecode(result as String) as Map; } - static Future> getSpotifyMetadata(String url) async { - _log.d('getSpotifyMetadata: $url'); - final result = await _channel.invokeMethod('getSpotifyMetadata', { - 'url': url, - }); - return jsonDecode(result as String) as Map; - } - - static Future> searchSpotify( - String query, { - int limit = 10, - }) async { - _log.d('searchSpotify: "$query" (limit: $limit)'); - final result = await _channel.invokeMethod('searchSpotify', { - 'query': query, - 'limit': limit, - }); - return jsonDecode(result as String) as Map; - } - - static Future> searchSpotifyAll( - String query, { - int trackLimit = 15, - int artistLimit = 3, - }) async { - _log.d('searchSpotifyAll: "$query"'); - final result = await _channel.invokeMethod('searchSpotifyAll', { - 'query': query, - 'track_limit': trackLimit, - 'artist_limit': artistLimit, - }); - return jsonDecode(result as String) as Map; - } - - static Future> getSpotifyRelatedArtists( - String artistId, { - int limit = 12, - }) async { - final result = await _channel.invokeMethod('getSpotifyRelatedArtists', { - 'artist_id': artistId, - 'limit': limit, - }); - return jsonDecode(result as String) as Map; - } - static Future> checkAvailability( String spotifyId, String isrc, @@ -517,21 +472,6 @@ class PlatformBridge { return result as bool; } - static Future setSpotifyCredentials( - String clientId, - String clientSecret, - ) async { - await _channel.invokeMethod('setSpotifyCredentials', { - 'client_id': clientId, - 'client_secret': clientSecret, - }); - } - - static Future hasSpotifyCredentials() async { - final result = await _channel.invokeMethod('hasSpotifyCredentials'); - return result as bool; - } - static Future preWarmTrackCache( List> tracks, ) async { @@ -1313,5 +1253,4 @@ class PlatformBridge { }); return jsonDecode(result as String) as Map; } - } From f2ae1398dbafea8c078684e2f50c370748fea9bc Mon Sep 17 00:00:00 2001 From: zarzet Date: Wed, 11 Mar 2026 01:06:26 +0700 Subject: [PATCH 099/104] feat: add 'By Playlist' folder organization option (#111) --- lib/l10n/app_localizations.dart | 12 ++++++++++++ lib/l10n/app_localizations_de.dart | 7 +++++++ lib/l10n/app_localizations_en.dart | 7 +++++++ lib/l10n/app_localizations_es.dart | 7 +++++++ lib/l10n/app_localizations_fr.dart | 7 +++++++ lib/l10n/app_localizations_hi.dart | 7 +++++++ lib/l10n/app_localizations_id.dart | 7 +++++++ lib/l10n/app_localizations_ja.dart | 7 +++++++ lib/l10n/app_localizations_ko.dart | 7 +++++++ lib/l10n/app_localizations_nl.dart | 7 +++++++ lib/l10n/app_localizations_pt.dart | 7 +++++++ lib/l10n/app_localizations_ru.dart | 7 +++++++ lib/l10n/app_localizations_tr.dart | 7 +++++++ lib/l10n/app_localizations_zh.dart | 7 +++++++ lib/l10n/arb/app_en.arb | 8 ++++++++ lib/models/download_item.dart | 4 ++++ lib/models/download_item.g.dart | 2 ++ lib/providers/download_queue_provider.dart | 19 ++++++++++++++++++- lib/screens/playlist_screen.dart | 8 ++++---- .../settings/download_settings_page.dart | 14 ++++++++++++++ 20 files changed, 153 insertions(+), 5 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index d722f825..488563dc 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1456,6 +1456,18 @@ abstract class AppLocalizations { /// **'No organization'** String get folderOrganizationNone; + /// Folder option - playlist folders + /// + /// In en, this message translates to: + /// **'By Playlist'** + String get folderOrganizationByPlaylist; + + /// Subtitle for playlist folder option + /// + /// In en, this message translates to: + /// **'Separate folder for each playlist'** + String get folderOrganizationByPlaylistSubtitle; + /// Folder option - artist folders /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 1bb33ef5..4cb5b75d 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -785,6 +785,13 @@ class AppLocalizationsDe extends AppLocalizations { @override String get folderOrganizationNone => 'Keine Organisation'; + @override + String get folderOrganizationByPlaylist => 'By Playlist'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Separate folder for each playlist'; + @override String get folderOrganizationByArtist => 'Nach Künstler'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index bd9d4589..646f9a58 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -772,6 +772,13 @@ class AppLocalizationsEn extends AppLocalizations { @override String get folderOrganizationNone => 'No organization'; + @override + String get folderOrganizationByPlaylist => 'By Playlist'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Separate folder for each playlist'; + @override String get folderOrganizationByArtist => 'By Artist'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index ed9236ad..e44fa9d8 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -772,6 +772,13 @@ class AppLocalizationsEs extends AppLocalizations { @override String get folderOrganizationNone => 'No organization'; + @override + String get folderOrganizationByPlaylist => 'By Playlist'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Separate folder for each playlist'; + @override String get folderOrganizationByArtist => 'By Artist'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index d6a3b948..ae24b7e9 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -774,6 +774,13 @@ class AppLocalizationsFr extends AppLocalizations { @override String get folderOrganizationNone => 'No organization'; + @override + String get folderOrganizationByPlaylist => 'By Playlist'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Separate folder for each playlist'; + @override String get folderOrganizationByArtist => 'By Artist'; diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index a461bd5b..08101eac 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -772,6 +772,13 @@ class AppLocalizationsHi extends AppLocalizations { @override String get folderOrganizationNone => 'No organization'; + @override + String get folderOrganizationByPlaylist => 'By Playlist'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Separate folder for each playlist'; + @override String get folderOrganizationByArtist => 'By Artist'; diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index b668481b..cab860dd 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -775,6 +775,13 @@ class AppLocalizationsId extends AppLocalizations { @override String get folderOrganizationNone => 'Tidak ada'; + @override + String get folderOrganizationByPlaylist => 'By Playlist'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Separate folder for each playlist'; + @override String get folderOrganizationByArtist => 'Berdasarkan Artis'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 71d10e1a..6ce38247 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -767,6 +767,13 @@ class AppLocalizationsJa extends AppLocalizations { @override String get folderOrganizationNone => '構成がありません'; + @override + String get folderOrganizationByPlaylist => 'By Playlist'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Separate folder for each playlist'; + @override String get folderOrganizationByArtist => 'アーティスト別'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 43bfbce3..56287787 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -754,6 +754,13 @@ class AppLocalizationsKo extends AppLocalizations { @override String get folderOrganizationNone => '정리하지 않음'; + @override + String get folderOrganizationByPlaylist => 'By Playlist'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Separate folder for each playlist'; + @override String get folderOrganizationByArtist => 'By Artist'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 2eeb00cc..5f4975d9 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -772,6 +772,13 @@ class AppLocalizationsNl extends AppLocalizations { @override String get folderOrganizationNone => 'No organization'; + @override + String get folderOrganizationByPlaylist => 'By Playlist'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Separate folder for each playlist'; + @override String get folderOrganizationByArtist => 'By Artist'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 9436f6eb..62b648ae 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -772,6 +772,13 @@ class AppLocalizationsPt extends AppLocalizations { @override String get folderOrganizationNone => 'No organization'; + @override + String get folderOrganizationByPlaylist => 'By Playlist'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Separate folder for each playlist'; + @override String get folderOrganizationByArtist => 'By Artist'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 943b75f9..66c75d45 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -786,6 +786,13 @@ class AppLocalizationsRu extends AppLocalizations { @override String get folderOrganizationNone => 'Без организации'; + @override + String get folderOrganizationByPlaylist => 'By Playlist'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Separate folder for each playlist'; + @override String get folderOrganizationByArtist => 'По исполнителю'; diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index 223fecd3..3a88e179 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -777,6 +777,13 @@ class AppLocalizationsTr extends AppLocalizations { @override String get folderOrganizationNone => 'Organizasyon yok'; + @override + String get folderOrganizationByPlaylist => 'By Playlist'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Separate folder for each playlist'; + @override String get folderOrganizationByArtist => 'Sanatçıya Göre'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 57f92319..3306bb1c 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -772,6 +772,13 @@ class AppLocalizationsZh extends AppLocalizations { @override String get folderOrganizationNone => 'No organization'; + @override + String get folderOrganizationByPlaylist => 'By Playlist'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Separate folder for each playlist'; + @override String get folderOrganizationByArtist => 'By Artist'; diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index bb3036c9..03313d7b 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1015,6 +1015,14 @@ "@folderOrganizationNone": { "description": "Folder option - flat structure" }, + "folderOrganizationByPlaylist": "By Playlist", + "@folderOrganizationByPlaylist": { + "description": "Folder option - playlist folders" + }, + "folderOrganizationByPlaylistSubtitle": "Separate folder for each playlist", + "@folderOrganizationByPlaylistSubtitle": { + "description": "Subtitle for playlist folder option" + }, "folderOrganizationByArtist": "By Artist", "@folderOrganizationByArtist": { "description": "Folder option - artist folders" diff --git a/lib/models/download_item.dart b/lib/models/download_item.dart index 4d8a650e..db55029b 100644 --- a/lib/models/download_item.dart +++ b/lib/models/download_item.dart @@ -34,6 +34,7 @@ class DownloadItem { final DownloadErrorType? errorType; final DateTime createdAt; final String? qualityOverride; // Override quality for this specific download + final String? playlistName; // Playlist context for folder organization const DownloadItem({ required this.id, @@ -48,6 +49,7 @@ class DownloadItem { this.errorType, required this.createdAt, this.qualityOverride, + this.playlistName, }); DownloadItem copyWith({ @@ -63,6 +65,7 @@ class DownloadItem { DownloadErrorType? errorType, DateTime? createdAt, String? qualityOverride, + String? playlistName, }) { return DownloadItem( id: id ?? this.id, @@ -77,6 +80,7 @@ class DownloadItem { errorType: errorType ?? this.errorType, createdAt: createdAt ?? this.createdAt, qualityOverride: qualityOverride ?? this.qualityOverride, + playlistName: playlistName ?? this.playlistName, ); } diff --git a/lib/models/download_item.g.dart b/lib/models/download_item.g.dart index 098290ce..961e6d6d 100644 --- a/lib/models/download_item.g.dart +++ b/lib/models/download_item.g.dart @@ -21,6 +21,7 @@ DownloadItem _$DownloadItemFromJson(Map json) => DownloadItem( errorType: $enumDecodeNullable(_$DownloadErrorTypeEnumMap, json['errorType']), createdAt: DateTime.parse(json['createdAt'] as String), qualityOverride: json['qualityOverride'] as String?, + playlistName: json['playlistName'] as String?, ); Map _$DownloadItemToJson(DownloadItem instance) => @@ -37,6 +38,7 @@ Map _$DownloadItemToJson(DownloadItem instance) => 'errorType': _$DownloadErrorTypeEnumMap[instance.errorType], 'createdAt': instance.createdAt.toIso8601String(), 'qualityOverride': instance.qualityOverride, + 'playlistName': instance.playlistName, }; const _$DownloadStatusEnumMap = { diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index 1e69a2fa..afb2e81c 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -1573,6 +1573,7 @@ class DownloadQueueNotifier extends Notifier { bool useAlbumArtistForFolders = true, bool usePrimaryArtistOnly = false, bool filterContributingArtistsInAlbumArtist = false, + String? playlistName, }) async { String baseDir = state.outputDir; final normalizedAlbumArtist = normalizeOptionalString(track.albumArtist); @@ -1647,6 +1648,11 @@ class DownloadQueueNotifier extends Notifier { String subPath = ''; switch (folderOrganization) { + case 'playlist': + if (playlistName != null && playlistName.isNotEmpty) { + subPath = _sanitizeFolderName(playlistName); + } + break; case 'artist': final artistName = _sanitizeFolderName(folderArtist); subPath = artistName; @@ -1725,6 +1731,7 @@ class DownloadQueueNotifier extends Notifier { bool useAlbumArtistForFolders = true, bool usePrimaryArtistOnly = false, bool filterContributingArtistsInAlbumArtist = false, + String? playlistName, }) async { final normalizedAlbumArtist = normalizeOptionalString(track.albumArtist); var folderArtist = useAlbumArtistForFolders @@ -1776,6 +1783,11 @@ class DownloadQueueNotifier extends Notifier { } switch (folderOrganization) { + case 'playlist': + if (playlistName != null && playlistName.isNotEmpty) { + return _sanitizeFolderName(playlistName); + } + return ''; case 'artist': return _sanitizeFolderName(folderArtist); case 'album': @@ -1900,7 +1912,7 @@ class DownloadQueueNotifier extends Notifier { ); } - String addToQueue(Track track, String service, {String? qualityOverride}) { + String addToQueue(Track track, String service, {String? qualityOverride, String? playlistName}) { final settings = ref.read(settingsProvider); updateSettings(settings); @@ -1912,6 +1924,7 @@ class DownloadQueueNotifier extends Notifier { service: service, createdAt: DateTime.now(), qualityOverride: qualityOverride, + playlistName: playlistName, ); state = state.copyWith(items: [...state.items, item]); @@ -1928,6 +1941,7 @@ class DownloadQueueNotifier extends Notifier { List tracks, String service, { String? qualityOverride, + String? playlistName, }) { final settings = ref.read(settingsProvider); updateSettings(settings); @@ -1942,6 +1956,7 @@ class DownloadQueueNotifier extends Notifier { service: service, createdAt: DateTime.now(), qualityOverride: qualityOverride, + playlistName: playlistName, ); }).toList(); @@ -3317,6 +3332,7 @@ class DownloadQueueNotifier extends Notifier { usePrimaryArtistOnly: settings.usePrimaryArtistOnly, filterContributingArtistsInAlbumArtist: settings.filterContributingArtistsInAlbumArtist, + playlistName: item.playlistName, ) : ''; String? appOutputDir; @@ -3331,6 +3347,7 @@ class DownloadQueueNotifier extends Notifier { usePrimaryArtistOnly: settings.usePrimaryArtistOnly, filterContributingArtistsInAlbumArtist: settings.filterContributingArtistsInAlbumArtist, + playlistName: item.playlistName, ); var effectiveOutputDir = initialOutputDir; var effectiveSafMode = isSafMode; diff --git a/lib/screens/playlist_screen.dart b/lib/screens/playlist_screen.dart index 7ffbd18e..37bf14cf 100644 --- a/lib/screens/playlist_screen.dart +++ b/lib/screens/playlist_screen.dart @@ -416,7 +416,7 @@ class _PlaylistScreenState extends ConsumerState { onSelect: (quality, service) { ref .read(downloadQueueProvider.notifier) - .addToQueue(track, service, qualityOverride: quality); + .addToQueue(track, service, qualityOverride: quality, playlistName: widget.playlistName); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(context.l10n.snackbarAddedToQueue(track.name)), @@ -427,7 +427,7 @@ class _PlaylistScreenState extends ConsumerState { } else { ref .read(downloadQueueProvider.notifier) - .addToQueue(track, settings.defaultService); + .addToQueue(track, settings.defaultService, playlistName: widget.playlistName); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(context.l10n.snackbarAddedToQueue(track.name))), ); @@ -586,7 +586,7 @@ class _PlaylistScreenState extends ConsumerState { onSelect: (quality, service) { ref .read(downloadQueueProvider.notifier) - .addMultipleToQueue(tracks, service, qualityOverride: quality); + .addMultipleToQueue(tracks, service, qualityOverride: quality, playlistName: widget.playlistName); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( @@ -599,7 +599,7 @@ class _PlaylistScreenState extends ConsumerState { } else { ref .read(downloadQueueProvider.notifier) - .addMultipleToQueue(tracks, settings.defaultService); + .addMultipleToQueue(tracks, settings.defaultService, playlistName: widget.playlistName); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(context.l10n.snackbarAddedTracksToQueue(tracks.length)), diff --git a/lib/screens/settings/download_settings_page.dart b/lib/screens/settings/download_settings_page.dart index 1987bb2d..7a60f72c 100644 --- a/lib/screens/settings/download_settings_page.dart +++ b/lib/screens/settings/download_settings_page.dart @@ -1405,6 +1405,8 @@ class _DownloadSettingsPageState extends ConsumerState { String _getFolderOrganizationLabel(String value) { switch (value) { + case 'playlist': + return 'By Playlist'; case 'artist': return 'By Artist'; case 'album': @@ -1982,6 +1984,18 @@ class _DownloadSettingsPageState extends ConsumerState { Navigator.pop(context); }, ), + _FolderOption( + title: context.l10n.folderOrganizationByPlaylist, + subtitle: context.l10n.folderOrganizationByPlaylistSubtitle, + example: 'SpotiFLAC/Playlist Name/Track.flac', + isSelected: current == 'playlist', + onTap: () { + ref + .read(settingsProvider.notifier) + .setFolderOrganization('playlist'); + Navigator.pop(context); + }, + ), _FolderOption( title: context.l10n.folderOrganizationByArtist, subtitle: context.l10n.folderOrganizationByArtistSubtitle, From 8d45e023b27793eee662c71a396e4e84817f3ec8 Mon Sep 17 00:00:00 2001 From: zarzet Date: Wed, 11 Mar 2026 01:10:50 +0700 Subject: [PATCH 100/104] feat: add totalTracks to Track model and refine EP/single classification (#202) --- lib/models/track.dart | 14 +++++++++++- lib/models/track.g.dart | 2 ++ lib/providers/download_queue_provider.dart | 4 ++++ lib/providers/track_provider.dart | 4 ++++ lib/screens/album_screen.dart | 2 ++ lib/screens/artist_screen.dart | 25 ++++++++++++++-------- 6 files changed, 41 insertions(+), 10 deletions(-) diff --git a/lib/models/track.dart b/lib/models/track.dart index 244a7a65..e4d850c2 100644 --- a/lib/models/track.dart +++ b/lib/models/track.dart @@ -21,6 +21,7 @@ class Track { final ServiceAvailability? availability; final String? source; final String? albumType; + final int? totalTracks; final String? itemType; const Track({ @@ -41,10 +42,21 @@ class Track { this.availability, this.source, this.albumType, + this.totalTracks, this.itemType, }); - bool get isSingle => albumType == 'single' || albumType == 'ep'; + bool get isSingle { + switch (albumType?.toLowerCase()) { + case 'single': + return true; + case 'ep': + final count = totalTracks; + return count == null || count <= 1; + default: + return false; + } + } bool get isAlbumItem => itemType == 'album'; diff --git a/lib/models/track.g.dart b/lib/models/track.g.dart index f640cfe7..5e361ab6 100644 --- a/lib/models/track.g.dart +++ b/lib/models/track.g.dart @@ -28,6 +28,7 @@ Track _$TrackFromJson(Map json) => Track( ), source: json['source'] as String?, albumType: json['albumType'] as String?, + totalTracks: (json['totalTracks'] as num?)?.toInt(), itemType: json['itemType'] as String?, ); @@ -49,6 +50,7 @@ Map _$TrackToJson(Track instance) => { 'availability': instance.availability, 'source': instance.source, 'albumType': instance.albumType, + 'totalTracks': instance.totalTracks, 'itemType': instance.itemType, }; diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index afb2e81c..18348f96 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -2362,6 +2362,7 @@ class DownloadQueueNotifier extends Notifier { deezerId: baseTrack.deezerId, availability: baseTrack.availability, albumType: baseTrack.albumType, + totalTracks: baseTrack.totalTracks, source: baseTrack.source, ); } @@ -3274,6 +3275,8 @@ class DownloadQueueNotifier extends Notifier { albumType: (data['album_type'] as String?) ?? trackToDownload.albumType, + totalTracks: + data['total_tracks'] as int? ?? trackToDownload.totalTracks, source: trackToDownload.source, ); _log.d( @@ -3488,6 +3491,7 @@ class DownloadQueueNotifier extends Notifier { deezerId: deezerTrackId, availability: trackToDownload.availability, albumType: trackToDownload.albumType, + totalTracks: trackToDownload.totalTracks, source: trackToDownload.source, ); _log.d( diff --git a/lib/providers/track_provider.dart b/lib/providers/track_provider.dart index 5adae7d0..1c3974c2 100644 --- a/lib/providers/track_provider.dart +++ b/lib/providers/track_provider.dart @@ -816,6 +816,7 @@ class TrackNotifier extends Notifier { discNumber: track.discNumber, releaseDate: track.releaseDate, albumType: track.albumType, + totalTracks: track.totalTracks, source: track.source, availability: ServiceAvailability( tidal: availability['tidal'] as bool? ?? false, @@ -897,6 +898,8 @@ class TrackNotifier extends Notifier { trackNumber: data['track_number'] as int?, discNumber: data['disc_number'] as int?, releaseDate: data['release_date'] as String?, + albumType: data['album_type'] as String?, + totalTracks: data['total_tracks'] as int?, ); } @@ -919,6 +922,7 @@ class TrackNotifier extends Notifier { trackNumber: data['track_number'] as int?, discNumber: data['disc_number'] as int?, releaseDate: data['release_date']?.toString(), + totalTracks: data['total_tracks'] as int?, source: source ?? data['source']?.toString() ?? diff --git a/lib/screens/album_screen.dart b/lib/screens/album_screen.dart index ba8d9d1d..f3f22790 100644 --- a/lib/screens/album_screen.dart +++ b/lib/screens/album_screen.dart @@ -224,6 +224,8 @@ class _AlbumScreenState extends ConsumerState { trackNumber: data['track_number'] as int?, discNumber: data['disc_number'] as int?, releaseDate: data['release_date'] as String?, + albumType: data['album_type'] as String?, + totalTracks: data['total_tracks'] as int?, ); } diff --git a/lib/screens/artist_screen.dart b/lib/screens/artist_screen.dart index 8ad6bf03..06fc5063 100644 --- a/lib/screens/artist_screen.dart +++ b/lib/screens/artist_screen.dart @@ -294,7 +294,7 @@ class _ArtistScreenState extends ConsumerState { } } - Track _parseTrack(Map data) { + Track _parseTrack(Map data, {ArtistAlbum? album}) { int durationMs = 0; final durationValue = data['duration_ms']; if (durationValue is int) { @@ -307,18 +307,22 @@ class _ArtistScreenState extends ConsumerState { id: (data['spotify_id'] ?? data['id'] ?? '').toString(), name: (data['name'] ?? '').toString(), artistName: (data['artists'] ?? data['artist'] ?? '').toString(), - albumName: (data['album_name'] ?? data['album'] ?? '').toString(), - albumArtist: data['album_artist']?.toString(), + albumName: (data['album_name'] ?? data['album'] ?? album?.name ?? '') + .toString(), + albumArtist: data['album_artist']?.toString() ?? widget.artistName, artistId: (data['artist_id'] ?? data['artistId'])?.toString() ?? widget.artistId, - albumId: data['album_id']?.toString(), - coverUrl: (data['cover_url'] ?? data['images'])?.toString(), + albumId: data['album_id']?.toString() ?? album?.id, + coverUrl: (data['cover_url'] ?? data['images'] ?? album?.coverUrl) + ?.toString(), isrc: data['isrc']?.toString(), duration: (durationMs / 1000).round(), trackNumber: data['track_number'] as int?, discNumber: data['disc_number'] as int?, releaseDate: data['release_date']?.toString(), + albumType: data['album_type']?.toString() ?? album?.albumType, + totalTracks: data['total_tracks'] as int? ?? album?.totalTracks, source: data['provider_id']?.toString(), ); } @@ -669,7 +673,9 @@ class _ArtistScreenState extends ConsumerState { List albums, ) { final albumsOnly = albums.where((a) => a.albumType == 'album').toList(); - final singles = albums.where((a) => a.albumType == 'single').toList(); + final singles = albums + .where((a) => a.albumType == 'single' || a.albumType == 'ep') + .toList(); final totalTracks = albums.fold(0, (sum, a) => sum + a.totalTracks); final albumTracks = albumsOnly.fold( @@ -940,7 +946,7 @@ class _ArtistScreenState extends ConsumerState { if (result != null && result['tracks'] != null) { final tracksList = result['tracks'] as List; return tracksList - .map((t) => _parseTrack(t as Map)) + .map((t) => _parseTrack(t as Map, album: album)) .toList(); } } else if (album.id.startsWith('deezer:')) { @@ -961,7 +967,7 @@ class _ArtistScreenState extends ConsumerState { if (result != null && result['tracks'] != null) { final tracksList = result['tracks'] as List; return tracksList - .map((t) => _parseTrack(t as Map)) + .map((t) => _parseTrack(t as Map, album: album)) .toList(); } @@ -970,7 +976,7 @@ class _ArtistScreenState extends ConsumerState { if (metadata['tracks'] != null) { final tracksList = metadata['tracks'] as List; return tracksList - .map((t) => _parseTrack(t as Map)) + .map((t) => _parseTrack(t as Map, album: album)) .toList(); } } @@ -1004,6 +1010,7 @@ class _ArtistScreenState extends ConsumerState { discNumber: data['disk_number'] as int? ?? data['disc_number'] as int?, releaseDate: album.releaseDate, albumType: album.albumType, + totalTracks: album.totalTracks, ); } From 5be5c869daa5e54454d1f22e1afc1e26212c91ab Mon Sep 17 00:00:00 2001 From: zarzet Date: Wed, 11 Mar 2026 01:23:20 +0700 Subject: [PATCH 101/104] chore: add 'a fan' as donor for this month --- lib/screens/settings/donate_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/screens/settings/donate_page.dart b/lib/screens/settings/donate_page.dart index 175b0793..f822f5d3 100644 --- a/lib/screens/settings/donate_page.dart +++ b/lib/screens/settings/donate_page.dart @@ -164,7 +164,7 @@ class _RecentDonorsCard extends StatelessWidget { @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; - const donorNames = []; + const donorNames = ['a fan']; // Match SettingsGroup color logic final cardColor = isDark From 93a7042a8416245957734c5c65fd024e7af34dc4 Mon Sep 17 00:00:00 2001 From: zarzet Date: Wed, 11 Mar 2026 15:03:14 +0700 Subject: [PATCH 102/104] chore: add design/ directory to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index e2e2649f..3c57112b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ Thumbs.db # Kiro specs (development only) .kiro/ +# Design assets (banners, mockups) +design/ + # Reference folder (development only) referensi/ From d82d255bae7c566c366a7b252c178077cc11b92d Mon Sep 17 00:00:00 2001 From: zarzet Date: Wed, 11 Mar 2026 15:56:42 +0700 Subject: [PATCH 103/104] feat: add dark/light theme-aware README banner and reorganize badges --- README.md | 26 +++++++++++--------------- assets/images/banner-readme-dark.png | Bin 0 -> 14813 bytes assets/images/banner-readme-light.png | Bin 0 -> 14871 bytes image.png | Bin 72121 -> 0 bytes 4 files changed, 11 insertions(+), 15 deletions(-) create mode 100644 assets/images/banner-readme-dark.png create mode 100644 assets/images/banner-readme-light.png delete mode 100644 image.png diff --git a/README.md b/README.md index ae6db959..f23bf87c 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,10 @@ -[![GitHub All Releases](https://img.shields.io/github/downloads/zarzet/SpotiFLAC-Mobile/total?style=for-the-badge&refresh=1)](https://github.com/zarzet/SpotiFLAC-Mobile/releases) -[![VirusTotal](https://img.shields.io/badge/VirusTotal-Safe-brightgreen?style=for-the-badge&logo=virustotal)](https://www.virustotal.com/gui/file/0a2bd2a033551983fc9fcd83f82fd912c83914fd1094cd8d1c7c6a68eb23233f) -[![Crowdin](https://img.shields.io/badge/HELP%20TRANSLATE%20ON-CROWDIN-%2321252b?style=for-the-badge&logo=crowdin)](https://crowdin.com/project/spotiflac-mobile) -
- - -Download music in true lossless FLAC from Tidal, Qobuz & Deezer — no account required. - -![Android](https://img.shields.io/badge/Android-7.0%2B-3DDC84?style=for-the-badge&logo=android&logoColor=white) -![iOS](https://img.shields.io/badge/iOS-14.0%2B-000000?style=for-the-badge&logo=apple&logoColor=white) + + + + SpotiFLAC Mobile +
@@ -24,6 +19,12 @@ Download music in true lossless FLAC from Tidal, Qobuz & Deezer — no account r

+[![GitHub All Releases](https://img.shields.io/github/downloads/zarzet/SpotiFLAC-Mobile/total?style=for-the-badge&refresh=1)](https://github.com/zarzet/SpotiFLAC-Mobile/releases) +[![VirusTotal](https://img.shields.io/badge/VirusTotal-Safe-brightgreen?style=for-the-badge&logo=virustotal)](https://www.virustotal.com/gui/file/0a2bd2a033551983fc9fcd83f82fd912c83914fd1094cd8d1c7c6a68eb23233f) +[![Crowdin](https://img.shields.io/badge/HELP%20TRANSLATE%20ON-CROWDIN-%2321252b?style=for-the-badge&logo=crowdin)](https://crowdin.com/project/spotiflac-mobile) +[![Telegram Channel](https://img.shields.io/badge/CHANNEL-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/spotiflac) +[![Telegram Community](https://img.shields.io/badge/COMMUNITY-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/spotiflac_chat) + ## Extensions Extensions allow the community to add new music sources and features without waiting for app updates. When a streaming service API changes or a new source becomes available, extensions can be updated independently. @@ -43,11 +44,6 @@ Want to create your own extension? Check out the [Extension Development Guide](h ### [SpotiFLAC (Desktop)](https://github.com/afkarxyz/SpotiFLAC) Download music in true lossless FLAC from Tidal, Qobuz & Amazon Music for Windows, macOS & Linux -## Telegram - -[![Telegram Channel](https://img.shields.io/badge/CHANNEL-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/spotiflac) -[![Telegram Community](https://img.shields.io/badge/COMMUNITY-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/spotiflac_chat) - ## FAQ **Q: Why is my download failing with "Song not found"?** diff --git a/assets/images/banner-readme-dark.png b/assets/images/banner-readme-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6dfbdc83cbb483a416ed94fd89e1f65ec4cc7cf5 GIT binary patch literal 14813 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 literal 0 HcmV?d00001 diff --git a/assets/images/banner-readme-light.png b/assets/images/banner-readme-light.png new file mode 100644 index 0000000000000000000000000000000000000000..de5365f9b2fdf969fb498e86b5a4aced60079378 GIT binary patch literal 14871 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 Date: Wed, 11 Mar 2026 16:00:51 +0700 Subject: [PATCH 104/104] fix: make README banner more compact for better mobile visibility --- README.md | 9 ++++++--- assets/images/banner-readme-dark.png | Bin 14813 -> 34206 bytes assets/images/banner-readme-light.png | Bin 14871 -> 34628 bytes 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f23bf87c..c99cb71b 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,11 @@ - SpotiFLAC Mobile + SpotiFLAC Mobile
-### [Download](https://github.com/zarzet/SpotiFLAC-Mobile/releases) - ## Screenshots

@@ -19,12 +17,17 @@

+
+ [![GitHub All Releases](https://img.shields.io/github/downloads/zarzet/SpotiFLAC-Mobile/total?style=for-the-badge&refresh=1)](https://github.com/zarzet/SpotiFLAC-Mobile/releases) [![VirusTotal](https://img.shields.io/badge/VirusTotal-Safe-brightgreen?style=for-the-badge&logo=virustotal)](https://www.virustotal.com/gui/file/0a2bd2a033551983fc9fcd83f82fd912c83914fd1094cd8d1c7c6a68eb23233f) [![Crowdin](https://img.shields.io/badge/HELP%20TRANSLATE%20ON-CROWDIN-%2321252b?style=for-the-badge&logo=crowdin)](https://crowdin.com/project/spotiflac-mobile) + [![Telegram Channel](https://img.shields.io/badge/CHANNEL-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/spotiflac) [![Telegram Community](https://img.shields.io/badge/COMMUNITY-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/spotiflac_chat) +
+ ## Extensions Extensions allow the community to add new music sources and features without waiting for app updates. When a streaming service API changes or a new source becomes available, extensions can be updated independently. diff --git a/assets/images/banner-readme-dark.png b/assets/images/banner-readme-dark.png index 6dfbdc83cbb483a416ed94fd89e1f65ec4cc7cf5..13c40d0b990e40f9abca1e34c7ecedf96f84d3be 100644 GIT binary patch literal 34206 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 HSx5d4dZNg} literal 14813 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 diff --git a/assets/images/banner-readme-light.png b/assets/images/banner-readme-light.png index de5365f9b2fdf969fb498e86b5a4aced60079378..2aaaba69c36182f7fc062b5169ee7402908e9225 100644 GIT binary patch literal 34628 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%0Dj^Ut)OUkPn% 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 z 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 Ku|mP()&B>W7`poa literal 14871 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 hrtDn)$JuD8t|{9o&g!*^Qof