From 4b219ad18e8901b606ee55447e48171ba32de2d5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Apr 2026 14:21:29 +0000 Subject: [PATCH 01/12] chore: update AltStore source to v4.3.1 --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 847fb6cd..8076425a 100644 --- a/apps.json +++ b/apps.json @@ -12,7 +12,7 @@ "downloadURL": "https://github.com/zarzet/SpotiFLAC-Mobile/releases/download/v4.3.1/SpotiFLAC-v4.3.1-ios-unsigned.ipa", "localizedDescription": "Mobile version of SpotiFLAC written in Flutter. Download Tracks in true FLAC from Tidal, Qobuz, & Amazon Music.", "iconURL": "https://raw.githubusercontent.com/zarzet/SpotiFLAC-Mobile/main/assets/images/logo.png", - "size": 34773598 + "size": 34773644 } ] } From 702b91792983293c76fb7bb1928caabbb3573a88 Mon Sep 17 00:00:00 2001 From: zarzet Date: Sat, 18 Apr 2026 22:35:57 +0700 Subject: [PATCH 02/12] chore: import l10n updates from l10n_dev into main --- lib/l10n/app_localizations_de.dart | 275 ++-- lib/l10n/app_localizations_es.dart | 1087 ++++++++++++++- lib/l10n/app_localizations_fr.dart | 68 +- lib/l10n/app_localizations_hi.dart | 17 +- lib/l10n/app_localizations_id.dart | 57 +- lib/l10n/app_localizations_ja.dart | 17 +- lib/l10n/app_localizations_ko.dart | 17 +- lib/l10n/app_localizations_nl.dart | 17 +- lib/l10n/app_localizations_pt.dart | 1033 +++++++++++++- lib/l10n/app_localizations_ru.dart | 40 +- lib/l10n/app_localizations_tr.dart | 1475 ++++++++++---------- lib/l10n/app_localizations_zh.dart | 2064 +++++++++++++++++++++++++++- lib/l10n/arb/app_de.arb | 1352 +++++++++++++++++- lib/l10n/arb/app_es_ES.arb | 1392 ++++++++++++++++++- lib/l10n/arb/app_fr.arb | 1378 ++++++++++++++++++- lib/l10n/arb/app_hi.arb | 1330 +++++++++++++++++- lib/l10n/arb/app_id.arb | 1297 ++++++++++++++++- lib/l10n/arb/app_ja.arb | 1330 +++++++++++++++++- lib/l10n/arb/app_ko.arb | 1330 +++++++++++++++++- lib/l10n/arb/app_nl.arb | 1330 +++++++++++++++++- lib/l10n/arb/app_pt_PT.arb | 1342 +++++++++++++++++- lib/l10n/arb/app_ru.arb | 1352 +++++++++++++++++- lib/l10n/arb/app_tr.arb | 1829 +++++++++++++++--------- lib/l10n/arb/app_zh_CN.arb | 1332 +++++++++++++++++- lib/l10n/arb/app_zh_TW.arb | 1332 +++++++++++++++++- 25 files changed, 22117 insertions(+), 1976 deletions(-) diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 88f2981c..71a29c0a 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -21,13 +21,13 @@ class AppLocalizationsDe extends AppLocalizations { String get navSettings => 'Einstellungen'; @override - String get navStore => 'Store'; + String get navStore => 'Repo'; @override String get homeTitle => 'Startseite'; @override - String get homeSubtitle => 'Spotify-Link einfügen oder nach Namen suchen'; + String get homeSubtitle => 'Unterstützte URL einfügen oder nach Namen suchen'; @override String get homeSupports => @@ -184,21 +184,21 @@ class AppLocalizationsDe extends AppLocalizations { 'Disabled: no loudness normalization tags'; @override - String get optionsArtistTagMode => 'Artist Tag Mode'; + String get optionsArtistTagMode => 'Künstler Tag-Modus'; @override String get optionsArtistTagModeDescription => - 'Choose how multiple artists are written into embedded tags.'; + 'Wähle aus, wie mehrere Künstler in eingebetteten Tags geschrieben sind.'; @override - String get optionsArtistTagModeJoined => 'Single joined value'; + String get optionsArtistTagModeJoined => 'Einzelne beigefügte Werte'; @override String get optionsArtistTagModeJoinedSubtitle => - 'Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.'; + 'Einen Künstler wert wie \"Artist A, Artist B\" für maximale Spieler-Kompatibilität schreiben.'; @override - String get optionsArtistTagModeSplitVorbis => 'Split tags for FLAC/Opus'; + String get optionsArtistTagModeSplitVorbis => 'Tags für FLAC/Opus aufteilen'; @override String get optionsArtistTagModeSplitVorbisSubtitle => @@ -220,11 +220,11 @@ class AppLocalizationsDe extends AppLocalizations { 'Parallele Downloads können Ratenlimitierung auslösen'; @override - String get optionsExtensionStore => 'Erweiterungs-Store'; + String get optionsExtensionStore => 'Erweiterungs-Repo'; @override String get optionsExtensionStoreSubtitle => - 'Store-Tab in Navigation anzeigen'; + 'Repo-Tab in der Navigation anzeigen'; @override String get optionsCheckUpdates => 'Nach Updates suchen'; @@ -303,7 +303,7 @@ class AppLocalizationsDe extends AppLocalizations { String get extensionsUninstall => 'Deinstallieren'; @override - String get storeTitle => 'Erweiterungs-Store'; + String get storeTitle => 'Erweiterungs-Repo'; @override String get storeSearch => 'Erweiterungen suchen...'; @@ -586,7 +586,7 @@ class AppLocalizationsDe extends AppLocalizations { String get dialogImport => 'Importieren'; @override - String get dialogDownload => 'Download'; + String get dialogDownload => 'Herunterladen'; @override String get dialogDiscard => 'Verwerfen'; @@ -819,37 +819,37 @@ class AppLocalizationsDe extends AppLocalizations { String get searchAlbums => 'Alben'; @override - String get searchPlaylists => 'Playlisten'; + String get searchPlaylists => 'Playlists'; @override - String get searchSortTitle => 'Sort Results'; + String get searchSortTitle => 'Ergebnisse sortieren'; @override - String get searchSortDefault => 'Default'; + String get searchSortDefault => 'Standard'; @override - String get searchSortTitleAZ => 'Title (A-Z)'; + String get searchSortTitleAZ => 'Titel (A-Z)'; @override - String get searchSortTitleZA => 'Title (Z-A)'; + String get searchSortTitleZA => 'Titel (Z-A)'; @override - String get searchSortArtistAZ => 'Artist (A-Z)'; + String get searchSortArtistAZ => 'Künstler (A-Z)'; @override - String get searchSortArtistZA => 'Artist (Z-A)'; + String get searchSortArtistZA => 'Künstler (Z-A)'; @override - String get searchSortDurationShort => 'Duration (Shortest)'; + String get searchSortDurationShort => 'Dauer (kürzeste)'; @override - String get searchSortDurationLong => 'Duration (Longest)'; + String get searchSortDurationLong => 'Dauer (längste)'; @override - String get searchSortDateOldest => 'Release Date (Oldest)'; + String get searchSortDateOldest => 'Veröffentlichungsdatum (älteste)'; @override - String get searchSortDateNewest => 'Release Date (Newest)'; + String get searchSortDateNewest => 'Veröffentlichungsdatum (Neueste)'; @override String get tooltipPlay => 'Abspielen'; @@ -1315,36 +1315,36 @@ class AppLocalizationsDe extends AppLocalizations { String get storeClearFilters => 'Filter entfernen'; @override - String get storeAddRepoTitle => 'Add Extension Repository'; + String get storeAddRepoTitle => 'Erweiterungs-Repository hinzufügen'; @override String get storeAddRepoDescription => - 'Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.'; + 'Gib eine GitHub Repository-URL ein, die eine Registry.json Datei enthält, um Erweiterungen zu durchsuchen und zu installieren.'; @override - String get storeRepoUrlLabel => 'Repository URL'; + String get storeRepoUrlLabel => 'Repository-URL'; @override String get storeRepoUrlHint => 'https://github.com/user/repo'; @override String get storeRepoUrlHelper => - 'e.g. https://github.com/user/extensions-repo'; + 'z.B. https://github.com/user/extensions-repo'; @override - String get storeAddRepoButton => 'Add Repository'; + String get storeAddRepoButton => 'Repository hinzufügen'; @override - String get storeChangeRepoTooltip => 'Change repository'; + String get storeChangeRepoTooltip => 'Repository ändern'; @override - String get storeRepoDialogTitle => 'Extension Repository'; + String get storeRepoDialogTitle => 'Erweiterungs-Repository'; @override - String get storeRepoDialogCurrent => 'Current repository:'; + String get storeRepoDialogCurrent => 'Aktuelles Repository:'; @override - String get storeNewRepoUrlLabel => 'New Repository URL'; + String get storeNewRepoUrlLabel => 'Neue Repository-URL'; @override String get storeLoadError => 'Failed to load repository'; @@ -1356,7 +1356,7 @@ class AppLocalizationsDe extends AppLocalizations { String get storeEmptyNoResults => 'No extensions found'; @override - String get extensionDefaultProvider => 'Standard (Deezer/Spotify)'; + String get extensionDefaultProvider => 'Standard (Deezer)'; @override String get extensionDefaultProviderSubtitle => 'Eingebaute Suche verwenden'; @@ -1517,36 +1517,36 @@ class AppLocalizationsDe extends AppLocalizations { String get qualityHiResFlacMaxSubtitle => '24-Bit / bis 192kHz'; @override - String get downloadLossy320 => 'Lossy 320kbps'; + String get downloadLossy320 => 'Verlustbehaftet 320kbps'; @override - String get downloadLossyFormat => 'Lossy Format'; + String get downloadLossyFormat => 'Verlustbehaftetes Format'; @override String get downloadLossy320Format => 'Lossy 320kbps Format'; @override String get downloadLossy320FormatDesc => - 'Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.'; + 'Wähle das Ausgabeformat für Tidal 320kbps verlustbehaftete Downloads. Der ursprüngliche AAC Stream wird in das ausgewählte Format konvertiert.'; @override String get downloadLossyMp3 => 'MP3 320kbps'; @override - String get downloadLossyMp3Subtitle => 'Best compatibility, ~10MB per track'; + String get downloadLossyMp3Subtitle => + 'Beste Kompatibilität, ~10MB pro Titel'; @override String get downloadLossyOpus256 => 'Opus 256kbps'; @override - String get downloadLossyOpus256Subtitle => - 'Best quality Opus, ~8MB per track'; + String get downloadLossyOpus256Subtitle => 'Beste Qualität, ~8MB pro Titel'; @override String get downloadLossyOpus128 => 'Opus 128kbps'; @override - String get downloadLossyOpus128Subtitle => 'Smallest size, ~4MB per track'; + String get downloadLossyOpus128Subtitle => 'Kleinste Größe, ~4MB pro Track'; @override String get qualityNote => @@ -1856,23 +1856,23 @@ class AppLocalizationsDe extends AppLocalizations { 'Bei der Suche nach vorhandenen Titeln anzeigen'; @override - String get libraryAutoScan => 'Auto Scan'; + String get libraryAutoScan => 'Auto-Scan'; @override String get libraryAutoScanSubtitle => 'Automatically scan your library for new files'; @override - String get libraryAutoScanOff => 'Off'; + String get libraryAutoScanOff => 'Aus'; @override - String get libraryAutoScanOnOpen => 'Every app open'; + String get libraryAutoScanOnOpen => 'Bei jeder App Öffnung'; @override - String get libraryAutoScanDaily => 'Daily'; + String get libraryAutoScanDaily => 'Täglich'; @override - String get libraryAutoScanWeekly => 'Weekly'; + String get libraryAutoScanWeekly => 'Wöchentlich'; @override String get libraryActions => 'Aktionen'; @@ -1929,8 +1929,8 @@ class AppLocalizationsDe extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'files', - one: 'file', + other: '$count Datein', + one: '1 Datei', ); return '$_temp0'; } @@ -1947,7 +1947,7 @@ class AppLocalizationsDe extends AppLocalizations { String get libraryScanning => 'Scannen...'; @override - String get libraryScanFinalizing => 'Finalizing library...'; + String get libraryScanFinalizing => 'Bibliothek wird aktualisiert...'; @override String libraryScanProgress(String progress, int total) { @@ -2018,22 +2018,23 @@ class AppLocalizationsDe extends AppLocalizations { String get libraryFilterFormat => 'Format'; @override - String get libraryFilterMetadata => 'Metadata'; + String get libraryFilterMetadata => 'Metadaten'; @override - String get libraryFilterMetadataComplete => 'Complete metadata'; + String get libraryFilterMetadataComplete => 'Komplette Metadaten'; @override - String get libraryFilterMetadataMissingAny => 'Missing any metadata'; + String get libraryFilterMetadataMissingAny => 'Metadaten fehlen'; @override - String get libraryFilterMetadataMissingYear => 'Missing year'; + String get libraryFilterMetadataMissingYear => 'Jahr fehlt'; @override - String get libraryFilterMetadataMissingGenre => 'Missing genre'; + String get libraryFilterMetadataMissingGenre => 'Genre fehlt'; @override - String get libraryFilterMetadataMissingAlbumArtist => 'Missing album artist'; + String get libraryFilterMetadataMissingAlbumArtist => + 'Fehlender Album-Künstler'; @override String get libraryFilterSort => 'Sortieren'; @@ -2065,7 +2066,7 @@ class AppLocalizationsDe extends AppLocalizations { count, locale: localeName, other: 'vor $count Minuten', - one: 'vor $count Minute', + one: 'vor 1 Minute', ); return '$_temp0'; } @@ -2076,7 +2077,7 @@ class AppLocalizationsDe extends AppLocalizations { count, locale: localeName, other: 'vor $count Stunden', - one: 'vor $count Stunde', + one: 'vor 1 Stunde', ); return '$_temp0'; } @@ -2142,7 +2143,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Im Store Tab findest du nützliche Erweiterungen'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => @@ -2396,11 +2397,11 @@ class AppLocalizationsDe extends AppLocalizations { 'FFmpeg Metadaten-Einbettung fehlgeschlagen'; @override - String get queueFlacAction => 'Queue FLAC'; + String get queueFlacAction => 'Warteschlange FLAC'; @override String queueFlacConfirmMessage(int count) { - return 'Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n$count selected'; + return 'Suche Online-Matches für ausgewählte Titel und Playlists für FLAC-Downloads.\n\nVorhandene Dateien werden weder geändert noch gelöscht.\n\nNur eindeutige Treffer werden automatisch zur Warteschlange hinzugefügt.\n\n$count ausgewählt'; } @override @@ -2426,7 +2427,8 @@ class AppLocalizationsDe extends AppLocalizations { String get trackConvertFormat => 'Format konvertieren'; @override - String get trackConvertFormatSubtitle => 'In MP3 oder Opus konvertieren'; + String get trackConvertFormatSubtitle => + 'Convert to MP3, Opus, ALAC, or FLAC'; @override String get trackConvertTitle => 'Audio konvertieren'; @@ -2454,7 +2456,7 @@ class AppLocalizationsDe extends AppLocalizations { String sourceFormat, String targetFormat, ) { - return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.'; + return 'Konvertieren von $sourceFormat in $targetFormat? (kein Qualitätsverlust)\n\nDie Originaldatei wird nach der Konvertierung gelöscht.'; } @override @@ -2534,7 +2536,7 @@ class AppLocalizationsDe extends AppLocalizations { String get collectionLoved => 'Lieblingssongs'; @override - String get collectionPlaylists => 'Playlisten'; + String get collectionPlaylists => 'Playlists'; @override String get collectionPlaylist => 'Playlist'; @@ -2721,10 +2723,10 @@ 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? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.'; + return 'Konvertiere $count $_temp0 in $format? (kein Qualitätsverlust)\n\nOriginaldateien werden nach der Konvertierung gelöscht.'; } @override @@ -2751,24 +2753,24 @@ class AppLocalizationsDe extends AppLocalizations { 'Künstler-Ordner nur für Titel-Künstler'; @override - String get lyricsProvidersTitle => 'Lyrics Providers'; + String get lyricsProvidersTitle => 'Lyrics-Anbieter'; @override String get lyricsProvidersDescription => - 'Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.'; + 'Lyrics aktivieren, deaktivieren und neu ordnen. Anbieter werden von oben nach unten ausprobiert, bis Lyrics gefunden werden.'; @override String get lyricsProvidersInfoText => - 'Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.'; + 'Erweiterungsanbieter werden immer vor eingebauten ausgeführt. Mindestens ein Anbieter muss aktiviert bleiben.'; @override String lyricsProvidersEnabledSection(int count) { - return 'Enabled ($count)'; + return '($count) aktiviert'; } @override String lyricsProvidersDisabledSection(int count) { - return 'Disabled ($count)'; + return '($count) deaktiviert'; } @override @@ -2787,52 +2789,53 @@ class AppLocalizationsDe extends AppLocalizations { @override String get lyricsProviderNeteaseDesc => - 'NetEase Cloud Music (good for Asian songs)'; + 'NetEase Cloud Music (gut für asiatische Lieder)'; @override String get lyricsProviderMusixmatchDesc => - 'Largest lyrics database (multi-language)'; + 'Größte Lyrics-Datenbank (mehrsprachig)'; @override String get lyricsProviderAppleMusicDesc => - 'Word-by-word synced lyrics (via proxy)'; + 'Wort-für-Wort-synchronisierte Lyrics (via Proxy)'; @override String get lyricsProviderQqMusicDesc => - 'QQ Music (good for Chinese songs, via proxy)'; + 'QQ Music (gut für chinesische Lieder, via Proxy)'; @override - String get lyricsProviderExtensionDesc => 'Extension provider'; + String get lyricsProviderExtensionDesc => 'Erweiterungsanbieter'; @override - String get safMigrationTitle => 'Storage Update Required'; + String get safMigrationTitle => 'Speicheraktualisierung erforderlich'; @override String get safMigrationMessage1 => - 'SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.'; + 'SpotiFLAC verwendet jetzt Android Storage Access Framework (SAF) beim Herunterladen. Dies behebt Fehler bei Android 10+.'; @override String get safMigrationMessage2 => - 'Please select your download folder again to switch to the new storage system.'; + 'Bitte wähle dein Download-Ordner erneut aus, um zum neuen System zu wechseln.'; @override String get safMigrationSuccess => 'Download folder updated to SAF mode'; @override - String get settingsDonate => 'Donate'; + String get settingsDonate => 'Unterstützen'; @override - String get settingsDonateSubtitle => 'Support SpotiFLAC-Mobile development'; + String get settingsDonateSubtitle => + 'Unterstütze die SpotiFLAC-Mobile Entwickler'; @override - String get tooltipLoveAll => 'Love All'; + String get tooltipLoveAll => 'Alle lieben'; @override - String get tooltipAddToPlaylist => 'Add to Playlist'; + String get tooltipAddToPlaylist => 'Zur Wiedergabeliste hinzufügen'; @override String snackbarRemovedTracksFromLoved(int count) { - return 'Removed $count tracks from Loved'; + return '$count Titel von geliebt entfernt'; } @override @@ -2841,7 +2844,7 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get dialogDownloadAllTitle => 'Download All'; + String get dialogDownloadAllTitle => 'Alle Herunterladen'; @override String dialogDownloadAllMessage(int count) { @@ -2852,7 +2855,7 @@ class AppLocalizationsDe extends AppLocalizations { String get homeSkipAlreadyDownloaded => 'Skip already downloaded songs'; @override - String get homeGoToAlbum => 'Go to Album'; + String get homeGoToAlbum => 'Zum Album gehen'; @override String get homeAlbumInfoUnavailable => 'Album info not available'; @@ -2871,7 +2874,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String snackbarError(String error) { - return 'Error: $error'; + return 'Fehler: $error'; } @override @@ -2891,7 +2894,7 @@ class AppLocalizationsDe extends AppLocalizations { String get storageModeAppFolderSubtitle => 'Use default Music/SpotiFLAC path'; @override - String get storageModeSaf => 'SAF folder'; + String get storageModeSaf => 'SAF-Ordner'; @override String get storageModeSafSubtitle => @@ -2902,7 +2905,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Customize how your files are named.'; @override - String get downloadFilenameInsertTag => 'Tap to insert tag:'; + String get downloadFilenameInsertTag => 'Tippe, um Tag einzufügen:'; @override String get downloadSeparateSinglesEnabled => 'Albums/ and Singles/ folders'; @@ -2930,10 +2933,10 @@ class AppLocalizationsDe extends AppLocalizations { 'By Playlist already places downloads inside a playlist folder.'; @override - String get downloadSongLinkRegion => 'SongLink Region'; + String get downloadSongLinkRegion => 'SongLink-Region'; @override - String get downloadNetworkCompatibilityMode => 'Network compatibility mode'; + String get downloadNetworkCompatibilityMode => 'Netzwerkkompatibilitätsmodus'; @override String get downloadNetworkCompatibilityModeEnabled => @@ -2976,7 +2979,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Append romanized lyrics when available'; @override - String get downloadNeteaseIncludeRomanizationDisabled => 'Disabled'; + String get downloadNeteaseIncludeRomanizationDisabled => 'Deaktiviert'; @override String get downloadAppleQqMultiPerson => 'Apple/QQ Multi-Person Word-by-Word'; @@ -3008,10 +3011,10 @@ class AppLocalizationsDe extends AppLocalizations { 'Keep full Album Artist metadata value'; @override - String get downloadProvidersNoneEnabled => 'None enabled'; + String get downloadProvidersNoneEnabled => 'Keine aktiviert'; @override - String get downloadMusixmatchLanguageCode => 'Language code'; + String get downloadMusixmatchLanguageCode => 'Sprach-Code'; @override String get downloadMusixmatchLanguageHint => 'auto / en / es / ja'; @@ -3024,7 +3027,7 @@ class AppLocalizationsDe extends AppLocalizations { String get downloadMusixmatchAuto => 'Auto'; @override - String get downloadNetworkAnySubtitle => 'WiFi + Mobile Data'; + String get downloadNetworkAnySubtitle => 'WLAN + Mobile Daten'; @override String get downloadNetworkWifiOnlySubtitle => @@ -3038,23 +3041,23 @@ class AppLocalizationsDe extends AppLocalizations { String get snackbarUnsupportedAudioFormat => 'Unsupported audio format'; @override - String get cacheRefresh => 'Refresh'; + String get cacheRefresh => 'Aktualisieren'; @override String dialogDownloadPlaylistsMessage(int trackCount, int playlistCount) { String _temp0 = intl.Intl.pluralLogic( trackCount, locale: localeName, - other: 'tracks', - one: 'track', + other: 'Titel', + one: 'Titel', ); String _temp1 = intl.Intl.pluralLogic( playlistCount, locale: localeName, - other: 'playlists', - one: 'playlist', + other: 'Playlists', + one: 'Playlist', ); - return 'Download $trackCount $_temp0 from $playlistCount $_temp1?'; + return 'Lade $trackCount $_temp0 von $playlistCount $_temp1?'; } @override @@ -3094,7 +3097,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Select fields to fill automatically from online metadata'; @override - String get editMetadataAutoFillFetch => 'Fetch & Fill'; + String get editMetadataAutoFillFetch => 'Abrufen & Ausfüllen'; @override String get editMetadataAutoFillSearching => 'Searching online...'; @@ -3119,25 +3122,25 @@ class AppLocalizationsDe extends AppLocalizations { 'Select at least one field to auto-fill'; @override - String get editMetadataFieldTitle => 'Title'; + String get editMetadataFieldTitle => 'Titel'; @override - String get editMetadataFieldArtist => 'Artist'; + String get editMetadataFieldArtist => 'Künstler'; @override String get editMetadataFieldAlbum => 'Album'; @override - String get editMetadataFieldAlbumArtist => 'Album Artist'; + String get editMetadataFieldAlbumArtist => 'Album Künstler'; @override - String get editMetadataFieldDate => 'Date'; + String get editMetadataFieldDate => 'Datum'; @override - String get editMetadataFieldTrackNum => 'Track #'; + String get editMetadataFieldTrackNum => 'Titel #'; @override - String get editMetadataFieldDiscNum => 'Disc #'; + String get editMetadataFieldDiscNum => 'Disk #'; @override String get editMetadataFieldGenre => 'Genre'; @@ -3149,16 +3152,16 @@ class AppLocalizationsDe extends AppLocalizations { String get editMetadataFieldLabel => 'Label'; @override - String get editMetadataFieldCopyright => 'Copyright'; + String get editMetadataFieldCopyright => 'Urheberrecht'; @override - String get editMetadataFieldCover => 'Cover Art'; + String get editMetadataFieldCover => 'Cover-Art'; @override - String get editMetadataSelectAll => 'All'; + String get editMetadataSelectAll => 'Alle'; @override - String get editMetadataSelectEmpty => 'Empty only'; + String get editMetadataSelectEmpty => 'Nur leer'; @override String queueDownloadingCount(int count) { @@ -3166,10 +3169,10 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get queueDownloadedHeader => 'Downloaded'; + String get queueDownloadedHeader => 'Heruntergeladen'; @override - String get queueFilteringIndicator => 'Filtering...'; + String get queueFilteringIndicator => 'Filtere...'; @override String queueTrackCount(int count) { @@ -3194,7 +3197,7 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get queueEmptyAlbums => 'No album downloads'; + String get queueEmptyAlbums => 'Keine Album-Downloads'; @override String get queueEmptyAlbumsSubtitle => @@ -3220,7 +3223,7 @@ class AppLocalizationsDe extends AppLocalizations { String get selectionTapPlaylistsToSelect => 'Tap playlists to select'; @override - String get selectionSelectPlaylistsToDelete => 'Select playlists to delete'; + String get selectionSelectPlaylistsToDelete => 'Playlist zum Löschen wählen'; @override String get audioAnalysisTitle => 'Audio Quality Analysis'; @@ -3230,37 +3233,37 @@ class AppLocalizationsDe extends AppLocalizations { 'Verify lossless quality with spectrum analysis'; @override - String get audioAnalysisAnalyzing => 'Analyzing audio...'; + String get audioAnalysisAnalyzing => 'Audio wird analysiert...'; @override String get audioAnalysisSampleRate => 'Sample Rate'; @override - String get audioAnalysisBitDepth => 'Bit Depth'; + String get audioAnalysisBitDepth => 'Bit-Tiefe'; @override - String get audioAnalysisChannels => 'Channels'; + String get audioAnalysisChannels => 'Kanäle'; @override - String get audioAnalysisDuration => 'Duration'; + String get audioAnalysisDuration => 'Länge'; @override String get audioAnalysisNyquist => 'Nyquist'; @override - String get audioAnalysisFileSize => 'Size'; + String get audioAnalysisFileSize => 'Größe'; @override - String get audioAnalysisDynamicRange => 'Dynamic Range'; + String get audioAnalysisDynamicRange => 'Dynamischer Bereich'; @override - String get audioAnalysisPeak => 'Peak'; + String get audioAnalysisPeak => 'Maximum'; @override String get audioAnalysisRms => 'RMS'; @override - String get audioAnalysisSamples => 'Samples'; + String get audioAnalysisSamples => 'Proben'; @override String extensionsSearchWith(String providerName) { @@ -3268,7 +3271,7 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get extensionsHomeFeedProvider => 'Home Feed Provider'; + String get extensionsHomeFeedProvider => 'Home Feed Anbieter'; @override String get extensionsHomeFeedDescription => @@ -3296,7 +3299,7 @@ class AppLocalizationsDe extends AppLocalizations { String get sortAlphaDesc => 'Z-A'; @override - String get cancelDownloadTitle => 'Cancel download?'; + String get cancelDownloadTitle => 'Download abbrechen?'; @override String cancelDownloadContent(String trackName) { @@ -3304,7 +3307,7 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get cancelDownloadKeep => 'Keep'; + String get cancelDownloadKeep => 'Behalten'; @override String get metadataSaveFailedFfmpeg => 'Failed to save metadata via FFmpeg'; @@ -3319,22 +3322,22 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get errorLoadAlbum => 'Failed to load album'; + String get errorLoadAlbum => 'Fehler beim Laden des Albums'; @override - String get errorLoadPlaylist => 'Failed to load playlist'; + String get errorLoadPlaylist => 'Fehler beim Laden der Playlist'; @override - String get errorLoadArtist => 'Failed to load artist'; + String get errorLoadArtist => 'Fehler beim Laden des Interpreten'; @override - String get notifChannelDownloadName => 'Download Progress'; + String get notifChannelDownloadName => 'Download Fortschritt'; @override String get notifChannelDownloadDesc => 'Shows download progress for tracks'; @override - String get notifChannelLibraryScanName => 'Library Scan'; + String get notifChannelLibraryScanName => 'Bibliotheksscan'; @override String get notifChannelLibraryScanDesc => 'Shows local library scan progress'; @@ -3358,7 +3361,7 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get notifAlreadyInLibrary => 'Already in Library'; + String get notifAlreadyInLibrary => 'Bereits in der Bibliothek'; @override String notifDownloadCompleteCount(int completed, int total) { @@ -3366,7 +3369,7 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get notifDownloadComplete => 'Download Complete'; + String get notifDownloadComplete => 'Download abgeschlossen'; @override String notifDownloadsFinished(int completed, int failed) { @@ -3408,12 +3411,12 @@ class AppLocalizationsDe extends AppLocalizations { @override String notifLibraryScanExcluded(int count) { - return '$count excluded'; + return '$count ausgeschlossen'; } @override String notifLibraryScanErrors(int count) { - return '$count errors'; + return '$count Fehler'; } @override @@ -3436,7 +3439,7 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get notifUpdateReady => 'Update Ready'; + String get notifUpdateReady => 'Update bereit'; @override String notifUpdateReadyBody(String version) { @@ -3444,7 +3447,7 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get notifUpdateFailed => 'Update Failed'; + String get notifUpdateFailed => 'Update fehlgeschlagen'; @override String get notifUpdateFailedBody => diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index c813f73b..45f74665 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -3436,13 +3436,13 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get navSettings => 'Ajustes'; @override - String get navStore => 'Tienda'; + String get navStore => 'Repositorio'; @override String get homeTitle => 'Inicio'; @override - String get homeSubtitle => 'Pegar enlace de Spotify o buscar por nombre'; + String get homeSubtitle => 'Pega una URL compatible o busca por nombre'; @override String get homeSupports => @@ -3491,6 +3491,13 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get downloadFilenameFormat => 'Formato del nombre del archivo'; + @override + String get downloadSingleFilenameFormat => 'Formato de título único'; + + @override + String get downloadSingleFilenameFormatDescription => + 'Patrón de título para sencillos y mini-álbumes. Usa las mismas etiquetas que un álbum completo.'; + @override String get downloadFolderOrganization => 'Organización de carpetas'; @@ -3537,6 +3544,13 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { return 'Usando la extensión: $extensionName'; } + @override + String get optionsDefaultSearchTab => 'Default Search Tab'; + + @override + String get optionsDefaultSearchTabSubtitle => + 'Choose which tab opens first for new search results.'; + @override String get optionsSwitchBack => 'Toque Deezer o Spotify para volver desde la extensión'; @@ -3573,6 +3587,39 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get optionsMaxQualityCoverSubtitle => 'Descargar carátula de resolución máxima'; + @override + String get optionsReplayGain => 'Nivelación de Ganancia'; + + @override + String get optionsReplayGainSubtitleOn => + 'Analizar volumen e incrustar etiquetas de RG (EBU-R128)'; + + @override + String get optionsReplayGainSubtitleOff => + 'Desactivado: sin etiquetas de normalización de volumen'; + + @override + String get optionsArtistTagMode => 'Modo de Etiqueta de Artista'; + + @override + String get optionsArtistTagModeDescription => + 'Elija cómo se ingresan múltiples artistas en etiquetas incrustadas.'; + + @override + String get optionsArtistTagModeJoined => 'Valor único ingresado'; + + @override + String get optionsArtistTagModeJoinedSubtitle => + 'Escribe un valor ARTIST, como \"Artista A, Artista B\" para mejor compatibilidad en reproductores.'; + + @override + String get optionsArtistTagModeSplitVorbis => + 'Dividir (recortar) etiquetar para FLAC/OPUS'; + + @override + String get optionsArtistTagModeSplitVorbisSubtitle => + 'Escribe una etiqueta de artista por artista para FLAC y OPUS; MP3 y M4A se mantienen agrupados.'; + @override String get optionsConcurrentDownloads => 'Descargas Simultáneas'; @@ -3589,11 +3636,10 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { 'Las descargas paralelas pueden activar la limitación de velocidad'; @override - String get optionsExtensionStore => 'Tienda de extensiones'; + String get optionsExtensionStore => 'Extensión .Repo (repositorio)'; @override - String get optionsExtensionStoreSubtitle => - 'Mostrar pestaña de tienda en la navegación'; + String get optionsExtensionStoreSubtitle => 'Mostar barra de navegación repo'; @override String get optionsCheckUpdates => 'Comprobar actualizaciones'; @@ -3650,7 +3696,7 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get optionsSpotifyDeprecationWarning => - 'Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.'; + 'La función de búsqueda de Spotify dejará de estar disponible el 3 de marzo de 2026 debido a cambios en la API de Spotify. Te recomendamos que te pases a Deezer.'; @override String get extensionsTitle => 'Extensiones'; @@ -3672,7 +3718,7 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get extensionsUninstall => 'Desinstalar'; @override - String get storeTitle => 'Tienda de extensiones'; + String get storeTitle => 'Extensión .Repo'; @override String get storeSearch => 'Buscar extensiones...'; @@ -3783,7 +3829,7 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal and Qobuz.'; + 'Descargar pistas de Spotify en alta calidad (sin pérdida) de Tidal y Qobuz.'; @override String get artistAlbums => 'Álbumes'; @@ -3954,6 +4000,9 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get dialogImport => 'Importar'; + @override + String get dialogDownload => 'Descargar'; + @override String get dialogDiscard => 'Descartar'; @@ -4119,15 +4168,15 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get errorNoTracksFound => 'No se encontraron pistas'; @override - String get errorUrlNotRecognized => 'Link not recognized'; + String get errorUrlNotRecognized => 'Enlace no reconocido'; @override String get errorUrlNotRecognizedMessage => - 'This link is not supported. Make sure the URL is correct and a compatible extension is installed.'; + 'Este enlace no es compatible. Asegúrate de que la URL sea correcta y de tener instalada una extensión compatible.'; @override String get errorUrlFetchFailed => - 'Failed to load content from this link. Please try again.'; + 'No se ha podido cargar el contenido de este enlace. Inténtalo de nuevo.'; @override String errorMissingExtensionSource(String item) { @@ -4186,6 +4235,36 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get searchPlaylists => 'Listas de reproducción'; + @override + String get searchSortTitle => 'Ordenar resultados'; + + @override + String get searchSortDefault => 'Por defecto'; + + @override + String get searchSortTitleAZ => 'Nombre (A-Z)'; + + @override + String get searchSortTitleZA => 'Nombre (Z-A)'; + + @override + String get searchSortArtistAZ => 'Artista (A-Z)'; + + @override + String get searchSortArtistZA => 'Artista (Z-A)'; + + @override + String get searchSortDurationShort => 'Duración (más corto)'; + + @override + String get searchSortDurationLong => 'Duración (más largo)'; + + @override + String get searchSortDateOldest => 'Fecha de lanzamiento (antiguo)'; + + @override + String get searchSortDateNewest => 'Fecha de lanzamiento (reciente)'; + @override String get tooltipPlay => 'Reproducir'; @@ -4193,21 +4272,21 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get filenameFormat => 'Formato del nombre del archivo'; @override - String get filenameShowAdvancedTags => 'Show advanced tags'; + String get filenameShowAdvancedTags => 'Mostrar etiquetas avanzadas'; @override String get filenameShowAdvancedTagsDescription => - 'Enable formatted tags for track padding and date patterns'; + 'Habilitar etiquetas con formato para el relleno de pistas y los formatos de fecha'; @override String get folderOrganizationNone => 'Ninguna organización'; @override - String get folderOrganizationByPlaylist => 'By Playlist'; + String get folderOrganizationByPlaylist => 'Por Playlist'; @override String get folderOrganizationByPlaylistSubtitle => - 'Separate folder for each playlist'; + 'Una carpeta independiente para cada Playlist'; @override String get folderOrganizationByArtist => 'Por Artista'; @@ -4285,6 +4364,17 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get providerPriorityInfo => 'Si una pista no está disponible en el primer proveedor, la aplicación intentará automáticamente el siguiente.'; + @override + String get providerPriorityFallbackExtensionsTitle => 'Fallback de extensión'; + + @override + String get providerPriorityFallbackExtensionsDescription => + 'Elije qué extensiones instaladas se pueden utilizar durante el cambio automático a una alternativa. Los proveedores integrados siguen el orden de prioridad indicado anteriormente.'; + + @override + String get providerPriorityFallbackExtensionsHint => + 'Solo las extensiones activas con proveedor de descarga se listan aquí.'; + @override String get providerBuiltIn => 'Integrado'; @@ -4566,6 +4656,12 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get trackLyricsNotAvailable => 'Letras no disponibles para este tema'; + @override + String get trackLyricsNotInFile => 'No se encontraron letras'; + + @override + String get trackFetchOnlineLyrics => 'Obtener en línea'; + @override String get trackLyricsTimeout => 'Tiempo de espera agotado. Inténtalo de nuevo más tarde.'; @@ -4635,7 +4731,48 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get storeClearFilters => 'Limpiar filtros'; @override - String get extensionDefaultProvider => 'Por defecto (Deezer/Spotify)'; + String get storeAddRepoTitle => 'Añadir repositorio de extensiones'; + + @override + String get storeAddRepoDescription => + 'Introduzca una URL de repositorio de GitHub que contenga un archivo registry.json para navegar e instalar extensiones.'; + + @override + String get storeRepoUrlLabel => 'URL del repositorio'; + + @override + String get storeRepoUrlHint => 'https://github.com/user/repo'; + + @override + String get storeRepoUrlHelper => + 'Ejemplo: https://github.com/user/extensions-repo'; + + @override + String get storeAddRepoButton => 'Añadir repositorio'; + + @override + String get storeChangeRepoTooltip => 'Cambiar repositorio'; + + @override + String get storeRepoDialogTitle => 'Repositorio de extensiones'; + + @override + String get storeRepoDialogCurrent => 'Repositorio actual:'; + + @override + String get storeNewRepoUrlLabel => 'Nueva URL del repositorio'; + + @override + String get storeLoadError => 'Falló al carga repositorio'; + + @override + String get storeEmptyNoExtensions => 'No hay extensiones disponibles'; + + @override + String get storeEmptyNoResults => 'No se encontraron extensiones'; + + @override + String get extensionDefaultProvider => 'Predeterminado (Deezer)'; @override String get extensionDefaultProviderSubtitle => 'Usar búsqueda integrada'; @@ -4737,6 +4874,13 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get extensionsDownloadPrioritySubtitle => 'Establecer orden de servicio de descarga'; + @override + String get extensionsFallbackTitle => 'Fallback de extensiones'; + + @override + String get extensionsFallbackSubtitle => + 'Elija que extensiones pueden usarse como reserva'; + @override String get extensionsNoDownloadProvider => 'No hay extensiones con proveedor de descargas'; @@ -4787,6 +4931,39 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get qualityHiResFlacMaxSubtitle => '24 bits / hasta 192kHz'; + @override + String get downloadLossy320 => 'Con pérdida, 320 kbps'; + + @override + String get downloadLossyFormat => 'Formato con pérdida'; + + @override + String get downloadLossy320Format => 'Formato con pérdida a 320 kbps'; + + @override + String get downloadLossy320FormatDesc => + 'Elige el formato de salida para las descargas con pérdida de Tidal a 320kbps. La transmisión AAC original se convertirá al formato que hayas seleccionado.'; + + @override + String get downloadLossyMp3 => 'MP3 (320kbps)'; + + @override + String get downloadLossyMp3Subtitle => + 'Óptima compatibilidad, ~10 MB por pista'; + + @override + String get downloadLossyOpus256 => 'OPUS (256kbps)'; + + @override + String get downloadLossyOpus256Subtitle => + 'Opus de la mejor calidad, ~8 MB por pista'; + + @override + String get downloadLossyOpus128 => 'OPUS (128kbps)'; + + @override + String get downloadLossyOpus128Subtitle => 'Tamaño mínimo: ~4 MB por pista'; + @override String get qualityNote => 'La calidad real depende de la disponibilidad de la pista del servicio'; @@ -4804,18 +4981,20 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get downloadAlbumFolderStructure => 'Estructura de carpeta del álbum'; @override - String get downloadUseAlbumArtistForFolders => 'Use Album Artist for folders'; + String get downloadUseAlbumArtistForFolders => + 'Usar álbum de artista cómo carpeta'; @override - String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders'; + String get downloadUsePrimaryArtistOnly => + 'Artista principal solo para carpetas'; @override String get downloadUsePrimaryArtistOnlyEnabled => - 'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)'; + 'Se han eliminado los nombres de los artistas destacados del nombre de la carpeta (p. ej., Justin Bieber, Quavo → Justin Bieber)'; @override String get downloadUsePrimaryArtistOnlyDisabled => - 'Full artist string used for folder name'; + 'Se utiliza el nombre completo del artista como nombre de la carpeta'; @override String get downloadSelectQuality => 'Seleccionar Calidad'; @@ -4889,6 +5068,13 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get albumFolderArtistAlbumSinglesSubtitle => 'Artista/Álbum/ y Artista/pistas/'; + @override + String get albumFolderArtistAlbumFlat => 'Artista / Álbum (sencillos planos)'; + + @override + String get albumFolderArtistAlbumFlatSubtitle => + 'Artist/Album/ and Artist/song.flac'; + @override String get downloadedAlbumDeleteSelected => 'Borrar Seleccionados'; @@ -4946,10 +5132,10 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get recentTypePlaylist => 'Lista de reproducción'; @override - String get recentEmpty => 'No recent items yet'; + String get recentEmpty => 'Aún no hay entradas recientes'; @override - String get recentShowAllDownloads => 'Show All Downloads'; + String get recentShowAllDownloads => 'Mostrar todas las descargas'; @override String recentPlaylistInfo(String name) { @@ -4980,31 +5166,31 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String discographySinglesOnlySubtitle(int count, int albumCount) { - return '$count tracks from $albumCount singles'; + return '$count Pistas de $albumCount sencillos'; } @override - String get discographySelectAlbums => 'Select Albums...'; + String get discographySelectAlbums => 'Seleccionar álbumes...'; @override String get discographySelectAlbumsSubtitle => - 'Choose specific albums or singles'; + 'Elige álbumes o sencillos concretos'; @override - String get discographyFetchingTracks => 'Fetching tracks...'; + String get discographyFetchingTracks => 'Cargando canciones...'; @override String discographyFetchingAlbum(int current, int total) { - return 'Fetching $current of $total...'; + return 'Cargando $current de $total...'; } @override String discographySelectedCount(int count) { - return '$count selected'; + return '$count seleccionados'; } @override - String get discographyDownloadSelected => 'Download Selected'; + String get discographyDownloadSelected => 'Descargar seleccionados'; @override String discographyAddedToQueue(int count) { @@ -5053,10 +5239,10 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get settingsLocalLibrarySubtitle => 'Scan music & detect duplicates'; @override - String get settingsCache => 'Storage & Cache'; + String get settingsCache => 'Almacenamiento & Caché'; @override - String get settingsCacheSubtitle => 'View size and clear cached data'; + String get settingsCacheSubtitle => 'Ver tamaño y borrar datos en caché'; @override String get libraryTitle => 'Local Library'; @@ -5069,7 +5255,7 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get libraryEnableLocalLibrarySubtitle => - 'Scan and track your existing music'; + 'Escanea y rastrea tu música existente'; @override String get libraryFolder => 'Library Folder'; @@ -5084,6 +5270,25 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Escaneo Automático'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Apagado'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; @@ -5134,6 +5339,17 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { return '$_temp0'; } + @override + String libraryFilesUnit(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'files', + one: 'file', + ); + return '$_temp0'; + } + @override String libraryLastScanned(String time) { return 'Last scanned: $time'; @@ -5145,6 +5361,9 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get libraryScanning => 'Scanning...'; + @override + String get libraryScanFinalizing => 'Finalizing library...'; + @override String libraryScanProgress(String progress, int total) { return '$progress% of $total files'; @@ -5213,6 +5432,24 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get libraryFilterFormat => 'Format'; + @override + String get libraryFilterMetadata => 'Metadata'; + + @override + String get libraryFilterMetadataComplete => 'Complete metadata'; + + @override + String get libraryFilterMetadataMissingAny => 'Missing any metadata'; + + @override + String get libraryFilterMetadataMissingYear => 'Missing year'; + + @override + String get libraryFilterMetadataMissingGenre => 'Missing genre'; + + @override + String get libraryFilterMetadataMissingAlbumArtist => 'Missing album artist'; + @override String get libraryFilterSort => 'Sort'; @@ -5222,6 +5459,18 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get libraryFilterSortOldest => 'Oldest'; + @override + String get libraryFilterSortAlbumAsc => 'Album (A-Z)'; + + @override + String get libraryFilterSortAlbumDesc => 'Album (Z-A)'; + + @override + String get libraryFilterSortGenreAsc => 'Genre (A-Z)'; + + @override + String get libraryFilterSortGenreDesc => 'Genre (Z-A)'; + @override String get timeJustNow => 'Just now'; @@ -5308,7 +5557,7 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => @@ -5506,6 +5755,30 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get trackReEnrichOnlineSubtitle => 'Search metadata online and embed into file'; + @override + String get trackReEnrichFieldsTitle => 'Fields to update'; + + @override + String get trackReEnrichFieldCover => 'Cover Art'; + + @override + String get trackReEnrichFieldLyrics => 'Lyrics'; + + @override + String get trackReEnrichFieldBasicTags => 'Album, Album Artist'; + + @override + String get trackReEnrichFieldTrackInfo => 'Track & Disc Number'; + + @override + String get trackReEnrichFieldReleaseInfo => 'Date & ISRC'; + + @override + String get trackReEnrichFieldExtra => 'Genre, Label, Copyright'; + + @override + String get trackReEnrichSelectAll => 'Select All'; + @override String get trackEditMetadata => 'Edit Metadata'; @@ -5534,6 +5807,28 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed'; + @override + String get queueFlacAction => 'Queue FLAC'; + + @override + String queueFlacConfirmMessage(int count) { + return 'Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n$count selected'; + } + + @override + String queueFlacFindingProgress(int current, int total) { + return 'Finding FLAC matches... ($current/$total)'; + } + + @override + String get queueFlacNoReliableMatches => + 'No reliable online matches found for the selection'; + + @override + String queueFlacQueuedWithSkipped(int addedCount, int skippedCount) { + return 'Added $addedCount tracks to queue, skipped $skippedCount'; + } + @override String trackSaveFailed(String error) { return 'Failed: $error'; @@ -5543,7 +5838,8 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get trackConvertFormat => 'Convert Format'; @override - String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + String get trackConvertFormatSubtitle => + 'Convert to MP3, Opus, ALAC, or FLAC'; @override String get trackConvertTitle => 'Convert Audio'; @@ -5566,6 +5862,18 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { return 'Convert from $sourceFormat to $targetFormat at $bitrate?\n\nThe original file will be deleted after conversion.'; } + @override + String trackConvertConfirmMessageLossless( + String sourceFormat, + String targetFormat, + ) { + return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.'; + } + + @override + String get trackConvertLosslessHint => + 'Lossless conversion — no quality loss'; + @override String get trackConvertConverting => 'Converting audio...'; @@ -5819,6 +6127,17 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { return 'Convert $count $_temp0 to $format at $bitrate?\n\nOriginal files will be deleted after conversion.'; } + @override + String selectionBatchConvertConfirmMessageLossless(int count, String format) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.'; + } + @override String selectionBatchConvertProgress(int current, int total) { return 'Converting $current of $total...'; @@ -5841,4 +6160,704 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get downloadUseAlbumArtistForFoldersTrackSubtitle => 'Artist folders use Track Artist only'; + + @override + String get lyricsProvidersTitle => 'Lyrics Providers'; + + @override + String get lyricsProvidersDescription => + 'Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.'; + + @override + String get lyricsProvidersInfoText => + 'Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.'; + + @override + String lyricsProvidersEnabledSection(int count) { + return 'Enabled ($count)'; + } + + @override + String lyricsProvidersDisabledSection(int count) { + return 'Disabled ($count)'; + } + + @override + String get lyricsProvidersAtLeastOne => + 'At least one provider must remain enabled'; + + @override + String get lyricsProvidersSaved => 'Lyrics provider priority saved'; + + @override + String get lyricsProvidersDiscardContent => + 'You have unsaved changes that will be lost.'; + + @override + String get lyricsProviderLrclibDesc => 'Open-source synced lyrics database'; + + @override + String get lyricsProviderNeteaseDesc => + 'NetEase Cloud Music (good for Asian songs)'; + + @override + String get lyricsProviderMusixmatchDesc => + 'Largest lyrics database (multi-language)'; + + @override + String get lyricsProviderAppleMusicDesc => + 'Word-by-word synced lyrics (via proxy)'; + + @override + String get lyricsProviderQqMusicDesc => + 'QQ Music (good for Chinese songs, via proxy)'; + + @override + String get lyricsProviderExtensionDesc => 'Extension provider'; + + @override + String get safMigrationTitle => 'Storage Update Required'; + + @override + String get safMigrationMessage1 => + 'SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.'; + + @override + String get safMigrationMessage2 => + 'Please select your download folder again to switch to the new storage system.'; + + @override + String get safMigrationSuccess => 'Download folder updated to SAF mode'; + + @override + String get settingsDonate => 'Donate'; + + @override + String get settingsDonateSubtitle => 'Support SpotiFLAC-Mobile development'; + + @override + String get tooltipLoveAll => 'Love All'; + + @override + String get tooltipAddToPlaylist => 'Add to Playlist'; + + @override + String snackbarRemovedTracksFromLoved(int count) { + return 'Removed $count tracks from Loved'; + } + + @override + String snackbarAddedTracksToLoved(int count) { + return 'Added $count tracks to Loved'; + } + + @override + String get dialogDownloadAllTitle => 'Download All'; + + @override + String dialogDownloadAllMessage(int count) { + return 'Download $count tracks?'; + } + + @override + String get homeSkipAlreadyDownloaded => 'Skip already downloaded songs'; + + @override + String get homeGoToAlbum => 'Go to Album'; + + @override + String get homeAlbumInfoUnavailable => 'Album info not available'; + + @override + String get snackbarLoadingCueSheet => 'Loading CUE sheet...'; + + @override + String get snackbarMetadataSaved => 'Metadata saved successfully'; + + @override + String get snackbarFailedToEmbedLyrics => 'Failed to embed lyrics'; + + @override + String get snackbarFailedToWriteStorage => 'Failed to write back to storage'; + + @override + String snackbarError(String error) { + return 'Error: $error'; + } + + @override + String get snackbarNoActionDefined => 'No action defined for this button'; + + @override + String get noTracksFoundForAlbum => 'No tracks found for this album'; + + @override + String get downloadLocationSubtitle => + 'Choose storage mode for downloaded files.'; + + @override + String get storageModeAppFolder => 'App folder (non-SAF)'; + + @override + String get storageModeAppFolderSubtitle => 'Use default Music/SpotiFLAC path'; + + @override + String get storageModeSaf => 'SAF folder'; + + @override + String get storageModeSafSubtitle => + 'Pick folder via Android Storage Access Framework'; + + @override + String get downloadFilenameDescription => + 'Customize how your files are named.'; + + @override + String get downloadFilenameInsertTag => 'Tap to insert tag:'; + + @override + String get downloadSeparateSinglesEnabled => 'Albums/ and Singles/ folders'; + + @override + String get downloadSeparateSinglesDisabled => 'All files in same structure'; + + @override + String get downloadArtistNameFilters => 'Artist Name Filters'; + + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + + @override + String get downloadSongLinkRegion => 'SongLink Region'; + + @override + String get downloadNetworkCompatibilityMode => 'Network compatibility mode'; + + @override + String get downloadNetworkCompatibilityModeEnabled => + 'Enabled: try HTTP + accept invalid TLS certificates (unsafe)'; + + @override + String get downloadNetworkCompatibilityModeDisabled => + 'Off: strict HTTPS certificate validation (recommended)'; + + @override + String get downloadSelectServiceToEnable => + 'Select a built-in service to enable'; + + @override + String get downloadSelectTidalQobuz => + 'Select Tidal or Qobuz above to configure quality'; + + @override + String get downloadEmbedLyricsDisabled => + 'Disabled while Embed Metadata is turned off'; + + @override + String get downloadNeteaseIncludeTranslation => + 'Netease: Include Translation'; + + @override + String get downloadNeteaseIncludeTranslationEnabled => + 'Append translated lyrics when available'; + + @override + String get downloadNeteaseIncludeTranslationDisabled => + 'Use original lyrics only'; + + @override + String get downloadNeteaseIncludeRomanization => + 'Netease: Include Romanization'; + + @override + String get downloadNeteaseIncludeRomanizationEnabled => + 'Append romanized lyrics when available'; + + @override + String get downloadNeteaseIncludeRomanizationDisabled => 'Disabled'; + + @override + String get downloadAppleQqMultiPerson => 'Apple/QQ Multi-Person Word-by-Word'; + + @override + String get downloadAppleQqMultiPersonEnabled => + 'Enable v1/v2 speaker and [bg:] tags'; + + @override + String get downloadAppleQqMultiPersonDisabled => + 'Simplified word-by-word formatting'; + + @override + String get downloadMusixmatchLanguage => 'Musixmatch Language'; + + @override + String get downloadMusixmatchLanguageAuto => 'Auto (original)'; + + @override + String get downloadFilterContributing => + 'Filter contributing artists in Album Artist'; + + @override + String get downloadFilterContributingEnabled => + 'Album Artist metadata uses primary artist only'; + + @override + String get downloadFilterContributingDisabled => + 'Keep full Album Artist metadata value'; + + @override + String get downloadProvidersNoneEnabled => 'None enabled'; + + @override + String get downloadMusixmatchLanguageCode => 'Language code'; + + @override + String get downloadMusixmatchLanguageHint => 'auto / en / es / ja'; + + @override + String get downloadMusixmatchLanguageDesc => + 'Set preferred language code (example: en, es, ja). Leave empty for auto.'; + + @override + String get downloadMusixmatchAuto => 'Auto'; + + @override + String get downloadNetworkAnySubtitle => 'WiFi + Mobile Data'; + + @override + String get downloadNetworkWifiOnlySubtitle => + 'Pause downloads on mobile data'; + + @override + String get downloadSongLinkRegionDesc => + 'Used as userCountry for SongLink API lookup.'; + + @override + String get snackbarUnsupportedAudioFormat => 'Unsupported audio format'; + + @override + String get cacheRefresh => 'Refresh'; + + @override + String dialogDownloadPlaylistsMessage(int trackCount, int playlistCount) { + String _temp0 = intl.Intl.pluralLogic( + trackCount, + locale: localeName, + other: 'tracks', + one: 'track', + ); + String _temp1 = intl.Intl.pluralLogic( + playlistCount, + locale: localeName, + other: 'playlists', + one: 'playlist', + ); + return 'Download $trackCount $_temp0 from $playlistCount $_temp1?'; + } + + @override + String bulkDownloadPlaylistsButton(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'playlists', + one: 'playlist', + ); + return 'Download $count $_temp0'; + } + + @override + String get bulkDownloadSelectPlaylists => 'Select playlists to download'; + + @override + String get snackbarSelectedPlaylistsEmpty => + 'Selected playlists have no tracks'; + + @override + String playlistsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count playlists', + one: '1 playlist', + ); + return '$_temp0'; + } + + @override + String get editMetadataAutoFill => 'Auto-fill from online'; + + @override + String get editMetadataAutoFillDesc => + 'Select fields to fill automatically from online metadata'; + + @override + String get editMetadataAutoFillFetch => 'Fetch & Fill'; + + @override + String get editMetadataAutoFillSearching => 'Searching online...'; + + @override + String get editMetadataAutoFillNoResults => + 'No matching metadata found online'; + + @override + String editMetadataAutoFillDone(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'fields', + one: 'field', + ); + return 'Filled $count $_temp0 from online metadata'; + } + + @override + String get editMetadataAutoFillNoneSelected => + 'Select at least one field to auto-fill'; + + @override + String get editMetadataFieldTitle => 'Title'; + + @override + String get editMetadataFieldArtist => 'Artist'; + + @override + String get editMetadataFieldAlbum => 'Album'; + + @override + String get editMetadataFieldAlbumArtist => 'Album Artist'; + + @override + String get editMetadataFieldDate => 'Date'; + + @override + String get editMetadataFieldTrackNum => 'Track #'; + + @override + String get editMetadataFieldDiscNum => 'Disc #'; + + @override + String get editMetadataFieldGenre => 'Genre'; + + @override + String get editMetadataFieldIsrc => 'ISRC'; + + @override + String get editMetadataFieldLabel => 'Label'; + + @override + String get editMetadataFieldCopyright => 'Copyright'; + + @override + String get editMetadataFieldCover => 'Cover Art'; + + @override + String get editMetadataSelectAll => 'All'; + + @override + String get editMetadataSelectEmpty => 'Empty only'; + + @override + String queueDownloadingCount(int count) { + return 'Downloading ($count)'; + } + + @override + String get queueDownloadedHeader => 'Downloaded'; + + @override + String get queueFilteringIndicator => 'Filtering...'; + + @override + String queueTrackCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count tracks', + one: '1 track', + ); + return '$_temp0'; + } + + @override + String queueAlbumCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count albums', + one: '1 album', + ); + return '$_temp0'; + } + + @override + String get queueEmptyAlbums => 'No album downloads'; + + @override + String get queueEmptyAlbumsSubtitle => + 'Download multiple tracks from an album to see them here'; + + @override + String get queueEmptySingles => 'No single downloads'; + + @override + String get queueEmptySinglesSubtitle => + 'Single track downloads will appear here'; + + @override + String get queueEmptyHistory => 'No download history'; + + @override + String get queueEmptyHistorySubtitle => 'Downloaded tracks will appear here'; + + @override + String get selectionAllPlaylistsSelected => 'All playlists selected'; + + @override + String get selectionTapPlaylistsToSelect => 'Tap playlists to select'; + + @override + String get selectionSelectPlaylistsToDelete => 'Select playlists to delete'; + + @override + String get audioAnalysisTitle => 'Audio Quality Analysis'; + + @override + String get audioAnalysisDescription => + 'Verify lossless quality with spectrum analysis'; + + @override + String get audioAnalysisAnalyzing => 'Analyzing audio...'; + + @override + String get audioAnalysisSampleRate => 'Sample Rate'; + + @override + String get audioAnalysisBitDepth => 'Bit Depth'; + + @override + String get audioAnalysisChannels => 'Channels'; + + @override + String get audioAnalysisDuration => 'Duration'; + + @override + String get audioAnalysisNyquist => 'Nyquist'; + + @override + String get audioAnalysisFileSize => 'Size'; + + @override + String get audioAnalysisDynamicRange => 'Dynamic Range'; + + @override + String get audioAnalysisPeak => 'Peak'; + + @override + String get audioAnalysisRms => 'RMS'; + + @override + String get audioAnalysisSamples => 'Samples'; + + @override + String extensionsSearchWith(String providerName) { + return 'Search with $providerName'; + } + + @override + String get extensionsHomeFeedProvider => 'Home Feed Provider'; + + @override + String get extensionsHomeFeedDescription => + 'Choose which extension provides the home feed on the main screen'; + + @override + String get extensionsHomeFeedAuto => 'Auto'; + + @override + String get extensionsHomeFeedAutoSubtitle => + 'Automatically select the best available'; + + @override + String extensionsHomeFeedUse(String extensionName) { + return 'Use $extensionName home feed'; + } + + @override + String get extensionsNoHomeFeedExtensions => 'No extensions with home feed'; + + @override + String get sortAlphaAsc => 'A-Z'; + + @override + String get sortAlphaDesc => 'Z-A'; + + @override + String get cancelDownloadTitle => 'Cancel download?'; + + @override + String cancelDownloadContent(String trackName) { + return 'This will cancel the active download for \"$trackName\".'; + } + + @override + String get cancelDownloadKeep => 'Keep'; + + @override + String get metadataSaveFailedFfmpeg => 'Failed to save metadata via FFmpeg'; + + @override + String get metadataSaveFailedStorage => + 'Failed to write metadata back to storage'; + + @override + String snackbarFolderPickerFailed(String error) { + return 'Failed to open folder picker: $error'; + } + + @override + String get errorLoadAlbum => 'Failed to load album'; + + @override + String get errorLoadPlaylist => 'Failed to load playlist'; + + @override + String get errorLoadArtist => 'Failed to load artist'; + + @override + String get notifChannelDownloadName => 'Download Progress'; + + @override + String get notifChannelDownloadDesc => 'Shows download progress for tracks'; + + @override + String get notifChannelLibraryScanName => 'Library Scan'; + + @override + String get notifChannelLibraryScanDesc => 'Shows local library scan progress'; + + @override + String notifDownloadingTrack(String trackName) { + return 'Downloading $trackName'; + } + + @override + String notifFinalizingTrack(String trackName) { + return 'Finalizing $trackName'; + } + + @override + String get notifEmbeddingMetadata => 'Embedding metadata...'; + + @override + String notifAlreadyInLibraryCount(int completed, int total) { + return 'Already in Library ($completed/$total)'; + } + + @override + String get notifAlreadyInLibrary => 'Already in Library'; + + @override + String notifDownloadCompleteCount(int completed, int total) { + return 'Download Complete ($completed/$total)'; + } + + @override + String get notifDownloadComplete => 'Download Complete'; + + @override + String notifDownloadsFinished(int completed, int failed) { + return 'Downloads Finished ($completed done, $failed failed)'; + } + + @override + String get notifAllDownloadsComplete => 'All Downloads Complete'; + + @override + String notifTracksDownloadedSuccess(int count) { + return '$count tracks downloaded successfully'; + } + + @override + String get notifScanningLibrary => 'Scanning local library'; + + @override + String notifLibraryScanProgressWithTotal( + int scanned, + int total, + int percentage, + ) { + return '$scanned/$total files • $percentage%'; + } + + @override + String notifLibraryScanProgressNoTotal(int scanned, int percentage) { + return '$scanned files scanned • $percentage%'; + } + + @override + String get notifLibraryScanComplete => 'Library scan complete'; + + @override + String notifLibraryScanCompleteBody(int count) { + return '$count tracks indexed'; + } + + @override + String notifLibraryScanExcluded(int count) { + return '$count excluded'; + } + + @override + String notifLibraryScanErrors(int count) { + return '$count errors'; + } + + @override + String get notifLibraryScanFailed => 'Library scan failed'; + + @override + String get notifLibraryScanCancelled => 'Library scan cancelled'; + + @override + String get notifLibraryScanStopped => 'Scan stopped before completion.'; + + @override + String notifDownloadingUpdate(String version) { + return 'Downloading SpotiFLAC v$version'; + } + + @override + String notifUpdateProgress(String received, String total, int percentage) { + return '$received / $total MB • $percentage%'; + } + + @override + String get notifUpdateReady => 'Update Ready'; + + @override + String notifUpdateReadyBody(String version) { + return 'SpotiFLAC v$version downloaded. Tap to install.'; + } + + @override + String get notifUpdateFailed => 'Update Failed'; + + @override + String get notifUpdateFailedBody => + 'Could not download update. Try again later.'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 95aa50b3..05d46275 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -21,13 +21,13 @@ class AppLocalizationsFr extends AppLocalizations { String get navSettings => 'Paramètres'; @override - String get navStore => 'Magasin'; + String get navStore => 'Repo'; @override String get homeTitle => 'Accueil'; @override - String get homeSubtitle => 'Coller un lien Spotify ou rechercher par nom'; + String get homeSubtitle => 'Paste a supported URL or search by name'; @override String get homeSupports => 'Supports: Piste, Album, Playlist, Artiste URLs'; @@ -218,10 +218,10 @@ class AppLocalizationsFr extends AppLocalizations { 'Parallel downloads may trigger rate limiting'; @override - String get optionsExtensionStore => 'Extension Store'; + String get optionsExtensionStore => 'Extension Repo'; @override - String get optionsExtensionStoreSubtitle => 'Show Store tab in navigation'; + String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation'; @override String get optionsCheckUpdates => 'Check for Updates'; @@ -282,7 +282,7 @@ class AppLocalizationsFr extends AppLocalizations { String get extensionsTitle => 'Extensions'; @override - String get extensionsDisabled => 'Disabled'; + String get extensionsDisabled => 'Désactivée'; @override String extensionsVersion(String version) { @@ -291,38 +291,38 @@ class AppLocalizationsFr extends AppLocalizations { @override String extensionsAuthor(String author) { - return 'by $author'; + return 'par $author'; } @override String get extensionsUninstall => 'Désinstaller'; @override - String get storeTitle => 'Magasin d\'extension'; + String get storeTitle => 'Extension Repo'; @override String get storeSearch => 'Recherche d\'extensions...'; @override - String get storeInstall => 'Install'; + String get storeInstall => 'Installer'; @override - String get storeInstalled => 'Installed'; + String get storeInstalled => 'Installé'; @override - String get storeUpdate => 'Update'; + String get storeUpdate => 'Mettre à jour'; @override - String get aboutTitle => 'About'; + String get aboutTitle => 'À propos de'; @override - String get aboutContributors => 'Contributors'; + String get aboutContributors => 'Contributeurs'; @override - String get aboutMobileDeveloper => 'Mobile version developer'; + String get aboutMobileDeveloper => 'Développeur de la version mobile'; @override - String get aboutOriginalCreator => 'Creator of the original SpotiFLAC'; + String get aboutOriginalCreator => 'Créateur de SpotiFLAC original'; @override String get aboutLogoArtist => @@ -362,7 +362,7 @@ class AppLocalizationsFr extends AppLocalizations { String get aboutTelegramChannel => 'Telegram Channel'; @override - String get aboutTelegramChannelSubtitle => 'Announcements and updates'; + String get aboutTelegramChannelSubtitle => 'Annonces et mises à jour'; @override String get aboutTelegramChat => 'Telegram Community'; @@ -520,10 +520,11 @@ class AppLocalizationsFr extends AppLocalizations { 'SpotiFLAC needs storage permission to save your downloaded music files.'; @override - String get setupNotificationGranted => 'Notification Permission Granted!'; + String get setupNotificationGranted => + 'Autorisation de notifications accordée!'; @override - String get setupNotificationEnable => 'Enable Notifications'; + String get setupNotificationEnable => 'Activer les notifications'; @override String get setupFolderChoose => 'Choisissez le dossier pour télécharger'; @@ -533,39 +534,39 @@ class AppLocalizationsFr extends AppLocalizations { 'Sélectionnez un dossier dans lequel votre musique téléchargée sera enregistrée.'; @override - String get setupSelectFolder => 'Select Folder'; + String get setupSelectFolder => 'Sélectionner un dossier'; @override - String get setupEnableNotifications => 'Enable Notifications'; + String get setupEnableNotifications => 'Activer les notifications'; @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 => 'Ignorer pour le moment'; @override - String get setupNext => 'Next'; + String get setupNext => 'Suivant'; @override - String get setupGetStarted => 'Get Started'; + String get setupGetStarted => 'Démarrer'; @override String get setupAllowAccessToManageFiles => - 'Please enable \"Allow access to manage all files\" in the next screen.'; + 'Veuillez activer \"Autoriser l\'accès à tous les fichiers\" sur l\'écran suivant.'; @override - String get dialogCancel => 'Cancel'; + String get dialogCancel => 'Annuler'; @override - String get dialogSave => 'Save'; + String get dialogSave => 'Sauvegarder'; @override - String get dialogDelete => 'Delete'; + String get dialogDelete => 'Supprimer'; @override - String get dialogRetry => 'Retry'; + String get dialogRetry => 'Réessayer'; @override String get dialogClear => 'Clear'; @@ -577,7 +578,7 @@ class AppLocalizationsFr extends AppLocalizations { String get dialogImport => 'Import'; @override - String get dialogDownload => 'Download'; + String get dialogDownload => 'Télécharger'; @override String get dialogDiscard => 'Discard'; @@ -586,10 +587,10 @@ class AppLocalizationsFr extends AppLocalizations { String get dialogRemove => 'Remove'; @override - String get dialogUninstall => 'Uninstall'; + String get dialogUninstall => 'Désinstaller'; @override - String get dialogDiscardChanges => 'Discard Changes?'; + String get dialogDiscardChanges => 'Ignorer les modifications ?'; @override String get dialogUnsavedChanges => @@ -1338,7 +1339,7 @@ class AppLocalizationsFr extends AppLocalizations { String get storeEmptyNoResults => 'No extensions found'; @override - String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; + String get extensionDefaultProvider => 'Default (Deezer)'; @override String get extensionDefaultProviderSubtitle => 'Use built-in search'; @@ -2116,7 +2117,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => @@ -2397,7 +2398,8 @@ class AppLocalizationsFr extends AppLocalizations { String get trackConvertFormat => 'Convert Format'; @override - String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + String get trackConvertFormatSubtitle => + 'Convert to MP3, Opus, ALAC, or FLAC'; @override String get trackConvertTitle => 'Convert Audio'; diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index d542fc63..ea56b417 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -21,13 +21,13 @@ class AppLocalizationsHi extends AppLocalizations { String get navSettings => 'विकल्प'; @override - String get navStore => 'Store'; + String get navStore => 'Repo'; @override String get homeTitle => 'Home'; @override - String get homeSubtitle => 'Paste a Spotify link or search by name'; + String get homeSubtitle => 'Paste a supported URL or search by name'; @override String get homeSupports => 'Supports: Track, Album, Playlist, Artist URLs'; @@ -216,10 +216,10 @@ class AppLocalizationsHi extends AppLocalizations { 'Parallel downloads may trigger rate limiting'; @override - String get optionsExtensionStore => 'Extension Store'; + String get optionsExtensionStore => 'Extension Repo'; @override - String get optionsExtensionStoreSubtitle => 'Show Store tab in navigation'; + String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation'; @override String get optionsCheckUpdates => 'Check for Updates'; @@ -296,7 +296,7 @@ class AppLocalizationsHi extends AppLocalizations { String get extensionsUninstall => 'Uninstall'; @override - String get storeTitle => 'Extension Store'; + String get storeTitle => 'Extension Repo'; @override String get storeSearch => 'Search extensions...'; @@ -1336,7 +1336,7 @@ class AppLocalizationsHi extends AppLocalizations { String get storeEmptyNoResults => 'No extensions found'; @override - String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; + String get extensionDefaultProvider => 'Default (Deezer)'; @override String get extensionDefaultProviderSubtitle => 'Use built-in search'; @@ -2114,7 +2114,7 @@ class AppLocalizationsHi extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => @@ -2395,7 +2395,8 @@ class AppLocalizationsHi extends AppLocalizations { String get trackConvertFormat => 'Convert Format'; @override - String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + String get trackConvertFormatSubtitle => + 'Convert to MP3, Opus, ALAC, or FLAC'; @override String get trackConvertTitle => 'Convert Audio'; diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index 9ff27578..5407881d 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -27,8 +27,7 @@ class AppLocalizationsId extends AppLocalizations { String get homeTitle => 'Beranda'; @override - String get homeSubtitle => - 'Tempel URL yang didukung atau cari berdasarkan nama'; + String get homeSubtitle => 'Paste a supported URL or search by name'; @override String get homeSupports => 'Mendukung: URL Track, Album, Playlist, Artis'; @@ -130,11 +129,11 @@ class AppLocalizationsId extends AppLocalizations { } @override - String get optionsDefaultSearchTab => 'Tab Pencarian Default'; + String get optionsDefaultSearchTab => 'Default Search Tab'; @override String get optionsDefaultSearchTabSubtitle => - 'Pilih tab yang dibuka lebih dulu untuk hasil pencarian baru.'; + 'Choose which tab opens first for new search results.'; @override String get optionsSwitchBack => @@ -220,10 +219,10 @@ class AppLocalizationsId extends AppLocalizations { 'Unduhan paralel dapat memicu pembatasan rate'; @override - String get optionsExtensionStore => 'Repo Ekstensi'; + String get optionsExtensionStore => 'Extension Repo'; @override - String get optionsExtensionStoreSubtitle => 'Tampilkan tab Repo di navigasi'; + String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation'; @override String get optionsCheckUpdates => 'Periksa Pembaruan'; @@ -299,7 +298,7 @@ class AppLocalizationsId extends AppLocalizations { String get extensionsUninstall => 'Copot'; @override - String get storeTitle => 'Repo Ekstensi'; + String get storeTitle => 'Extension Repo'; @override String get storeSearch => 'Cari ekstensi...'; @@ -745,15 +744,15 @@ class AppLocalizationsId extends AppLocalizations { String get errorNoTracksFound => 'Tidak ada lagu ditemukan'; @override - String get errorUrlNotRecognized => 'Link tidak dikenali'; + String get errorUrlNotRecognized => 'Tautan tidak dikenali'; @override String get errorUrlNotRecognizedMessage => - 'Link ini tidak didukung. Pastikan URL benar dan ekstensi yang kompatibel sudah terpasang.'; + 'Tautan ini tidak didukung. Pastikan URL sudah benar dan ekstensi yang kompatibel telah terpasang.'; @override String get errorUrlFetchFailed => - 'Gagal memuat konten dari link ini. Silakan coba lagi.'; + 'Konten dari tautan ini gagal dimuat. Silakan coba lagi.'; @override String errorMissingExtensionSource(String item) { @@ -941,15 +940,15 @@ class AppLocalizationsId extends AppLocalizations { 'Jika lagu tidak tersedia di provider pertama, aplikasi akan otomatis mencoba yang berikutnya.'; @override - String get providerPriorityFallbackExtensionsTitle => 'Fallback Ekstensi'; + String get providerPriorityFallbackExtensionsTitle => 'Extension Fallback'; @override String get providerPriorityFallbackExtensionsDescription => - 'Pilih ekstensi unduhan terpasang mana yang boleh dipakai saat fallback otomatis. Provider bawaan tetap mengikuti urutan prioritas di atas.'; + 'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.'; @override String get providerPriorityFallbackExtensionsHint => - 'Hanya ekstensi aktif dengan kemampuan download provider yang ditampilkan di sini.'; + 'Only enabled extensions with download-provider capability are listed here.'; @override String get providerBuiltIn => 'Bawaan'; @@ -1334,7 +1333,7 @@ class AppLocalizationsId extends AppLocalizations { String get storeNewRepoUrlLabel => 'New Repository URL'; @override - String get storeLoadError => 'Gagal memuat repo'; + String get storeLoadError => 'Failed to load repository'; @override String get storeEmptyNoExtensions => 'No extensions available'; @@ -1343,7 +1342,7 @@ class AppLocalizationsId extends AppLocalizations { String get storeEmptyNoResults => 'No extensions found'; @override - String get extensionDefaultProvider => 'Bawaan (Deezer/Spotify)'; + String get extensionDefaultProvider => 'Default (Deezer)'; @override String get extensionDefaultProviderSubtitle => 'Gunakan pencarian bawaan'; @@ -1449,7 +1448,7 @@ class AppLocalizationsId extends AppLocalizations { @override String get extensionsFallbackSubtitle => - 'Pilih ekstensi unduhan terpasang yang boleh dipakai saat fallback'; + 'Choose which installed download extensions can be used as fallback'; @override String get extensionsNoDownloadProvider => @@ -2124,7 +2123,7 @@ class AppLocalizationsId extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Buka tab Repo untuk menemukan ekstensi yang berguna'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => @@ -2375,25 +2374,25 @@ class AppLocalizationsId extends AppLocalizations { String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed'; @override - String get queueFlacAction => 'Antrekan FLAC'; + String get queueFlacAction => 'Queue FLAC'; @override String queueFlacConfirmMessage(int count) { - return 'Cari kecocokan online untuk track yang dipilih lalu antrekan download FLAC.\n\nFile yang sudah ada tidak akan diubah atau dihapus.\n\nHanya kecocokan dengan keyakinan tinggi yang akan diantrikan otomatis.\n\n$count dipilih'; + return 'Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n$count selected'; } @override String queueFlacFindingProgress(int current, int total) { - return 'Mencari kecocokan FLAC... ($current/$total)'; + return 'Finding FLAC matches... ($current/$total)'; } @override String get queueFlacNoReliableMatches => - 'Tidak ada kecocokan online yang cukup meyakinkan untuk pilihan ini'; + 'No reliable online matches found for the selection'; @override String queueFlacQueuedWithSkipped(int addedCount, int skippedCount) { - return 'Menambahkan $addedCount track ke antrean, melewati $skippedCount'; + return 'Added $addedCount tracks to queue, skipped $skippedCount'; } @override @@ -2406,7 +2405,7 @@ class AppLocalizationsId extends AppLocalizations { @override String get trackConvertFormatSubtitle => - 'Konversi ke MP3, Opus, ALAC, atau FLAC'; + 'Convert to MP3, Opus, ALAC, or FLAC'; @override String get trackConvertTitle => 'Convert Audio'; @@ -2434,12 +2433,12 @@ class AppLocalizationsId extends AppLocalizations { String sourceFormat, String targetFormat, ) { - return 'Konversi dari $sourceFormat ke $targetFormat? (Lossless — tanpa kehilangan kualitas)\n\nFile asli akan dihapus setelah konversi.'; + return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.'; } @override String get trackConvertLosslessHint => - 'Konversi lossless — tanpa kehilangan kualitas'; + 'Lossless conversion — no quality loss'; @override String get trackConvertConverting => 'Converting audio...'; @@ -2893,19 +2892,19 @@ class AppLocalizationsId extends AppLocalizations { @override String get downloadCreatePlaylistSourceFolder => - 'Buat folder sumber playlist'; + 'Create playlist source folder'; @override String get downloadCreatePlaylistSourceFolderEnabled => - 'Unduhan dari playlist memakai Playlist/ lalu struktur folder normal Anda.'; + 'Playlist downloads use Playlist/ plus your normal folder structure.'; @override String get downloadCreatePlaylistSourceFolderDisabled => - 'Unduhan dari playlist hanya memakai struktur folder normal.'; + 'Playlist downloads use the normal folder structure only.'; @override String get downloadCreatePlaylistSourceFolderRedundant => - 'Mode Berdasarkan Playlist sudah menaruh unduhan ke dalam folder playlist.'; + 'By Playlist already places downloads inside a playlist folder.'; @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index baa3777e..030e46ed 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -21,13 +21,13 @@ class AppLocalizationsJa extends AppLocalizations { String get navSettings => '設定'; @override - String get navStore => 'ストア'; + String get navStore => 'Repo'; @override String get homeTitle => 'ホーム'; @override - String get homeSubtitle => 'Spotify のリンクを貼り付けるか、名前で検索します'; + String get homeSubtitle => 'Paste a supported URL or search by name'; @override String get homeSupports => 'サポート: トラック、アルバム、プレイリスト、アーティスト、URL'; @@ -214,10 +214,10 @@ class AppLocalizationsJa extends AppLocalizations { 'Parallel downloads may trigger rate limiting'; @override - String get optionsExtensionStore => '拡張ストア'; + String get optionsExtensionStore => 'Extension Repo'; @override - String get optionsExtensionStoreSubtitle => 'ナビゲーションにストアタブを表示'; + String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation'; @override String get optionsCheckUpdates => '更新を確認'; @@ -293,7 +293,7 @@ class AppLocalizationsJa extends AppLocalizations { String get extensionsUninstall => 'アンインストール'; @override - String get storeTitle => '拡張ストア'; + String get storeTitle => 'Extension Repo'; @override String get storeSearch => '拡張を検索...'; @@ -1330,7 +1330,7 @@ class AppLocalizationsJa extends AppLocalizations { String get storeEmptyNoResults => 'No extensions found'; @override - String get extensionDefaultProvider => 'デフォルト (Deezer/Spotify)'; + String get extensionDefaultProvider => 'Default (Deezer)'; @override String get extensionDefaultProviderSubtitle => '内蔵の検索を使用する'; @@ -2101,7 +2101,7 @@ class AppLocalizationsJa extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => @@ -2382,7 +2382,8 @@ class AppLocalizationsJa extends AppLocalizations { String get trackConvertFormat => '変換の形式'; @override - String get trackConvertFormatSubtitle => 'MP3 または Opus に変換'; + String get trackConvertFormatSubtitle => + 'Convert to MP3, Opus, ALAC, or FLAC'; @override String get trackConvertTitle => 'オーディオを変換'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index b7f01002..7168341d 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -21,13 +21,13 @@ class AppLocalizationsKo extends AppLocalizations { String get navSettings => 'Settings'; @override - String get navStore => 'Store'; + String get navStore => 'Repo'; @override String get homeTitle => 'Home'; @override - String get homeSubtitle => 'Spotify URL을 붙여 넣거나 검색'; + String get homeSubtitle => 'Paste a supported URL or search by name'; @override String get homeSupports => '지원 항목: 트랙, 앨범, 플레이리스트, 아티스트 URLs'; @@ -209,10 +209,10 @@ class AppLocalizationsKo extends AppLocalizations { String get optionsConcurrentWarning => '동시에 다수의 음반을 다운로드하면 속도 제한이 발생할 수 있습니다'; @override - String get optionsExtensionStore => '확장 기능 스토어'; + String get optionsExtensionStore => 'Extension Repo'; @override - String get optionsExtensionStoreSubtitle => '탐색 메뉴에 스토어 탭 표시'; + String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation'; @override String get optionsCheckUpdates => '업데이트 확인'; @@ -286,7 +286,7 @@ class AppLocalizationsKo extends AppLocalizations { String get extensionsUninstall => '삭제'; @override - String get storeTitle => '확장 기능 스토어'; + String get storeTitle => 'Extension Repo'; @override String get storeSearch => '확장 기능 검색'; @@ -1316,7 +1316,7 @@ class AppLocalizationsKo extends AppLocalizations { String get storeEmptyNoResults => 'No extensions found'; @override - String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; + String get extensionDefaultProvider => 'Default (Deezer)'; @override String get extensionDefaultProviderSubtitle => 'Use built-in search'; @@ -2094,7 +2094,7 @@ class AppLocalizationsKo extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => @@ -2375,7 +2375,8 @@ class AppLocalizationsKo extends AppLocalizations { String get trackConvertFormat => 'Convert Format'; @override - String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + String get trackConvertFormatSubtitle => + 'Convert to MP3, Opus, ALAC, or FLAC'; @override String get trackConvertTitle => 'Convert Audio'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index d89b3158..7f82b113 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -21,13 +21,13 @@ class AppLocalizationsNl extends AppLocalizations { String get navSettings => 'Settings'; @override - String get navStore => 'Store'; + String get navStore => 'Repo'; @override String get homeTitle => 'Home'; @override - String get homeSubtitle => 'Paste a Spotify link or search by name'; + String get homeSubtitle => 'Paste a supported URL or search by name'; @override String get homeSupports => 'Supports: Track, Album, Playlist, Artist URLs'; @@ -216,10 +216,10 @@ class AppLocalizationsNl extends AppLocalizations { 'Parallel downloaden kan leiden tot rate-limiting'; @override - String get optionsExtensionStore => 'Extension Store'; + String get optionsExtensionStore => 'Extension Repo'; @override - String get optionsExtensionStoreSubtitle => 'Show Store tab in navigation'; + String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation'; @override String get optionsCheckUpdates => 'Check for Updates'; @@ -296,7 +296,7 @@ class AppLocalizationsNl extends AppLocalizations { String get extensionsUninstall => 'Uninstall'; @override - String get storeTitle => 'Extension Store'; + String get storeTitle => 'Extension Repo'; @override String get storeSearch => 'Search extensions...'; @@ -1336,7 +1336,7 @@ class AppLocalizationsNl extends AppLocalizations { String get storeEmptyNoResults => 'No extensions found'; @override - String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; + String get extensionDefaultProvider => 'Default (Deezer)'; @override String get extensionDefaultProviderSubtitle => 'Use built-in search'; @@ -2114,7 +2114,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => @@ -2395,7 +2395,8 @@ class AppLocalizationsNl extends AppLocalizations { String get trackConvertFormat => 'Convert Format'; @override - String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + String get trackConvertFormatSubtitle => + 'Convert to MP3, Opus, ALAC, or FLAC'; @override String get trackConvertTitle => 'Convert Audio'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index cd81ead6..65283f0d 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -3436,13 +3436,13 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get navSettings => 'Configurações'; @override - String get navStore => 'Loja'; + String get navStore => 'Repo'; @override String get homeTitle => 'Início'; @override - String get homeSubtitle => 'Cole um link do Spotify ou procure por nome'; + String get homeSubtitle => 'Paste a supported URL or search by name'; @override String get homeSupports => @@ -3491,6 +3491,13 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get downloadFilenameFormat => 'Formato do Nome do Arquivo'; + @override + String get downloadSingleFilenameFormat => 'Single Filename Format'; + + @override + String get downloadSingleFilenameFormatDescription => + 'Filename pattern for singles and EPs. Uses the same tags as the album format.'; + @override String get downloadFolderOrganization => 'Organização de Pastas'; @@ -3537,6 +3544,13 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { return 'Usando a extensão: $extensionName'; } + @override + String get optionsDefaultSearchTab => 'Default Search Tab'; + + @override + String get optionsDefaultSearchTabSubtitle => + 'Choose which tab opens first for new search results.'; + @override String get optionsSwitchBack => 'Toque no Deezer ou Spotify para alternar de volta da extensão'; @@ -3573,6 +3587,38 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get optionsMaxQualityCoverSubtitle => 'Baixar capa do álbum com a mais alta resolução'; + @override + String get optionsReplayGain => 'ReplayGain'; + + @override + String get optionsReplayGainSubtitleOn => + 'Scan loudness and embed ReplayGain tags (EBU R128)'; + + @override + String get optionsReplayGainSubtitleOff => + 'Disabled: no loudness normalization tags'; + + @override + String get optionsArtistTagMode => 'Artist Tag Mode'; + + @override + String get optionsArtistTagModeDescription => + 'Choose how multiple artists are written into embedded tags.'; + + @override + String get optionsArtistTagModeJoined => 'Single joined value'; + + @override + String get optionsArtistTagModeJoinedSubtitle => + 'Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.'; + + @override + String get optionsArtistTagModeSplitVorbis => 'Split tags for FLAC/Opus'; + + @override + String get optionsArtistTagModeSplitVorbisSubtitle => + 'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.'; + @override String get optionsConcurrentDownloads => 'Downloads Simultâneos'; @@ -3589,11 +3635,10 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { 'Downloads simultâneos podem causar um limite da taxa (ratelimit)'; @override - String get optionsExtensionStore => 'Loja de Extensões'; + String get optionsExtensionStore => 'Extension Repo'; @override - String get optionsExtensionStoreSubtitle => - 'Mostrar aba da Loja na navegação'; + String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation'; @override String get optionsCheckUpdates => 'Procurar Atualizações'; @@ -3672,7 +3717,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get extensionsUninstall => 'Desinstalar'; @override - String get storeTitle => 'Loja de Extensões'; + String get storeTitle => 'Extension Repo'; @override String get storeSearch => 'Pesquisar extensões...'; @@ -3953,6 +3998,9 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get dialogImport => 'Importar'; + @override + String get dialogDownload => 'Download'; + @override String get dialogDiscard => 'Descartar'; @@ -4185,6 +4233,36 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get searchPlaylists => 'Playlists'; + @override + String get searchSortTitle => 'Sort Results'; + + @override + String get searchSortDefault => 'Default'; + + @override + String get searchSortTitleAZ => 'Title (A-Z)'; + + @override + String get searchSortTitleZA => 'Title (Z-A)'; + + @override + String get searchSortArtistAZ => 'Artist (A-Z)'; + + @override + String get searchSortArtistZA => 'Artist (Z-A)'; + + @override + String get searchSortDurationShort => 'Duration (Shortest)'; + + @override + String get searchSortDurationLong => 'Duration (Longest)'; + + @override + String get searchSortDateOldest => 'Release Date (Oldest)'; + + @override + String get searchSortDateNewest => 'Release Date (Newest)'; + @override String get tooltipPlay => 'Reproduzir'; @@ -4284,6 +4362,17 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get providerPriorityInfo => 'Se uma faixa não estiver disponível no primeiro provedor, o aplicativo irá tentar automaticamente a próxima.'; + @override + String get providerPriorityFallbackExtensionsTitle => 'Extension Fallback'; + + @override + String get providerPriorityFallbackExtensionsDescription => + 'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.'; + + @override + String get providerPriorityFallbackExtensionsHint => + 'Only enabled extensions with download-provider capability are listed here.'; + @override String get providerBuiltIn => 'Embutido'; @@ -4563,6 +4652,12 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get trackLyricsNotAvailable => 'Letra não disponível para esta faixa'; + @override + String get trackLyricsNotInFile => 'No lyrics found in this file'; + + @override + String get trackFetchOnlineLyrics => 'Fetch from Online'; + @override String get trackLyricsTimeout => 'A solicitação expirou. Tente novamente mais tarde.'; @@ -4632,7 +4727,48 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get storeClearFilters => 'Limpar filtros'; @override - String get extensionDefaultProvider => 'Padrão (Deezer/Spotify)'; + String get storeAddRepoTitle => 'Add Extension Repository'; + + @override + String get storeAddRepoDescription => + 'Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.'; + + @override + String get storeRepoUrlLabel => 'Repository URL'; + + @override + String get storeRepoUrlHint => 'https://github.com/user/repo'; + + @override + String get storeRepoUrlHelper => + 'e.g. https://github.com/user/extensions-repo'; + + @override + String get storeAddRepoButton => 'Add Repository'; + + @override + String get storeChangeRepoTooltip => 'Change repository'; + + @override + String get storeRepoDialogTitle => 'Extension Repository'; + + @override + String get storeRepoDialogCurrent => 'Current repository:'; + + @override + String get storeNewRepoUrlLabel => 'New Repository URL'; + + @override + String get storeLoadError => 'Failed to load repository'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + + @override + String get extensionDefaultProvider => 'Default (Deezer)'; @override String get extensionDefaultProviderSubtitle => 'Usar pesquisa integrada'; @@ -4734,6 +4870,13 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get extensionsDownloadPrioritySubtitle => 'Definir ordem do serviço de download'; + @override + String get extensionsFallbackTitle => 'Fallback Extensions'; + + @override + String get extensionsFallbackSubtitle => + 'Choose which installed download extensions can be used as fallback'; + @override String get extensionsNoDownloadProvider => 'Nenhuma extensão com provedor de download'; @@ -4784,6 +4927,38 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get qualityHiResFlacMaxSubtitle => '24-bit / até 192kHz'; + @override + String get downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @override + String get downloadLossy320Format => 'Lossy 320kbps Format'; + + @override + String get downloadLossy320FormatDesc => + 'Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.'; + + @override + String get downloadLossyMp3 => 'MP3 320kbps'; + + @override + String get downloadLossyMp3Subtitle => 'Best compatibility, ~10MB per track'; + + @override + String get downloadLossyOpus256 => 'Opus 256kbps'; + + @override + String get downloadLossyOpus256Subtitle => + 'Best quality Opus, ~8MB per track'; + + @override + String get downloadLossyOpus128 => 'Opus 128kbps'; + + @override + String get downloadLossyOpus128Subtitle => 'Smallest size, ~4MB per track'; + @override String get qualityNote => 'A qualidade real depende da faixa que estiver disponível no serviço'; @@ -4886,6 +5061,13 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get albumFolderArtistAlbumSinglesSubtitle => 'Artista/Álbum/ e Artista/Singles/'; + @override + String get albumFolderArtistAlbumFlat => 'Artist / Album (Singles flat)'; + + @override + String get albumFolderArtistAlbumFlatSubtitle => + 'Artist/Album/ and Artist/song.flac'; + @override String get downloadedAlbumDeleteSelected => 'Apagar Selecionados'; @@ -5081,6 +5263,25 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; @@ -5131,6 +5332,17 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { return '$_temp0'; } + @override + String libraryFilesUnit(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'files', + one: 'file', + ); + return '$_temp0'; + } + @override String libraryLastScanned(String time) { return 'Last scanned: $time'; @@ -5142,6 +5354,9 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get libraryScanning => 'Scanning...'; + @override + String get libraryScanFinalizing => 'Finalizing library...'; + @override String libraryScanProgress(String progress, int total) { return '$progress% of $total files'; @@ -5210,6 +5425,24 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get libraryFilterFormat => 'Format'; + @override + String get libraryFilterMetadata => 'Metadata'; + + @override + String get libraryFilterMetadataComplete => 'Complete metadata'; + + @override + String get libraryFilterMetadataMissingAny => 'Missing any metadata'; + + @override + String get libraryFilterMetadataMissingYear => 'Missing year'; + + @override + String get libraryFilterMetadataMissingGenre => 'Missing genre'; + + @override + String get libraryFilterMetadataMissingAlbumArtist => 'Missing album artist'; + @override String get libraryFilterSort => 'Sort'; @@ -5219,6 +5452,18 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get libraryFilterSortOldest => 'Oldest'; + @override + String get libraryFilterSortAlbumAsc => 'Album (A-Z)'; + + @override + String get libraryFilterSortAlbumDesc => 'Album (Z-A)'; + + @override + String get libraryFilterSortGenreAsc => 'Genre (A-Z)'; + + @override + String get libraryFilterSortGenreDesc => 'Genre (Z-A)'; + @override String get timeJustNow => 'Just now'; @@ -5305,7 +5550,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => @@ -5503,6 +5748,30 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get trackReEnrichOnlineSubtitle => 'Search metadata online and embed into file'; + @override + String get trackReEnrichFieldsTitle => 'Fields to update'; + + @override + String get trackReEnrichFieldCover => 'Cover Art'; + + @override + String get trackReEnrichFieldLyrics => 'Lyrics'; + + @override + String get trackReEnrichFieldBasicTags => 'Album, Album Artist'; + + @override + String get trackReEnrichFieldTrackInfo => 'Track & Disc Number'; + + @override + String get trackReEnrichFieldReleaseInfo => 'Date & ISRC'; + + @override + String get trackReEnrichFieldExtra => 'Genre, Label, Copyright'; + + @override + String get trackReEnrichSelectAll => 'Select All'; + @override String get trackEditMetadata => 'Edit Metadata'; @@ -5531,6 +5800,28 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed'; + @override + String get queueFlacAction => 'Queue FLAC'; + + @override + String queueFlacConfirmMessage(int count) { + return 'Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n$count selected'; + } + + @override + String queueFlacFindingProgress(int current, int total) { + return 'Finding FLAC matches... ($current/$total)'; + } + + @override + String get queueFlacNoReliableMatches => + 'No reliable online matches found for the selection'; + + @override + String queueFlacQueuedWithSkipped(int addedCount, int skippedCount) { + return 'Added $addedCount tracks to queue, skipped $skippedCount'; + } + @override String trackSaveFailed(String error) { return 'Failed: $error'; @@ -5540,7 +5831,8 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get trackConvertFormat => 'Convert Format'; @override - String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + String get trackConvertFormatSubtitle => + 'Convert to MP3, Opus, ALAC, or FLAC'; @override String get trackConvertTitle => 'Convert Audio'; @@ -5563,6 +5855,18 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { return 'Convert from $sourceFormat to $targetFormat at $bitrate?\n\nThe original file will be deleted after conversion.'; } + @override + String trackConvertConfirmMessageLossless( + String sourceFormat, + String targetFormat, + ) { + return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.'; + } + + @override + String get trackConvertLosslessHint => + 'Lossless conversion — no quality loss'; + @override String get trackConvertConverting => 'Converting audio...'; @@ -5816,6 +6120,17 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { return 'Convert $count $_temp0 to $format at $bitrate?\n\nOriginal files will be deleted after conversion.'; } + @override + String selectionBatchConvertConfirmMessageLossless(int count, String format) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.'; + } + @override String selectionBatchConvertProgress(int current, int total) { return 'Converting $current of $total...'; @@ -5838,4 +6153,704 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get downloadUseAlbumArtistForFoldersTrackSubtitle => 'Artist folders use Track Artist only'; + + @override + String get lyricsProvidersTitle => 'Lyrics Providers'; + + @override + String get lyricsProvidersDescription => + 'Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.'; + + @override + String get lyricsProvidersInfoText => + 'Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.'; + + @override + String lyricsProvidersEnabledSection(int count) { + return 'Enabled ($count)'; + } + + @override + String lyricsProvidersDisabledSection(int count) { + return 'Disabled ($count)'; + } + + @override + String get lyricsProvidersAtLeastOne => + 'At least one provider must remain enabled'; + + @override + String get lyricsProvidersSaved => 'Lyrics provider priority saved'; + + @override + String get lyricsProvidersDiscardContent => + 'You have unsaved changes that will be lost.'; + + @override + String get lyricsProviderLrclibDesc => 'Open-source synced lyrics database'; + + @override + String get lyricsProviderNeteaseDesc => + 'NetEase Cloud Music (good for Asian songs)'; + + @override + String get lyricsProviderMusixmatchDesc => + 'Largest lyrics database (multi-language)'; + + @override + String get lyricsProviderAppleMusicDesc => + 'Word-by-word synced lyrics (via proxy)'; + + @override + String get lyricsProviderQqMusicDesc => + 'QQ Music (good for Chinese songs, via proxy)'; + + @override + String get lyricsProviderExtensionDesc => 'Extension provider'; + + @override + String get safMigrationTitle => 'Storage Update Required'; + + @override + String get safMigrationMessage1 => + 'SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.'; + + @override + String get safMigrationMessage2 => + 'Please select your download folder again to switch to the new storage system.'; + + @override + String get safMigrationSuccess => 'Download folder updated to SAF mode'; + + @override + String get settingsDonate => 'Donate'; + + @override + String get settingsDonateSubtitle => 'Support SpotiFLAC-Mobile development'; + + @override + String get tooltipLoveAll => 'Love All'; + + @override + String get tooltipAddToPlaylist => 'Add to Playlist'; + + @override + String snackbarRemovedTracksFromLoved(int count) { + return 'Removed $count tracks from Loved'; + } + + @override + String snackbarAddedTracksToLoved(int count) { + return 'Added $count tracks to Loved'; + } + + @override + String get dialogDownloadAllTitle => 'Download All'; + + @override + String dialogDownloadAllMessage(int count) { + return 'Download $count tracks?'; + } + + @override + String get homeSkipAlreadyDownloaded => 'Skip already downloaded songs'; + + @override + String get homeGoToAlbum => 'Go to Album'; + + @override + String get homeAlbumInfoUnavailable => 'Album info not available'; + + @override + String get snackbarLoadingCueSheet => 'Loading CUE sheet...'; + + @override + String get snackbarMetadataSaved => 'Metadata saved successfully'; + + @override + String get snackbarFailedToEmbedLyrics => 'Failed to embed lyrics'; + + @override + String get snackbarFailedToWriteStorage => 'Failed to write back to storage'; + + @override + String snackbarError(String error) { + return 'Error: $error'; + } + + @override + String get snackbarNoActionDefined => 'No action defined for this button'; + + @override + String get noTracksFoundForAlbum => 'No tracks found for this album'; + + @override + String get downloadLocationSubtitle => + 'Choose storage mode for downloaded files.'; + + @override + String get storageModeAppFolder => 'App folder (non-SAF)'; + + @override + String get storageModeAppFolderSubtitle => 'Use default Music/SpotiFLAC path'; + + @override + String get storageModeSaf => 'SAF folder'; + + @override + String get storageModeSafSubtitle => + 'Pick folder via Android Storage Access Framework'; + + @override + String get downloadFilenameDescription => + 'Customize how your files are named.'; + + @override + String get downloadFilenameInsertTag => 'Tap to insert tag:'; + + @override + String get downloadSeparateSinglesEnabled => 'Albums/ and Singles/ folders'; + + @override + String get downloadSeparateSinglesDisabled => 'All files in same structure'; + + @override + String get downloadArtistNameFilters => 'Artist Name Filters'; + + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + + @override + String get downloadSongLinkRegion => 'SongLink Region'; + + @override + String get downloadNetworkCompatibilityMode => 'Network compatibility mode'; + + @override + String get downloadNetworkCompatibilityModeEnabled => + 'Enabled: try HTTP + accept invalid TLS certificates (unsafe)'; + + @override + String get downloadNetworkCompatibilityModeDisabled => + 'Off: strict HTTPS certificate validation (recommended)'; + + @override + String get downloadSelectServiceToEnable => + 'Select a built-in service to enable'; + + @override + String get downloadSelectTidalQobuz => + 'Select Tidal or Qobuz above to configure quality'; + + @override + String get downloadEmbedLyricsDisabled => + 'Disabled while Embed Metadata is turned off'; + + @override + String get downloadNeteaseIncludeTranslation => + 'Netease: Include Translation'; + + @override + String get downloadNeteaseIncludeTranslationEnabled => + 'Append translated lyrics when available'; + + @override + String get downloadNeteaseIncludeTranslationDisabled => + 'Use original lyrics only'; + + @override + String get downloadNeteaseIncludeRomanization => + 'Netease: Include Romanization'; + + @override + String get downloadNeteaseIncludeRomanizationEnabled => + 'Append romanized lyrics when available'; + + @override + String get downloadNeteaseIncludeRomanizationDisabled => 'Disabled'; + + @override + String get downloadAppleQqMultiPerson => 'Apple/QQ Multi-Person Word-by-Word'; + + @override + String get downloadAppleQqMultiPersonEnabled => + 'Enable v1/v2 speaker and [bg:] tags'; + + @override + String get downloadAppleQqMultiPersonDisabled => + 'Simplified word-by-word formatting'; + + @override + String get downloadMusixmatchLanguage => 'Musixmatch Language'; + + @override + String get downloadMusixmatchLanguageAuto => 'Auto (original)'; + + @override + String get downloadFilterContributing => + 'Filter contributing artists in Album Artist'; + + @override + String get downloadFilterContributingEnabled => + 'Album Artist metadata uses primary artist only'; + + @override + String get downloadFilterContributingDisabled => + 'Keep full Album Artist metadata value'; + + @override + String get downloadProvidersNoneEnabled => 'None enabled'; + + @override + String get downloadMusixmatchLanguageCode => 'Language code'; + + @override + String get downloadMusixmatchLanguageHint => 'auto / en / es / ja'; + + @override + String get downloadMusixmatchLanguageDesc => + 'Set preferred language code (example: en, es, ja). Leave empty for auto.'; + + @override + String get downloadMusixmatchAuto => 'Auto'; + + @override + String get downloadNetworkAnySubtitle => 'WiFi + Mobile Data'; + + @override + String get downloadNetworkWifiOnlySubtitle => + 'Pause downloads on mobile data'; + + @override + String get downloadSongLinkRegionDesc => + 'Used as userCountry for SongLink API lookup.'; + + @override + String get snackbarUnsupportedAudioFormat => 'Unsupported audio format'; + + @override + String get cacheRefresh => 'Refresh'; + + @override + String dialogDownloadPlaylistsMessage(int trackCount, int playlistCount) { + String _temp0 = intl.Intl.pluralLogic( + trackCount, + locale: localeName, + other: 'tracks', + one: 'track', + ); + String _temp1 = intl.Intl.pluralLogic( + playlistCount, + locale: localeName, + other: 'playlists', + one: 'playlist', + ); + return 'Download $trackCount $_temp0 from $playlistCount $_temp1?'; + } + + @override + String bulkDownloadPlaylistsButton(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'playlists', + one: 'playlist', + ); + return 'Download $count $_temp0'; + } + + @override + String get bulkDownloadSelectPlaylists => 'Select playlists to download'; + + @override + String get snackbarSelectedPlaylistsEmpty => + 'Selected playlists have no tracks'; + + @override + String playlistsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count playlists', + one: '1 playlist', + ); + return '$_temp0'; + } + + @override + String get editMetadataAutoFill => 'Auto-fill from online'; + + @override + String get editMetadataAutoFillDesc => + 'Select fields to fill automatically from online metadata'; + + @override + String get editMetadataAutoFillFetch => 'Fetch & Fill'; + + @override + String get editMetadataAutoFillSearching => 'Searching online...'; + + @override + String get editMetadataAutoFillNoResults => + 'No matching metadata found online'; + + @override + String editMetadataAutoFillDone(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'fields', + one: 'field', + ); + return 'Filled $count $_temp0 from online metadata'; + } + + @override + String get editMetadataAutoFillNoneSelected => + 'Select at least one field to auto-fill'; + + @override + String get editMetadataFieldTitle => 'Title'; + + @override + String get editMetadataFieldArtist => 'Artist'; + + @override + String get editMetadataFieldAlbum => 'Album'; + + @override + String get editMetadataFieldAlbumArtist => 'Album Artist'; + + @override + String get editMetadataFieldDate => 'Date'; + + @override + String get editMetadataFieldTrackNum => 'Track #'; + + @override + String get editMetadataFieldDiscNum => 'Disc #'; + + @override + String get editMetadataFieldGenre => 'Genre'; + + @override + String get editMetadataFieldIsrc => 'ISRC'; + + @override + String get editMetadataFieldLabel => 'Label'; + + @override + String get editMetadataFieldCopyright => 'Copyright'; + + @override + String get editMetadataFieldCover => 'Cover Art'; + + @override + String get editMetadataSelectAll => 'All'; + + @override + String get editMetadataSelectEmpty => 'Empty only'; + + @override + String queueDownloadingCount(int count) { + return 'Downloading ($count)'; + } + + @override + String get queueDownloadedHeader => 'Downloaded'; + + @override + String get queueFilteringIndicator => 'Filtering...'; + + @override + String queueTrackCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count tracks', + one: '1 track', + ); + return '$_temp0'; + } + + @override + String queueAlbumCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count albums', + one: '1 album', + ); + return '$_temp0'; + } + + @override + String get queueEmptyAlbums => 'No album downloads'; + + @override + String get queueEmptyAlbumsSubtitle => + 'Download multiple tracks from an album to see them here'; + + @override + String get queueEmptySingles => 'No single downloads'; + + @override + String get queueEmptySinglesSubtitle => + 'Single track downloads will appear here'; + + @override + String get queueEmptyHistory => 'No download history'; + + @override + String get queueEmptyHistorySubtitle => 'Downloaded tracks will appear here'; + + @override + String get selectionAllPlaylistsSelected => 'All playlists selected'; + + @override + String get selectionTapPlaylistsToSelect => 'Tap playlists to select'; + + @override + String get selectionSelectPlaylistsToDelete => 'Select playlists to delete'; + + @override + String get audioAnalysisTitle => 'Audio Quality Analysis'; + + @override + String get audioAnalysisDescription => + 'Verify lossless quality with spectrum analysis'; + + @override + String get audioAnalysisAnalyzing => 'Analyzing audio...'; + + @override + String get audioAnalysisSampleRate => 'Sample Rate'; + + @override + String get audioAnalysisBitDepth => 'Bit Depth'; + + @override + String get audioAnalysisChannels => 'Channels'; + + @override + String get audioAnalysisDuration => 'Duration'; + + @override + String get audioAnalysisNyquist => 'Nyquist'; + + @override + String get audioAnalysisFileSize => 'Size'; + + @override + String get audioAnalysisDynamicRange => 'Dynamic Range'; + + @override + String get audioAnalysisPeak => 'Peak'; + + @override + String get audioAnalysisRms => 'RMS'; + + @override + String get audioAnalysisSamples => 'Samples'; + + @override + String extensionsSearchWith(String providerName) { + return 'Search with $providerName'; + } + + @override + String get extensionsHomeFeedProvider => 'Home Feed Provider'; + + @override + String get extensionsHomeFeedDescription => + 'Choose which extension provides the home feed on the main screen'; + + @override + String get extensionsHomeFeedAuto => 'Auto'; + + @override + String get extensionsHomeFeedAutoSubtitle => + 'Automatically select the best available'; + + @override + String extensionsHomeFeedUse(String extensionName) { + return 'Use $extensionName home feed'; + } + + @override + String get extensionsNoHomeFeedExtensions => 'No extensions with home feed'; + + @override + String get sortAlphaAsc => 'A-Z'; + + @override + String get sortAlphaDesc => 'Z-A'; + + @override + String get cancelDownloadTitle => 'Cancel download?'; + + @override + String cancelDownloadContent(String trackName) { + return 'This will cancel the active download for \"$trackName\".'; + } + + @override + String get cancelDownloadKeep => 'Keep'; + + @override + String get metadataSaveFailedFfmpeg => 'Failed to save metadata via FFmpeg'; + + @override + String get metadataSaveFailedStorage => + 'Failed to write metadata back to storage'; + + @override + String snackbarFolderPickerFailed(String error) { + return 'Failed to open folder picker: $error'; + } + + @override + String get errorLoadAlbum => 'Failed to load album'; + + @override + String get errorLoadPlaylist => 'Failed to load playlist'; + + @override + String get errorLoadArtist => 'Failed to load artist'; + + @override + String get notifChannelDownloadName => 'Download Progress'; + + @override + String get notifChannelDownloadDesc => 'Shows download progress for tracks'; + + @override + String get notifChannelLibraryScanName => 'Library Scan'; + + @override + String get notifChannelLibraryScanDesc => 'Shows local library scan progress'; + + @override + String notifDownloadingTrack(String trackName) { + return 'Downloading $trackName'; + } + + @override + String notifFinalizingTrack(String trackName) { + return 'Finalizing $trackName'; + } + + @override + String get notifEmbeddingMetadata => 'Embedding metadata...'; + + @override + String notifAlreadyInLibraryCount(int completed, int total) { + return 'Already in Library ($completed/$total)'; + } + + @override + String get notifAlreadyInLibrary => 'Already in Library'; + + @override + String notifDownloadCompleteCount(int completed, int total) { + return 'Download Complete ($completed/$total)'; + } + + @override + String get notifDownloadComplete => 'Download Complete'; + + @override + String notifDownloadsFinished(int completed, int failed) { + return 'Downloads Finished ($completed done, $failed failed)'; + } + + @override + String get notifAllDownloadsComplete => 'All Downloads Complete'; + + @override + String notifTracksDownloadedSuccess(int count) { + return '$count tracks downloaded successfully'; + } + + @override + String get notifScanningLibrary => 'Scanning local library'; + + @override + String notifLibraryScanProgressWithTotal( + int scanned, + int total, + int percentage, + ) { + return '$scanned/$total files • $percentage%'; + } + + @override + String notifLibraryScanProgressNoTotal(int scanned, int percentage) { + return '$scanned files scanned • $percentage%'; + } + + @override + String get notifLibraryScanComplete => 'Library scan complete'; + + @override + String notifLibraryScanCompleteBody(int count) { + return '$count tracks indexed'; + } + + @override + String notifLibraryScanExcluded(int count) { + return '$count excluded'; + } + + @override + String notifLibraryScanErrors(int count) { + return '$count errors'; + } + + @override + String get notifLibraryScanFailed => 'Library scan failed'; + + @override + String get notifLibraryScanCancelled => 'Library scan cancelled'; + + @override + String get notifLibraryScanStopped => 'Scan stopped before completion.'; + + @override + String notifDownloadingUpdate(String version) { + return 'Downloading SpotiFLAC v$version'; + } + + @override + String notifUpdateProgress(String received, String total, int percentage) { + return '$received / $total MB • $percentage%'; + } + + @override + String get notifUpdateReady => 'Update Ready'; + + @override + String notifUpdateReadyBody(String version) { + return 'SpotiFLAC v$version downloaded. Tap to install.'; + } + + @override + String get notifUpdateFailed => 'Update Failed'; + + @override + String get notifUpdateFailedBody => + 'Could not download update. Try again later.'; } diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 73c69db7..13ed10eb 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -21,13 +21,13 @@ class AppLocalizationsRu extends AppLocalizations { String get navSettings => 'Настройки'; @override - String get navStore => 'Магазин'; + String get navStore => 'Repo'; @override String get homeTitle => 'Главная'; @override - String get homeSubtitle => 'Вставьте ссылку Spotify или ищите по названию'; + String get homeSubtitle => 'Paste a supported URL or search by name'; @override String get homeSupports => @@ -221,11 +221,10 @@ class AppLocalizationsRu extends AppLocalizations { 'Параллельные загрузки могут вызвать ограничение скорости'; @override - String get optionsExtensionStore => 'Магазин расширений'; + String get optionsExtensionStore => 'Extension Repo'; @override - String get optionsExtensionStoreSubtitle => - 'Показывать вкладку Магазин в гл. меню'; + String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation'; @override String get optionsCheckUpdates => 'Проверить обновления'; @@ -302,7 +301,7 @@ class AppLocalizationsRu extends AppLocalizations { String get extensionsUninstall => 'Удалить'; @override - String get storeTitle => 'Магазин расширений'; + String get storeTitle => 'Extension Repo'; @override String get storeSearch => 'Поиск расширений...'; @@ -636,9 +635,9 @@ class AppLocalizationsRu extends AppLocalizations { count, locale: localeName, other: 'треков', + one: 'трек', many: 'треков', few: 'трека', - one: 'трек', ); return 'Удалить $count $_temp0 из истории?\n\nЭто также удалит файлы из хранилища.'; } @@ -691,9 +690,9 @@ class AppLocalizationsRu extends AppLocalizations { count, locale: localeName, other: 'треков', + one: 'трек', many: 'треков', few: 'трека', - one: 'трек', ); return 'Удалено $count $_temp0'; } @@ -1161,9 +1160,9 @@ class AppLocalizationsRu extends AppLocalizations { count, locale: localeName, other: '$count треков', + one: '1 трек', many: '$count треков', few: '$count трека', - one: '$count трек', ); return '$_temp0'; } @@ -1357,7 +1356,7 @@ class AppLocalizationsRu extends AppLocalizations { String get storeEmptyNoResults => 'No extensions found'; @override - String get extensionDefaultProvider => 'По умолчанию (Deezer/Spotify)'; + String get extensionDefaultProvider => 'Default (Deezer)'; @override String get extensionDefaultProviderSubtitle => @@ -1670,9 +1669,9 @@ class AppLocalizationsRu extends AppLocalizations { count, locale: localeName, other: 'треков', + one: 'трек', many: 'треков', few: 'трека', - one: 'трек', ); return 'Удалить $count $_temp0 из этого альбома?\n\nЭто также удалит файлы из хранилища.'; } @@ -1694,9 +1693,9 @@ class AppLocalizationsRu extends AppLocalizations { count, locale: localeName, other: 'треков', + one: 'трек', many: 'треков', few: 'трека', - one: 'трек', ); return 'Удалить $count $_temp0'; } @@ -1927,9 +1926,9 @@ class AppLocalizationsRu extends AppLocalizations { count, locale: localeName, other: 'треков', + one: 'трек', many: 'треков', few: 'трека', - one: 'трек', ); return '$_temp0'; } @@ -2083,9 +2082,9 @@ class AppLocalizationsRu extends AppLocalizations { count, locale: localeName, other: '$count минут', + one: '1 минуту', many: '$count минут', few: '$count минуты', - one: '$count минуту', ); return '$_temp0 назад'; } @@ -2096,9 +2095,9 @@ class AppLocalizationsRu extends AppLocalizations { count, locale: localeName, other: '$count часов', + one: '1 час', many: '$count часов', few: '$count часа', - one: '$count час', ); return '$_temp0 назад'; } @@ -2164,7 +2163,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get tutorialExtensionsTip1 => - 'Просмотрите вкладку Магазина, чтобы найти полезные расширения'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => @@ -2448,7 +2447,8 @@ class AppLocalizationsRu extends AppLocalizations { String get trackConvertFormat => 'Переконвертировать формат'; @override - String get trackConvertFormatSubtitle => 'Конвертировать в MP3 или Opus'; + String get trackConvertFormatSubtitle => + 'Convert to MP3, Opus, ALAC, or FLAC'; @override String get trackConvertTitle => 'Конвертировать аудио'; @@ -2579,9 +2579,9 @@ class AppLocalizationsRu extends AppLocalizations { count, locale: localeName, other: '$count треков', + one: '1 трек', many: '$count треков', few: '$count трека', - one: '$count трек', ); return '$_temp0'; } @@ -2698,9 +2698,9 @@ class AppLocalizationsRu extends AppLocalizations { count, locale: localeName, other: 'треков', + one: 'трек', many: 'треков', few: 'трека', - one: 'трек', ); return 'Отправить $count $_temp0'; } @@ -2715,9 +2715,9 @@ class AppLocalizationsRu extends AppLocalizations { count, locale: localeName, other: 'треков', + one: 'трек', many: 'треков', few: 'трека', - one: 'трек', ); return 'Конвертировать $count $_temp0'; } diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index eda85991..7e5ed92d 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -12,30 +12,30 @@ class AppLocalizationsTr extends AppLocalizations { String get appName => 'SpotiFLAC'; @override - String get navHome => 'Ana Sayfa'; + String get navHome => 'Ana sayfa'; @override - String get navLibrary => 'Kitaplık'; + String get navLibrary => 'Kitaplığın'; @override String get navSettings => 'Ayarlar'; @override - String get navStore => 'Mağaza'; + String get navStore => 'Depo'; @override - String get homeTitle => 'Ana Sayfa'; + String get homeTitle => 'Ana sayfa'; @override String get homeSubtitle => - 'Bir Spotify bağlantısı yapıştırın veya şarkı arayın'; + 'Desteklenen bir URL yapıştırın veya isme göre arayın'; @override String get homeSupports => - 'Desteklenenler: Şarkı, Albüm, Çalma Listesi, Sanatçı bağlantıları'; + 'Desteklenen linkler: Şarkı, Albüm, Çalma Listesi, Sanatçı linkleri'; @override - String get homeRecent => 'Son Arananlar'; + String get homeRecent => 'En son'; @override String get historyFilterAll => 'Tümü'; @@ -47,7 +47,7 @@ class AppLocalizationsTr extends AppLocalizations { String get historyFilterSingles => 'Single\'lar'; @override - String get historySearchHint => 'Geçmişte ara...'; + String get historySearchHint => 'Arama geçmişi...'; @override String get settingsTitle => 'Ayarlar'; @@ -72,20 +72,20 @@ class AppLocalizationsTr extends AppLocalizations { @override String get downloadAskQualitySubtitle => - 'Her indirmede kalite seçme ekranını göster'; + 'Her indirmeden önce kalite seçim ekranını göster'; @override - String get downloadFilenameFormat => 'Dosya Adı Formatı'; + String get downloadFilenameFormat => 'Dosya adı formatı'; @override - String get downloadSingleFilenameFormat => 'Single Filename Format'; + String get downloadSingleFilenameFormat => 'Single Dosya Adı Formatı'; @override String get downloadSingleFilenameFormatDescription => - 'Filename pattern for singles and EPs. Uses the same tags as the album format.'; + 'Single ve EP\'ler için dosya adı örneği. Albüm formatıyla aynı etiketleri kullanır.'; @override - String get downloadFolderOrganization => 'Klasör Düzeni Seçimi'; + String get downloadFolderOrganization => 'Dosya Organizasyonu'; @override String get appearanceTitle => 'Görünüm'; @@ -100,14 +100,14 @@ class AppLocalizationsTr extends AppLocalizations { String get appearanceThemeDark => 'Koyu'; @override - String get appearanceDynamicColor => 'Dinamik Renkler'; + String get appearanceDynamicColor => 'Dinamik Renk'; @override String get appearanceDynamicColorSubtitle => - 'Uygulama renklerini duvar kağıdınızdan alır'; + 'Duvar kağıdının renklerini kullan'; @override - String get appearanceHistoryView => 'Geçmiş Görünümü'; + String get appearanceHistoryView => 'Geçmiş Düzeni'; @override String get appearanceHistoryViewList => 'Liste'; @@ -119,15 +119,15 @@ class AppLocalizationsTr extends AppLocalizations { String get optionsTitle => 'Seçenekler'; @override - String get optionsPrimaryProvider => 'Ana Sağlayıcı'; + String get optionsPrimaryProvider => 'Ana Kaynek'; @override String get optionsPrimaryProviderSubtitle => - 'Şarkı adıyla arama yaparken kullanılacak servis.'; + 'Şarkı ismi aratılırken kullanılan kaynak.'; @override String optionsUsingExtension(String extensionName) { - return 'Şu anki eklenti: $extensionName'; + return 'Kullanılan eklenti: $extensionName'; } @override @@ -139,77 +139,77 @@ class AppLocalizationsTr extends AppLocalizations { @override String get optionsSwitchBack => - 'Eklentiden çıkıp varsayılana dönmek için Deezer veya Spotify\'a dokunun'; + 'Dahili kaynaklara dönmek için Deezer veya Spotify\'a tıkla'; @override - String get optionsAutoFallback => 'Otomatik Geçiş'; + String get optionsAutoFallback => 'Diğerlerini dene'; @override String get optionsAutoFallbackSubtitle => - 'İndirme başarısız olursa otomatik olarak diğer servisleri dener'; + 'İndirme başarısız olursa diğer hizmetleri dene'; @override - String get optionsUseExtensionProviders => 'Eklenti Sağlayıcılarını Kullan'; + String get optionsUseExtensionProviders => 'Eklenti sağlayıcılarını kullan'; @override - String get optionsUseExtensionProvidersOn => - 'İndirme için önce eklentiler denenecek'; + String get optionsUseExtensionProvidersOn => 'Eklentiler ilk denenecek'; @override String get optionsUseExtensionProvidersOff => - 'Sadece yerleşik sağlayıcılar kullanılıyor'; + 'Sadece dahili sağlayıcıları kullan'; @override - String get optionsEmbedLyrics => 'Şarkı Sözlerini Gömer'; + String get optionsEmbedLyrics => 'Şarkı Sözlerini Göm'; @override String get optionsEmbedLyricsSubtitle => - 'Senkronize şarkı sözlerini FLAC dosyalarının içine gömer'; + 'Senkronize şarkı sözlerini FLAC dosyalarına göm'; @override - String get optionsMaxQualityCover => 'En Yüksek Kalite Albüm Kapağı'; + String get optionsMaxQualityCover => 'En Yüksek Kapak Kalitesi'; @override String get optionsMaxQualityCoverSubtitle => - 'Albüm kapağını bulunabilen en yüksek çözünürlükte indirir'; + 'En yüksek kalitedeki albüm kapaklarını indir'; @override String get optionsReplayGain => 'ReplayGain'; @override String get optionsReplayGainSubtitleOn => - 'Scan loudness and embed ReplayGain tags (EBU R128)'; + 'Ses yüksekliğini tara ve ReplayGain etiketlerini göm (EBU R128)'; @override String get optionsReplayGainSubtitleOff => - 'Disabled: no loudness normalization tags'; + 'Devre dışı: Ses normalleştirme etiketi yok'; @override - String get optionsArtistTagMode => 'Artist Tag Mode'; + String get optionsArtistTagMode => 'Sanatçı Etiketi Modu'; @override String get optionsArtistTagModeDescription => - 'Choose how multiple artists are written into embedded tags.'; + 'Birden fazla sanatçının gömülü etiketlere nasıl yazılacağını seçin.'; @override - String get optionsArtistTagModeJoined => 'Single joined value'; + String get optionsArtistTagModeJoined => 'Birleşik tek değer'; @override String get optionsArtistTagModeJoinedSubtitle => - 'Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.'; + 'Maksimum oynatıcı uyumluluğu için \'Sanatçı A, Sanatçı B\' şeklinde tek bir SANATÇI değeri yazın.'; @override - String get optionsArtistTagModeSplitVorbis => 'Split tags for FLAC/Opus'; + String get optionsArtistTagModeSplitVorbis => + 'FLAC/Opus için ayrılmış etiketler'; @override String get optionsArtistTagModeSplitVorbisSubtitle => - 'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.'; + 'FLAC ve Opus için her sanatçıya ayrı bir etiket yazın; MP3 ve M4A birleşik kalır.'; @override - String get optionsConcurrentDownloads => 'Eşzamanlı İndirmeler'; + String get optionsConcurrentDownloads => 'Eş Zamanlı İndirmeler'; @override - String get optionsConcurrentSequential => 'Sırayla (Tek tek)'; + String get optionsConcurrentSequential => 'Sıralı (Birer birer)'; @override String optionsConcurrentParallel(int count) { @@ -218,81 +218,79 @@ class AppLocalizationsTr extends AppLocalizations { @override String get optionsConcurrentWarning => - 'Aynı anda çok fazla indirme yapmak kısıtlamaya takılmanıza neden olabilir'; + 'Aynı anda birden fazla indirme sınırlamaya takılabilir'; @override - String get optionsExtensionStore => 'Eklenti Mağazası'; + String get optionsExtensionStore => 'Eklenti Deposu'; @override String get optionsExtensionStoreSubtitle => - 'Gezinme çubuğunda Mağaza sekmesini göster'; + 'Gezinme menüsünde Depo sekmesini göster'; @override - String get optionsCheckUpdates => 'Güncellemeleri Kontrol Et'; + String get optionsCheckUpdates => 'Güncelleştirmeleri Denetle'; @override - String get optionsCheckUpdatesSubtitle => - 'Yeni bir sürüm çıktığında haber ver'; + String get optionsCheckUpdatesSubtitle => 'Yeni sürüm çıktığında bildir'; @override String get optionsUpdateChannel => 'Güncelleme Kanalı'; @override - String get optionsUpdateChannelStable => 'Sadece kararlı sürümler'; + String get optionsUpdateChannelStable => 'Sadece stabil sürümler'; @override - String get optionsUpdateChannelPreview => - 'Ön izleme (Beta) sürümlerini de al'; + String get optionsUpdateChannelPreview => 'Önizleme sürümlerini al'; @override String get optionsUpdateChannelWarning => - 'Beta sürümler hatalar içerebilir veya tamamlanmamış özellikler barındırabilir'; + 'Önizleme sürümleri hatalar veya tamamlanmamış özellikler içerebilir'; @override String get optionsClearHistory => 'İndirme Geçmişini Temizle'; @override String get optionsClearHistorySubtitle => - 'İndirilen tüm şarkıları geçmişten siler'; + 'İndirilen bütün şarkıları geçmişten temizle'; @override - String get optionsDetailedLogging => 'Detaylı Hata Ayıklama (Log)'; + String get optionsDetailedLogging => 'Detaylı Günlükleme'; @override - String get optionsDetailedLoggingOn => 'Arka planda detaylı kayıt tutuluyor'; + String get optionsDetailedLoggingOn => 'Detaylı günlük kayıt ediliyor'; @override - String get optionsDetailedLoggingOff => 'Hata bildirimi yapacaksanız açın'; + String get optionsDetailedLoggingOff => 'Hata bildirmek için aç'; @override - String get optionsSpotifyCredentials => 'Spotify API Kimlik Bilgileri'; + String get optionsSpotifyCredentials => 'Spotify Kimlik Bilgileri'; @override String optionsSpotifyCredentialsConfigured(String clientId) { - return 'İstemci Kimliği (Client ID): $clientId...'; + return 'Client ID: $clientId...'; } @override String get optionsSpotifyCredentialsRequired => - 'Gerekli - ayarlamak için dokunun'; + 'Zorunlu - değiştirmek için tıkla'; @override String get optionsSpotifyWarning => - 'Spotify, kendi API kimlik bilgilerinizi kullanmanızı gerektirir. developer.spotify.com adresinden ücretsiz alabilirsiniz.'; + 'Spotify\'ın senin API kimlik bilgilerine ihtiyacı var. Onları developer.spotify.com\'dan alabilirsin'; @override String get optionsSpotifyDeprecationWarning => - 'Spotify API\'sindeki değişiklikler nedeniyle Spotify araması 3 Mart 2026\'da kullanımdan kaldırılacak. Lütfen Deezer\'a geçin.'; + 'Spotify API değişiklikleri nedeniyle Spotify araması 3 Mart 2026 tarihinde kullanımdan kaldırılacaktır. Lütfen Deezer\'a geçiş yapın.'; @override String get extensionsTitle => 'Eklentiler'; @override - String get extensionsDisabled => 'Devre dışı'; + String get extensionsDisabled => 'Devre Dışı'; @override String extensionsVersion(String version) { - return 'Sürüm $version'; + return 'Versiyon $version'; } @override @@ -304,16 +302,16 @@ class AppLocalizationsTr extends AppLocalizations { String get extensionsUninstall => 'Kaldır'; @override - String get storeTitle => 'Eklenti Mağazası'; + String get storeTitle => 'Uzantı Deposu'; @override - String get storeSearch => 'Eklentilerde ara...'; + String get storeSearch => 'Eklenti ara...'; @override - String get storeInstall => 'Yükle'; + String get storeInstall => 'Kur'; @override - String get storeInstalled => 'Yüklü'; + String get storeInstalled => 'Kuruldu'; @override String get storeUpdate => 'Güncelle'; @@ -325,23 +323,23 @@ class AppLocalizationsTr extends AppLocalizations { String get aboutContributors => 'Katkıda Bulunanlar'; @override - String get aboutMobileDeveloper => 'Mobil sürüm geliştiricisi'; + String get aboutMobileDeveloper => 'Mobil versiyon geliştiricisi'; @override - String get aboutOriginalCreator => 'Orijinal SpotiFLAC\'ın yaratıcısı'; + String get aboutOriginalCreator => 'Orijinal SpotiFLAC\'ın kurucusu'; @override String get aboutLogoArtist => - 'Uygulamanın harika logosunu tasarlayan yetenekli sanatçı!'; + 'Uygulama logomuzu yaratmış yetenekli sanatçımız!'; @override String get aboutTranslators => 'Çevirmenler'; @override - String get aboutSpecialThanks => 'Özel Teşekkürler'; + String get aboutSpecialThanks => 'Özel teşekkür'; @override - String get aboutLinks => 'Bağlantılar'; + String get aboutLinks => 'Linkler'; @override String get aboutMobileSource => 'Mobil kaynak kodu'; @@ -357,14 +355,14 @@ class AppLocalizationsTr extends AppLocalizations { @override String get aboutReportIssueSubtitle => - 'Karşılaştığınız sorunları bize iletin'; + 'Karşılaştığın herhangi bir problemi bildir'; @override String get aboutFeatureRequest => 'Özellik isteği'; @override String get aboutFeatureRequestSubtitle => - 'Uygulama için yeni özellikler önerin'; + 'Uygulama için yeni özellikler isteyin'; @override String get aboutTelegramChannel => 'Telegram Kanalı'; @@ -373,49 +371,49 @@ class AppLocalizationsTr extends AppLocalizations { String get aboutTelegramChannelSubtitle => 'Duyurular ve güncellemeler'; @override - String get aboutTelegramChat => 'Telegram Topluluğu'; + String get aboutTelegramChat => 'Telegram Grubu'; @override - String get aboutTelegramChatSubtitle => 'Diğer kullanıcılarla sohbet edin'; + String get aboutTelegramChatSubtitle => 'Diğer kullanıcılarla sohbet et'; @override - String get aboutSocial => 'Sosyal Medya'; + String get aboutSocial => 'Sosyal ağlar'; @override - String get aboutApp => 'Uygulama Bilgisi'; + String get aboutApp => 'Uygulama'; @override - String get aboutVersion => 'Sürüm'; + String get aboutVersion => 'Versiyon'; @override String get aboutBinimumDesc => - 'QQDL ve HiFi API\'nin yaratıcısı. Bu API olmasaydı Tidal indirmeleri var olamazdı!'; + 'QQDL ve HiFi API\'ın kurucusu. Bu API olmadan, Tidal indirmeleri olmazdı!'; @override String get aboutSachinsenalDesc => - 'Orijinal HiFi projesinin kurucusu. Tidal entegrasyonunun temel taşı!'; + 'Orijinal HiFi projesi kurucusu. Tidal entegrasyonun temeli!'; @override String get aboutSjdonadoDesc => - 'I Don\'t Have Spotify (IDHS) projesinin yaratıcısı. Günü kurtaran bağlantı çözümleyicimiz!'; + 'I Don\'t Have Spotify (IDHS) yaratıcısı. Günü kurtaran yedek bağlantı çözücü!'; @override String get aboutDabMusic => 'DAB Music'; @override String get aboutDabMusicDesc => - 'En iyi Qobuz yayın API\'si. Hi-Res indirmeler onlar olmadan mümkün olamazdı!'; + 'En iyi Qobuz streaming API\'ı. Yüksek kalite indirmeler bunun sayesinde!'; @override String get aboutSpotiSaver => 'SpotiSaver'; @override String get aboutSpotiSaverDesc => - 'Tidal Hi-Res FLAC altyapısı. Kayıpsız ses deneyiminin kilit parçası!'; + 'Tidal Hi-Res FLAC akış uç noktaları. Kayıpsız ses yapbozunun kilit bir parçası!'; @override String get aboutAppDescription => - 'Spotify şarkılarını Tidal ve Qobuz üzerinden kayıpsız kalitede indirin.'; + 'Spotify parçalarını Tidal ve Qobuz aracılığıyla kayıpsız kalitede indirin.'; @override String get artistAlbums => 'Albümler'; @@ -435,7 +433,7 @@ class AppLocalizationsTr extends AppLocalizations { } @override - String get trackMetadataService => 'Sağlayıcı'; + String get trackMetadataService => 'Hizmet'; @override String get trackMetadataPlay => 'Oynat'; @@ -453,35 +451,35 @@ class AppLocalizationsTr extends AppLocalizations { String get setupSkip => 'Şimdilik atla'; @override - String get setupStorageAccessRequired => 'Depolama İzni Gerekli'; + String get setupStorageAccessRequired => 'Depolama Erişimi Gerekli'; @override String get setupStorageAccessMessageAndroid11 => - 'Android 11 ve üzeri, müzikleri seçtiğiniz klasöre kaydedebilmek için \'Tüm dosyalara erişim\' izni gerektirir.'; + '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.'; @override String get setupOpenSettings => 'Ayarları Aç'; @override String get setupPermissionDeniedMessage => - 'İzin reddedildi. Devam etmek için lütfen gerekli izinleri verin.'; + 'İzin reddedildi. Devam etmek için lütfen bütün izinleri verin.'; @override String setupPermissionRequired(String permissionType) { - return '$permissionType İzni Gerekli'; + return '$permissionType İzni Zorunlu'; } @override String setupPermissionRequiredMessage(String permissionType) { - return 'En iyi deneyim için $permissionType iznine ihtiyacımız var. Bunu daha sonra Ayarlar\'dan değiştirebilirsiniz.'; + return 'En iyi deneyim için $permissionType izni zorunludur. Bunu ayarlardan daha sonra değiştirebilirsiniz.'; } @override - String get setupUseDefaultFolder => 'Varsayılan Klasör Kullanılsın mı?'; + String get setupUseDefaultFolder => 'Varsayılan Klasörü Kullan?'; @override String get setupNoFolderSelected => - 'Hiçbir klasör seçilmedi. İndirilenler için cihazınızdaki varsayılan Müzik klasörü kullanılsın mı?'; + 'Klasör seçilmedi. Varsayılan \"Music\" klasörünü kullanmak ister misiniz?'; @override String get setupUseDefault => 'Varsayılanı Kullan'; @@ -491,33 +489,31 @@ class AppLocalizationsTr extends AppLocalizations { @override String get setupDownloadLocationIosMessage => - 'iOS\'te indirilen dosyalar uygulamanın Belgeler klasörüne kaydedilir. Bunlara Dosyalar uygulaması üzerinden erişebilirsiniz.'; + 'iOS\'ta indirilenler uygulamanın \"Documents\" dosyasına kaydedilir. Onlara Dosyalar uygulamasından erişebilirsiniz.'; @override - String get setupAppDocumentsFolder => 'Uygulama Belgeleri Klasörü'; + String get setupAppDocumentsFolder => 'Uygulama Belgeler Klasörü'; @override String get setupAppDocumentsFolderSubtitle => - 'Önerilen - Dosyalar uygulamasıyla erişilebilir'; + 'Tavsiye edilen - Dosyalar uygulamasından erişilebilir'; @override String get setupChooseFromFiles => 'Dosyalar\'dan Seç'; @override - String get setupChooseFromFilesSubtitle => - 'iCloud veya başka bir konum seçin'; + String get setupChooseFromFilesSubtitle => 'iCloud veya başka konum seç'; @override String get setupIosEmptyFolderWarning => - 'iOS Kısıtlaması: Boş klasörler seçilemez. Lütfen içinde en az bir dosya olan bir klasör seçin.'; + 'iOS\'un sınırlaması: Boş klasörler seçilemiyor. İçinde en az bir dosya bulunan bir klasör seçin.'; @override String get setupIcloudNotSupported => - 'iCloud Drive desteklenmiyor. Lütfen uygulamanın Belgeler klasörünü kullanın.'; + 'iCloud Drive desteklenmiyor. Lütfen uygulama Belgeler klasörünü kullanın.'; @override - String get setupDownloadInFlac => - 'Spotify müziklerini FLAC formatında indirin'; + String get setupDownloadInFlac => 'Spotify şarkılarını FLAC olarak indirin'; @override String get setupStorageGranted => 'Depolama İzni Verildi!'; @@ -527,43 +523,43 @@ class AppLocalizationsTr extends AppLocalizations { @override String get setupStorageDescription => - 'İndirdiğiniz şarkıları kaydedebilmemiz için SpotiFLAC\'ın depolama iznine ihtiyacı var.'; + 'SpotiFLAC\'ın şarkılarınızı kaydetmek için depolama iznine ihtiyacı var.'; @override String get setupNotificationGranted => 'Bildirim İzni Verildi!'; @override - String get setupNotificationEnable => 'Bildirimleri Aç'; + String get setupNotificationEnable => 'Bildirimleri Etkinleştir'; @override - String get setupFolderChoose => 'İndirme Klasörü Seç'; + String get setupFolderChoose => 'İndirilecek Klasörü Seç'; @override String get setupFolderDescription => - 'İndirilen şarkıların nereye kaydedileceğini seçin.'; + 'İndirdiğin şarkıların kaydedileceği klasörü seç.'; @override String get setupSelectFolder => 'Klasör Seç'; @override - String get setupEnableNotifications => 'Bildirimleri Aç'; + String get setupEnableNotifications => 'Bildirimleri Etkinleştir'; @override String get setupNotificationBackgroundDescription => - 'İndirme durumları ve tamamlanan şarkılar hakkında anında bildirim alın. Bu, uygulama arka plandayken süreci takip etmenizi kolaylaştırır.'; + 'İndirmelerin durumu hakkında bildirim al. Bunu açmak uygulama arka plandayken indirmelerinizi takip etmenizi sağlar.'; @override String get setupSkipForNow => 'Şimdilik atla'; @override - String get setupNext => 'İleri'; + String get setupNext => 'Sıradaki'; @override - String get setupGetStarted => 'Hadi Başlayalım'; + String get setupGetStarted => 'Başla'; @override String get setupAllowAccessToManageFiles => - 'Lütfen sonraki ekranda \"Tüm dosyaları yönetme erişimine izin ver\" seçeneğini açın.'; + 'Lütfen bir sonraki ekranda \"Bütün dosyalara eriş\" iznini sağlayın.'; @override String get dialogCancel => 'İptal'; @@ -575,35 +571,35 @@ class AppLocalizationsTr extends AppLocalizations { String get dialogDelete => 'Sil'; @override - String get dialogRetry => 'Yeniden Dene'; + String get dialogRetry => 'Yeniden dene'; @override String get dialogClear => 'Temizle'; @override - String get dialogDone => 'Bitti'; + String get dialogDone => 'Tamamlandı'; @override - String get dialogImport => 'İçe Aktar'; + String get dialogImport => 'İçe aktar'; @override String get dialogDownload => 'İndir'; @override - String get dialogDiscard => 'Değişiklikleri Sil'; + String get dialogDiscard => 'Vazgeç'; @override String get dialogRemove => 'Kaldır'; @override - String get dialogUninstall => 'Sil'; + String get dialogUninstall => 'Kaldır'; @override - String get dialogDiscardChanges => 'Değişiklikler İptal Edilsin mi?'; + String get dialogDiscardChanges => 'Değişiklikleri İptal Et?'; @override String get dialogUnsavedChanges => - 'Kaydedilmemiş değişiklikleriniz var. Çıkmak istediğinize emin misiniz?'; + 'Kaydedilmeyen değişiklikler mevcut. Bu değişiklikleri iptal etmek istiyor musunuz?'; @override String get dialogClearAll => 'Tümünü Temizle'; @@ -613,14 +609,14 @@ class AppLocalizationsTr extends AppLocalizations { @override String get dialogRemoveExtensionMessage => - 'Bu eklentiyi kaldırmak istediğinize emin misiniz? Bu işlem geri alınamaz.'; + 'Bu eklentiyi kaldırmak istediğine emin misin? Bu işlem geri alınamaz.'; @override - String get dialogUninstallExtension => 'Eklentiyi Sil?'; + String get dialogUninstallExtension => 'Eklentiyi Kaldır?'; @override String dialogUninstallExtensionMessage(String extensionName) { - return '$extensionName eklentisini silmek istediğinize emin misiniz?'; + return '$extensionName eklentisini kaldırmak istediğine emin misin?'; } @override @@ -628,28 +624,28 @@ class AppLocalizationsTr extends AppLocalizations { @override String get dialogClearHistoryMessage => - 'Tüm indirme geçmişinizi silmek istediğinize emin misiniz? Bu işlem geri alınamaz.'; + 'Tüm indirme geçmişini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.'; @override - String get dialogDeleteSelectedTitle => 'Seçilenleri Sil'; + String get dialogDeleteSelectedTitle => 'Seçileni Sil'; @override String dialogDeleteSelectedMessage(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'şarkı', - one: 'şarkı', + other: 'şarkıyı', + one: 'şarkıyı', ); - return 'Geçmişten $count $_temp0 silinsin mi?\n\nBu işlem, indirilen dosyaları cihazınızdan da tamamen silecek.'; + return '$count $_temp0 geçmişten silmeye emin misiniz?\n\nBu işlem seçilenleri cihazınızdan da silecektir.'; } @override - String get dialogImportPlaylistTitle => 'Çalma Listesi İçe Aktar'; + String get dialogImportPlaylistTitle => 'Çalma listesini içe aktar'; @override String dialogImportPlaylistMessage(int count) { - return 'CSV dosyasında $count şarkı bulundu. İndirme sırasına eklensin mi?'; + return 'CSV\'de $count şarkı bulundu. İndirme kuyruğuna ekle?'; } @override @@ -659,17 +655,17 @@ class AppLocalizationsTr extends AppLocalizations { @override String snackbarAddedToQueue(String trackName) { - return '\"$trackName\" indirme sırasına eklendi'; + return '\"$trackName\" kuyruğa eklendi'; } @override String snackbarAddedTracksToQueue(int count) { - return '$count şarkı indirme sırasına eklendi'; + return '$count şarkı kuyruğa eklendi'; } @override String snackbarAlreadyDownloaded(String trackName) { - return '\"$trackName\" zaten inmiş durumda'; + return '\"$trackName\" zaten indirilmiş'; } @override @@ -681,10 +677,10 @@ class AppLocalizationsTr extends AppLocalizations { String get snackbarHistoryCleared => 'Geçmiş temizlendi'; @override - String get snackbarCredentialsSaved => 'API bilgileri kaydedildi'; + String get snackbarCredentialsSaved => 'Kimlik bilgileri kaydedildi'; @override - String get snackbarCredentialsCleared => 'API bilgileri silindi'; + String get snackbarCredentialsCleared => 'Kimlik bilgileri temizlendi'; @override String snackbarDeletedTracks(int count) { @@ -699,65 +695,65 @@ class AppLocalizationsTr extends AppLocalizations { @override String snackbarCannotOpenFile(String error) { - return 'Dosya açılamıyor: $error'; + return 'Dosya açılamadı: $error'; } @override String get snackbarFillAllFields => 'Lütfen tüm alanları doldurun'; @override - String get snackbarViewQueue => 'Sırayı Gör'; + String get snackbarViewQueue => 'Kuyruğu Görüntüle'; @override String snackbarUrlCopied(String platform) { - return '$platform bağlantısı panoya kopyalandı'; + return '$platform Bağlantı panoya kopyalandı'; } @override String get snackbarFileNotFound => 'Dosya bulunamadı'; @override - String get snackbarSelectExtFile => 'Lütfen bir .spotiflac-ext dosyası seçin'; + String get snackbarSelectExtFile => 'Lütfen .spotiflac-ext dosyasını seçin'; @override String get snackbarProviderPrioritySaved => 'Sağlayıcı önceliği kaydedildi'; @override String get snackbarMetadataProviderSaved => - 'Veri sağlayıcı önceliği kaydedildi'; + 'Meta veri sağlayıcı önceliği kaydedildi'; @override String snackbarExtensionInstalled(String extensionName) { - return '$extensionName başarıyla yüklendi.'; + return '$extensionName yüklendi.'; } @override String snackbarExtensionUpdated(String extensionName) { - return '$extensionName başarıyla güncellendi.'; + return '$extensionName güncellendi.'; } @override - String get snackbarFailedToInstall => 'Eklenti yüklenemedi'; + String get snackbarFailedToInstall => 'Eklenti yüklenirken hata oluştu'; @override - String get snackbarFailedToUpdate => 'Eklenti güncellenemedi'; + String get snackbarFailedToUpdate => 'Eklenti güncellenirken hata oluştu'; @override - String get errorRateLimited => 'Hız Sınırına Takıldınız'; + String get errorRateLimited => 'Aşırı istek gönderildi'; @override String get errorRateLimitedMessage => - 'Çok fazla istek gönderdiniz. Yeniden arama yapmadan önce lütfen biraz bekleyin.'; + 'Çok fazla istek. Lütfen arama yapmadan önce biraz bekleyin.'; @override - String get errorNoTracksFound => 'Şarkı bulunamadı'; + String get errorNoTracksFound => 'Parça bulunamadı'; @override - String get errorUrlNotRecognized => 'Bağlantı algılanamadı'; + String get errorUrlNotRecognized => 'Bağlantı tanınamadı'; @override String get errorUrlNotRecognizedMessage => - 'Bu bağlantı desteklenmiyor. Bağlantının doğru olduğundan ve gerekli eklentinin yüklü olduğundan emin olun.'; + 'Bu bağlantı desteklenmiyor. URL\'nin doğru olduğundan ve uyumlu bir uzantının yüklü olduğundan emin olun.'; @override String get errorUrlFetchFailed => @@ -765,29 +761,29 @@ class AppLocalizationsTr extends AppLocalizations { @override String errorMissingExtensionSource(String item) { - return '$item yüklenemiyor: Eklenti kaynağı eksik'; + return '$item yüklenemedi: Eksik eklenti kaynağı'; } @override String get actionPause => 'Duraklat'; @override - String get actionResume => 'Devam Et'; + String get actionResume => 'Devam et'; @override - String get actionCancel => 'İptal'; + String get actionCancel => 'Vazgeç'; @override String get actionSelectAll => 'Tümünü Seç'; @override - String get actionDeselect => 'Seçimi Kaldır'; + String get actionDeselect => 'Seçimi kaldır'; @override - String get actionRemoveCredentials => 'API Bilgilerini Sil'; + String get actionRemoveCredentials => 'Özellikleri kaldır'; @override - String get actionSaveCredentials => 'API Bilgilerini Kaydet'; + String get actionSaveCredentials => 'Özellikleri kaydet'; @override String selectionSelected(int count) { @@ -795,18 +791,18 @@ class AppLocalizationsTr extends AppLocalizations { } @override - String get selectionAllSelected => 'Tüm şarkılar seçildi'; + String get selectionAllSelected => 'Tüm parçalar seçildi'; @override - String get selectionSelectToDelete => 'Silinecek şarkıları seçin'; + String get selectionSelectToDelete => 'Silinecek parçaları seçin'; @override String progressFetchingMetadata(int current, int total) { - return 'Veriler alınıyor... $current/$total'; + return 'Meta verileri alınıyor... $current/$total'; } @override - String get progressReadingCsv => 'CSV dosyası okunuyor...'; + String get progressReadingCsv => 'CSV okunuyor...'; @override String get searchSongs => 'Şarkılar'; @@ -827,10 +823,10 @@ class AppLocalizationsTr extends AppLocalizations { String get searchSortDefault => 'Varsayılan'; @override - String get searchSortTitleAZ => 'Şarkı Adı (A-Z)'; + String get searchSortTitleAZ => 'Başlık (A-Z)'; @override - String get searchSortTitleZA => 'Şarkı Adı (Z-A)'; + String get searchSortTitleZA => 'Başlık (Z-A)'; @override String get searchSortArtistAZ => 'Sanatçı (A-Z)'; @@ -839,39 +835,39 @@ class AppLocalizationsTr extends AppLocalizations { String get searchSortArtistZA => 'Sanatçı (Z-A)'; @override - String get searchSortDurationShort => 'Süre (Önce kısalar)'; + String get searchSortDurationShort => 'Süre (en kısa)'; @override - String get searchSortDurationLong => 'Süre (Önce uzunlar)'; + String get searchSortDurationLong => 'Süre (en uzun)'; @override - String get searchSortDateOldest => 'Çıkış Tarihi (Önce eskiler)'; + String get searchSortDateOldest => 'Yayın Tarihi (En eski)'; @override - String get searchSortDateNewest => 'Çıkış Tarihi (Önce yeniler)'; + String get searchSortDateNewest => 'Yayın Tarihi (En yeni)'; @override String get tooltipPlay => 'Oynat'; @override - String get filenameFormat => 'Dosya Adı Formatı'; + String get filenameFormat => 'Dosya adı formatı'; @override String get filenameShowAdvancedTags => 'Gelişmiş etiketleri göster'; @override String get filenameShowAdvancedTagsDescription => - 'Boşluk doldurma ve tarih formatları için gelişmiş dosya adı etiketlerini açar'; + 'Parça numarası tamamlama ve tarih desenleri için biçimlendirilmiş etiketleri etkinleştir'; @override - String get folderOrganizationNone => 'Düzen yok'; + String get folderOrganizationNone => 'Organizasyon yok'; @override String get folderOrganizationByPlaylist => 'Çalma Listesine Göre'; @override String get folderOrganizationByPlaylistSubtitle => - 'Her çalma listesi için ayrı bir klasör oluşturur'; + 'Her çalma listesi için ayrı klasör'; @override String get folderOrganizationByArtist => 'Sanatçıya Göre'; @@ -880,48 +876,47 @@ class AppLocalizationsTr extends AppLocalizations { String get folderOrganizationByAlbum => 'Albüme Göre'; @override - String get folderOrganizationByArtistAlbum => 'Sanatçı / Albüm'; + String get folderOrganizationByArtistAlbum => 'Sanatçı/Albüm'; @override String get folderOrganizationDescription => - 'İndirilen dosyaları klasörlere düzenler'; + 'İndirilenleri klasörlerle organize et'; @override String get folderOrganizationNoneSubtitle => - 'Tüm dosyalar tek bir klasöre atılır'; + 'Her şey indirilen dosyasına kaydedilecek'; @override String get folderOrganizationByArtistSubtitle => - 'Her sanatçı için ayrı klasör oluşturur'; + 'Her sanatçı için ayrı klasör'; @override - String get folderOrganizationByAlbumSubtitle => - 'Her albüm için ayrı klasör oluşturur'; + String get folderOrganizationByAlbumSubtitle => 'Her albüm için ayrı klasör'; @override String get folderOrganizationByArtistAlbumSubtitle => - 'İç içe Sanatçı ve Albüm klasörleri oluşturur'; + 'Sanatçı klasörlerinin içinde Albüm klasörleri'; @override - String get updateAvailable => 'Güncelleme Var'; + String get updateAvailable => 'Güncelleme Mevcut'; @override String get updateLater => 'Daha Sonra'; @override - String get updateStartingDownload => 'İndirme başlatılıyor...'; + String get updateStartingDownload => 'İndirme başlıyor...'; @override String get updateDownloadFailed => 'İndirme başarısız'; @override - String get updateFailedMessage => 'Güncelleme dosyası indirilemedi'; + String get updateFailedMessage => 'Güncelleme indirilemedi'; @override - String get updateNewVersionReady => 'Uygulamanın yeni bir sürümü hazır'; + String get updateNewVersionReady => 'Yeni bir sürüm hazır'; @override - String get updateCurrent => 'Mevcut'; + String get updateCurrent => 'Şimdiki'; @override String get updateNew => 'Yeni'; @@ -930,73 +925,73 @@ class AppLocalizationsTr extends AppLocalizations { String get updateDownloading => 'İndiriliyor...'; @override - String get updateWhatsNew => 'Neler Yeni?'; + String get updateWhatsNew => 'Yenilikler'; @override - String get updateDownloadInstall => 'İndir ve Yükle'; + String get updateDownloadInstall => 'İndir & Yükle'; @override - String get updateDontRemind => 'Bir daha hatırlatma'; + String get updateDontRemind => 'Bir daha sorma'; @override - String get providerPriorityTitle => 'Sağlayıcı Önceliği'; + String get providerPriorityTitle => 'İndirme hizmetleri öncelik sırası'; @override String get providerPriorityDescription => - 'İndirme sağlayıcılarını sürükleyip sıralayın. Uygulama, şarkıları indirirken sağlayıcıları yukarıdan aşağıya doğru dener.'; + 'İndirme hizmetlerini sıralamak için kaydır. Uygulama şarkı indirirken hizmetleri yukarıdan aşağıya doğru deneyecektir.'; @override String get providerPriorityInfo => - 'Bir şarkı ilk sağlayıcıda bulunamazsa, uygulama otomatik olarak listedeki bir sonrakini dener.'; + 'Eğer bir şarkı ilk hizmette mevcut değilse uygulama otomatik olarak bir sonrakini deneyecektir.'; @override - String get providerPriorityFallbackExtensionsTitle => 'Extension Fallback'; + String get providerPriorityFallbackExtensionsTitle => 'Uzantı Yedeği'; @override String get providerPriorityFallbackExtensionsDescription => - 'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.'; + 'Otomatik yedekleme sırasında hangi yüklü indirme uzantılarının kullanılabileceğini seçin. Yerleşik sağlayıcılar hâlâ yukarıdaki öncelik sırasını takip eder.'; @override String get providerPriorityFallbackExtensionsHint => - 'Only enabled extensions with download-provider capability are listed here.'; + 'Burada yalnızca indirme sağlayıcısı yeteneğine sahip olan ve etkinleştirilmiş uzantılar listelenir.'; @override - String get providerBuiltIn => 'Yerleşik'; + String get providerBuiltIn => 'Dahili'; @override String get providerExtension => 'Eklenti'; @override - String get metadataProviderPriorityTitle => 'Arama Kaynağı Önceliği'; + String get metadataProviderPriorityTitle => 'Meta Veri Önceliği'; @override String get metadataProviderPriorityDescription => - 'Arama kaynaklarını sürükleyip sıralayın. Uygulama, şarkı ararken ve veri çekerken kaynakları yukarıdan aşağıya doğru dener.'; + 'Meta veri sağlayıcılarını yeniden sıralamak için sürükleyin. Uygulama, parça ararken ve meta verileri alırken sağlayıcıları yukarıdan aşağıya doğru deneyecektir.'; @override String get metadataProviderPriorityInfo => - 'Deezer\'da hız sınırı yoktur, bu yüzden ana kaynak olarak kullanılması tavsiye edilir. Spotify, çok fazla istekte bulunduğunuzda kısıtlama yapabilir.'; + 'Deezer\'da istek sınırı yoktur ve birincil olarak önerilir. Spotify, çok sayıda istekten sonra hız sınırlaması uygulayabilir.'; @override - String get metadataNoRateLimits => 'Hız sınırı yok'; + String get metadataNoRateLimits => 'İstek sınırı yok'; @override - String get metadataMayRateLimit => 'Hız sınırı yapabilir'; + String get metadataMayRateLimit => 'Hız sınırlaması uygulanabilir'; @override - String get logTitle => 'Hata Kayıtları (Log)'; + String get logTitle => 'Kayıtlar'; @override String get logCopied => 'Kayıtlar panoya kopyalandı'; @override - String get logSearchHint => 'Kayıtlarda ara...'; + String get logSearchHint => 'Kayıtları Ara...'; @override String get logFilterLevel => 'Seviye'; @override - String get logFilterSection => 'Filtrele'; + String get logFilterSection => 'Filtre'; @override String get logShareLogs => 'Kayıtları paylaş'; @@ -1005,21 +1000,21 @@ class AppLocalizationsTr extends AppLocalizations { String get logClearLogs => 'Kayıtları temizle'; @override - String get logClearLogsTitle => 'Kayıtları Temizle'; + String get logClearLogsTitle => 'Kayıtları temizle'; @override String get logClearLogsMessage => - 'Tüm hata kayıtlarını silmek istediğinize emin misiniz?'; + 'Tüm kayıtları temizlemek istediğinize emin misiniz?'; @override - String get logFilterBySeverity => 'Önem derecesine göre filtrele'; + String get logFilterBySeverity => 'Günlükleri önem derecesine göre filtrele'; @override String get logNoLogsYet => 'Henüz kayıt yok'; @override String get logNoLogsYetSubtitle => - 'Uygulamayı kullandıkça teknik kayıtlar burada görünecek'; + 'Uygulamayı kullandıkça günlükler burada görünecektir'; @override String logEntriesFiltered(int count) { @@ -1032,20 +1027,20 @@ class AppLocalizationsTr extends AppLocalizations { } @override - String get credentialsTitle => 'Spotify API Bilgileri'; + String get credentialsTitle => 'Spotify Kimlik Bilgileri'; @override String get credentialsDescription => - 'Kendi Spotify uygulamanızın kota limitlerini kullanmak için İstemci Kimliği (Client ID) ve Gizli Anahtarınızı (Client Secret) girin.'; + 'Kendi Spotify uygulama kotanızı kullanmak için Client ID ve Secret girin.'; @override - String get credentialsClientId => 'Client ID (İstemci Kimliği)'; + String get credentialsClientId => 'Client ID'; @override String get credentialsClientIdHint => 'Client ID yapıştır'; @override - String get credentialsClientSecret => 'Client Secret (Gizli Anahtar)'; + String get credentialsClientSecret => 'Client Secret'; @override String get credentialsClientSecretHint => 'Client Secret yapıştır'; @@ -1054,13 +1049,13 @@ class AppLocalizationsTr extends AppLocalizations { String get channelStable => 'Kararlı'; @override - String get channelPreview => 'Beta (Ön İzleme)'; + String get channelPreview => 'Önizleme'; @override String get sectionSearchSource => 'Arama Kaynağı'; @override - String get sectionDownload => 'İndirme'; + String get sectionDownload => 'İndir'; @override String get sectionPerformance => 'Performans'; @@ -1069,13 +1064,13 @@ class AppLocalizationsTr extends AppLocalizations { String get sectionApp => 'Uygulama'; @override - String get sectionData => 'Veri Yönetimi'; + String get sectionData => 'Veri'; @override - String get sectionDebug => 'Hata Ayıklama'; + String get sectionDebug => 'Hata ayıklama'; @override - String get sectionService => 'Servisler'; + String get sectionService => 'Servis'; @override String get sectionAudioQuality => 'Ses Kalitesi'; @@ -1084,44 +1079,44 @@ class AppLocalizationsTr extends AppLocalizations { String get sectionFileSettings => 'Dosya Ayarları'; @override - String get sectionLyrics => 'Şarkı Sözleri'; + String get sectionLyrics => 'Şarkı sözleri'; @override - String get lyricsMode => 'Şarkı Sözü Formatı'; + String get lyricsMode => 'Şarkı Sözü Modu'; @override String get lyricsModeDescription => - 'Şarkı sözlerinin nasıl kaydedileceğini seçin'; + 'Şarkı sözlerinin indirmelerinizle birlikte nasıl kaydedileceğini seçin'; @override String get lyricsModeEmbed => 'Dosyaya göm'; @override String get lyricsModeEmbedSubtitle => - 'Şarkı sözleri FLAC dosyasının içine işlenir'; + 'Şarkı sözleri FLAC meta verilerinin içinde saklanır'; @override String get lyricsModeExternal => 'Harici .lrc dosyası'; @override String get lyricsModeExternalSubtitle => - 'Bazı müzik çalarlar için şarkının yanına ayrı bir .lrc dosyası açar'; + 'Samsung Music gibi oynatıcılar için ayrı .lrc dosyası'; @override String get lyricsModeBoth => 'Her ikisi de'; @override String get lyricsModeBothSubtitle => - 'Hem dosyaya gömer hem de .lrc dosyası olarak kaydeder'; + 'Hem göm hem de .lrc dosyası olarak kaydet'; @override - String get sectionColor => 'Renkler'; + String get sectionColor => 'Renk'; @override String get sectionTheme => 'Tema'; @override - String get sectionLayout => 'Tasarım'; + String get sectionLayout => 'Düzen'; @override String get sectionLanguage => 'Dil'; @@ -1130,29 +1125,27 @@ class AppLocalizationsTr extends AppLocalizations { String get appearanceLanguage => 'Uygulama Dili'; @override - String get settingsAppearanceSubtitle => 'Temalar, renkler, görünümler'; + String get settingsAppearanceSubtitle => 'Tema, renkler, görünüm'; @override - String get settingsDownloadSubtitle => - 'İndirme servisi, ses kalitesi, dosya adı düzeni'; + String get settingsDownloadSubtitle => 'Servis, kalite, dosya adı formatı'; @override String get settingsOptionsSubtitle => - 'İndirme limitleri, şarkı sözleri, güncellemeler'; + 'Yedekleme, sözler, kapak resmi, güncellemeler'; @override - String get settingsExtensionsSubtitle => - 'Yeni müzik kaynakları ve eklentileri yönetin'; + String get settingsExtensionsSubtitle => 'İndirme sağlayıcılarını yönet'; @override String get settingsLogsSubtitle => - 'Sorun tespiti için uygulama kayıtlarına göz atın'; + 'Hata ayıklama için uygulama günlüklerini görüntüle'; @override String get loadingSharedLink => 'Paylaşılan bağlantı yükleniyor...'; @override - String get pressBackAgainToExit => 'Çıkmak için tekrar geri dokunun'; + String get pressBackAgainToExit => 'Çıkmak için tekrar geri basın'; @override String downloadAllCount(int count) { @@ -1164,8 +1157,8 @@ class AppLocalizationsTr extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count şarkı', - one: '1 şarkı', + other: '$count parça', + one: '1 parça', ); return '$_temp0'; } @@ -1174,37 +1167,37 @@ class AppLocalizationsTr extends AppLocalizations { String get trackCopyFilePath => 'Dosya yolunu kopyala'; @override - String get trackRemoveFromDevice => 'Cihazdan sil'; + String get trackRemoveFromDevice => 'Cihazdan kaldır'; @override - String get trackLoadLyrics => 'Sözleri Yükle'; + String get trackLoadLyrics => 'Şarkı Sözlerini Yükle'; @override - String get trackMetadata => 'Şarkı Künyesi'; + String get trackMetadata => 'Meta Veri'; @override String get trackFileInfo => 'Dosya Bilgisi'; @override - String get trackLyrics => 'Sözler'; + String get trackLyrics => 'Şarkı Sözleri'; @override String get trackFileNotFound => 'Dosya bulunamadı'; @override - String get trackOpenInDeezer => 'Deezer\'da Aç'; + String get trackOpenInDeezer => 'Deezer\'da aç'; @override - String get trackOpenInSpotify => 'Spotify\'da Aç'; + String get trackOpenInSpotify => 'Spotify\'da aç'; @override - String get trackTrackName => 'Şarkı Adı'; + String get trackTrackName => 'Parça adı'; @override String get trackArtist => 'Sanatçı'; @override - String get trackAlbumArtist => 'Albüm Sanatçısı'; + String get trackAlbumArtist => 'Albüm sanatçısı'; @override String get trackAlbum => 'Albüm'; @@ -1222,57 +1215,57 @@ class AppLocalizationsTr extends AppLocalizations { String get trackAudioQuality => 'Ses kalitesi'; @override - String get trackReleaseDate => 'Çıkış tarihi'; + String get trackReleaseDate => 'Yayın tarihi'; @override String get trackGenre => 'Tür'; @override - String get trackLabel => 'Plak Şirketi'; + String get trackLabel => 'Etiket / Müzik Şirketi'; @override String get trackCopyright => 'Telif Hakkı'; @override - String get trackDownloaded => 'İndirilme tarihi'; + String get trackDownloaded => 'İndirildi'; @override - String get trackCopyLyrics => 'Sözleri kopyala'; + String get trackCopyLyrics => 'Şarkı sözlerini kopyala'; @override - String get trackLyricsNotAvailable => 'Bu şarkının sözleri bulunamadı'; + String get trackLyricsNotAvailable => 'Bu parça için şarkı sözü mevcut değil'; @override - String get trackLyricsNotInFile => 'No lyrics found in this file'; + String get trackLyricsNotInFile => 'Bu dosyada şarkı sözü bulunamadı'; @override - String get trackFetchOnlineLyrics => 'Fetch from Online'; + String get trackFetchOnlineLyrics => 'İnternetten Getir'; @override String get trackLyricsTimeout => - 'Zaman aşımına uğradı. Lütfen daha sonra tekrar deneyin.'; + 'İstek zaman aşımına uğradı. Daha sonra tekrar deneyin.'; @override String get trackLyricsLoadFailed => 'Şarkı sözleri yüklenemedi'; @override - String get trackEmbedLyrics => 'Şarkı Sözlerini Gömer'; + String get trackEmbedLyrics => 'Şarkı Sözlerini Göm'; @override - String get trackLyricsEmbedded => 'Şarkı sözleri dosyaya başarıyla eklendi'; + String get trackLyricsEmbedded => 'Şarkı sözleri başarıyla gömüldü'; @override - String get trackInstrumental => 'Enstrümantal parça (Sözsüz)'; + String get trackInstrumental => 'Enstrümantal parça'; @override String get trackCopiedToClipboard => 'Panoya kopyalandı'; @override - String get trackDeleteConfirmTitle => 'Cihazdan silinsin mi?'; + String get trackDeleteConfirmTitle => 'Cihazdan kaldırılsın mı?'; @override String get trackDeleteConfirmMessage => - 'Bu işlem indirdiğiniz dosyayı tamamen silecek ve geçmişinizden kaldıracak.'; + 'Bu işlem, indirilen dosyayı kalıcı olarak silecek ve geçmişinizden kaldıracaktır.'; @override String get dateToday => 'Bugün'; @@ -1299,16 +1292,16 @@ class AppLocalizationsTr extends AppLocalizations { String get storeFilterAll => 'Tümü'; @override - String get storeFilterMetadata => 'Şarkı Verisi'; + String get storeFilterMetadata => 'Meta Veri'; @override - String get storeFilterDownload => 'İndirme'; + String get storeFilterDownload => 'İndir'; @override - String get storeFilterUtility => 'Araçlar'; + String get storeFilterUtility => 'Araç'; @override - String get storeFilterLyrics => 'Şarkı Sözü'; + String get storeFilterLyrics => 'Şarkı Sözleri'; @override String get storeFilterIntegration => 'Entegrasyon'; @@ -1317,82 +1310,81 @@ class AppLocalizationsTr extends AppLocalizations { String get storeClearFilters => 'Filtreleri temizle'; @override - String get storeAddRepoTitle => 'Eklenti Deposu (Repository) Ekle'; + String get storeAddRepoTitle => 'Uzantı Deposu Ekle'; @override String get storeAddRepoDescription => - 'Eklentilere göz atmak ve yüklemek için içinde registry.json dosyası olan bir GitHub depo bağlantısı girin.'; + 'Uzantılara göz atmak ve yüklemek için registry.json dosyası içeren bir GitHub depo URL\'si girin.'; @override - String get storeRepoUrlLabel => 'Depo Bağlantısı (URL)'; + String get storeRepoUrlLabel => 'Depo URL\'si'; @override - String get storeRepoUrlHint => 'https://github.com/kullaniciadi/depo'; + String get storeRepoUrlHint => 'https://github.com/user/repo'; @override String get storeRepoUrlHelper => - 'Örn: https://github.com/spoti/extensions-repo'; + 'örn. https://github.com/user/extensions-repo'; @override - String get storeAddRepoButton => 'Depoyu Ekle'; + String get storeAddRepoButton => 'Depo Ekle'; @override String get storeChangeRepoTooltip => 'Depoyu değiştir'; @override - String get storeRepoDialogTitle => 'Eklenti Deposu'; + String get storeRepoDialogTitle => 'Uzantı Deposu'; @override String get storeRepoDialogCurrent => 'Mevcut depo:'; @override - String get storeNewRepoUrlLabel => 'Yeni Depo Bağlantısı'; + String get storeNewRepoUrlLabel => 'Yeni Depo URL\'si'; @override - String get storeLoadError => 'Mağaza yüklenemedi'; + String get storeLoadError => 'Depo yüklenemedi'; @override - String get storeEmptyNoExtensions => 'Kullanılabilir eklenti yok'; + String get storeEmptyNoExtensions => 'Uygun uzantı yok'; @override - String get storeEmptyNoResults => 'Aramanıza uygun eklenti bulunamadı'; + String get storeEmptyNoResults => 'Uzantı bulunamadı'; @override String get extensionDefaultProvider => 'Varsayılan (Deezer)'; @override - String get extensionDefaultProviderSubtitle => - 'Uygulamanın kendi aramasını kullan'; + String get extensionDefaultProviderSubtitle => 'Yerleşik aramayı kullan'; @override - String get extensionAuthor => 'Geliştirici'; + String get extensionAuthor => 'Oluşturan'; @override - String get extensionId => 'Kimlik (ID)'; + String get extensionId => 'ID'; @override String get extensionError => 'Hata'; @override - String get extensionCapabilities => 'Yetenekler'; + String get extensionCapabilities => 'Özellikler'; @override - String get extensionMetadataProvider => 'Şarkı Verisi (Metadata) Kaynağı'; + String get extensionMetadataProvider => 'Meta Veri Sağlayıcı'; @override - String get extensionDownloadProvider => 'İndirme Sağlayıcısı'; + String get extensionDownloadProvider => 'İndirme Sağlayıcı'; @override - String get extensionLyricsProvider => 'Şarkı Sözü Sağlayıcısı'; + String get extensionLyricsProvider => 'Şarkı Sözü Sağlayıcı'; @override - String get extensionUrlHandler => 'Bağlantı Okuyucu'; + String get extensionUrlHandler => 'URL İşleyici'; @override String get extensionQualityOptions => 'Kalite Seçenekleri'; @override - String get extensionPostProcessingHooks => 'İndirme Sonrası İşlemler'; + String get extensionPostProcessingHooks => 'Son İşlem Kancaları'; @override String get extensionPermissions => 'İzinler'; @@ -1401,103 +1393,102 @@ class AppLocalizationsTr extends AppLocalizations { String get extensionSettings => 'Ayarlar'; @override - String get extensionRemoveButton => 'Eklentiyi Kaldır'; + String get extensionRemoveButton => 'Uzantıyı Kaldır'; @override - String get extensionUpdated => 'Son Güncelleme'; + String get extensionUpdated => 'Güncellendi'; @override String get extensionMinAppVersion => 'Minimum Uygulama Sürümü'; @override - String get extensionCustomTrackMatching => 'Özel Eşleştirme Algoritması'; + String get extensionCustomTrackMatching => 'Özel Parça Eşleştirme'; @override - String get extensionPostProcessing => 'İşlem Sonrası Özellikleri'; + String get extensionPostProcessing => 'Son İşlem'; @override String extensionHooksAvailable(int count) { - return '$count özel kanca (hook) mevcut'; + return '$count kanca kullanılabilir'; } @override String extensionPatternsCount(int count) { - return '$count bağlantı kalıbı'; + return '$count desen'; } @override String extensionStrategy(String strategy) { - return 'Eşleştirme Stratejisi: $strategy'; + return 'Strateji: $strategy'; } @override String get extensionsProviderPrioritySection => 'Sağlayıcı Önceliği'; @override - String get extensionsInstalledSection => 'Yüklü Eklentiler'; + String get extensionsInstalledSection => 'Kurulu uzantılar'; @override - String get extensionsNoExtensions => 'Henüz eklenti yüklenmemiş'; + String get extensionsNoExtensions => 'Hiçbir eklenti kurulmamış'; @override String get extensionsNoExtensionsSubtitle => - 'Yeni müzik kaynakları eklemek için .spotiflac-ext dosyalarını yükleyin'; + 'Yeni sağlayıcılar eklemek için .spotiflac-ext dosyalarını yükleyin'; @override - String get extensionsInstallButton => 'Eklenti Yükle'; + String get extensionsInstallButton => 'Uzantı Yükle'; @override String get extensionsInfoTip => - 'Eklentiler yeni veri ve indirme kaynakları ekleyebilir. Lütfen eklentileri sadece güvendiğiniz kaynaklardan yükleyin.'; + 'Uzantılar yeni meta veri ve indirme sağlayıcıları ekleyebilir. Yalnızca güvenilir kaynaklardan gelen uzantıları yükleyin.'; @override - String get extensionsInstalledSuccess => 'Eklenti başarıyla yüklendi'; + String get extensionsInstalledSuccess => 'Uzantı başarıyla yüklendi'; @override String get extensionsDownloadPriority => 'İndirme Önceliği'; @override String get extensionsDownloadPrioritySubtitle => - 'İndirme servislerinin deneneceği sırayı belirleyin'; + 'İndirme servisi sırasını ayarla'; @override - String get extensionsFallbackTitle => 'Fallback Extensions'; + String get extensionsFallbackTitle => 'Yedekleme Uzantıları'; @override String get extensionsFallbackSubtitle => - 'Choose which installed download extensions can be used as fallback'; + 'Hangi yüklü indirme uzantılarının yedekleme olarak kullanılabileceğini seçin'; @override String get extensionsNoDownloadProvider => - 'İndirme sağlayıcısı barındıran bir eklenti yok'; + 'İndirme sağlayıcısı olan uzantı yok'; @override - String get extensionsMetadataPriority => 'Arama Kaynağı Önceliği'; + String get extensionsMetadataPriority => 'Meta Veri Önceliği'; @override String get extensionsMetadataPrioritySubtitle => - 'Arama ve veri kaynaklarının sırasını belirleyin'; + 'Arama ve meta veri kaynağı sırasını ayarla'; @override String get extensionsNoMetadataProvider => - 'Şarkı verisi (metadata) barındıran bir eklenti yok'; + 'Meta veri sağlayıcısı içeren uzantı bulunamadı'; @override - String get extensionsSearchProvider => 'Arama Servisi'; + String get extensionsSearchProvider => 'Arama Sağlayıcısı'; @override - String get extensionsNoCustomSearch => - 'Özel arama özelliği olan bir eklenti yok'; + String get extensionsNoCustomSearch => 'Özel arama içeren uzantı bulunamadı'; @override String get extensionsSearchProviderDescription => - 'Şarkı aramak için kullanılacak servisi seçin'; + 'Parça aramak için hangi servisin kullanılacağını seçin'; @override String get extensionsCustomSearch => 'Özel arama'; @override - String get extensionsErrorLoading => 'Eklenti yüklenirken hata oluştu'; + String get extensionsErrorLoading => 'Uzantı yüklenirken hata oluştu'; @override String get qualityFlacLossless => 'FLAC Kayıpsız'; @@ -1506,121 +1497,122 @@ class AppLocalizationsTr extends AppLocalizations { String get qualityFlacLosslessSubtitle => '16-bit / 44.1kHz'; @override - String get qualityHiResFlac => 'Hi-Res FLAC (Yüksek Çözünürlüklü)'; + String get qualityHiResFlac => 'Hi-Res FLAC'; @override String get qualityHiResFlacSubtitle => '24-bit / 96kHz\'e kadar'; @override - String get qualityHiResFlacMax => 'Hi-Res FLAC Maksimum'; + String get qualityHiResFlacMax => 'Hi-Res FLAC Max'; @override String get qualityHiResFlacMaxSubtitle => '24-bit / 192kHz\'e kadar'; @override - String get downloadLossy320 => 'Kayıplı (Lossy) 320kbps'; + String get downloadLossy320 => 'Kayıplı 320kbps'; @override - String get downloadLossyFormat => 'Kayıplı Formatı'; + String get downloadLossyFormat => 'Kayıplı Format'; @override - String get downloadLossy320Format => 'Kayıplı (Lossy) 320kbps Formatı'; + String get downloadLossy320Format => 'Kayıplı 320kbps Formatı'; @override String get downloadLossy320FormatDesc => - 'Tidal\'dan 320kbps kalitesinde indirirken kullanılacak formatı seçin. Orijinal AAC yayını seçtiğiniz formata dönüştürülecektir.'; + 'Tidal 320kbps kayıplı indirmeler için çıktı formatını seçin. Orijinal AAC akışı seçtiğiniz formata dönüştürülecektir.'; @override String get downloadLossyMp3 => 'MP3 320kbps'; @override - String get downloadLossyMp3Subtitle => 'En iyi uyumluluk, şarkı başı ~10MB'; + String get downloadLossyMp3Subtitle => + 'En iyi uyumluluk, parça başına ~10 Mb'; @override String get downloadLossyOpus256 => 'Opus 256kbps'; @override String get downloadLossyOpus256Subtitle => - 'En iyi Opus kalitesi, şarkı başı ~8MB'; + 'En iyi Opus kalitesi, parça başına ~8 Mb'; @override String get downloadLossyOpus128 => 'Opus 128kbps'; @override - String get downloadLossyOpus128Subtitle => 'En küçük boyut, şarkı başı ~4MB'; + String get downloadLossyOpus128Subtitle => + 'En küçük boyut, parça başına ~4 Mb'; @override String get qualityNote => - 'Gerçek kalite, şarkının serviste hangi kalitede bulunduğuna bağlıdır.'; + 'Gerçek kalite, parçanın servisteki uygunluğuna bağlıdır'; @override String get downloadAskBeforeDownload => 'İndirmeden Önce Sor'; @override - String get downloadDirectory => 'İndirme Klasörü'; + String get downloadDirectory => 'İndirme Dizini'; @override - String get downloadSeparateSinglesFolder => 'Single\'ları Ayrı Klasöre Koy'; + String get downloadSeparateSinglesFolder => 'Ayrı Single Klasörü'; @override - String get downloadAlbumFolderStructure => 'Albüm Klasörü Düzeni'; + String get downloadAlbumFolderStructure => 'Albüm Klasör Yapısı'; @override String get downloadUseAlbumArtistForFolders => - 'Klasörler için Albüm Sanatçısını Kullan'; + 'Klasörler için Albüm Sanatçısı\'nı kullan'; @override String get downloadUsePrimaryArtistOnly => - 'Klasörlerde Sadece Ana Sanatçı (Düetleri Gizle)'; + 'Klasörler için yalnızca birincil sanatçıyı kullan'; @override String get downloadUsePrimaryArtistOnlyEnabled => - 'Konuk sanatçılar klasör adından silinir (Örn: Justin Bieber, Quavo → Justin Bieber)'; + 'Düet sanatçıları klasör adından kaldırılır (örn. Justin Bieber, Quavo → Justin Bieber)'; @override String get downloadUsePrimaryArtistOnlyDisabled => - 'Klasör adına tüm sanatçılar yazılır'; + 'Klasör adı için tam sanatçı dizesi kullanılır'; @override - String get downloadSelectQuality => 'Kaliteyi Seçin'; + String get downloadSelectQuality => 'Kalite seçin'; @override - String get downloadFrom => 'İndirme Kaynağı:'; + String get downloadFrom => 'İndirme Kaynağı'; @override - String get appearanceAmoledDark => 'AMOLED Koyu (Tam Siyah)'; + String get appearanceAmoledDark => 'AMOLED Koyu'; @override - String get appearanceAmoledDarkSubtitle => - 'Tamamen siyah arka plan (OLED ekranlar için)'; + String get appearanceAmoledDarkSubtitle => 'Saf siyah arka plan'; @override String get queueClearAll => 'Tümünü Temizle'; @override String get queueClearAllMessage => - 'Tüm indirme sırasını temizlemek istediğinize emin misiniz?'; + 'Tüm indirmeleri temizlemek istediğinizden emin misiniz?'; @override String get settingsAutoExportFailed => - 'Başarısız İndirmeleri Otomatik Dışa Aktar'; + 'Başarısız indirmeleri otomatik dışa aktar'; @override String get settingsAutoExportFailedSubtitle => - 'İndirilemeyen şarkıların listesini TXT dosyası olarak kaydeder'; + 'Başarısız indirmeleri otomatik olarak TXT dosyasına kaydet'; @override - String get settingsDownloadNetwork => 'İndirme İçin Kullanılacak Ağ'; + String get settingsDownloadNetwork => 'İndirme Ağı'; @override - String get settingsDownloadNetworkAny => 'Wi-Fi + Mobil Veri'; + String get settingsDownloadNetworkAny => 'WiFi + Mobil Veri'; @override - String get settingsDownloadNetworkWifiOnly => 'Sadece Wi-Fi'; + String get settingsDownloadNetworkWifiOnly => 'Yalnızca WiFi'; @override String get settingsDownloadNetworkSubtitle => - 'Sadece Wi-Fi seçildiğinde, mobil verideyken indirmeler otomatik duraklatılır.'; + 'İndirmeler için hangi ağın kullanılacağını seçin. Yalnızca WiFi olarak ayarlandığında, mobil veriye geçildiğinde indirmeler duraklatılır.'; @override String get albumFolderArtistAlbum => 'Sanatçı / Albüm'; @@ -1637,7 +1629,7 @@ class AppLocalizationsTr extends AppLocalizations { 'Albümler/Sanatçı Adı/[2005] Albüm Adı/'; @override - String get albumFolderAlbumOnly => 'Sadece Albüm'; + String get albumFolderAlbumOnly => 'Yalnızca Albüm'; @override String get albumFolderAlbumOnlySubtitle => 'Albümler/Albüm Adı/'; @@ -1649,19 +1641,19 @@ class AppLocalizationsTr extends AppLocalizations { String get albumFolderYearAlbumSubtitle => 'Albümler/[2005] Albüm Adı/'; @override - String get albumFolderArtistAlbumSingles => 'Sanatçı / Albüm + Single\'lar'; + String get albumFolderArtistAlbumSingles => 'Sanatçı / Albüm + Singlelar'; @override String get albumFolderArtistAlbumSinglesSubtitle => - 'Sanatçı/Albüm/ ve Sanatçı/Single\'lar/'; + 'Sanatçı/Albüm/ ve Sanatçı/Singlelar/'; @override String get albumFolderArtistAlbumFlat => - 'Sanatçı / Albüm (Single\'lar ayrı klasörsüz)'; + 'Sanatçı / Albüm (Singlelar alt klasörsüz)'; @override String get albumFolderArtistAlbumFlatSubtitle => - 'Sanatçı/Albüm/ ve Sanatçı/sarki.flac'; + 'Sanatçı/Albüm/ ve Sanatçı/şarkı.flac'; @override String get downloadedAlbumDeleteSelected => 'Seçilenleri Sil'; @@ -1671,10 +1663,10 @@ class AppLocalizationsTr extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'şarkı', - one: 'şarkı', + other: 'parça', + one: 'parça', ); - return 'Bu albümden $count $_temp0 silinsin mi?\n\nBu işlem, dosyaları cihazınızdan da tamamen silecek.'; + return 'Bu albümden $count $_temp0 parça silinsin mi?\n\nBu işlem dosyaları depolama alanından da kalıcı olarak silecektir.'; } @override @@ -1683,24 +1675,24 @@ class AppLocalizationsTr extends AppLocalizations { } @override - String get downloadedAlbumAllSelected => 'Tüm şarkılar seçildi'; + String get downloadedAlbumAllSelected => 'Tüm parçalar seçildi'; @override - String get downloadedAlbumTapToSelect => 'Seçmek için şarkılara dokunun'; + String get downloadedAlbumTapToSelect => 'Seçmek için parçalara dokunun'; @override String downloadedAlbumDeleteCount(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'Şarkıyı', - one: 'Şarkıyı', + other: 'parçayı', + one: 'parçayı', ); - return '$count $_temp0 Sil'; + return '$count $_temp0 sil'; } @override - String get downloadedAlbumSelectToDelete => 'Silinecek şarkıları seçin'; + String get downloadedAlbumSelectToDelete => 'Silinecek parçaları seçin'; @override String downloadedAlbumDiscHeader(int discNumber) { @@ -1720,7 +1712,7 @@ class AppLocalizationsTr extends AppLocalizations { String get recentTypePlaylist => 'Çalma Listesi'; @override - String get recentEmpty => 'Henüz yeni bir arama yok'; + String get recentEmpty => 'Henüz son kullanılan öğe yok'; @override String get recentShowAllDownloads => 'Tüm İndirmeleri Göster'; @@ -1731,30 +1723,30 @@ class AppLocalizationsTr extends AppLocalizations { } @override - String get discographyDownload => 'Tüm Diskografiyi İndir'; + String get discographyDownload => 'Diskografiyi İndir'; @override String get discographyDownloadAll => 'Tümünü İndir'; @override String discographyDownloadAllSubtitle(int count, int albumCount) { - return '$albumCount albüm/single üzerinden toplam $count şarkı'; + return '$albumCount yayından $count parça'; } @override - String get discographyAlbumsOnly => 'Sadece Albümler'; + String get discographyAlbumsOnly => 'Yalnızca Albümler'; @override String discographyAlbumsOnlySubtitle(int count, int albumCount) { - return '$albumCount albümden toplam $count şarkı'; + return '$albumCount albümden $count parça'; } @override - String get discographySinglesOnly => 'Sadece Single\'lar ve EP\'ler'; + String get discographySinglesOnly => 'Yalnızca Single\'lar ve EP\'ler'; @override String discographySinglesOnlySubtitle(int count, int albumCount) { - return '$albumCount single üzerinden toplam $count şarkı'; + return '$albumCount tekliden $count parça'; } @override @@ -1762,14 +1754,14 @@ class AppLocalizationsTr extends AppLocalizations { @override String get discographySelectAlbumsSubtitle => - 'İstediğiniz albümleri veya single\'ları kendiniz seçin'; + 'Belirli albümleri veya single\'ları seçin'; @override - String get discographyFetchingTracks => 'Şarkılar alınıyor...'; + String get discographyFetchingTracks => 'Parçalar getiriliyor...'; @override String discographyFetchingAlbum(int current, int total) { - return 'Alınıyor: $current / $total...'; + return '$total üzerinden $current getiriliyor...'; } @override @@ -1782,19 +1774,19 @@ class AppLocalizationsTr extends AppLocalizations { @override String discographyAddedToQueue(int count) { - return '$count şarkı indirme sırasına eklendi'; + return '$count parça kuyruğa eklendi'; } @override String discographySkippedDownloaded(int added, int skipped) { - return '$added şarkı eklendi, zaten inmiş olan $skipped şarkı atlandı'; + return '$added eklendi, $skipped zaten indirilmiş'; } @override - String get discographyNoAlbums => 'Bu sanatçıya ait albüm bulunamadı'; + String get discographyNoAlbums => 'Kullanılabilir albüm yok'; @override - String get discographyFailedToFetch => 'Bazı albümler yüklenemedi'; + String get discographyFailedToFetch => 'Bazı albümler getirilemedi'; @override String get sectionStorageAccess => 'Depolama Erişimi'; @@ -1803,38 +1795,37 @@ class AppLocalizationsTr extends AppLocalizations { String get allFilesAccess => 'Tüm Dosyalara Erişim'; @override - String get allFilesAccessEnabledSubtitle => - 'Cihazdaki herhangi bir klasöre yazabilir'; + String get allFilesAccessEnabledSubtitle => 'Herhangi bir klasöre yazabilir'; @override String get allFilesAccessDisabledSubtitle => - 'Sadece medya klasörleriyle sınırlı'; + 'Yalnızca medya klasörleriyle sınırlı'; @override String get allFilesAccessDescription => - 'Özel klasörlere kaydederken yazma hatası alıyorsanız bunu açın. Android 13 ve sonrasında bazı klasörlere erişim varsayılan olarak kısıtlanmıştır.'; + 'Özel klasörlere kaydederken yazma hatalarıyla karşılaşırsanız bunu etkinleştirin. Android 13 ve üzeri, varsayılan olarak belirli dizinlere erişimi kısıtlar.'; @override String get allFilesAccessDeniedMessage => - 'İzin reddedildi. Lütfen sistem ayarlarından \'Tüm dosyalara erişim\' iznini manuel olarak verin.'; + 'İzin reddedildi. Lütfen sistem ayarlarından \'Tüm dosyalara erişim\' iznini manuel olarak etkinleştirin.'; @override String get allFilesAccessDisabledMessage => - 'Tüm Dosyalara Erişim devre dışı. Uygulama sınırlı depolama izniyle çalışacak.'; + 'Tüm Dosyalara Erişim devre dışı bırakıldı. Uygulama kısıtlı depolama erişimi kullanacak.'; @override String get settingsLocalLibrary => 'Yerel Kitaplık'; @override String get settingsLocalLibrarySubtitle => - 'Telefonunuzdaki müzikleri tarayıp kopyaları bulun'; + 'Müziği tara ve kopyaları tespit et'; @override - String get settingsCache => 'Önbellek ve Depolama'; + String get settingsCache => 'Depolama ve Önbellek'; @override String get settingsCacheSubtitle => - 'Boyutu görüntüleyin ve gereksiz dosyaları temizleyin'; + 'Boyutu görüntüle ve önbelleğe alınmış verileri temizle'; @override String get libraryTitle => 'Yerel Kitaplık'; @@ -1843,91 +1834,90 @@ class AppLocalizationsTr extends AppLocalizations { String get libraryScanSettings => 'Tarama Ayarları'; @override - String get libraryEnableLocalLibrary => 'Yerel Kitaplık Taramasını Aç'; + String get libraryEnableLocalLibrary => 'Yerel Kitaplığı Etkinleştir'; @override String get libraryEnableLocalLibrarySubtitle => - 'İndirme yaparken elinizde olan şarkıları takip eder'; + 'Mevcut müziğinizi tarayın ve takip edin'; @override - String get libraryFolder => 'Taranacak Klasör'; + String get libraryFolder => 'Kitaplık Klasörü'; @override String get libraryFolderHint => 'Klasör seçmek için dokunun'; @override - String get libraryShowDuplicateIndicator => 'Kopya İndikatörünü Göster'; + String get libraryShowDuplicateIndicator => 'Kopya Belirtecini Göster'; @override String get libraryShowDuplicateIndicatorSubtitle => - 'Zaten indirmiş olduğunuz şarkıların yanında belirteç gösterir'; + 'Mevcut parçalar aranırken göster'; @override String get libraryAutoScan => 'Otomatik Tarama'; @override String get libraryAutoScanSubtitle => - 'Cihazınıza yeni eklenen müzikleri otomatik olarak bulur'; + 'Kitaplığınızı yeni dosyalar için otomatik olarak tarayın'; @override String get libraryAutoScanOff => 'Kapalı'; @override - String get libraryAutoScanOnOpen => 'Uygulama her açıldığında'; + String get libraryAutoScanOnOpen => 'Her uygulama açılışında'; @override - String get libraryAutoScanDaily => 'Günde bir'; + String get libraryAutoScanDaily => 'Günlük'; @override - String get libraryAutoScanWeekly => 'Haftada bir'; + String get libraryAutoScanWeekly => 'Haftalık'; @override - String get libraryActions => 'İşlemler'; + String get libraryActions => 'Eylemler'; @override String get libraryScan => 'Kitaplığı Tara'; @override - String get libraryScanSubtitle => 'Klasördeki müzik dosyalarını tarar'; + String get libraryScanSubtitle => 'Ses dosyaları için tara'; @override - String get libraryScanSelectFolderFirst => - 'Lütfen önce taranacak bir klasör seçin'; + String get libraryScanSelectFolderFirst => 'Önce bir klasör seçin'; @override String get libraryCleanupMissingFiles => 'Eksik Dosyaları Temizle'; @override String get libraryCleanupMissingFilesSubtitle => - 'Artık cihazınızda olmayan dosyaların kayıtlarını kaldırır'; + 'Eski dosya kalıntılarını temizleyin'; @override - String get libraryClear => 'Kitaplığı Temizle'; + String get libraryClear => 'Kitaplığı temizle'; @override - String get libraryClearSubtitle => 'Taranmış tüm şarkı kayıtlarını sıfırlar'; + String get libraryClearSubtitle => 'Taranan tüm parçaları sil'; @override - String get libraryClearConfirmTitle => 'Kitaplık Temizlensin mi?'; + String get libraryClearConfirmTitle => 'Kütüphaneyi temizle'; @override String get libraryClearConfirmMessage => - 'Uygulamanın kaydettiği tüm taranmış şarkı verileri silinecek. (Gerçek müzik dosyalarınız SİLİNMEYECEK).'; + 'Bu işlem, kitaplığınızdaki tüm taranmış parçaları siler. Asıl müzik dosyalarınız silinmez.'; @override - String get libraryAbout => 'Yerel Kitaplık Hakkında'; + String get libraryAbout => 'Yerel Kütüphane Hakkında'; @override String get libraryAboutDescription => - 'İndirme yaparken kopyaları (zaten inmiş olanları) tespit etmek için mevcut müzik arşivinizi tarar. FLAC, M4A, MP3, Opus ve OGG formatlarını destekler. Bilgiler şarkı dosyalarının kendi etiketlerinden (ID3 tag vb.) okunur.'; + 'İndirme işlemi sırasında mevcut müzik koleksiyonunuzu tarayarak yinelenen dosyaları tespit eder. FLAC, M4A, MP3, Opus ve OGG formatlarını destekler. Varsa, meta veriler dosya etiketlerinden okunur.'; @override String libraryTracksUnit(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'şarkı', - one: 'şarkı', + other: 'tracks', + one: 'track', ); return '$_temp0'; } @@ -1945,82 +1935,82 @@ class AppLocalizationsTr extends AppLocalizations { @override String libraryLastScanned(String time) { - return 'Son tarama: $time'; + return 'Last scanned: $time'; } @override - String get libraryLastScannedNever => 'Hiç taranmadı'; + String get libraryLastScannedNever => 'Never'; @override - String get libraryScanning => 'Taranıyor...'; + String get libraryScanning => 'Scanning...'; @override String get libraryScanFinalizing => 'Finalizing library...'; @override String libraryScanProgress(String progress, int total) { - return '%$progress (Toplam $total dosya)'; + return '$progress% of $total files'; } @override - String get libraryInLibrary => 'Cihazda Var'; + String get libraryInLibrary => 'In Library'; @override String libraryRemovedMissingFiles(int count) { - return 'Cihazda olmayan $count dosyanın kaydı temizlendi'; + return 'Removed $count missing files from library'; } @override - String get libraryCleared => 'Kitaplık kayıtları temizlendi'; + String get libraryCleared => 'Library cleared'; @override - String get libraryStorageAccessRequired => 'Depolama İzni Gerekli'; + String get libraryStorageAccessRequired => 'Storage Access Required'; @override String get libraryStorageAccessMessage => - 'Müzik kitaplığınızı taramak için SpotiFLAC\'ın depolama iznine ihtiyacı var. Lütfen ayarlardan izin verin.'; + 'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.'; @override - String get libraryFolderNotExist => 'Seçilen klasör artık mevcut değil'; + String get libraryFolderNotExist => 'Selected folder does not exist'; @override - String get librarySourceDownloaded => 'İndirildi'; + String get librarySourceDownloaded => 'Downloaded'; @override - String get librarySourceLocal => 'Cihazdan'; + String get librarySourceLocal => 'Local'; @override - String get libraryFilterAll => 'Tümü'; + String get libraryFilterAll => 'All'; @override - String get libraryFilterDownloaded => 'Uygulama İle İndirilenler'; + String get libraryFilterDownloaded => 'Downloaded'; @override - String get libraryFilterLocal => 'Yerel Dosyalar'; + String get libraryFilterLocal => 'Local'; @override String get libraryFilterTitle => 'Filtreler'; @override - String get libraryFilterReset => 'Sıfırla'; + String get libraryFilterReset => 'Reset'; @override - String get libraryFilterApply => 'Uygula'; + String get libraryFilterApply => 'Apply'; @override - String get libraryFilterSource => 'Kaynak'; + String get libraryFilterSource => 'Source'; @override - String get libraryFilterQuality => 'Kalite'; + String get libraryFilterQuality => 'Quality'; @override - String get libraryFilterQualityHiRes => 'Hi-Res (24-bit)'; + String get libraryFilterQualityHiRes => 'Hi-Res (24bit)'; @override - String get libraryFilterQualityCD => 'CD Kalitesi (16-bit)'; + String get libraryFilterQualityCD => 'CD (16bit)'; @override - String get libraryFilterQualityLossy => 'Kayıplı (Lossy)'; + String get libraryFilterQualityLossy => 'Lossy'; @override String get libraryFilterFormat => 'Format'; @@ -2044,13 +2034,13 @@ class AppLocalizationsTr extends AppLocalizations { String get libraryFilterMetadataMissingAlbumArtist => 'Missing album artist'; @override - String get libraryFilterSort => 'Sıralama'; + String get libraryFilterSort => 'Sort'; @override - String get libraryFilterSortLatest => 'En Yeniler'; + String get libraryFilterSortLatest => 'Latest'; @override - String get libraryFilterSortOldest => 'En Eskiler'; + String get libraryFilterSortOldest => 'Oldest'; @override String get libraryFilterSortAlbumAsc => 'Album (A-Z)'; @@ -2065,15 +2055,15 @@ class AppLocalizationsTr extends AppLocalizations { String get libraryFilterSortGenreDesc => 'Genre (Z-A)'; @override - String get timeJustNow => 'Az önce'; + String get timeJustNow => 'Just now'; @override String timeMinutesAgo(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count dakika önce', - one: '1 dakika önce', + other: '$count minutes ago', + one: '1 minute ago', ); return '$_temp0'; } @@ -2083,202 +2073,199 @@ class AppLocalizationsTr extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count saat önce', - one: '1 saat önce', + other: '$count hours ago', + one: '1 hour ago', ); return '$_temp0'; } @override - String get tutorialWelcomeTitle => 'SpotiFLAC\'a Hoş Geldiniz!'; + String get tutorialWelcomeTitle => 'Welcome to SpotiFLAC!'; @override String get tutorialWelcomeDesc => - 'En sevdiğiniz müzikleri kayıpsız kalitede nasıl indireceğinizi öğrenelim. Bu kısa rehber size temelleri gösterecek.'; + 'En sevdiğiniz müzikleri kayıpsız kalitede nasıl indirebileceğinizi öğrenelim. Bu kısa eğitim size temel bilgileri gösterecek.'; @override String get tutorialWelcomeTip1 => - 'Müzikleri bulmak için bir Spotify ya da Deezer bağlantısı yapıştırabilir veya adıyla arayabilirsiniz'; + 'Spotify, Deezer\'dan müzik indirin veya desteklenen herhangi bir URL\'yi yapıştırın'; @override String get tutorialWelcomeTip2 => - 'Şarkıları Tidal, Qobuz veya Deezer altyapısıyla en yüksek kalitede (FLAC) indirin'; + 'Tidal, Qobuz veya Deezer\'dan FLAC kalitesinde ses alın'; @override String get tutorialWelcomeTip3 => - 'Albüm kapağı, şarkı sözleri ve tüm şarkı verileri dosyanın içine otomatik olarak gömülür'; + 'Otomatik meta veri, kapak resmi ve şarkı sözü gömme'; @override - String get tutorialSearchTitle => 'Müzik Bulmak Çok Kolay'; + String get tutorialSearchTitle => 'Müzik Bulma'; @override String get tutorialSearchDesc => - 'İstediğiniz müzikleri bulmanın iki basit yolu var.'; + 'İndirmek istediğiniz müziği bulmanın iki kolay yolu vardır.'; @override - String get tutorialDownloadTitle => 'Müzikleri İndirme'; + String get tutorialDownloadTitle => 'Müzik İndirme'; @override String get tutorialDownloadDesc => - 'Şarkıları indirmek hızlı ve pürüzsüzdür. İşte süreç böyle işliyor:'; + 'Müzik indirmek basit ve hızlıdır. İşte nasıl çalıştığı.'; @override - String get tutorialLibraryTitle => 'Kişisel Kitaplığınız'; + String get tutorialLibraryTitle => 'Kitaplığınız'; @override String get tutorialLibraryDesc => - 'İndirdiğiniz tüm müzikler Kitaplık sekmesinde düzenli bir şekilde tutulur.'; + 'İndirdiğiniz tüm müzikler Kitaplık sekmesinde düzenlenir.'; @override String get tutorialLibraryTip1 => - 'İndirme ilerlemenizi ve sırayı Kitaplık sekmesinden takip edin'; + 'Kitaplık sekmesinden indirme ilerlemesini ve kuyruğu görüntüleyin'; @override String get tutorialLibraryTip2 => - 'İndirdiğiniz şarkıyı favori müzik çalarınızda açmak için üzerine dokunun'; + 'Müzik çalarınızla oynatmak için herhangi bir parçaya dokunun'; @override String get tutorialLibraryTip3 => - 'Daha rahat göz atmak için liste ve ızgara görünümleri arasında geçiş yapın'; + 'Daha iyi göz atmak için liste ve ızgara görünümü arasında geçiş yapın'; @override - String get tutorialExtensionsTitle => 'Eklentilerle Güçlendirin'; + String get tutorialExtensionsTitle => 'Uzantılar'; @override String get tutorialExtensionsDesc => - 'Topluluğun geliştirdiği eklentilerle uygulamanın sınırlarını aşın.'; + 'Topluluk uzantılarıyla uygulamanın yeteneklerini artırın.'; @override String get tutorialExtensionsTip1 => - 'İlginizi çekebilecek eklentileri keşfetmek için Mağaza sekmesine göz atın'; + 'Faydalı uzantıları keşfetmek için Depo sekmesine göz atın'; @override String get tutorialExtensionsTip2 => - 'Uygulamaya yepyeni indirme ve arama kaynakları ekleyin'; + 'Yeni indirme sağlayıcıları veya arama kaynakları ekleyin'; @override String get tutorialExtensionsTip3 => - 'Farklı şarkı sözü sağlayıcıları ve yepyeni özellikler kazanın'; + 'Şarkı sözleri, gelişmiş meta veriler ve daha fazla özellik edinin'; @override - String get tutorialSettingsTitle => 'Deneyiminizi Kişiselleştirin'; + String get tutorialSettingsTitle => 'Deneyiminizi Özelleştirin'; @override String get tutorialSettingsDesc => - 'Uygulamanın nasıl davranacağını Ayarlar menüsünden zevkinize göre özelleştirin.'; + 'Uygulamayı Ayarlar\'dan tercihlerinize göre kişiselleştirin.'; @override String get tutorialSettingsTip1 => - 'İndirme konumunu ve klasörleme biçimini değiştirin'; + 'İndirme konumunu ve klasör düzenini değiştirin'; @override String get tutorialSettingsTip2 => - 'Varsayılan ses kalitesini ve indirme formatınızı belirleyin'; + 'Varsayılan ses kalitesi ve format tercihlerini ayarlayın'; @override - String get tutorialSettingsTip3 => - 'Temayı, renkleri ve uygulamanın görünümünü ayarlayın'; + String get tutorialSettingsTip3 => 'Customize app theme and appearance'; @override String get tutorialReadyMessage => - 'İşte bu kadar! Artık favori müziklerinizi indirmeye hazırsınız.'; + 'You\'re all set! Start downloading your favorite music now.'; @override - String get libraryForceFullScan => 'Tam Taramaya Zorla'; + String get libraryForceFullScan => 'Force Full Scan'; @override - String get libraryForceFullScanSubtitle => - 'Önbelleği yoksayarak klasördeki tüm dosyaları baştan tarar'; + String get libraryForceFullScanSubtitle => 'Rescan all files, ignoring cache'; @override - String get cleanupOrphanedDownloads => 'Geçersiz İndirmeleri Temizle'; + String get cleanupOrphanedDownloads => 'Cleanup Orphaned Downloads'; @override String get cleanupOrphanedDownloadsSubtitle => - 'Cihazdan silinmiş dosyalara ait eski geçmiş kayıtlarını kaldırır'; + 'Remove history entries for files that no longer exist'; @override String cleanupOrphanedDownloadsResult(int count) { - return 'Geçmişten $count geçersiz kayıt kaldırıldı'; + return 'Removed $count orphaned entries from history'; } @override - String get cleanupOrphanedDownloadsNone => - 'Temizlenecek geçersiz kayıt bulunamadı'; + String get cleanupOrphanedDownloadsNone => 'No orphaned entries found'; @override - String get cacheTitle => 'Önbellek ve Depolama'; + String get cacheTitle => 'Storage & Cache'; @override - String get cacheSummaryTitle => 'Önbellek Özeti'; + String get cacheSummaryTitle => 'Cache overview'; @override String get cacheSummarySubtitle => - 'Önbelleği temizlemek indirdiğiniz müzik dosyalarını SİLMEZ.'; + 'Clearing cache will not remove downloaded music files.'; @override String cacheEstimatedTotal(String size) { - return 'Tahmini önbellek kullanımı: $size'; + return 'Estimated cache usage: $size'; } @override - String get cacheSectionStorage => 'Önbelleğe Alınan Veriler'; + String get cacheSectionStorage => 'Cached Data'; @override - String get cacheSectionMaintenance => 'Bakım ve Temizlik'; + String get cacheSectionMaintenance => 'Maintenance'; @override - String get cacheAppDirectory => 'Uygulama Önbelleği'; + String get cacheAppDirectory => 'App cache directory'; @override String get cacheAppDirectoryDesc => - 'İnternet yanıtları, küçük resimler ve uygulamanın tuttuğu geçici dosyalar.'; + 'HTTP responses, WebView data, and other temporary app data.'; @override - String get cacheTempDirectory => 'Geçici Klasör'; + String get cacheTempDirectory => 'Temporary directory'; @override String get cacheTempDirectoryDesc => - 'İndirme ve ses dönüştürme işlemleri sırasında oluşan artık dosyalar.'; + 'Temporary files from downloads and audio conversion.'; @override - String get cacheCoverImage => 'Kapak Resmi Önbelleği'; + String get cacheCoverImage => 'Cover image cache'; @override String get cacheCoverImageDesc => - 'Önceden yüklenmiş albüm kapakları. Silinirse tekrar görüntülediğinizde yeniden indirilir.'; + 'Downloaded album and track cover art. Will re-download when viewed.'; @override - String get cacheLibraryCover => 'Kitaplık Kapağı Önbelleği'; + String get cacheLibraryCover => 'Library cover cache'; @override String get cacheLibraryCoverDesc => - 'Yerel müzik dosyalarınızdan çıkarılmış kapaklar. Silinirse sonraki taramada yeniden oluşturulur.'; + 'Cover art extracted from local music files. Will re-extract on next scan.'; @override - String get cacheExploreFeed => 'Keşfet Akışı Önbelleği'; + String get cacheExploreFeed => 'Explore feed cache'; @override String get cacheExploreFeedDesc => - 'Keşfet sekmesindeki (yeni çıkanlar vb.) içerikler. Silerseniz sayfayı açtığınızda yenilenir.'; + 'Explore tab content (new releases, trending). Will refresh on next visit.'; @override - String get cacheTrackLookup => 'Şarkı Kimliği Önbelleği'; + String get cacheTrackLookup => 'Track lookup cache'; @override String get cacheTrackLookupDesc => - 'Spotify/Deezer ID eşleşmeleri. Temizlerseniz ilk birkaç aramanız biraz yavaşlayabilir.'; + 'Spotify/Deezer track ID lookups. Clearing may slow next few searches.'; @override String get cacheCleanupUnusedDesc => - 'Artık cihazınızda var olmayan dosyaların geçmiş kayıtlarını ve kitaplık verilerini temizler.'; + 'Remove orphaned download history and library entries for missing files.'; @override - String get cacheNoData => 'Veri yok'; + String get cacheNoData => 'No cached data'; @override String cacheSizeWithFiles(String size, int count) { - return '$size ($count dosya)'; + return '$size in $count files'; } @override @@ -2288,70 +2275,68 @@ class AppLocalizationsTr extends AppLocalizations { @override String cacheEntries(int count) { - return '$count kayıt'; + return '$count entries'; } @override String cacheClearSuccess(String target) { - return 'Temizlendi: $target'; + return 'Cleared: $target'; } @override - String get cacheClearConfirmTitle => 'Önbelleği Temizle?'; + String get cacheClearConfirmTitle => 'Clear cache?'; @override String cacheClearConfirmMessage(String target) { - return 'Sadece \"$target\" için olan önbellek silinecek. İndirdiğiniz hiçbir müzik dosyasına dokunulmayacak.'; + return 'This will clear cached data for $target. Downloaded music files will not be deleted.'; } @override - String get cacheClearAllConfirmTitle => 'Tüm Önbelleği Temizle?'; + String get cacheClearAllConfirmTitle => 'Clear all cache?'; @override String get cacheClearAllConfirmMessage => - 'Bu sayfadaki tüm önbellek kategorileri temizlenecek. İndirdiğiniz müzik dosyaları kesinlikle SİLİNMEYECEK.'; + 'This will clear all cache categories on this page. Downloaded music files will not be deleted.'; @override - String get cacheClearAll => 'Tüm Önbelleği Temizle'; + String get cacheClearAll => 'Clear all cache'; @override - String get cacheCleanupUnused => 'Gereksiz Dosyaları Temizle'; + String get cacheCleanupUnused => 'Cleanup unused data'; @override String get cacheCleanupUnusedSubtitle => - 'Cihazda olmayan dosyalara ait geçmiş ve kitaplık kayıtlarını kaldırır'; + 'Remove orphaned download history and missing library entries'; @override String cacheCleanupResult(int downloadCount, int libraryCount) { - return 'Temizlik Bitti: $downloadCount geçersiz geçmiş, $libraryCount eksik kitaplık kaydı kaldırıldı'; + return 'Cleanup completed: $downloadCount orphaned downloads, $libraryCount missing library entries'; } @override - String get cacheRefreshStats => 'Boyutları Yenile'; + String get cacheRefreshStats => 'Refresh stats'; @override - String get trackSaveCoverArt => 'Albüm Kapağını Kaydet'; + String get trackSaveCoverArt => 'Save Cover Art'; @override - String get trackSaveCoverArtSubtitle => - 'Albüm kapağını resim (.jpg) dosyası olarak dışa aktar'; + String get trackSaveCoverArtSubtitle => 'Save album art as .jpg file'; @override - String get trackSaveLyrics => 'Şarkı Sözlerini Kaydet (.lrc)'; + String get trackSaveLyrics => 'Save Lyrics (.lrc)'; @override - String get trackSaveLyricsSubtitle => - 'Şarkı sözlerini çekip .lrc dosyası olarak kaydeder'; + String get trackSaveLyricsSubtitle => 'Fetch and save lyrics as .lrc file'; @override - String get trackSaveLyricsProgress => 'Şarkı sözleri kaydediliyor...'; + String get trackSaveLyricsProgress => 'Saving lyrics...'; @override - String get trackReEnrich => 'Bilgileri İnternetten Güncelle (Re-enrich)'; + String get trackReEnrich => 'Re-enrich'; @override String get trackReEnrichOnlineSubtitle => - 'İnternetten şarkı verilerini (metadata) bulup dosyaya yeniden işler'; + 'Search metadata online and embed into file'; @override String get trackReEnrichFieldsTitle => 'Fields to update'; @@ -2378,82 +2363,78 @@ class AppLocalizationsTr extends AppLocalizations { String get trackReEnrichSelectAll => 'Select All'; @override - String get trackEditMetadata => 'Şarkı Bilgilerini Düzenle'; + String get trackEditMetadata => 'Edit Metadata'; @override String trackCoverSaved(String fileName) { - return 'Kapak resmi \"$fileName\" adıyla kaydedildi'; + return 'Cover art saved to $fileName'; } @override - String get trackCoverNoSource => 'Geçerli bir kapak resmi kaynağı bulunamadı'; + String get trackCoverNoSource => 'No cover art source available'; @override String trackLyricsSaved(String fileName) { - return 'Sözler \"$fileName\" adıyla kaydedildi'; + return 'Lyrics saved to $fileName'; } @override - String get trackReEnrichProgress => - 'Şarkı bilgileri (metadata) güncelleniyor...'; + String get trackReEnrichProgress => 'Re-enriching metadata...'; @override - String get trackReEnrichSearching => - 'İnternette şarkı bilgisi (metadata) aranıyor...'; + String get trackReEnrichSearching => 'Searching metadata online...'; @override - String get trackReEnrichSuccess => - 'Şarkı bilgileri dosyaya başarıyla işlendi'; + String get trackReEnrichSuccess => 'Metadata re-enriched successfully'; @override - String get trackReEnrichFfmpegFailed => - 'Sözleri (veya verileri) dosyaya yazarken hata oluştu'; + String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed'; @override - String get queueFlacAction => 'Bunu FLAC Olarak İndir'; + String get queueFlacAction => 'Queue FLAC'; @override String queueFlacConfirmMessage(int count) { - return 'Seçilen şarkılar için internette FLAC eşleşmesi aranacak ve indirme sırasına eklenecek.\n\nMevcut dosyalarınıza dokunulmayacak veya silinmeyecek.\n\nSadece yüksek oranda eşleşenler otomatik olarak sıraya eklenir.\n\n$count şarkı seçildi'; + return 'Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n$count selected'; } @override String queueFlacFindingProgress(int current, int total) { - return 'FLAC eşleşmeleri aranıyor... ($current/$total)'; + return 'Finding FLAC matches... ($current/$total)'; } @override String get queueFlacNoReliableMatches => - 'Seçiminiz için internette güvenilir bir eşleşme bulunamadı'; + 'No reliable online matches found for the selection'; @override String queueFlacQueuedWithSkipped(int addedCount, int skippedCount) { - return '$addedCount şarkı sıraya eklendi, $skippedCount şarkı eşleşmediği için atlandı'; + return 'Added $addedCount tracks to queue, skipped $skippedCount'; } @override String trackSaveFailed(String error) { - return 'İşlem başarısız: $error'; + return 'Failed: $error'; } @override - String get trackConvertFormat => 'Ses Formatını Dönüştür'; + String get trackConvertFormat => 'Convert Format'; @override String get trackConvertFormatSubtitle => - 'Dosyayı MP3, Opus, ALAC veya FLAC formatına çevirin'; + 'Convert to MP3, Opus, ALAC, or FLAC'; @override - String get trackConvertTitle => 'Sesi Dönüştür'; + String get trackConvertTitle => 'Convert Audio'; @override - String get trackConvertTargetFormat => 'Hedef Format'; + String get trackConvertTargetFormat => 'Target Format'; @override - String get trackConvertBitrate => 'Bit Hızı (Kalite)'; + String get trackConvertBitrate => 'Bitrate'; @override - String get trackConvertConfirmTitle => 'Dönüşümü Onayla'; + String get trackConvertConfirmTitle => 'Confirm Conversion'; @override String trackConvertConfirmMessage( @@ -2461,7 +2442,7 @@ class AppLocalizationsTr extends AppLocalizations { String targetFormat, String bitrate, ) { - return '$sourceFormat formatından $targetFormat formatına ($bitrate) dönüştürülsün mü?\n\nDönüşüm bittikten sonra orijinal dosya tamamen silinecektir.'; + return 'Convert from $sourceFormat to $targetFormat at $bitrate?\n\nThe original file will be deleted after conversion.'; } @override @@ -2469,254 +2450,250 @@ class AppLocalizationsTr extends AppLocalizations { String sourceFormat, String targetFormat, ) { - return '$sourceFormat formatından $targetFormat formatına dönüştürülsün mü? (Kayıpsız format, kalite kaybı yaşanmaz)\n\nDönüşüm bittikten sonra orijinal dosya tamamen silinecektir.'; + return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.'; } @override String get trackConvertLosslessHint => - 'Kayıpsız bir formata dönüştürülüyor (Kalite düşüşü olmaz)'; + 'Lossless conversion — no quality loss'; @override - String get trackConvertConverting => 'Ses dönüştürülüyor...'; + String get trackConvertConverting => 'Converting audio...'; @override String trackConvertSuccess(String format) { - return 'Dosya başarıyla $format formatına çevrildi'; + return 'Converted to $format successfully'; } @override - String get trackConvertFailed => 'Dönüşüm işlemi başarısız oldu'; + String get trackConvertFailed => 'Conversion failed'; @override - String get cueSplitTitle => 'CUE Dosyasını Parçalara Böl'; + String get cueSplitTitle => 'Split CUE Sheet'; @override - String get cueSplitSubtitle => - 'Tek parça olan CUE+FLAC dosyasını ayrı şarkılara böler'; + String get cueSplitSubtitle => 'Split CUE+FLAC into individual tracks'; @override String cueSplitAlbum(String album) { - return 'Albüm: $album'; + return 'Album: $album'; } @override String cueSplitArtist(String artist) { - return 'Sanatçı: $artist'; + return 'Artist: $artist'; } @override String cueSplitTrackCount(int count) { - return '$count şarkı var'; + return '$count tracks'; } @override - String get cueSplitConfirmTitle => 'CUE Dosyasını Böl'; + String get cueSplitConfirmTitle => 'Split CUE Album'; @override String cueSplitConfirmMessage(String album, int count) { - return '\"$album\" albümünü $count ayrı FLAC dosyasına bölmek istiyor musunuz?\n\nYeni dosyalar orijinal dosyanın bulunduğu klasöre kaydedilecektir.'; + 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 'CUE dosyası ayrıştırılıyor... ($current/$total)'; + return 'Splitting CUE sheet... ($current/$total)'; } @override String cueSplitSuccess(int count) { - return 'Dosya başarıyla $count şarkıya bölündü'; + return 'Split into $count tracks successfully'; } @override - String get cueSplitFailed => 'CUE bölme işlemi başarısız'; + String get cueSplitFailed => 'CUE split failed'; @override - String get cueSplitNoAudioFile => - 'Bu CUE ile eşleşen bir ses dosyası bulunamadı'; + String get cueSplitNoAudioFile => 'Audio file not found for this CUE sheet'; @override - String get cueSplitButton => 'Şarkılara Böl'; + String get cueSplitButton => 'Split into Tracks'; @override - String get actionCreate => 'Oluştur'; + String get actionCreate => 'Create'; @override - String get collectionFoldersTitle => 'Klasörlerim'; + String get collectionFoldersTitle => 'My folders'; @override - String get collectionWishlist => 'İstek Listesi'; + String get collectionWishlist => 'Wishlist'; @override - String get collectionLoved => 'Favoriler'; + String get collectionLoved => 'Loved'; @override - String get collectionPlaylists => 'Çalma Listeleri'; + String get collectionPlaylists => 'Playlists'; @override - String get collectionPlaylist => 'Çalma Listesi'; + String get collectionPlaylist => 'Playlist'; @override - String get collectionAddToPlaylist => 'Çalma listesine ekle'; + String get collectionAddToPlaylist => 'Add to playlist'; @override - String get collectionCreatePlaylist => 'Yeni çalma listesi oluştur'; + String get collectionCreatePlaylist => 'Create playlist'; @override - String get collectionNoPlaylistsYet => 'Henüz listeniz yok'; + String get collectionNoPlaylistsYet => 'No playlists yet'; @override String get collectionNoPlaylistsSubtitle => - 'Müziklerinizi kategorize etmek için bir çalma listesi oluşturun'; + 'Create a playlist to start categorizing tracks'; @override String collectionPlaylistTracks(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count şarkı', - one: '1 şarkı', + other: '$count tracks', + one: '1 track', ); return '$_temp0'; } @override String collectionAddedToPlaylist(String playlistName) { - return '\"$playlistName\" listesine eklendi'; + return 'Added to \"$playlistName\"'; } @override String collectionAlreadyInPlaylist(String playlistName) { - return 'Zaten \"$playlistName\" listesinde var'; + return 'Already in \"$playlistName\"'; } @override - String get collectionPlaylistCreated => 'Çalma listesi oluşturuldu'; + String get collectionPlaylistCreated => 'Playlist created'; @override - String get collectionPlaylistNameHint => 'Çalma listesi adı'; + String get collectionPlaylistNameHint => 'Playlist name'; @override - String get collectionPlaylistNameRequired => - 'Lütfen liste için bir isim girin'; + String get collectionPlaylistNameRequired => 'Playlist name is required'; @override - String get collectionRenamePlaylist => 'Yeniden adlandır'; + String get collectionRenamePlaylist => 'Rename playlist'; @override - String get collectionDeletePlaylist => 'Listeyi sil'; + String get collectionDeletePlaylist => 'Delete playlist'; @override String collectionDeletePlaylistMessage(String playlistName) { - return '\"$playlistName\" listesini ve içindeki tüm şarkıları silmek istiyor musunuz?'; + return 'Delete \"$playlistName\" and all tracks inside it?'; } @override - String get collectionPlaylistDeleted => 'Çalma listesi silindi'; + String get collectionPlaylistDeleted => 'Playlist deleted'; @override - String get collectionPlaylistRenamed => 'Çalma listesi adı değiştirildi'; + String get collectionPlaylistRenamed => 'Playlist renamed'; @override - String get collectionWishlistEmptyTitle => 'İstek Listeniz boş'; + String get collectionWishlistEmptyTitle => 'Wishlist is empty'; @override String get collectionWishlistEmptySubtitle => - 'Daha sonra indirmek istediğiniz şarkıların yanındaki (+) simgesine dokunun'; + 'Tap + on tracks to save what you want to download later'; @override - String get collectionLovedEmptyTitle => 'Favori klasörünüz boş'; + String get collectionLovedEmptyTitle => 'Loved folder is empty'; @override String get collectionLovedEmptySubtitle => - 'Sevdiğiniz şarkıları burada toplamak için kalp ikonuna dokunun'; + 'Tap love on tracks to keep your favorites'; @override - String get collectionPlaylistEmptyTitle => 'Bu çalma listesi boş'; + String get collectionPlaylistEmptyTitle => 'Playlist is empty'; @override String get collectionPlaylistEmptySubtitle => - 'Buraya eklemek için istediğiniz şarkının üzerindeki (+) butonuna basılı tutun'; + 'Long-press + on any track to add it here'; @override - String get collectionRemoveFromPlaylist => 'Çalma listesinden çıkar'; + String get collectionRemoveFromPlaylist => 'Remove from playlist'; @override - String get collectionRemoveFromFolder => 'Klasörden çıkar'; + String get collectionRemoveFromFolder => 'Remove from folder'; @override String collectionRemoved(String trackName) { - return '\"$trackName\" listeden çıkarıldı'; + return '\"$trackName\" removed'; } @override String collectionAddedToLoved(String trackName) { - return '\"$trackName\" Favoriler klasörüne eklendi'; + return '\"$trackName\" added to Loved'; } @override String collectionRemovedFromLoved(String trackName) { - return '\"$trackName\" Favorilerinizden çıkarıldı'; + return '\"$trackName\" removed from Loved'; } @override String collectionAddedToWishlist(String trackName) { - return '\"$trackName\" İstek Listenize eklendi'; + return '\"$trackName\" added to Wishlist'; } @override String collectionRemovedFromWishlist(String trackName) { - return '\"$trackName\" İstek Listenizden çıkarıldı'; + return '\"$trackName\" removed from Wishlist'; } @override - String get trackOptionAddToLoved => 'Favorilere Ekle'; + String get trackOptionAddToLoved => 'Add to Loved'; @override - String get trackOptionRemoveFromLoved => 'Favorilerden Çıkar'; + String get trackOptionRemoveFromLoved => 'Remove from Loved'; @override - String get trackOptionAddToWishlist => 'İstek Listesine Ekle'; + String get trackOptionAddToWishlist => 'Add to Wishlist'; @override - String get trackOptionRemoveFromWishlist => 'İstek Listesinden Çıkar'; + String get trackOptionRemoveFromWishlist => 'Remove from Wishlist'; @override - String get collectionPlaylistChangeCover => 'Kapak resmini değiştir'; + String get collectionPlaylistChangeCover => 'Change cover image'; @override - String get collectionPlaylistRemoveCover => 'Kapak resmini kaldır'; + String get collectionPlaylistRemoveCover => 'Remove cover image'; @override String selectionShareCount(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'şarkıyı', - one: 'şarkıyı', + other: 'tracks', + one: 'track', ); - return '$count $_temp0 paylaş'; + return 'Share $count $_temp0'; } @override - String get selectionShareNoFiles => 'Paylaşılabilir bir dosya bulunamadı'; + String get selectionShareNoFiles => 'No shareable files found'; @override String selectionConvertCount(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'şarkıyı', - one: 'şarkıyı', + other: 'tracks', + one: 'track', ); - return '$count $_temp0 dönüştür'; + return 'Convert $count $_temp0'; } @override - String get selectionConvertNoConvertible => - 'Dönüştürülebilir formatta bir şarkı seçilmedi'; + String get selectionConvertNoConvertible => 'No convertible tracks selected'; @override - String get selectionBatchConvertConfirmTitle => 'Toplu Dönüştürme'; + String get selectionBatchConvertConfirmTitle => 'Batch Convert'; @override String selectionBatchConvertConfirmMessage( @@ -2727,10 +2704,10 @@ class AppLocalizationsTr extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'şarkıyı', - one: 'şarkıyı', + other: 'tracks', + one: 'track', ); - return '$count $_temp0 $format formatına ($bitrate) dönüştürmek istiyor musunuz?\n\nDönüşüm işlemi bittikten sonra orijinal dosyalar tamamen silinecektir.'; + return 'Convert $count $_temp0 to $format at $bitrate?\n\nOriginal files will be deleted after conversion.'; } @override @@ -2738,345 +2715,340 @@ class AppLocalizationsTr extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'şarkıyı', - one: 'şarkıyı', + other: 'tracks', + one: 'track', ); - return '$count $_temp0 $format formatına dönüştürmek istiyor musunuz? (Kayıpsız işlem — kalite kaybı olmaz)\n\nDönüşüm işlemi bittikten sonra orijinal dosyalar tamamen silinecektir.'; + return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.'; } @override String selectionBatchConvertProgress(int current, int total) { - return 'Dönüştürülüyor: $current / $total...'; + return 'Converting $current of $total...'; } @override String selectionBatchConvertSuccess(int success, int total, String format) { - return '$total şarkıdan $success tanesi $format formatına dönüştürüldü'; + return 'Converted $success of $total tracks to $format'; } @override String downloadedAlbumDownloadedCount(int count) { - return '$count tanesi indirildi'; + return '$count downloaded'; } @override String get downloadUseAlbumArtistForFoldersAlbumSubtitle => - 'Sanatçı klasörleri için Albüm Sanatçısı adı kullanılır'; + 'Artist folders use Album Artist when available'; @override String get downloadUseAlbumArtistForFoldersTrackSubtitle => - 'Sanatçı klasörleri için sadece Şarkı Sanatçısı adı kullanılır'; + 'Artist folders use Track Artist only'; @override - String get lyricsProvidersTitle => 'Şarkı Sözü Sağlayıcıları'; + String get lyricsProvidersTitle => 'Lyrics Providers'; @override String get lyricsProvidersDescription => - 'Şarkı sözü kaynaklarını açıp kapatın veya sıralamalarını değiştirin. Uygulama sözleri bulana kadar sağlayıcıları yukarıdan aşağıya doğru sırayla dener.'; + 'Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.'; @override String get lyricsProvidersInfoText => - 'Mağazadan yüklediğiniz eklentiler her zaman varsayılan yerleşik sağlayıcılardan önce çalışır. En az bir sağlayıcı her zaman açık kalmalıdır.'; + 'Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.'; @override String lyricsProvidersEnabledSection(int count) { - return 'Açık ($count)'; + return 'Enabled ($count)'; } @override String lyricsProvidersDisabledSection(int count) { - return 'Kapalı ($count)'; + return 'Disabled ($count)'; } @override String get lyricsProvidersAtLeastOne => - 'En az bir sağlayıcı her zaman açık kalmalıdır'; + 'At least one provider must remain enabled'; @override - String get lyricsProvidersSaved => - 'Şarkı sözü sağlayıcılarının sıralaması kaydedildi'; + String get lyricsProvidersSaved => 'Lyrics provider priority saved'; @override String get lyricsProvidersDiscardContent => - 'Kaydedilmemiş değişiklikleriniz iptal edilecek.'; + 'You have unsaved changes that will be lost.'; @override - String get lyricsProviderLrclibDesc => - 'Açık kaynaklı, senkronize şarkı sözü veritabanı'; + String get lyricsProviderLrclibDesc => 'Open-source synced lyrics database'; @override String get lyricsProviderNeteaseDesc => - 'NetEase Cloud Music (Özellikle Asya müzikleri için ideal)'; + 'NetEase Cloud Music (good for Asian songs)'; @override String get lyricsProviderMusixmatchDesc => - 'En geniş şarkı sözü arşivi (Çok dilli)'; + 'Largest lyrics database (multi-language)'; @override String get lyricsProviderAppleMusicDesc => - 'Kelime kelime akan senkronize sözler (Proxy üzerinden)'; + 'Word-by-word synced lyrics (via proxy)'; @override String get lyricsProviderQqMusicDesc => - 'QQ Music (Özellikle Çince şarkılar için, Proxy üzerinden)'; + 'QQ Music (good for Chinese songs, via proxy)'; @override - String get lyricsProviderExtensionDesc => 'Eklenti ile sağlanan kaynak'; + String get lyricsProviderExtensionDesc => 'Extension provider'; @override - String get safMigrationTitle => 'Depolama Sistem Güncellemesi'; + String get safMigrationTitle => 'Storage Update Required'; @override String get safMigrationMessage1 => - 'SpotiFLAC artık indirme işlemleri için Android\'in yeni Depolama Erişim Çerçevesi\'ni (SAF) kullanıyor. Bu sayede Android 10 ve üzeri sürümlerdeki \"izin reddedildi\" hataları ortadan kalkıyor.'; + 'SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.'; @override String get safMigrationMessage2 => - 'Yeni depolama sistemine geçiş yapmak için lütfen indirme klasörünüzü tekrar seçin.'; + 'Please select your download folder again to switch to the new storage system.'; @override - String get safMigrationSuccess => - 'İndirme klasörü başarıyla yeni (SAF) moda geçirildi'; + String get safMigrationSuccess => 'Download folder updated to SAF mode'; @override - String get settingsDonate => 'Bağış Yap'; + String get settingsDonate => 'Donate'; @override - String get settingsDonateSubtitle => - 'SpotiFLAC-Mobile gelişimine destek olun'; + String get settingsDonateSubtitle => 'Support SpotiFLAC-Mobile development'; @override - String get tooltipLoveAll => 'Tümünü Favorilere Ekle'; + String get tooltipLoveAll => 'Love All'; @override - String get tooltipAddToPlaylist => 'Çalma Listesine Ekle'; + String get tooltipAddToPlaylist => 'Add to Playlist'; @override String snackbarRemovedTracksFromLoved(int count) { - return '$count şarkı Favoriler\'den çıkarıldı'; + return 'Removed $count tracks from Loved'; } @override String snackbarAddedTracksToLoved(int count) { - return '$count şarkı Favoriler\'e eklendi'; + return 'Added $count tracks to Loved'; } @override - String get dialogDownloadAllTitle => 'Tümünü İndir'; + String get dialogDownloadAllTitle => 'Download All'; @override String dialogDownloadAllMessage(int count) { - return 'Bu listedeki $count şarkı indirilsin mi?'; + return 'Download $count tracks?'; } @override - String get homeSkipAlreadyDownloaded => 'Daha önce inmiş olan şarkıları atla'; + String get homeSkipAlreadyDownloaded => 'Skip already downloaded songs'; @override - String get homeGoToAlbum => 'Albüme Git'; + String get homeGoToAlbum => 'Go to Album'; @override - String get homeAlbumInfoUnavailable => 'Albüm bilgisine ulaşılamıyor'; + String get homeAlbumInfoUnavailable => 'Album info not available'; @override - String get snackbarLoadingCueSheet => 'CUE dosyası yükleniyor...'; + String get snackbarLoadingCueSheet => 'Loading CUE sheet...'; @override - String get snackbarMetadataSaved => - 'Şarkı verileri dosyaya başarıyla kaydedildi'; + String get snackbarMetadataSaved => 'Metadata saved successfully'; @override - String get snackbarFailedToEmbedLyrics => 'Şarkı sözleri dosyaya eklenemedi'; + String get snackbarFailedToEmbedLyrics => 'Failed to embed lyrics'; @override - String get snackbarFailedToWriteStorage => - 'Değişiklikler asıl dosyaya yazılamadı'; + String get snackbarFailedToWriteStorage => 'Failed to write back to storage'; @override String snackbarError(String error) { - return 'Hata: $error'; + return 'Error: $error'; } @override - String get snackbarNoActionDefined => - 'Bu buton için henüz bir işlev tanımlanmamış'; + String get snackbarNoActionDefined => 'No action defined for this button'; @override - String get noTracksFoundForAlbum => 'Bu albümün içinde hiç şarkı bulunamadı'; + String get noTracksFoundForAlbum => 'No tracks found for this album'; @override String get downloadLocationSubtitle => - 'İndirdiğiniz dosyaların cihazınızda nasıl tutulacağını seçin.'; + 'Choose storage mode for downloaded files.'; @override - String get storageModeAppFolder => 'Uygulama Klasörü'; + String get storageModeAppFolder => 'App folder (non-SAF)'; @override - String get storageModeAppFolderSubtitle => - 'Telefonunuzdaki varsayılan Müzik klasörünü kullanır'; + String get storageModeAppFolderSubtitle => 'Use default Music/SpotiFLAC path'; @override - String get storageModeSaf => 'SAF ile Özel Klasör (Önerilen)'; + String get storageModeSaf => 'SAF folder'; @override String get storageModeSafSubtitle => - 'Android dosya seçicisi ile cihazınızdan dilediğiniz klasörü seçin'; + 'Pick folder via Android Storage Access Framework'; @override String get downloadFilenameDescription => - 'Şarkıların cihazınızda hangi dosya adıyla kaydedileceğini özelleştirin.'; + 'Customize how your files are named.'; @override - String get downloadFilenameInsertTag => 'Eklemek için dokunun:'; + String get downloadFilenameInsertTag => 'Tap to insert tag:'; @override - String get downloadSeparateSinglesEnabled => - 'Sanatçı klasörünün içinde Single\'ları ayrı bir klasöre ayırır'; + String get downloadSeparateSinglesEnabled => 'Albums/ and Singles/ folders'; @override - String get downloadSeparateSinglesDisabled => - 'Single\'lar ile albümler aynı yerde durur'; + String get downloadSeparateSinglesDisabled => 'All files in same structure'; @override - String get downloadArtistNameFilters => 'Sanatçı Adı Filtreleri'; + String get downloadArtistNameFilters => 'Artist Name Filters'; @override String get downloadCreatePlaylistSourceFolder => - 'Çalma Listeleri İçin Ana Klasör Oluştur'; + 'Create playlist source folder'; @override String get downloadCreatePlaylistSourceFolderEnabled => - 'Çalma listesi indirildiğinde en dışa \'Çalma Listesi Adı\' isimli bir klasör oluşturur ve içini normal düzeninize göre dizer.'; + 'Playlist downloads use Playlist/ plus your normal folder structure.'; @override String get downloadCreatePlaylistSourceFolderDisabled => - 'Çalma listesindeki şarkılar da diğerleri gibi doğrudan albüm ve sanatçı klasörlerinize atılır.'; + 'Playlist downloads use the normal folder structure only.'; @override String get downloadCreatePlaylistSourceFolderRedundant => - 'Klasör Düzeni zaten \'Çalma Listesine Göre\' ayarlı olduğu için bu seçenek pasiftir.'; + 'By Playlist already places downloads inside a playlist folder.'; @override - String get downloadSongLinkRegion => 'SongLink Arama Bölgesi'; + String get downloadSongLinkRegion => 'SongLink Region'; @override - String get downloadNetworkCompatibilityMode => 'Ağ Uyumluluk Modu'; + String get downloadNetworkCompatibilityMode => 'Network compatibility mode'; @override String get downloadNetworkCompatibilityModeEnabled => - 'Açık: Bağlantı HTTP ile denenir ve geçersiz sertifikalar kabul edilir (Güvensiz ama çözümleyici)'; + 'Enabled: try HTTP + accept invalid TLS certificates (unsafe)'; @override String get downloadNetworkCompatibilityModeDisabled => - 'Kapalı: Katı HTTPS kuralları uygulanır (Önerilen)'; + 'Off: strict HTTPS certificate validation (recommended)'; @override String get downloadSelectServiceToEnable => - 'Seçenekleri açmak için yerleşik bir sağlayıcı seçin'; + 'Select a built-in service to enable'; @override String get downloadSelectTidalQobuz => - 'Kaliteyi ayarlamak için lütfen yukarıdan Tidal veya Qobuz seçin'; + 'Select Tidal or Qobuz above to configure quality'; @override String get downloadEmbedLyricsDisabled => - 'Şarkı Verilerini Dosyaya Gömme ayarı kapalıyken kullanılamaz'; + 'Disabled while Embed Metadata is turned off'; @override String get downloadNeteaseIncludeTranslation => - 'Netease: Çevirileri Dahil Et'; + 'Netease: Include Translation'; @override String get downloadNeteaseIncludeTranslationEnabled => - 'Varsa, orijinal sözlere çevirilerini ekler'; + 'Append translated lyrics when available'; @override String get downloadNeteaseIncludeTranslationDisabled => - 'Sadece şarkının kendi sözleri kullanılır'; + 'Use original lyrics only'; @override String get downloadNeteaseIncludeRomanization => - 'Netease: Okunuşları (Romanizasyon) Dahil Et'; + 'Netease: Include Romanization'; @override String get downloadNeteaseIncludeRomanizationEnabled => - 'Varsa, Asya şarkıları için Latin alfabesi okunuşlarını ekler'; + 'Append romanized lyrics when available'; @override - String get downloadNeteaseIncludeRomanizationDisabled => 'Kapalı'; + String get downloadNeteaseIncludeRomanizationDisabled => 'Disabled'; @override - String get downloadAppleQqMultiPerson => - 'Apple/QQ: Çoklu Sanatçı Düzeni (Kelime kelime akan sözler)'; + String get downloadAppleQqMultiPerson => 'Apple/QQ Multi-Person Word-by-Word'; @override String get downloadAppleQqMultiPersonEnabled => - 'Gelişmiş v1/v2 ve arka plan [bg:] etiketlerini açık tutar'; + 'Enable v1/v2 speaker and [bg:] tags'; @override String get downloadAppleQqMultiPersonDisabled => - 'Standart kelime kelime senkronizasyon kullanır'; + 'Simplified word-by-word formatting'; @override - String get downloadMusixmatchLanguage => 'Musixmatch Tercih Edilen Dil'; + String get downloadMusixmatchLanguage => 'Musixmatch Language'; @override - String get downloadMusixmatchLanguageAuto => 'Otomatik (Orijinal Dil)'; + String get downloadMusixmatchLanguageAuto => 'Auto (original)'; @override String get downloadFilterContributing => - 'Albüm Sanatçısı etiketinde konuk sanatçıları filtrele'; + 'Filter contributing artists in Album Artist'; @override String get downloadFilterContributingEnabled => - 'Albüm Sanatçısı verisinde sadece ana sanatçı ismi tutulur'; + 'Album Artist metadata uses primary artist only'; @override String get downloadFilterContributingDisabled => - 'Tüm sanatçı isimlerini olduğu gibi korur'; + 'Keep full Album Artist metadata value'; @override - String get downloadProvidersNoneEnabled => 'Hiçbir sağlayıcı açık değil'; + String get downloadProvidersNoneEnabled => 'None enabled'; @override - String get downloadMusixmatchLanguageCode => 'Dil Kodu'; + String get downloadMusixmatchLanguageCode => 'Language code'; @override - String get downloadMusixmatchLanguageHint => 'auto / tr / en / es'; + String get downloadMusixmatchLanguageHint => 'auto / en / es / ja'; @override String get downloadMusixmatchLanguageDesc => - 'Tercih ettiğiniz söz dilini belirleyin (örnek: tr, en, es). Otomatik seçim için boş bırakın.'; + 'Set preferred language code (example: en, es, ja). Leave empty for auto.'; @override - String get downloadMusixmatchAuto => 'Otomatik'; + String get downloadMusixmatchAuto => 'Auto'; @override - String get downloadNetworkAnySubtitle => 'Wi-Fi + Mobil Veri üzerinden indir'; + String get downloadNetworkAnySubtitle => 'WiFi + Mobile Data'; @override String get downloadNetworkWifiOnlySubtitle => - 'Wi-Fi\'dan çıkarsanız indirmeler duraklatılır'; + 'Pause downloads on mobile data'; @override String get downloadSongLinkRegionDesc => - 'Şarkı aramalarında SongLink API\'ye iletilecek ülke kodunu belirler.'; + 'Used as userCountry for SongLink API lookup.'; @override - String get snackbarUnsupportedAudioFormat => 'Bu ses formatı desteklenmiyor'; + String get snackbarUnsupportedAudioFormat => 'Unsupported audio format'; @override - String get cacheRefresh => 'Yenile'; + String get cacheRefresh => 'Refresh'; @override String dialogDownloadPlaylistsMessage(int trackCount, int playlistCount) { String _temp0 = intl.Intl.pluralLogic( + trackCount, + locale: localeName, + other: 'tracks', + one: 'track', + ); + String _temp1 = intl.Intl.pluralLogic( playlistCount, locale: localeName, - other: 'listeden', - one: 'listeden', + other: 'playlists', + one: 'playlist', ); - return '$playlistCount $_temp0 toplam $trackCount şarkı indirilsin mi?'; + return 'Download $trackCount $_temp0 from $playlistCount $_temp1?'; } @override @@ -3084,123 +3056,122 @@ class AppLocalizationsTr extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'listeyi', - one: 'listeyi', + other: 'playlists', + one: 'playlist', ); - return 'Seçili $count $_temp0 indir'; + return 'Download $count $_temp0'; } @override - String get bulkDownloadSelectPlaylists => - 'İndirilecek çalma listelerini seçin'; + String get bulkDownloadSelectPlaylists => 'Select playlists to download'; @override String get snackbarSelectedPlaylistsEmpty => - 'Seçilen çalma listelerinde şarkı yok'; + 'Selected playlists have no tracks'; @override String playlistsCount(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count çalma listesi', - one: '1 çalma listesi', + other: '$count playlists', + one: '1 playlist', ); return '$_temp0'; } @override - String get editMetadataAutoFill => 'İnternetten Otomatik Doldur'; + String get editMetadataAutoFill => 'Auto-fill from online'; @override String get editMetadataAutoFillDesc => - 'İnternetteki verilerle otomatik doldurulmasını istediğiniz alanları seçin'; + 'Select fields to fill automatically from online metadata'; @override - String get editMetadataAutoFillFetch => 'Bul ve Doldur'; + String get editMetadataAutoFillFetch => 'Fetch & Fill'; @override - String get editMetadataAutoFillSearching => 'İnternette eşleşme aranıyor...'; + String get editMetadataAutoFillSearching => 'Searching online...'; @override String get editMetadataAutoFillNoResults => - 'İnternette bu şarkıya uygun bir veri bulunamadı'; + 'No matching metadata found online'; @override String editMetadataAutoFillDone(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'alan', - one: 'alan', + other: 'fields', + one: 'field', ); - return 'Şarkı verilerinden $count $_temp0 internetten çekilerek dolduruldu'; + return 'Filled $count $_temp0 from online metadata'; } @override String get editMetadataAutoFillNoneSelected => - 'Lütfen otomatik doldurulacak en az bir alan seçin'; + 'Select at least one field to auto-fill'; @override - String get editMetadataFieldTitle => 'Şarkı Adı'; + String get editMetadataFieldTitle => 'Title'; @override - String get editMetadataFieldArtist => 'Sanatçı'; + String get editMetadataFieldArtist => 'Artist'; @override - String get editMetadataFieldAlbum => 'Albüm'; + String get editMetadataFieldAlbum => 'Album'; @override - String get editMetadataFieldAlbumArtist => 'Albüm Sanatçısı'; + String get editMetadataFieldAlbumArtist => 'Album Artist'; @override - String get editMetadataFieldDate => 'Tarih'; + String get editMetadataFieldDate => 'Date'; @override - String get editMetadataFieldTrackNum => 'Şarkı Sırası'; + String get editMetadataFieldTrackNum => 'Track #'; @override - String get editMetadataFieldDiscNum => 'Disk #'; + String get editMetadataFieldDiscNum => 'Disc #'; @override - String get editMetadataFieldGenre => 'Tür'; + String get editMetadataFieldGenre => 'Genre'; @override String get editMetadataFieldIsrc => 'ISRC'; @override - String get editMetadataFieldLabel => 'Plak Şirketi'; + String get editMetadataFieldLabel => 'Label'; @override - String get editMetadataFieldCopyright => 'Telif Hakkı'; + String get editMetadataFieldCopyright => 'Copyright'; @override - String get editMetadataFieldCover => 'Albüm Kapağı'; + String get editMetadataFieldCover => 'Cover Art'; @override - String get editMetadataSelectAll => 'Tümü'; + String get editMetadataSelectAll => 'All'; @override - String get editMetadataSelectEmpty => 'Sadece boşlar'; + String get editMetadataSelectEmpty => 'Empty only'; @override String queueDownloadingCount(int count) { - return 'Şu An İnenler ($count)'; + return 'Downloading ($count)'; } @override - String get queueDownloadedHeader => 'İnenler'; + String get queueDownloadedHeader => 'Downloaded'; @override - String get queueFilteringIndicator => 'Filtreleniyor...'; + String get queueFilteringIndicator => 'Filtering...'; @override String queueTrackCount(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count şarkı', - one: '1 şarkı', + other: '$count tracks', + one: '1 track', ); return '$_temp0'; } @@ -3210,82 +3181,80 @@ class AppLocalizationsTr extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count albüm', - one: '1 albüm', + other: '$count albums', + one: '1 album', ); return '$_temp0'; } @override - String get queueEmptyAlbums => 'İndirilmiş bir albüm yok'; + String get queueEmptyAlbums => 'No album downloads'; @override String get queueEmptyAlbumsSubtitle => - 'Bir albümden birden fazla şarkı indirdiğinizde burada görünecektir.'; + 'Download multiple tracks from an album to see them here'; @override - String get queueEmptySingles => 'İndirilmiş bir şarkı yok'; + String get queueEmptySingles => 'No single downloads'; @override String get queueEmptySinglesSubtitle => - 'Tek tek indirdiğiniz şarkılar burada görünecektir.'; + 'Single track downloads will appear here'; @override - String get queueEmptyHistory => 'İndirme geçmişi boş'; + String get queueEmptyHistory => 'No download history'; @override - String get queueEmptyHistorySubtitle => - 'İndirdiğiniz şarkılar başarıyla tamamlandığında burada görünecektir.'; + String get queueEmptyHistorySubtitle => 'Downloaded tracks will appear here'; @override - String get selectionAllPlaylistsSelected => 'Tüm listeler seçildi'; + String get selectionAllPlaylistsSelected => 'All playlists selected'; @override - String get selectionTapPlaylistsToSelect => 'Seçmek için listelere dokunun'; + String get selectionTapPlaylistsToSelect => 'Tap playlists to select'; @override - String get selectionSelectPlaylistsToDelete => - 'Silinecek çalma listelerini seçin'; + String get selectionSelectPlaylistsToDelete => 'Select playlists to delete'; @override - String get audioAnalysisTitle => 'Ses Kalitesi Analizi'; + String get audioAnalysisTitle => 'Audio Quality Analysis'; @override String get audioAnalysisDescription => - 'Kayıpsız kalite doğrulaması için spektrum analizi yapın'; + 'Verify lossless quality with spectrum analysis'; @override - String get audioAnalysisAnalyzing => 'Ses dosyası analiz ediliyor...'; + String get audioAnalysisAnalyzing => 'Analyzing audio...'; @override - String get audioAnalysisSampleRate => 'Örnekleme Hızı (Sample Rate)'; + String get audioAnalysisSampleRate => 'Sample Rate'; @override - String get audioAnalysisBitDepth => 'Bit Derinliği'; + String get audioAnalysisBitDepth => 'Bit Depth'; @override - String get audioAnalysisChannels => 'Kanal'; + String get audioAnalysisChannels => 'Channels'; @override - String get audioAnalysisDuration => 'Süre'; + String get audioAnalysisDuration => 'Duration'; @override - String get audioAnalysisNyquist => 'Nyquist Frekansı'; + String get audioAnalysisNyquist => 'Nyquist'; @override - String get audioAnalysisFileSize => 'Boyut'; + String get audioAnalysisFileSize => 'Size'; @override - String get audioAnalysisDynamicRange => 'Dinamik Aralık'; + String get audioAnalysisDynamicRange => 'Dynamic Range'; @override - String get audioAnalysisPeak => 'Tepe Değeri (Peak)'; + String get audioAnalysisPeak => 'Peak'; @override - String get audioAnalysisRms => 'Ortalama Değer (RMS)'; + String get audioAnalysisRms => 'RMS'; @override - String get audioAnalysisSamples => 'Toplam Örneklem (Samples)'; + String get audioAnalysisSamples => 'Samples'; @override String extensionsSearchWith(String providerName) { diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index f6230541..6fbbdfc6 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -3436,13 +3436,13 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { String get navSettings => '设置'; @override - String get navStore => '商店'; + String get navStore => 'Repo'; @override String get homeTitle => '主页'; @override - String get homeSubtitle => '粘贴 Spotify 链接或按名称搜索'; + String get homeSubtitle => 'Paste a supported URL or search by name'; @override String get homeSupports => '支持:歌曲、专辑、播放列表、艺人网址'; @@ -3489,6 +3489,13 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get downloadFilenameFormat => '文件名格式'; + @override + String get downloadSingleFilenameFormat => 'Single Filename Format'; + + @override + String get downloadSingleFilenameFormatDescription => + 'Filename pattern for singles and EPs. Uses the same tags as the album format.'; + @override String get downloadFolderOrganization => '文件夹结构'; @@ -3533,6 +3540,13 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { return '使用扩展:$extensionName'; } + @override + String get optionsDefaultSearchTab => 'Default Search Tab'; + + @override + String get optionsDefaultSearchTabSubtitle => + 'Choose which tab opens first for new search results.'; + @override String get optionsSwitchBack => '点击 Deezer 或 Spotify 即可从扩展程序切换回来'; @@ -3563,6 +3577,38 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get optionsMaxQualityCoverSubtitle => '下载最高分辨率封面'; + @override + String get optionsReplayGain => 'ReplayGain'; + + @override + String get optionsReplayGainSubtitleOn => + 'Scan loudness and embed ReplayGain tags (EBU R128)'; + + @override + String get optionsReplayGainSubtitleOff => + 'Disabled: no loudness normalization tags'; + + @override + String get optionsArtistTagMode => 'Artist Tag Mode'; + + @override + String get optionsArtistTagModeDescription => + 'Choose how multiple artists are written into embedded tags.'; + + @override + String get optionsArtistTagModeJoined => 'Single joined value'; + + @override + String get optionsArtistTagModeJoinedSubtitle => + 'Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.'; + + @override + String get optionsArtistTagModeSplitVorbis => 'Split tags for FLAC/Opus'; + + @override + String get optionsArtistTagModeSplitVorbisSubtitle => + 'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.'; + @override String get optionsConcurrentDownloads => '并行下载数'; @@ -3578,10 +3624,10 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { String get optionsConcurrentWarning => '并行下载可能会触发速率限制'; @override - String get optionsExtensionStore => '扩展商店'; + String get optionsExtensionStore => 'Extension Repo'; @override - String get optionsExtensionStoreSubtitle => '在导航中显示商店标签'; + String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation'; @override String get optionsCheckUpdates => '检查更新'; @@ -3655,7 +3701,7 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { String get extensionsUninstall => '卸载'; @override - String get storeTitle => '扩展商店'; + String get storeTitle => 'Extension Repo'; @override String get storeSearch => '搜索扩展……'; @@ -3931,6 +3977,9 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get dialogImport => 'Import'; + @override + String get dialogDownload => 'Download'; + @override String get dialogDiscard => 'Discard'; @@ -4161,6 +4210,36 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get searchPlaylists => 'Playlists'; + @override + String get searchSortTitle => 'Sort Results'; + + @override + String get searchSortDefault => 'Default'; + + @override + String get searchSortTitleAZ => 'Title (A-Z)'; + + @override + String get searchSortTitleZA => 'Title (Z-A)'; + + @override + String get searchSortArtistAZ => 'Artist (A-Z)'; + + @override + String get searchSortArtistZA => 'Artist (Z-A)'; + + @override + String get searchSortDurationShort => 'Duration (Shortest)'; + + @override + String get searchSortDurationLong => 'Duration (Longest)'; + + @override + String get searchSortDateOldest => 'Release Date (Oldest)'; + + @override + String get searchSortDateNewest => 'Release Date (Newest)'; + @override String get tooltipPlay => 'Play'; @@ -4259,6 +4338,17 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { String get providerPriorityInfo => 'If a track is not available on the first provider, the app will automatically try the next one.'; + @override + String get providerPriorityFallbackExtensionsTitle => 'Extension Fallback'; + + @override + String get providerPriorityFallbackExtensionsDescription => + 'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.'; + + @override + String get providerPriorityFallbackExtensionsHint => + 'Only enabled extensions with download-provider capability are listed here.'; + @override String get providerBuiltIn => 'Built-in'; @@ -4533,6 +4623,12 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get trackLyricsNotAvailable => 'Lyrics not available for this track'; + @override + String get trackLyricsNotInFile => 'No lyrics found in this file'; + + @override + String get trackFetchOnlineLyrics => 'Fetch from Online'; + @override String get trackLyricsTimeout => 'Request timed out. Try again later.'; @@ -4601,7 +4697,48 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { String get storeClearFilters => 'Clear filters'; @override - String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; + String get storeAddRepoTitle => 'Add Extension Repository'; + + @override + String get storeAddRepoDescription => + 'Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.'; + + @override + String get storeRepoUrlLabel => 'Repository URL'; + + @override + String get storeRepoUrlHint => 'https://github.com/user/repo'; + + @override + String get storeRepoUrlHelper => + 'e.g. https://github.com/user/extensions-repo'; + + @override + String get storeAddRepoButton => 'Add Repository'; + + @override + String get storeChangeRepoTooltip => 'Change repository'; + + @override + String get storeRepoDialogTitle => 'Extension Repository'; + + @override + String get storeRepoDialogCurrent => 'Current repository:'; + + @override + String get storeNewRepoUrlLabel => 'New Repository URL'; + + @override + String get storeLoadError => 'Failed to load repository'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + + @override + String get extensionDefaultProvider => 'Default (Deezer)'; @override String get extensionDefaultProviderSubtitle => 'Use built-in search'; @@ -4701,6 +4838,13 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get extensionsDownloadPrioritySubtitle => 'Set download service order'; + @override + String get extensionsFallbackTitle => 'Fallback Extensions'; + + @override + String get extensionsFallbackSubtitle => + 'Choose which installed download extensions can be used as fallback'; + @override String get extensionsNoDownloadProvider => 'No extensions with download provider'; @@ -4750,6 +4894,38 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz'; + @override + String get downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @override + String get downloadLossy320Format => 'Lossy 320kbps Format'; + + @override + String get downloadLossy320FormatDesc => + 'Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.'; + + @override + String get downloadLossyMp3 => 'MP3 320kbps'; + + @override + String get downloadLossyMp3Subtitle => 'Best compatibility, ~10MB per track'; + + @override + String get downloadLossyOpus256 => 'Opus 256kbps'; + + @override + String get downloadLossyOpus256Subtitle => + 'Best quality Opus, ~8MB per track'; + + @override + String get downloadLossyOpus128 => 'Opus 128kbps'; + + @override + String get downloadLossyOpus128Subtitle => 'Smallest size, ~4MB per track'; + @override String get qualityNote => 'Actual quality depends on track availability from the service'; @@ -4851,6 +5027,13 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { String get albumFolderArtistAlbumSinglesSubtitle => 'Artist/Album/ and Artist/Singles/'; + @override + String get albumFolderArtistAlbumFlat => 'Artist / Album (Singles flat)'; + + @override + String get albumFolderArtistAlbumFlatSubtitle => + 'Artist/Album/ and Artist/song.flac'; + @override String get downloadedAlbumDeleteSelected => 'Delete Selected'; @@ -5046,6 +5229,25 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; @@ -5096,6 +5298,17 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { return '$_temp0'; } + @override + String libraryFilesUnit(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'files', + one: 'file', + ); + return '$_temp0'; + } + @override String libraryLastScanned(String time) { return 'Last scanned: $time'; @@ -5107,6 +5320,9 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get libraryScanning => 'Scanning...'; + @override + String get libraryScanFinalizing => 'Finalizing library...'; + @override String libraryScanProgress(String progress, int total) { return '$progress% of $total files'; @@ -5175,6 +5391,24 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get libraryFilterFormat => 'Format'; + @override + String get libraryFilterMetadata => 'Metadata'; + + @override + String get libraryFilterMetadataComplete => 'Complete metadata'; + + @override + String get libraryFilterMetadataMissingAny => 'Missing any metadata'; + + @override + String get libraryFilterMetadataMissingYear => 'Missing year'; + + @override + String get libraryFilterMetadataMissingGenre => 'Missing genre'; + + @override + String get libraryFilterMetadataMissingAlbumArtist => 'Missing album artist'; + @override String get libraryFilterSort => 'Sort'; @@ -5184,6 +5418,18 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get libraryFilterSortOldest => 'Oldest'; + @override + String get libraryFilterSortAlbumAsc => 'Album (A-Z)'; + + @override + String get libraryFilterSortAlbumDesc => 'Album (Z-A)'; + + @override + String get libraryFilterSortGenreAsc => 'Genre (A-Z)'; + + @override + String get libraryFilterSortGenreDesc => 'Genre (Z-A)'; + @override String get timeJustNow => 'Just now'; @@ -5270,7 +5516,7 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => @@ -5468,6 +5714,30 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { String get trackReEnrichOnlineSubtitle => 'Search metadata online and embed into file'; + @override + String get trackReEnrichFieldsTitle => 'Fields to update'; + + @override + String get trackReEnrichFieldCover => 'Cover Art'; + + @override + String get trackReEnrichFieldLyrics => 'Lyrics'; + + @override + String get trackReEnrichFieldBasicTags => 'Album, Album Artist'; + + @override + String get trackReEnrichFieldTrackInfo => 'Track & Disc Number'; + + @override + String get trackReEnrichFieldReleaseInfo => 'Date & ISRC'; + + @override + String get trackReEnrichFieldExtra => 'Genre, Label, Copyright'; + + @override + String get trackReEnrichSelectAll => 'Select All'; + @override String get trackEditMetadata => 'Edit Metadata'; @@ -5496,6 +5766,28 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed'; + @override + String get queueFlacAction => 'Queue FLAC'; + + @override + String queueFlacConfirmMessage(int count) { + return 'Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n$count selected'; + } + + @override + String queueFlacFindingProgress(int current, int total) { + return 'Finding FLAC matches... ($current/$total)'; + } + + @override + String get queueFlacNoReliableMatches => + 'No reliable online matches found for the selection'; + + @override + String queueFlacQueuedWithSkipped(int addedCount, int skippedCount) { + return 'Added $addedCount tracks to queue, skipped $skippedCount'; + } + @override String trackSaveFailed(String error) { return 'Failed: $error'; @@ -5505,7 +5797,8 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { String get trackConvertFormat => 'Convert Format'; @override - String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + String get trackConvertFormatSubtitle => + 'Convert to MP3, Opus, ALAC, or FLAC'; @override String get trackConvertTitle => 'Convert Audio'; @@ -5528,6 +5821,18 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { return 'Convert from $sourceFormat to $targetFormat at $bitrate?\n\nThe original file will be deleted after conversion.'; } + @override + String trackConvertConfirmMessageLossless( + String sourceFormat, + String targetFormat, + ) { + return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.'; + } + + @override + String get trackConvertLosslessHint => + 'Lossless conversion — no quality loss'; + @override String get trackConvertConverting => 'Converting audio...'; @@ -5781,6 +6086,17 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { return 'Convert $count $_temp0 to $format at $bitrate?\n\nOriginal files will be deleted after conversion.'; } + @override + String selectionBatchConvertConfirmMessageLossless(int count, String format) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.'; + } + @override String selectionBatchConvertProgress(int current, int total) { return 'Converting $current of $total...'; @@ -5803,6 +6119,706 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String get downloadUseAlbumArtistForFoldersTrackSubtitle => 'Artist folders use Track Artist only'; + + @override + String get lyricsProvidersTitle => 'Lyrics Providers'; + + @override + String get lyricsProvidersDescription => + 'Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.'; + + @override + String get lyricsProvidersInfoText => + 'Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.'; + + @override + String lyricsProvidersEnabledSection(int count) { + return 'Enabled ($count)'; + } + + @override + String lyricsProvidersDisabledSection(int count) { + return 'Disabled ($count)'; + } + + @override + String get lyricsProvidersAtLeastOne => + 'At least one provider must remain enabled'; + + @override + String get lyricsProvidersSaved => 'Lyrics provider priority saved'; + + @override + String get lyricsProvidersDiscardContent => + 'You have unsaved changes that will be lost.'; + + @override + String get lyricsProviderLrclibDesc => 'Open-source synced lyrics database'; + + @override + String get lyricsProviderNeteaseDesc => + 'NetEase Cloud Music (good for Asian songs)'; + + @override + String get lyricsProviderMusixmatchDesc => + 'Largest lyrics database (multi-language)'; + + @override + String get lyricsProviderAppleMusicDesc => + 'Word-by-word synced lyrics (via proxy)'; + + @override + String get lyricsProviderQqMusicDesc => + 'QQ Music (good for Chinese songs, via proxy)'; + + @override + String get lyricsProviderExtensionDesc => 'Extension provider'; + + @override + String get safMigrationTitle => 'Storage Update Required'; + + @override + String get safMigrationMessage1 => + 'SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.'; + + @override + String get safMigrationMessage2 => + 'Please select your download folder again to switch to the new storage system.'; + + @override + String get safMigrationSuccess => 'Download folder updated to SAF mode'; + + @override + String get settingsDonate => 'Donate'; + + @override + String get settingsDonateSubtitle => 'Support SpotiFLAC-Mobile development'; + + @override + String get tooltipLoveAll => 'Love All'; + + @override + String get tooltipAddToPlaylist => 'Add to Playlist'; + + @override + String snackbarRemovedTracksFromLoved(int count) { + return 'Removed $count tracks from Loved'; + } + + @override + String snackbarAddedTracksToLoved(int count) { + return 'Added $count tracks to Loved'; + } + + @override + String get dialogDownloadAllTitle => 'Download All'; + + @override + String dialogDownloadAllMessage(int count) { + return 'Download $count tracks?'; + } + + @override + String get homeSkipAlreadyDownloaded => 'Skip already downloaded songs'; + + @override + String get homeGoToAlbum => 'Go to Album'; + + @override + String get homeAlbumInfoUnavailable => 'Album info not available'; + + @override + String get snackbarLoadingCueSheet => 'Loading CUE sheet...'; + + @override + String get snackbarMetadataSaved => 'Metadata saved successfully'; + + @override + String get snackbarFailedToEmbedLyrics => 'Failed to embed lyrics'; + + @override + String get snackbarFailedToWriteStorage => 'Failed to write back to storage'; + + @override + String snackbarError(String error) { + return 'Error: $error'; + } + + @override + String get snackbarNoActionDefined => 'No action defined for this button'; + + @override + String get noTracksFoundForAlbum => 'No tracks found for this album'; + + @override + String get downloadLocationSubtitle => + 'Choose storage mode for downloaded files.'; + + @override + String get storageModeAppFolder => 'App folder (non-SAF)'; + + @override + String get storageModeAppFolderSubtitle => 'Use default Music/SpotiFLAC path'; + + @override + String get storageModeSaf => 'SAF folder'; + + @override + String get storageModeSafSubtitle => + 'Pick folder via Android Storage Access Framework'; + + @override + String get downloadFilenameDescription => + 'Customize how your files are named.'; + + @override + String get downloadFilenameInsertTag => 'Tap to insert tag:'; + + @override + String get downloadSeparateSinglesEnabled => 'Albums/ and Singles/ folders'; + + @override + String get downloadSeparateSinglesDisabled => 'All files in same structure'; + + @override + String get downloadArtistNameFilters => 'Artist Name Filters'; + + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + + @override + String get downloadSongLinkRegion => 'SongLink Region'; + + @override + String get downloadNetworkCompatibilityMode => 'Network compatibility mode'; + + @override + String get downloadNetworkCompatibilityModeEnabled => + 'Enabled: try HTTP + accept invalid TLS certificates (unsafe)'; + + @override + String get downloadNetworkCompatibilityModeDisabled => + 'Off: strict HTTPS certificate validation (recommended)'; + + @override + String get downloadSelectServiceToEnable => + 'Select a built-in service to enable'; + + @override + String get downloadSelectTidalQobuz => + 'Select Tidal or Qobuz above to configure quality'; + + @override + String get downloadEmbedLyricsDisabled => + 'Disabled while Embed Metadata is turned off'; + + @override + String get downloadNeteaseIncludeTranslation => + 'Netease: Include Translation'; + + @override + String get downloadNeteaseIncludeTranslationEnabled => + 'Append translated lyrics when available'; + + @override + String get downloadNeteaseIncludeTranslationDisabled => + 'Use original lyrics only'; + + @override + String get downloadNeteaseIncludeRomanization => + 'Netease: Include Romanization'; + + @override + String get downloadNeteaseIncludeRomanizationEnabled => + 'Append romanized lyrics when available'; + + @override + String get downloadNeteaseIncludeRomanizationDisabled => 'Disabled'; + + @override + String get downloadAppleQqMultiPerson => 'Apple/QQ Multi-Person Word-by-Word'; + + @override + String get downloadAppleQqMultiPersonEnabled => + 'Enable v1/v2 speaker and [bg:] tags'; + + @override + String get downloadAppleQqMultiPersonDisabled => + 'Simplified word-by-word formatting'; + + @override + String get downloadMusixmatchLanguage => 'Musixmatch Language'; + + @override + String get downloadMusixmatchLanguageAuto => 'Auto (original)'; + + @override + String get downloadFilterContributing => + 'Filter contributing artists in Album Artist'; + + @override + String get downloadFilterContributingEnabled => + 'Album Artist metadata uses primary artist only'; + + @override + String get downloadFilterContributingDisabled => + 'Keep full Album Artist metadata value'; + + @override + String get downloadProvidersNoneEnabled => 'None enabled'; + + @override + String get downloadMusixmatchLanguageCode => 'Language code'; + + @override + String get downloadMusixmatchLanguageHint => 'auto / en / es / ja'; + + @override + String get downloadMusixmatchLanguageDesc => + 'Set preferred language code (example: en, es, ja). Leave empty for auto.'; + + @override + String get downloadMusixmatchAuto => 'Auto'; + + @override + String get downloadNetworkAnySubtitle => 'WiFi + Mobile Data'; + + @override + String get downloadNetworkWifiOnlySubtitle => + 'Pause downloads on mobile data'; + + @override + String get downloadSongLinkRegionDesc => + 'Used as userCountry for SongLink API lookup.'; + + @override + String get snackbarUnsupportedAudioFormat => 'Unsupported audio format'; + + @override + String get cacheRefresh => 'Refresh'; + + @override + String dialogDownloadPlaylistsMessage(int trackCount, int playlistCount) { + String _temp0 = intl.Intl.pluralLogic( + trackCount, + locale: localeName, + other: 'tracks', + one: 'track', + ); + String _temp1 = intl.Intl.pluralLogic( + playlistCount, + locale: localeName, + other: 'playlists', + one: 'playlist', + ); + return 'Download $trackCount $_temp0 from $playlistCount $_temp1?'; + } + + @override + String bulkDownloadPlaylistsButton(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'playlists', + one: 'playlist', + ); + return 'Download $count $_temp0'; + } + + @override + String get bulkDownloadSelectPlaylists => 'Select playlists to download'; + + @override + String get snackbarSelectedPlaylistsEmpty => + 'Selected playlists have no tracks'; + + @override + String playlistsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count playlists', + one: '1 playlist', + ); + return '$_temp0'; + } + + @override + String get editMetadataAutoFill => 'Auto-fill from online'; + + @override + String get editMetadataAutoFillDesc => + 'Select fields to fill automatically from online metadata'; + + @override + String get editMetadataAutoFillFetch => 'Fetch & Fill'; + + @override + String get editMetadataAutoFillSearching => 'Searching online...'; + + @override + String get editMetadataAutoFillNoResults => + 'No matching metadata found online'; + + @override + String editMetadataAutoFillDone(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'fields', + one: 'field', + ); + return 'Filled $count $_temp0 from online metadata'; + } + + @override + String get editMetadataAutoFillNoneSelected => + 'Select at least one field to auto-fill'; + + @override + String get editMetadataFieldTitle => 'Title'; + + @override + String get editMetadataFieldArtist => 'Artist'; + + @override + String get editMetadataFieldAlbum => 'Album'; + + @override + String get editMetadataFieldAlbumArtist => 'Album Artist'; + + @override + String get editMetadataFieldDate => 'Date'; + + @override + String get editMetadataFieldTrackNum => 'Track #'; + + @override + String get editMetadataFieldDiscNum => 'Disc #'; + + @override + String get editMetadataFieldGenre => 'Genre'; + + @override + String get editMetadataFieldIsrc => 'ISRC'; + + @override + String get editMetadataFieldLabel => 'Label'; + + @override + String get editMetadataFieldCopyright => 'Copyright'; + + @override + String get editMetadataFieldCover => 'Cover Art'; + + @override + String get editMetadataSelectAll => 'All'; + + @override + String get editMetadataSelectEmpty => 'Empty only'; + + @override + String queueDownloadingCount(int count) { + return 'Downloading ($count)'; + } + + @override + String get queueDownloadedHeader => 'Downloaded'; + + @override + String get queueFilteringIndicator => 'Filtering...'; + + @override + String queueTrackCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count tracks', + one: '1 track', + ); + return '$_temp0'; + } + + @override + String queueAlbumCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count albums', + one: '1 album', + ); + return '$_temp0'; + } + + @override + String get queueEmptyAlbums => 'No album downloads'; + + @override + String get queueEmptyAlbumsSubtitle => + 'Download multiple tracks from an album to see them here'; + + @override + String get queueEmptySingles => 'No single downloads'; + + @override + String get queueEmptySinglesSubtitle => + 'Single track downloads will appear here'; + + @override + String get queueEmptyHistory => 'No download history'; + + @override + String get queueEmptyHistorySubtitle => 'Downloaded tracks will appear here'; + + @override + String get selectionAllPlaylistsSelected => 'All playlists selected'; + + @override + String get selectionTapPlaylistsToSelect => 'Tap playlists to select'; + + @override + String get selectionSelectPlaylistsToDelete => 'Select playlists to delete'; + + @override + String get audioAnalysisTitle => 'Audio Quality Analysis'; + + @override + String get audioAnalysisDescription => + 'Verify lossless quality with spectrum analysis'; + + @override + String get audioAnalysisAnalyzing => 'Analyzing audio...'; + + @override + String get audioAnalysisSampleRate => 'Sample Rate'; + + @override + String get audioAnalysisBitDepth => 'Bit Depth'; + + @override + String get audioAnalysisChannels => 'Channels'; + + @override + String get audioAnalysisDuration => 'Duration'; + + @override + String get audioAnalysisNyquist => 'Nyquist'; + + @override + String get audioAnalysisFileSize => 'Size'; + + @override + String get audioAnalysisDynamicRange => 'Dynamic Range'; + + @override + String get audioAnalysisPeak => 'Peak'; + + @override + String get audioAnalysisRms => 'RMS'; + + @override + String get audioAnalysisSamples => 'Samples'; + + @override + String extensionsSearchWith(String providerName) { + return 'Search with $providerName'; + } + + @override + String get extensionsHomeFeedProvider => 'Home Feed Provider'; + + @override + String get extensionsHomeFeedDescription => + 'Choose which extension provides the home feed on the main screen'; + + @override + String get extensionsHomeFeedAuto => 'Auto'; + + @override + String get extensionsHomeFeedAutoSubtitle => + 'Automatically select the best available'; + + @override + String extensionsHomeFeedUse(String extensionName) { + return 'Use $extensionName home feed'; + } + + @override + String get extensionsNoHomeFeedExtensions => 'No extensions with home feed'; + + @override + String get sortAlphaAsc => 'A-Z'; + + @override + String get sortAlphaDesc => 'Z-A'; + + @override + String get cancelDownloadTitle => 'Cancel download?'; + + @override + String cancelDownloadContent(String trackName) { + return 'This will cancel the active download for \"$trackName\".'; + } + + @override + String get cancelDownloadKeep => 'Keep'; + + @override + String get metadataSaveFailedFfmpeg => 'Failed to save metadata via FFmpeg'; + + @override + String get metadataSaveFailedStorage => + 'Failed to write metadata back to storage'; + + @override + String snackbarFolderPickerFailed(String error) { + return 'Failed to open folder picker: $error'; + } + + @override + String get errorLoadAlbum => 'Failed to load album'; + + @override + String get errorLoadPlaylist => 'Failed to load playlist'; + + @override + String get errorLoadArtist => 'Failed to load artist'; + + @override + String get notifChannelDownloadName => 'Download Progress'; + + @override + String get notifChannelDownloadDesc => 'Shows download progress for tracks'; + + @override + String get notifChannelLibraryScanName => 'Library Scan'; + + @override + String get notifChannelLibraryScanDesc => 'Shows local library scan progress'; + + @override + String notifDownloadingTrack(String trackName) { + return 'Downloading $trackName'; + } + + @override + String notifFinalizingTrack(String trackName) { + return 'Finalizing $trackName'; + } + + @override + String get notifEmbeddingMetadata => 'Embedding metadata...'; + + @override + String notifAlreadyInLibraryCount(int completed, int total) { + return 'Already in Library ($completed/$total)'; + } + + @override + String get notifAlreadyInLibrary => 'Already in Library'; + + @override + String notifDownloadCompleteCount(int completed, int total) { + return 'Download Complete ($completed/$total)'; + } + + @override + String get notifDownloadComplete => 'Download Complete'; + + @override + String notifDownloadsFinished(int completed, int failed) { + return 'Downloads Finished ($completed done, $failed failed)'; + } + + @override + String get notifAllDownloadsComplete => 'All Downloads Complete'; + + @override + String notifTracksDownloadedSuccess(int count) { + return '$count tracks downloaded successfully'; + } + + @override + String get notifScanningLibrary => 'Scanning local library'; + + @override + String notifLibraryScanProgressWithTotal( + int scanned, + int total, + int percentage, + ) { + return '$scanned/$total files • $percentage%'; + } + + @override + String notifLibraryScanProgressNoTotal(int scanned, int percentage) { + return '$scanned files scanned • $percentage%'; + } + + @override + String get notifLibraryScanComplete => 'Library scan complete'; + + @override + String notifLibraryScanCompleteBody(int count) { + return '$count tracks indexed'; + } + + @override + String notifLibraryScanExcluded(int count) { + return '$count excluded'; + } + + @override + String notifLibraryScanErrors(int count) { + return '$count errors'; + } + + @override + String get notifLibraryScanFailed => 'Library scan failed'; + + @override + String get notifLibraryScanCancelled => 'Library scan cancelled'; + + @override + String get notifLibraryScanStopped => 'Scan stopped before completion.'; + + @override + String notifDownloadingUpdate(String version) { + return 'Downloading SpotiFLAC v$version'; + } + + @override + String notifUpdateProgress(String received, String total, int percentage) { + return '$received / $total MB • $percentage%'; + } + + @override + String get notifUpdateReady => 'Update Ready'; + + @override + String notifUpdateReadyBody(String version) { + return 'SpotiFLAC v$version downloaded. Tap to install.'; + } + + @override + String get notifUpdateFailed => 'Update Failed'; + + @override + String get notifUpdateFailedBody => + 'Could not download update. Try again later.'; } /// The translations for Chinese, as used in Taiwan (`zh_TW`). @@ -5822,13 +6838,13 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get navSettings => 'Settings'; @override - String get navStore => 'Store'; + String get navStore => 'Repo'; @override String get homeTitle => 'Home'; @override - String get homeSubtitle => 'Paste a Spotify link or search by name'; + String get homeSubtitle => 'Paste a supported URL or search by name'; @override String get homeSupports => 'Supports: Track, Album, Playlist, Artist URLs'; @@ -5876,6 +6892,13 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get downloadFilenameFormat => 'Filename Format'; + @override + String get downloadSingleFilenameFormat => 'Single Filename Format'; + + @override + String get downloadSingleFilenameFormatDescription => + 'Filename pattern for singles and EPs. Uses the same tags as the album format.'; + @override String get downloadFolderOrganization => 'Folder Organization'; @@ -5921,6 +6944,13 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { return 'Using extension: $extensionName'; } + @override + String get optionsDefaultSearchTab => 'Default Search Tab'; + + @override + String get optionsDefaultSearchTabSubtitle => + 'Choose which tab opens first for new search results.'; + @override String get optionsSwitchBack => 'Tap Deezer or Spotify to switch back from extension'; @@ -5955,6 +6985,38 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get optionsMaxQualityCoverSubtitle => 'Download highest resolution cover art'; + @override + String get optionsReplayGain => 'ReplayGain'; + + @override + String get optionsReplayGainSubtitleOn => + 'Scan loudness and embed ReplayGain tags (EBU R128)'; + + @override + String get optionsReplayGainSubtitleOff => + 'Disabled: no loudness normalization tags'; + + @override + String get optionsArtistTagMode => 'Artist Tag Mode'; + + @override + String get optionsArtistTagModeDescription => + 'Choose how multiple artists are written into embedded tags.'; + + @override + String get optionsArtistTagModeJoined => 'Single joined value'; + + @override + String get optionsArtistTagModeJoinedSubtitle => + 'Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.'; + + @override + String get optionsArtistTagModeSplitVorbis => 'Split tags for FLAC/Opus'; + + @override + String get optionsArtistTagModeSplitVorbisSubtitle => + 'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.'; + @override String get optionsConcurrentDownloads => 'Concurrent Downloads'; @@ -5971,10 +7033,10 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { 'Parallel downloads may trigger rate limiting'; @override - String get optionsExtensionStore => 'Extension Store'; + String get optionsExtensionStore => 'Extension Repo'; @override - String get optionsExtensionStoreSubtitle => 'Show Store tab in navigation'; + String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation'; @override String get optionsCheckUpdates => 'Check for Updates'; @@ -6051,7 +7113,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get extensionsUninstall => 'Uninstall'; @override - String get storeTitle => 'Extension Store'; + String get storeTitle => 'Extension Repo'; @override String get storeSearch => 'Search extensions...'; @@ -6329,6 +7391,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get dialogImport => 'Import'; + @override + String get dialogDownload => 'Download'; + @override String get dialogDiscard => 'Discard'; @@ -6560,6 +7625,36 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get searchPlaylists => 'Playlists'; + @override + String get searchSortTitle => 'Sort Results'; + + @override + String get searchSortDefault => 'Default'; + + @override + String get searchSortTitleAZ => 'Title (A-Z)'; + + @override + String get searchSortTitleZA => 'Title (Z-A)'; + + @override + String get searchSortArtistAZ => 'Artist (A-Z)'; + + @override + String get searchSortArtistZA => 'Artist (Z-A)'; + + @override + String get searchSortDurationShort => 'Duration (Shortest)'; + + @override + String get searchSortDurationLong => 'Duration (Longest)'; + + @override + String get searchSortDateOldest => 'Release Date (Oldest)'; + + @override + String get searchSortDateNewest => 'Release Date (Newest)'; + @override String get tooltipPlay => 'Play'; @@ -6658,6 +7753,17 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get providerPriorityInfo => 'If a track is not available on the first provider, the app will automatically try the next one.'; + @override + String get providerPriorityFallbackExtensionsTitle => 'Extension Fallback'; + + @override + String get providerPriorityFallbackExtensionsDescription => + 'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.'; + + @override + String get providerPriorityFallbackExtensionsHint => + 'Only enabled extensions with download-provider capability are listed here.'; + @override String get providerBuiltIn => 'Built-in'; @@ -6932,6 +8038,12 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get trackLyricsNotAvailable => 'Lyrics not available for this track'; + @override + String get trackLyricsNotInFile => 'No lyrics found in this file'; + + @override + String get trackFetchOnlineLyrics => 'Fetch from Online'; + @override String get trackLyricsTimeout => 'Request timed out. Try again later.'; @@ -7000,7 +8112,48 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get storeClearFilters => 'Clear filters'; @override - String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; + String get storeAddRepoTitle => 'Add Extension Repository'; + + @override + String get storeAddRepoDescription => + 'Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.'; + + @override + String get storeRepoUrlLabel => 'Repository URL'; + + @override + String get storeRepoUrlHint => 'https://github.com/user/repo'; + + @override + String get storeRepoUrlHelper => + 'e.g. https://github.com/user/extensions-repo'; + + @override + String get storeAddRepoButton => 'Add Repository'; + + @override + String get storeChangeRepoTooltip => 'Change repository'; + + @override + String get storeRepoDialogTitle => 'Extension Repository'; + + @override + String get storeRepoDialogCurrent => 'Current repository:'; + + @override + String get storeNewRepoUrlLabel => 'New Repository URL'; + + @override + String get storeLoadError => 'Failed to load repository'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + + @override + String get extensionDefaultProvider => 'Default (Deezer)'; @override String get extensionDefaultProviderSubtitle => 'Use built-in search'; @@ -7100,6 +8253,13 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get extensionsDownloadPrioritySubtitle => 'Set download service order'; + @override + String get extensionsFallbackTitle => 'Fallback Extensions'; + + @override + String get extensionsFallbackSubtitle => + 'Choose which installed download extensions can be used as fallback'; + @override String get extensionsNoDownloadProvider => 'No extensions with download provider'; @@ -7149,6 +8309,38 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz'; + @override + String get downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @override + String get downloadLossy320Format => 'Lossy 320kbps Format'; + + @override + String get downloadLossy320FormatDesc => + 'Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.'; + + @override + String get downloadLossyMp3 => 'MP3 320kbps'; + + @override + String get downloadLossyMp3Subtitle => 'Best compatibility, ~10MB per track'; + + @override + String get downloadLossyOpus256 => 'Opus 256kbps'; + + @override + String get downloadLossyOpus256Subtitle => + 'Best quality Opus, ~8MB per track'; + + @override + String get downloadLossyOpus128 => 'Opus 128kbps'; + + @override + String get downloadLossyOpus128Subtitle => 'Smallest size, ~4MB per track'; + @override String get qualityNote => 'Actual quality depends on track availability from the service'; @@ -7250,6 +8442,13 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get albumFolderArtistAlbumSinglesSubtitle => 'Artist/Album/ and Artist/Singles/'; + @override + String get albumFolderArtistAlbumFlat => 'Artist / Album (Singles flat)'; + + @override + String get albumFolderArtistAlbumFlatSubtitle => + 'Artist/Album/ and Artist/song.flac'; + @override String get downloadedAlbumDeleteSelected => 'Delete Selected'; @@ -7445,6 +8644,25 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; @@ -7495,6 +8713,17 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { return '$_temp0'; } + @override + String libraryFilesUnit(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'files', + one: 'file', + ); + return '$_temp0'; + } + @override String libraryLastScanned(String time) { return 'Last scanned: $time'; @@ -7506,6 +8735,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get libraryScanning => 'Scanning...'; + @override + String get libraryScanFinalizing => 'Finalizing library...'; + @override String libraryScanProgress(String progress, int total) { return '$progress% of $total files'; @@ -7574,6 +8806,24 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get libraryFilterFormat => 'Format'; + @override + String get libraryFilterMetadata => 'Metadata'; + + @override + String get libraryFilterMetadataComplete => 'Complete metadata'; + + @override + String get libraryFilterMetadataMissingAny => 'Missing any metadata'; + + @override + String get libraryFilterMetadataMissingYear => 'Missing year'; + + @override + String get libraryFilterMetadataMissingGenre => 'Missing genre'; + + @override + String get libraryFilterMetadataMissingAlbumArtist => 'Missing album artist'; + @override String get libraryFilterSort => 'Sort'; @@ -7583,6 +8833,18 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get libraryFilterSortOldest => 'Oldest'; + @override + String get libraryFilterSortAlbumAsc => 'Album (A-Z)'; + + @override + String get libraryFilterSortAlbumDesc => 'Album (Z-A)'; + + @override + String get libraryFilterSortGenreAsc => 'Genre (A-Z)'; + + @override + String get libraryFilterSortGenreDesc => 'Genre (Z-A)'; + @override String get timeJustNow => 'Just now'; @@ -7669,7 +8931,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get tutorialExtensionsTip1 => - 'Browse the Store tab to discover useful extensions'; + 'Browse the Repo tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => @@ -7867,6 +9129,30 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get trackReEnrichOnlineSubtitle => 'Search metadata online and embed into file'; + @override + String get trackReEnrichFieldsTitle => 'Fields to update'; + + @override + String get trackReEnrichFieldCover => 'Cover Art'; + + @override + String get trackReEnrichFieldLyrics => 'Lyrics'; + + @override + String get trackReEnrichFieldBasicTags => 'Album, Album Artist'; + + @override + String get trackReEnrichFieldTrackInfo => 'Track & Disc Number'; + + @override + String get trackReEnrichFieldReleaseInfo => 'Date & ISRC'; + + @override + String get trackReEnrichFieldExtra => 'Genre, Label, Copyright'; + + @override + String get trackReEnrichSelectAll => 'Select All'; + @override String get trackEditMetadata => 'Edit Metadata'; @@ -7895,6 +9181,28 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed'; + @override + String get queueFlacAction => 'Queue FLAC'; + + @override + String queueFlacConfirmMessage(int count) { + return 'Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n$count selected'; + } + + @override + String queueFlacFindingProgress(int current, int total) { + return 'Finding FLAC matches... ($current/$total)'; + } + + @override + String get queueFlacNoReliableMatches => + 'No reliable online matches found for the selection'; + + @override + String queueFlacQueuedWithSkipped(int addedCount, int skippedCount) { + return 'Added $addedCount tracks to queue, skipped $skippedCount'; + } + @override String trackSaveFailed(String error) { return 'Failed: $error'; @@ -7904,7 +9212,8 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get trackConvertFormat => 'Convert Format'; @override - String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + String get trackConvertFormatSubtitle => + 'Convert to MP3, Opus, ALAC, or FLAC'; @override String get trackConvertTitle => 'Convert Audio'; @@ -7927,6 +9236,18 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { return 'Convert from $sourceFormat to $targetFormat at $bitrate?\n\nThe original file will be deleted after conversion.'; } + @override + String trackConvertConfirmMessageLossless( + String sourceFormat, + String targetFormat, + ) { + return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.'; + } + + @override + String get trackConvertLosslessHint => + 'Lossless conversion — no quality loss'; + @override String get trackConvertConverting => 'Converting audio...'; @@ -8180,6 +9501,17 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { return 'Convert $count $_temp0 to $format at $bitrate?\n\nOriginal files will be deleted after conversion.'; } + @override + String selectionBatchConvertConfirmMessageLossless(int count, String format) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.'; + } + @override String selectionBatchConvertProgress(int current, int total) { return 'Converting $current of $total...'; @@ -8202,4 +9534,704 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get downloadUseAlbumArtistForFoldersTrackSubtitle => 'Artist folders use Track Artist only'; + + @override + String get lyricsProvidersTitle => 'Lyrics Providers'; + + @override + String get lyricsProvidersDescription => + 'Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.'; + + @override + String get lyricsProvidersInfoText => + 'Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.'; + + @override + String lyricsProvidersEnabledSection(int count) { + return 'Enabled ($count)'; + } + + @override + String lyricsProvidersDisabledSection(int count) { + return 'Disabled ($count)'; + } + + @override + String get lyricsProvidersAtLeastOne => + 'At least one provider must remain enabled'; + + @override + String get lyricsProvidersSaved => 'Lyrics provider priority saved'; + + @override + String get lyricsProvidersDiscardContent => + 'You have unsaved changes that will be lost.'; + + @override + String get lyricsProviderLrclibDesc => 'Open-source synced lyrics database'; + + @override + String get lyricsProviderNeteaseDesc => + 'NetEase Cloud Music (good for Asian songs)'; + + @override + String get lyricsProviderMusixmatchDesc => + 'Largest lyrics database (multi-language)'; + + @override + String get lyricsProviderAppleMusicDesc => + 'Word-by-word synced lyrics (via proxy)'; + + @override + String get lyricsProviderQqMusicDesc => + 'QQ Music (good for Chinese songs, via proxy)'; + + @override + String get lyricsProviderExtensionDesc => 'Extension provider'; + + @override + String get safMigrationTitle => 'Storage Update Required'; + + @override + String get safMigrationMessage1 => + 'SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.'; + + @override + String get safMigrationMessage2 => + 'Please select your download folder again to switch to the new storage system.'; + + @override + String get safMigrationSuccess => 'Download folder updated to SAF mode'; + + @override + String get settingsDonate => 'Donate'; + + @override + String get settingsDonateSubtitle => 'Support SpotiFLAC-Mobile development'; + + @override + String get tooltipLoveAll => 'Love All'; + + @override + String get tooltipAddToPlaylist => 'Add to Playlist'; + + @override + String snackbarRemovedTracksFromLoved(int count) { + return 'Removed $count tracks from Loved'; + } + + @override + String snackbarAddedTracksToLoved(int count) { + return 'Added $count tracks to Loved'; + } + + @override + String get dialogDownloadAllTitle => 'Download All'; + + @override + String dialogDownloadAllMessage(int count) { + return 'Download $count tracks?'; + } + + @override + String get homeSkipAlreadyDownloaded => 'Skip already downloaded songs'; + + @override + String get homeGoToAlbum => 'Go to Album'; + + @override + String get homeAlbumInfoUnavailable => 'Album info not available'; + + @override + String get snackbarLoadingCueSheet => 'Loading CUE sheet...'; + + @override + String get snackbarMetadataSaved => 'Metadata saved successfully'; + + @override + String get snackbarFailedToEmbedLyrics => 'Failed to embed lyrics'; + + @override + String get snackbarFailedToWriteStorage => 'Failed to write back to storage'; + + @override + String snackbarError(String error) { + return 'Error: $error'; + } + + @override + String get snackbarNoActionDefined => 'No action defined for this button'; + + @override + String get noTracksFoundForAlbum => 'No tracks found for this album'; + + @override + String get downloadLocationSubtitle => + 'Choose storage mode for downloaded files.'; + + @override + String get storageModeAppFolder => 'App folder (non-SAF)'; + + @override + String get storageModeAppFolderSubtitle => 'Use default Music/SpotiFLAC path'; + + @override + String get storageModeSaf => 'SAF folder'; + + @override + String get storageModeSafSubtitle => + 'Pick folder via Android Storage Access Framework'; + + @override + String get downloadFilenameDescription => + 'Customize how your files are named.'; + + @override + String get downloadFilenameInsertTag => 'Tap to insert tag:'; + + @override + String get downloadSeparateSinglesEnabled => 'Albums/ and Singles/ folders'; + + @override + String get downloadSeparateSinglesDisabled => 'All files in same structure'; + + @override + String get downloadArtistNameFilters => 'Artist Name Filters'; + + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + + @override + String get downloadSongLinkRegion => 'SongLink Region'; + + @override + String get downloadNetworkCompatibilityMode => 'Network compatibility mode'; + + @override + String get downloadNetworkCompatibilityModeEnabled => + 'Enabled: try HTTP + accept invalid TLS certificates (unsafe)'; + + @override + String get downloadNetworkCompatibilityModeDisabled => + 'Off: strict HTTPS certificate validation (recommended)'; + + @override + String get downloadSelectServiceToEnable => + 'Select a built-in service to enable'; + + @override + String get downloadSelectTidalQobuz => + 'Select Tidal or Qobuz above to configure quality'; + + @override + String get downloadEmbedLyricsDisabled => + 'Disabled while Embed Metadata is turned off'; + + @override + String get downloadNeteaseIncludeTranslation => + 'Netease: Include Translation'; + + @override + String get downloadNeteaseIncludeTranslationEnabled => + 'Append translated lyrics when available'; + + @override + String get downloadNeteaseIncludeTranslationDisabled => + 'Use original lyrics only'; + + @override + String get downloadNeteaseIncludeRomanization => + 'Netease: Include Romanization'; + + @override + String get downloadNeteaseIncludeRomanizationEnabled => + 'Append romanized lyrics when available'; + + @override + String get downloadNeteaseIncludeRomanizationDisabled => 'Disabled'; + + @override + String get downloadAppleQqMultiPerson => 'Apple/QQ Multi-Person Word-by-Word'; + + @override + String get downloadAppleQqMultiPersonEnabled => + 'Enable v1/v2 speaker and [bg:] tags'; + + @override + String get downloadAppleQqMultiPersonDisabled => + 'Simplified word-by-word formatting'; + + @override + String get downloadMusixmatchLanguage => 'Musixmatch Language'; + + @override + String get downloadMusixmatchLanguageAuto => 'Auto (original)'; + + @override + String get downloadFilterContributing => + 'Filter contributing artists in Album Artist'; + + @override + String get downloadFilterContributingEnabled => + 'Album Artist metadata uses primary artist only'; + + @override + String get downloadFilterContributingDisabled => + 'Keep full Album Artist metadata value'; + + @override + String get downloadProvidersNoneEnabled => 'None enabled'; + + @override + String get downloadMusixmatchLanguageCode => 'Language code'; + + @override + String get downloadMusixmatchLanguageHint => 'auto / en / es / ja'; + + @override + String get downloadMusixmatchLanguageDesc => + 'Set preferred language code (example: en, es, ja). Leave empty for auto.'; + + @override + String get downloadMusixmatchAuto => 'Auto'; + + @override + String get downloadNetworkAnySubtitle => 'WiFi + Mobile Data'; + + @override + String get downloadNetworkWifiOnlySubtitle => + 'Pause downloads on mobile data'; + + @override + String get downloadSongLinkRegionDesc => + 'Used as userCountry for SongLink API lookup.'; + + @override + String get snackbarUnsupportedAudioFormat => 'Unsupported audio format'; + + @override + String get cacheRefresh => 'Refresh'; + + @override + String dialogDownloadPlaylistsMessage(int trackCount, int playlistCount) { + String _temp0 = intl.Intl.pluralLogic( + trackCount, + locale: localeName, + other: 'tracks', + one: 'track', + ); + String _temp1 = intl.Intl.pluralLogic( + playlistCount, + locale: localeName, + other: 'playlists', + one: 'playlist', + ); + return 'Download $trackCount $_temp0 from $playlistCount $_temp1?'; + } + + @override + String bulkDownloadPlaylistsButton(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'playlists', + one: 'playlist', + ); + return 'Download $count $_temp0'; + } + + @override + String get bulkDownloadSelectPlaylists => 'Select playlists to download'; + + @override + String get snackbarSelectedPlaylistsEmpty => + 'Selected playlists have no tracks'; + + @override + String playlistsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count playlists', + one: '1 playlist', + ); + return '$_temp0'; + } + + @override + String get editMetadataAutoFill => 'Auto-fill from online'; + + @override + String get editMetadataAutoFillDesc => + 'Select fields to fill automatically from online metadata'; + + @override + String get editMetadataAutoFillFetch => 'Fetch & Fill'; + + @override + String get editMetadataAutoFillSearching => 'Searching online...'; + + @override + String get editMetadataAutoFillNoResults => + 'No matching metadata found online'; + + @override + String editMetadataAutoFillDone(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'fields', + one: 'field', + ); + return 'Filled $count $_temp0 from online metadata'; + } + + @override + String get editMetadataAutoFillNoneSelected => + 'Select at least one field to auto-fill'; + + @override + String get editMetadataFieldTitle => 'Title'; + + @override + String get editMetadataFieldArtist => 'Artist'; + + @override + String get editMetadataFieldAlbum => 'Album'; + + @override + String get editMetadataFieldAlbumArtist => 'Album Artist'; + + @override + String get editMetadataFieldDate => 'Date'; + + @override + String get editMetadataFieldTrackNum => 'Track #'; + + @override + String get editMetadataFieldDiscNum => 'Disc #'; + + @override + String get editMetadataFieldGenre => 'Genre'; + + @override + String get editMetadataFieldIsrc => 'ISRC'; + + @override + String get editMetadataFieldLabel => 'Label'; + + @override + String get editMetadataFieldCopyright => 'Copyright'; + + @override + String get editMetadataFieldCover => 'Cover Art'; + + @override + String get editMetadataSelectAll => 'All'; + + @override + String get editMetadataSelectEmpty => 'Empty only'; + + @override + String queueDownloadingCount(int count) { + return 'Downloading ($count)'; + } + + @override + String get queueDownloadedHeader => 'Downloaded'; + + @override + String get queueFilteringIndicator => 'Filtering...'; + + @override + String queueTrackCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count tracks', + one: '1 track', + ); + return '$_temp0'; + } + + @override + String queueAlbumCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count albums', + one: '1 album', + ); + return '$_temp0'; + } + + @override + String get queueEmptyAlbums => 'No album downloads'; + + @override + String get queueEmptyAlbumsSubtitle => + 'Download multiple tracks from an album to see them here'; + + @override + String get queueEmptySingles => 'No single downloads'; + + @override + String get queueEmptySinglesSubtitle => + 'Single track downloads will appear here'; + + @override + String get queueEmptyHistory => 'No download history'; + + @override + String get queueEmptyHistorySubtitle => 'Downloaded tracks will appear here'; + + @override + String get selectionAllPlaylistsSelected => 'All playlists selected'; + + @override + String get selectionTapPlaylistsToSelect => 'Tap playlists to select'; + + @override + String get selectionSelectPlaylistsToDelete => 'Select playlists to delete'; + + @override + String get audioAnalysisTitle => 'Audio Quality Analysis'; + + @override + String get audioAnalysisDescription => + 'Verify lossless quality with spectrum analysis'; + + @override + String get audioAnalysisAnalyzing => 'Analyzing audio...'; + + @override + String get audioAnalysisSampleRate => 'Sample Rate'; + + @override + String get audioAnalysisBitDepth => 'Bit Depth'; + + @override + String get audioAnalysisChannels => 'Channels'; + + @override + String get audioAnalysisDuration => 'Duration'; + + @override + String get audioAnalysisNyquist => 'Nyquist'; + + @override + String get audioAnalysisFileSize => 'Size'; + + @override + String get audioAnalysisDynamicRange => 'Dynamic Range'; + + @override + String get audioAnalysisPeak => 'Peak'; + + @override + String get audioAnalysisRms => 'RMS'; + + @override + String get audioAnalysisSamples => 'Samples'; + + @override + String extensionsSearchWith(String providerName) { + return 'Search with $providerName'; + } + + @override + String get extensionsHomeFeedProvider => 'Home Feed Provider'; + + @override + String get extensionsHomeFeedDescription => + 'Choose which extension provides the home feed on the main screen'; + + @override + String get extensionsHomeFeedAuto => 'Auto'; + + @override + String get extensionsHomeFeedAutoSubtitle => + 'Automatically select the best available'; + + @override + String extensionsHomeFeedUse(String extensionName) { + return 'Use $extensionName home feed'; + } + + @override + String get extensionsNoHomeFeedExtensions => 'No extensions with home feed'; + + @override + String get sortAlphaAsc => 'A-Z'; + + @override + String get sortAlphaDesc => 'Z-A'; + + @override + String get cancelDownloadTitle => 'Cancel download?'; + + @override + String cancelDownloadContent(String trackName) { + return 'This will cancel the active download for \"$trackName\".'; + } + + @override + String get cancelDownloadKeep => 'Keep'; + + @override + String get metadataSaveFailedFfmpeg => 'Failed to save metadata via FFmpeg'; + + @override + String get metadataSaveFailedStorage => + 'Failed to write metadata back to storage'; + + @override + String snackbarFolderPickerFailed(String error) { + return 'Failed to open folder picker: $error'; + } + + @override + String get errorLoadAlbum => 'Failed to load album'; + + @override + String get errorLoadPlaylist => 'Failed to load playlist'; + + @override + String get errorLoadArtist => 'Failed to load artist'; + + @override + String get notifChannelDownloadName => 'Download Progress'; + + @override + String get notifChannelDownloadDesc => 'Shows download progress for tracks'; + + @override + String get notifChannelLibraryScanName => 'Library Scan'; + + @override + String get notifChannelLibraryScanDesc => 'Shows local library scan progress'; + + @override + String notifDownloadingTrack(String trackName) { + return 'Downloading $trackName'; + } + + @override + String notifFinalizingTrack(String trackName) { + return 'Finalizing $trackName'; + } + + @override + String get notifEmbeddingMetadata => 'Embedding metadata...'; + + @override + String notifAlreadyInLibraryCount(int completed, int total) { + return 'Already in Library ($completed/$total)'; + } + + @override + String get notifAlreadyInLibrary => 'Already in Library'; + + @override + String notifDownloadCompleteCount(int completed, int total) { + return 'Download Complete ($completed/$total)'; + } + + @override + String get notifDownloadComplete => 'Download Complete'; + + @override + String notifDownloadsFinished(int completed, int failed) { + return 'Downloads Finished ($completed done, $failed failed)'; + } + + @override + String get notifAllDownloadsComplete => 'All Downloads Complete'; + + @override + String notifTracksDownloadedSuccess(int count) { + return '$count tracks downloaded successfully'; + } + + @override + String get notifScanningLibrary => 'Scanning local library'; + + @override + String notifLibraryScanProgressWithTotal( + int scanned, + int total, + int percentage, + ) { + return '$scanned/$total files • $percentage%'; + } + + @override + String notifLibraryScanProgressNoTotal(int scanned, int percentage) { + return '$scanned files scanned • $percentage%'; + } + + @override + String get notifLibraryScanComplete => 'Library scan complete'; + + @override + String notifLibraryScanCompleteBody(int count) { + return '$count tracks indexed'; + } + + @override + String notifLibraryScanExcluded(int count) { + return '$count excluded'; + } + + @override + String notifLibraryScanErrors(int count) { + return '$count errors'; + } + + @override + String get notifLibraryScanFailed => 'Library scan failed'; + + @override + String get notifLibraryScanCancelled => 'Library scan cancelled'; + + @override + String get notifLibraryScanStopped => 'Scan stopped before completion.'; + + @override + String notifDownloadingUpdate(String version) { + return 'Downloading SpotiFLAC v$version'; + } + + @override + String notifUpdateProgress(String received, String total, int percentage) { + return '$received / $total MB • $percentage%'; + } + + @override + String get notifUpdateReady => 'Update Ready'; + + @override + String notifUpdateReadyBody(String version) { + return 'SpotiFLAC v$version downloaded. Tap to install.'; + } + + @override + String get notifUpdateFailed => 'Update Failed'; + + @override + String get notifUpdateFailedBody => + 'Could not download update. Try again later.'; } diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 02aa4799..188f5d34 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -17,7 +17,7 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "Store", + "navStore": "Repo", "@navStore": { "description": "Bottom navigation - Extension store tab" }, @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Spotify-Link einfügen oder nach Namen suchen", + "homeSubtitle": "Unterstützte URL einfügen oder nach Namen suchen", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -89,9 +89,17 @@ "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, "downloadFolderOrganization": "Ordnerstruktur", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "Erscheinungsbild", "@appearanceTitle": { @@ -150,6 +158,14 @@ } } }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, "optionsSwitchBack": "Tippe auf Deezer oder Spotify, um von der Erweiterung zurückzuwechseln", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" @@ -190,6 +206,42 @@ "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Künstler Tag-Modus", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Wähle aus, wie mehrere Künstler in eingebetteten Tags geschrieben sind.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Einzelne beigefügte Werte", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Einen Künstler wert wie \"Artist A, Artist B\" für maximale Spieler-Kompatibilität schreiben.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Tags für FLAC/Opus aufteilen", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, "optionsConcurrentDownloads": "Parallele Downloads", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" @@ -211,11 +263,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "Erweiterungs-Store", + "optionsExtensionStore": "Erweiterungs-Repo", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "Store-Tab in Navigation anzeigen", + "optionsExtensionStoreSubtitle": "Repo-Tab in der Navigation anzeigen", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -318,7 +370,7 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "Erweiterungs-Store", + "storeTitle": "Erweiterungs-Repo", "@storeTitle": { "description": "Store screen title" }, @@ -675,6 +727,10 @@ "@dialogImport": { "description": "Dialog button - import data" }, + "dialogDownload": "Herunterladen", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, "dialogDiscard": "Verwerfen", "@dialogDiscard": { "description": "Dialog button - discard changes" @@ -732,7 +788,7 @@ "@dialogDeleteSelectedTitle": { "description": "Dialog title - delete selected items" }, - "dialogDeleteSelectedMessage": "Lösche {count} {count, plural, one {Track} other{Tracks}} aus dem Verlauf?\n\nDies löscht auch die Dateien aus dem Speicher.", + "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": { "description": "Dialog message - delete selected tracks", "placeholders": { @@ -811,7 +867,7 @@ "@snackbarCredentialsCleared": { "description": "Snackbar - Spotify credentials removed" }, - "snackbarDeletedTracks": "{count} {count, plural, one {Titel} other{Titel}}", + "snackbarDeletedTracks": "{count} {count, plural, one {}=1{Titel} other{Titel}}", "@snackbarDeletedTracks": { "description": "Snackbar - tracks deleted", "placeholders": { @@ -995,10 +1051,50 @@ "@searchAlbums": { "description": "Search result category - albums" }, - "searchPlaylists": "Playlisten", + "searchPlaylists": "Playlists", "@searchPlaylists": { "description": "Search result category - playlists" }, + "searchSortTitle": "Ergebnisse sortieren", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Standard", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Titel (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Titel (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Künstler (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Künstler (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Dauer (kürzeste)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Dauer (längste)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Veröffentlichungsdatum (älteste)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Veröffentlichungsdatum (Neueste)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, "tooltipPlay": "Abspielen", "@tooltipPlay": { "description": "Tooltip - play button" @@ -1119,6 +1215,18 @@ "@providerPriorityInfo": { "description": "Info tip about fallback behavior" }, + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, "providerBuiltIn": "Integriert", "@providerBuiltIn": { "description": "Label for built-in providers (Tidal/Qobuz)" @@ -1479,6 +1587,14 @@ "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, "trackLyricsTimeout": "Anfrage Timeout. Versuche es später erneut.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" @@ -1574,7 +1690,59 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "extensionDefaultProvider": "Standard (Deezer/Spotify)", + "storeAddRepoTitle": "Erweiterungs-Repository hinzufügen", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Gib eine GitHub Repository-URL ein, die eine Registry.json Datei enthält, um Erweiterungen zu durchsuchen und zu installieren.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository-URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "z.B. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Repository hinzufügen", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Repository ändern", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Erweiterungs-Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Aktuelles Repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "Neue Repository-URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionDefaultProvider": "Standard (Deezer)", "@extensionDefaultProvider": { "description": "Default search provider option" }, @@ -1713,6 +1881,14 @@ "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, + "extensionsFallbackTitle": "Fallback Extensions", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, "extensionsNoDownloadProvider": "Keine Erweiterungen mit Download-Provider", "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" @@ -1773,6 +1949,46 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, + "downloadLossy320": "Verlustbehaftet 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Verlustbehaftetes Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Wähle das Ausgabeformat für Tidal 320kbps verlustbehaftete Downloads. Der ursprüngliche AAC Stream wird in das ausgewählte Format konvertiert.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Beste Kompatibilität, ~10MB pro Titel", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Beste Qualität, ~8MB pro Titel", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Kleinste Größe, ~4MB pro Track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, "qualityNote": "Die eigentliche Qualität hängt von der Verfügbarkeit des Dienstes ab", "@qualityNote": { "description": "Note about quality availability" @@ -1897,11 +2113,19 @@ "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, "downloadedAlbumDeleteSelected": "Ausgewählte löschen", "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" }, - "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": "{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": { @@ -1927,7 +2151,7 @@ "@downloadedAlbumTapToSelect": { "description": "Selection hint" }, - "downloadedAlbumDeleteCount": "Lösche {count} {count, plural, one {Titel} other{Titel}}", + "downloadedAlbumDeleteCount": "Lösche {count} {count, plural, one {}=1{Titel}other{Titel}}", "@downloadedAlbumDeleteCount": { "description": "Delete button text with count", "placeholders": { @@ -2178,6 +2402,30 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, + "libraryAutoScan": "Auto-Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Aus", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Bei jeder App Öffnung", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Täglich", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Wöchentlich", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, "libraryActions": "Aktionen", "@libraryActions": { "description": "Section header for library actions" @@ -2235,6 +2483,15 @@ } } }, + "libraryFilesUnit": "{count, plural, =1{1 Datei} other{{count} Datein}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Zuletzt gescannt: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -2252,6 +2509,10 @@ "@libraryScanning": { "description": "Status during scan" }, + "libraryScanFinalizing": "Bibliothek wird aktualisiert...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, "libraryScanProgress": "{progress}% von {total} Dateien", "@libraryScanProgress": { "description": "Scan progress display", @@ -2349,6 +2610,30 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, + "libraryFilterMetadata": "Metadaten", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Komplette Metadaten", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Metadaten fehlen", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Jahr fehlt", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Genre fehlt", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Fehlender Album-Künstler", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, "libraryFilterSort": "Sortieren", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -2361,11 +2646,27 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, "timeJustNow": "Gerade eben", "@timeJustNow": { "description": "Relative time - less than a minute ago" }, - "timeMinutesAgo": "{count, plural, one {vor {count} Minute} other{vor {count} Minuten}}", + "timeMinutesAgo": "{count, plural, one {}=1{vor 1 Minute} other{vor {count} Minuten}}", "@timeMinutesAgo": { "description": "Relative time - minutes ago", "placeholders": { @@ -2374,7 +2675,7 @@ } } }, - "timeHoursAgo": "{count, plural, one {vor {count} Stunde} other{vor {count} Stunden}}", + "timeHoursAgo": "{count, plural, one {}=1{vor 1 Stunde} other{vor {count} Stunden}}", "@timeHoursAgo": { "description": "Relative time - hours ago", "placeholders": { @@ -2447,7 +2748,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Im Store Tab findest du nützliche Erweiterungen", + "tutorialExtensionsTip1": "Browse the Repo tab to discover useful extensions", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -2713,6 +3014,38 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, "trackEditMetadata": "Metadaten bearbeiten", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" @@ -2755,6 +3088,47 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, + "queueFlacAction": "Warteschlange FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Suche Online-Matches für ausgewählte Titel und Playlists für FLAC-Downloads.\n\nVorhandene Dateien werden weder geändert noch gelöscht.\n\nNur eindeutige Treffer werden automatisch zur Warteschlange hinzugefügt.\n\n{count} ausgewählt", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, "trackSaveFailed": "Fehler: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", @@ -2768,7 +3142,7 @@ "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "In MP3 oder Opus konvertieren", + "trackConvertFormatSubtitle": "Convert to MP3, Opus, ALAC, or FLAC", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, @@ -2803,6 +3177,22 @@ } } }, + "trackConvertConfirmMessageLossless": "Konvertieren von {sourceFormat} in {targetFormat}? (kein Qualitätsverlust)\n\nDie Originaldatei wird nach der Konvertierung gelöscht.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, "trackConvertConverting": "Konvertiere Audio...", "@trackConvertConverting": { "description": "Snackbar while converting" @@ -2920,7 +3310,7 @@ "@collectionLoved": { "description": "Custom folder for favorite tracks" }, - "collectionPlaylists": "Playlisten", + "collectionPlaylists": "Playlists", "@collectionPlaylists": { "description": "Custom user playlists folder" }, @@ -3109,7 +3499,7 @@ "@collectionPlaylistRemoveCover": { "description": "Bottom sheet action to remove custom cover image from a playlist" }, - "selectionShareCount": "Teile {count} {count, plural, one {Titel} other{Titel}}", + "selectionShareCount": "Teile {count} {count, plural, one {}=1{Titel}other{Titel}}", "@selectionShareCount": { "description": "Share button text with count in selection mode", "placeholders": { @@ -3122,7 +3512,7 @@ "@selectionShareNoFiles": { "description": "Snackbar when no selected files exist on disk" }, - "selectionConvertCount": "Konvertiere {count} {count, plural, one {Titel} other{Titel}}", + "selectionConvertCount": "Konvertiere {count} {count, plural, one {}=1{Titel}other{Titel}}", "@selectionConvertCount": { "description": "Convert button text with count in selection mode", "placeholders": { @@ -3139,7 +3529,7 @@ "@selectionBatchConvertConfirmTitle": { "description": "Confirmation dialog title for batch conversion" }, - "selectionBatchConvertConfirmMessage": "Konvertiere {count} {format} {count, plural, one {Titel} other{Titel}} zu {bitrate}?\n\nOriginaldateien werden nach der Konvertierung gelöscht.", + "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": { @@ -3154,6 +3544,18 @@ } } }, + "selectionBatchConvertConfirmMessageLossless": "Konvertiere {count} {count, plural, one {}=1{Titel} other{Titel}} in {format}? (kein Qualitätsverlust)\n\nOriginaldateien werden nach der Konvertierung gelöscht.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, "selectionBatchConvertProgress": "Konvertiere {current} von {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", @@ -3197,5 +3599,915 @@ "downloadUseAlbumArtistForFoldersTrackSubtitle": "Künstler-Ordner nur für Titel-Künstler", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" + }, + "lyricsProvidersTitle": "Lyrics-Anbieter", + "@lyricsProvidersTitle": { + "description": "Title for the lyrics provider priority page" + }, + "lyricsProvidersDescription": "Lyrics aktivieren, deaktivieren und neu ordnen. Anbieter werden von oben nach unten ausprobiert, bis Lyrics gefunden werden.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Erweiterungsanbieter werden immer vor eingebauten ausgeführt. Mindestens ein Anbieter muss aktiviert bleiben.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "({count}) aktiviert", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "({count}) deaktiviert", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (gut für asiatische Lieder)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Größte Lyrics-Datenbank (mehrsprachig)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Wort-für-Wort-synchronisierte Lyrics (via Proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (gut für chinesische Lieder, via Proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Erweiterungsanbieter", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Speicheraktualisierung erforderlich", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC verwendet jetzt Android Storage Access Framework (SAF) beim Herunterladen. Dies behebt Fehler bei Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Bitte wähle dein Download-Ordner erneut aus, um zum neuen System zu wechseln.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Unterstützen", + "@settingsDonate": { + "description": "Settings menu item - donate" + }, + "settingsDonateSubtitle": "Unterstütze die SpotiFLAC-Mobile Entwickler", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Alle lieben", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Zur Wiedergabeliste hinzufügen", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "{count} Titel von geliebt entfernt", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Alle Herunterladen", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Zum Album gehen", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Fehler: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose storage mode for downloaded files.", + "@downloadLocationSubtitle": { + "description": "Subtitle text in Android download location bottom sheet" + }, + "storageModeAppFolder": "App folder (non-SAF)", + "@storageModeAppFolder": { + "description": "Storage mode option - use legacy app folder" + }, + "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "SAF-Ordner", + "@storageModeSaf": { + "description": "Storage mode option - use Android SAF picker" + }, + "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Customize how your files are named.", + "@downloadFilenameDescription": { + "description": "Description text in filename format bottom sheet" + }, + "downloadFilenameInsertTag": "Tippe, um Tag einzufügen:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is enabled" + }, + "downloadSeparateSinglesDisabled": "All files in same structure", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is disabled" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, + "downloadSongLinkRegion": "SongLink-Region", + "@downloadSongLinkRegion": { + "description": "Setting title for SongLink country region" + }, + "downloadNetworkCompatibilityMode": "Netzwerkkompatibilitätsmodus", + "@downloadNetworkCompatibilityMode": { + "description": "Setting title for network compatibility toggle" + }, + "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is enabled" + }, + "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is disabled" + }, + "downloadSelectServiceToEnable": "Select a built-in service to enable", + "@downloadSelectServiceToEnable": { + "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", + "@downloadSelectTidalQobuz": { + "description": "Info hint when non-Tidal/Qobuz service is selected" + }, + "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Toggle title for including Netease translated lyrics" + }, + "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is enabled" + }, + "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is disabled" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Toggle title for including Netease romanized lyrics" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is enabled" + }, + "downloadNeteaseIncludeRomanizationDisabled": "Deaktiviert", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is disabled" + }, + "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", + "@downloadAppleQqMultiPerson": { + "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + }, + "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person word-by-word is enabled" + }, + "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person word-by-word is disabled" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting title for Musixmatch language preference" + }, + "downloadMusixmatchLanguageAuto": "Auto (original)", + "@downloadMusixmatchLanguageAuto": { + "description": "Option label when Musixmatch uses original language" + }, + "downloadFilterContributing": "Filter contributing artists in Album Artist", + "@downloadFilterContributing": { + "description": "Toggle title for filtering contributing artists in Album Artist metadata" + }, + "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is enabled" + }, + "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is disabled" + }, + "downloadProvidersNoneEnabled": "Keine aktiviert", + "@downloadProvidersNoneEnabled": { + "description": "Subtitle for lyrics providers setting when no providers are enabled" + }, + "downloadMusixmatchLanguageCode": "Sprach-Code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for the Musixmatch language code text field" + }, + "downloadMusixmatchLanguageHint": "auto / en / es / ja", + "@downloadMusixmatchLanguageHint": { + "description": "Hint text for the Musixmatch language code field" + }, + "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in the Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to reset Musixmatch language to automatic" + }, + "downloadNetworkAnySubtitle": "WLAN + Mobile Daten", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for 'Any' network mode option" + }, + "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for 'WiFi only' network mode option" + }, + "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", + "@downloadSongLinkRegionDesc": { + "description": "Description in the SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Aktualisieren", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Lade {trackCount} {trackCount, plural, one {}=1{Titel} other{Titel}} von {playlistCount} {playlistCount, plural, one {}=1{Playlist} other{Playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Abrufen & Ausfüllen", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Titel", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Künstler", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Künstler", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Datum", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Titel #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disk #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Urheberrecht", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover-Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "Alle", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Nur leer", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Heruntergeladen", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtere...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "Keine Album-Downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Playlist zum Löschen wählen", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Audio wird analysiert...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit-Tiefe", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Kanäle", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Länge", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Größe", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamischer Bereich", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Maximum", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Proben", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Anbieter", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Download abbrechen?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Behalten", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Fehler beim Laden des Albums", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Fehler beim Laden der Playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Fehler beim Laden des Interpreten", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Fortschritt", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Bibliotheksscan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Bereits in der Bibliothek", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download abgeschlossen", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} ausgeschlossen", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} Fehler", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update bereit", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update fehlgeschlagen", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_es_ES.arb b/lib/l10n/arb/app_es_ES.arb index d5b2f6a4..286f0a0c 100644 --- a/lib/l10n/arb/app_es_ES.arb +++ b/lib/l10n/arb/app_es_ES.arb @@ -17,7 +17,7 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "Tienda", + "navStore": "Repositorio", "@navStore": { "description": "Bottom navigation - Extension store tab" }, @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Pegar enlace de Spotify o buscar por nombre", + "homeSubtitle": "Pega una URL compatible o busca por nombre", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -89,9 +89,17 @@ "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, + "downloadSingleFilenameFormat": "Formato de título único", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Patrón de título para sencillos y mini-álbumes. Usa las mismas etiquetas que un álbum completo.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, "downloadFolderOrganization": "Organización de carpetas", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "Apariencia", "@appearanceTitle": { @@ -150,6 +158,14 @@ } } }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, "optionsSwitchBack": "Toque Deezer o Spotify para volver desde la extensión", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" @@ -190,6 +206,42 @@ "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, + "optionsReplayGain": "Nivelación de Ganancia", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Analizar volumen e incrustar etiquetas de RG (EBU-R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Desactivado: sin etiquetas de normalización de volumen", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Modo de Etiqueta de Artista", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Elija cómo se ingresan múltiples artistas en etiquetas incrustadas.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Valor único ingresado", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Escribe un valor ARTIST, como \"Artista A, Artista B\" para mejor compatibilidad en reproductores.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Dividir (recortar) etiquetar para FLAC/OPUS", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Escribe una etiqueta de artista por artista para FLAC y OPUS; MP3 y M4A se mantienen agrupados.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, "optionsConcurrentDownloads": "Descargas Simultáneas", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" @@ -211,11 +263,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "Tienda de extensiones", + "optionsExtensionStore": "Extensión .Repo (repositorio)", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "Mostrar pestaña de tienda en la navegación", + "optionsExtensionStoreSubtitle": "Mostar barra de navegación repo", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -284,7 +336,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": "La función de búsqueda de Spotify dejará de estar disponible el 3 de marzo de 2026 debido a cambios en la API de Spotify. Te recomendamos que te pases a Deezer.", "@optionsSpotifyDeprecationWarning": { "description": "Warning about Spotify API deprecation" }, @@ -318,7 +370,7 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "Tienda de extensiones", + "storeTitle": "Extensión .Repo", "@storeTitle": { "description": "Store screen title" }, @@ -454,7 +506,7 @@ "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.", + "aboutAppDescription": "Descargar pistas de Spotify en alta calidad (sin pérdida) de Tidal y Qobuz.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -675,6 +727,10 @@ "@dialogImport": { "description": "Dialog button - import data" }, + "dialogDownload": "Descargar", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, "dialogDiscard": "Descartar", "@dialogDiscard": { "description": "Dialog button - discard changes" @@ -732,7 +788,7 @@ "@dialogDeleteSelectedTitle": { "description": "Dialog title - delete selected items" }, - "dialogDeleteSelectedMessage": "¿Eliminar {count} {count, plural, one {pista} other{pistas}} del historial?\n\nEsto también eliminará los archivos del almacenamiento.", + "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": { @@ -811,7 +867,7 @@ "@snackbarCredentialsCleared": { "description": "Snackbar - Spotify credentials removed" }, - "snackbarDeletedTracks": "Eliminado {count} {count, plural, one {pista} other{pistas}}", + "snackbarDeletedTracks": "Eliminado {count} {count, plural, one {}=1{pista} other{pistas}}", "@snackbarDeletedTracks": { "description": "Snackbar - tracks deleted", "placeholders": { @@ -901,15 +957,15 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorUrlNotRecognized": "Link not recognized", + "errorUrlNotRecognized": "Enlace no reconocido", "@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": "Este enlace no es compatible. Asegúrate de que la URL sea correcta y de tener instalada una extensión compatible.", "@errorUrlNotRecognizedMessage": { "description": "Error message - URL not recognized explanation" }, - "errorUrlFetchFailed": "Failed to load content from this link. Please try again.", + "errorUrlFetchFailed": "No se ha podido cargar el contenido de este enlace. Inténtalo de nuevo.", "@errorUrlFetchFailed": { "description": "Error message - generic URL fetch failure" }, @@ -999,6 +1055,46 @@ "@searchPlaylists": { "description": "Search result category - playlists" }, + "searchSortTitle": "Ordenar resultados", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Por defecto", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Nombre (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Nombre (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artista (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artista (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duración (más corto)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duración (más largo)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Fecha de lanzamiento (antiguo)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Fecha de lanzamiento (reciente)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, "tooltipPlay": "Reproducir", "@tooltipPlay": { "description": "Tooltip - play button" @@ -1007,11 +1103,11 @@ "@filenameFormat": { "description": "Setting title - filename pattern" }, - "filenameShowAdvancedTags": "Show advanced tags", + "filenameShowAdvancedTags": "Mostrar etiquetas avanzadas", "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" }, - "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "filenameShowAdvancedTagsDescription": "Habilitar etiquetas con formato para el relleno de pistas y los formatos de fecha", "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, @@ -1019,11 +1115,11 @@ "@folderOrganizationNone": { "description": "Folder option - flat structure" }, - "folderOrganizationByPlaylist": "By Playlist", + "folderOrganizationByPlaylist": "Por Playlist", "@folderOrganizationByPlaylist": { "description": "Folder option - playlist folders" }, - "folderOrganizationByPlaylistSubtitle": "Separate folder for each playlist", + "folderOrganizationByPlaylistSubtitle": "Una carpeta independiente para cada Playlist", "@folderOrganizationByPlaylistSubtitle": { "description": "Subtitle for playlist folder option" }, @@ -1119,6 +1215,18 @@ "@providerPriorityInfo": { "description": "Info tip about fallback behavior" }, + "providerPriorityFallbackExtensionsTitle": "Fallback de extensión", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Elije qué extensiones instaladas se pueden utilizar durante el cambio automático a una alternativa. Los proveedores integrados siguen el orden de prioridad indicado anteriormente.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Solo las extensiones activas con proveedor de descarga se listan aquí.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, "providerBuiltIn": "Integrado", "@providerBuiltIn": { "description": "Label for built-in providers (Tidal/Qobuz)" @@ -1374,7 +1482,7 @@ } } }, - "tracksCount": "{count, plural, one {1 pista} other{{count} pistas}}", + "tracksCount": "{count, plural, one {}=1{1 pista} other{{count} pistas}}", "@tracksCount": { "description": "Track count display", "placeholders": { @@ -1479,6 +1587,14 @@ "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, + "trackLyricsNotInFile": "No se encontraron letras", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Obtener en línea", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, "trackLyricsTimeout": "Tiempo de espera agotado. Inténtalo de nuevo más tarde.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" @@ -1574,7 +1690,59 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "extensionDefaultProvider": "Por defecto (Deezer/Spotify)", + "storeAddRepoTitle": "Añadir repositorio de extensiones", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Introduzca una URL de repositorio de GitHub que contenga un archivo registry.json para navegar e instalar extensiones.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "URL del repositorio", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "Ejemplo: https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Añadir repositorio", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Cambiar repositorio", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Repositorio de extensiones", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Repositorio actual:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "Nueva URL del repositorio", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Falló al carga repositorio", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No hay extensiones disponibles", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No se encontraron extensiones", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionDefaultProvider": "Predeterminado (Deezer)", "@extensionDefaultProvider": { "description": "Default search provider option" }, @@ -1713,6 +1881,14 @@ "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, + "extensionsFallbackTitle": "Fallback de extensiones", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Elija que extensiones pueden usarse como reserva", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, "extensionsNoDownloadProvider": "No hay extensiones con proveedor de descargas", "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" @@ -1773,6 +1949,46 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, + "downloadLossy320": "Con pérdida, 320 kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Formato con pérdida", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Formato con pérdida a 320 kbps", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Elige el formato de salida para las descargas con pérdida de Tidal a 320kbps. La transmisión AAC original se convertirá al formato que hayas seleccionado.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 (320kbps)", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Óptima compatibilidad, ~10 MB por pista", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "OPUS (256kbps)", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Opus de la mejor calidad, ~8 MB por pista", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "OPUS (128kbps)", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Tamaño mínimo: ~4 MB por pista", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, "qualityNote": "La calidad real depende de la disponibilidad de la pista del servicio", "@qualityNote": { "description": "Note about quality availability" @@ -1793,19 +2009,19 @@ "@downloadAlbumFolderStructure": { "description": "Setting - album folder organization" }, - "downloadUseAlbumArtistForFolders": "Use Album Artist for folders", + "downloadUseAlbumArtistForFolders": "Usar álbum de artista cómo carpeta", "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUsePrimaryArtistOnly": "Primary artist only for folders", + "downloadUsePrimaryArtistOnly": "Artista principal solo para carpetas", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" }, - "downloadUsePrimaryArtistOnlyEnabled": "Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)", + "downloadUsePrimaryArtistOnlyEnabled": "Se han eliminado los nombres de los artistas destacados del nombre de la carpeta (p. ej., Justin Bieber, Quavo → Justin Bieber)", "@downloadUsePrimaryArtistOnlyEnabled": { "description": "Subtitle when primary artist only is enabled" }, - "downloadUsePrimaryArtistOnlyDisabled": "Full artist string used for folder name", + "downloadUsePrimaryArtistOnlyDisabled": "Se utiliza el nombre completo del artista como nombre de la carpeta", "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, @@ -1897,11 +2113,19 @@ "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, + "albumFolderArtistAlbumFlat": "Artista / Álbum (sencillos planos)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, "downloadedAlbumDeleteSelected": "Borrar Seleccionados", "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" }, - "downloadedAlbumDeleteMessage": "¿Eliminar {count} {count, plural, one {pista} other{pistas}} del historial?\n\nEsto también eliminará los archivos del almacenamiento.", + "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": { @@ -1927,7 +2151,7 @@ "@downloadedAlbumTapToSelect": { "description": "Selection hint" }, - "downloadedAlbumDeleteCount": "¡Eliminar {count} {count, plural, one {pista} other{pistas}}", + "downloadedAlbumDeleteCount": "¡Eliminar {count} {count, plural, one {}=1{pista} other{pistas}}", "@downloadedAlbumDeleteCount": { "description": "Delete button text with count", "placeholders": { @@ -1966,11 +2190,11 @@ "@recentTypePlaylist": { "description": "Recent access item type - playlist" }, - "recentEmpty": "No recent items yet", + "recentEmpty": "Aún no hay entradas recientes", "@recentEmpty": { "description": "Empty state text for recent access list" }, - "recentShowAllDownloads": "Show All Downloads", + "recentShowAllDownloads": "Mostrar todas las descargas", "@recentShowAllDownloads": { "description": "Button label to unhide hidden downloads in recent access" }, @@ -2024,7 +2248,7 @@ "@discographySinglesOnly": { "description": "Option - download only singles" }, - "discographySinglesOnlySubtitle": "{count} tracks from {albumCount} singles", + "discographySinglesOnlySubtitle": "{count} Pistas de {albumCount} sencillos", "@discographySinglesOnlySubtitle": { "description": "Subtitle showing singles tracks count", "placeholders": { @@ -2036,19 +2260,19 @@ } } }, - "discographySelectAlbums": "Select Albums...", + "discographySelectAlbums": "Seleccionar álbumes...", "@discographySelectAlbums": { "description": "Option - manually select albums to download" }, - "discographySelectAlbumsSubtitle": "Choose specific albums or singles", + "discographySelectAlbumsSubtitle": "Elige álbumes o sencillos concretos", "@discographySelectAlbumsSubtitle": { "description": "Subtitle for select albums option" }, - "discographyFetchingTracks": "Fetching tracks...", + "discographyFetchingTracks": "Cargando canciones...", "@discographyFetchingTracks": { "description": "Progress - fetching album tracks" }, - "discographyFetchingAlbum": "Fetching {current} of {total}...", + "discographyFetchingAlbum": "Cargando {current} de {total}...", "@discographyFetchingAlbum": { "description": "Progress - fetching specific album", "placeholders": { @@ -2060,7 +2284,7 @@ } } }, - "discographySelectedCount": "{count} selected", + "discographySelectedCount": "{count} seleccionados", "@discographySelectedCount": { "description": "Selection count badge", "placeholders": { @@ -2069,7 +2293,7 @@ } } }, - "discographyDownloadSelected": "Download Selected", + "discographyDownloadSelected": "Descargar seleccionados", "@discographyDownloadSelected": { "description": "Button - download selected albums" }, @@ -2138,11 +2362,11 @@ "@settingsLocalLibrarySubtitle": { "description": "Subtitle for local library settings" }, - "settingsCache": "Storage & Cache", + "settingsCache": "Almacenamiento & Caché", "@settingsCache": { "description": "Settings menu item - cache management" }, - "settingsCacheSubtitle": "View size and clear cached data", + "settingsCacheSubtitle": "Ver tamaño y borrar datos en caché", "@settingsCacheSubtitle": { "description": "Subtitle for cache management menu" }, @@ -2158,7 +2382,7 @@ "@libraryEnableLocalLibrary": { "description": "Toggle to enable library scanning" }, - "libraryEnableLocalLibrarySubtitle": "Scan and track your existing music", + "libraryEnableLocalLibrarySubtitle": "Escanea y rastrea tu música existente", "@libraryEnableLocalLibrarySubtitle": { "description": "Subtitle for enable toggle" }, @@ -2178,6 +2402,30 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, + "libraryAutoScan": "Escaneo Automático", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Apagado", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, "libraryActions": "Actions", "@libraryActions": { "description": "Section header for library actions" @@ -2235,6 +2483,15 @@ } } }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -2252,6 +2509,10 @@ "@libraryScanning": { "description": "Status during scan" }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, "libraryScanProgress": "{progress}% of {total} files", "@libraryScanProgress": { "description": "Scan progress display", @@ -2349,6 +2610,30 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -2361,6 +2646,22 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -2447,7 +2748,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "tutorialExtensionsTip1": "Browse the Repo tab to discover useful extensions", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -2713,6 +3014,38 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, "trackEditMetadata": "Edit Metadata", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" @@ -2755,6 +3088,47 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, + "queueFlacAction": "Queue FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, "trackSaveFailed": "Failed: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", @@ -2768,7 +3142,7 @@ "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "trackConvertFormatSubtitle": "Convert to MP3, Opus, ALAC, or FLAC", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, @@ -2803,6 +3177,22 @@ } } }, + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, "trackConvertConverting": "Converting audio...", "@trackConvertConverting": { "description": "Snackbar while converting" @@ -3154,6 +3544,18 @@ } } }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, "selectionBatchConvertProgress": "Converting {current} of {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", @@ -3197,5 +3599,915 @@ "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" + }, + "lyricsProvidersTitle": "Lyrics Providers", + "@lyricsProvidersTitle": { + "description": "Title for the lyrics provider priority page" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Donate", + "@settingsDonate": { + "description": "Settings menu item - donate" + }, + "settingsDonateSubtitle": "Support SpotiFLAC-Mobile development", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose storage mode for downloaded files.", + "@downloadLocationSubtitle": { + "description": "Subtitle text in Android download location bottom sheet" + }, + "storageModeAppFolder": "App folder (non-SAF)", + "@storageModeAppFolder": { + "description": "Storage mode option - use legacy app folder" + }, + "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "SAF folder", + "@storageModeSaf": { + "description": "Storage mode option - use Android SAF picker" + }, + "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Customize how your files are named.", + "@downloadFilenameDescription": { + "description": "Description text in filename format bottom sheet" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is enabled" + }, + "downloadSeparateSinglesDisabled": "All files in same structure", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is disabled" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting title for SongLink country region" + }, + "downloadNetworkCompatibilityMode": "Network compatibility mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting title for network compatibility toggle" + }, + "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is enabled" + }, + "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is disabled" + }, + "downloadSelectServiceToEnable": "Select a built-in service to enable", + "@downloadSelectServiceToEnable": { + "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", + "@downloadSelectTidalQobuz": { + "description": "Info hint when non-Tidal/Qobuz service is selected" + }, + "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Toggle title for including Netease translated lyrics" + }, + "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is enabled" + }, + "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is disabled" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Toggle title for including Netease romanized lyrics" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is enabled" + }, + "downloadNeteaseIncludeRomanizationDisabled": "Disabled", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is disabled" + }, + "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", + "@downloadAppleQqMultiPerson": { + "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + }, + "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person word-by-word is enabled" + }, + "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person word-by-word is disabled" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting title for Musixmatch language preference" + }, + "downloadMusixmatchLanguageAuto": "Auto (original)", + "@downloadMusixmatchLanguageAuto": { + "description": "Option label when Musixmatch uses original language" + }, + "downloadFilterContributing": "Filter contributing artists in Album Artist", + "@downloadFilterContributing": { + "description": "Toggle title for filtering contributing artists in Album Artist metadata" + }, + "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is enabled" + }, + "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is disabled" + }, + "downloadProvidersNoneEnabled": "None enabled", + "@downloadProvidersNoneEnabled": { + "description": "Subtitle for lyrics providers setting when no providers are enabled" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for the Musixmatch language code text field" + }, + "downloadMusixmatchLanguageHint": "auto / en / es / ja", + "@downloadMusixmatchLanguageHint": { + "description": "Hint text for the Musixmatch language code field" + }, + "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in the Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to reset Musixmatch language to automatic" + }, + "downloadNetworkAnySubtitle": "WiFi + Mobile Data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for 'Any' network mode option" + }, + "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for 'WiFi only' network mode option" + }, + "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", + "@downloadSongLinkRegionDesc": { + "description": "Description in the SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" } -} \ No newline at end of file +} diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index 9a91ed2e..dccad293 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -17,7 +17,7 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "Magasin", + "navStore": "Repo", "@navStore": { "description": "Bottom navigation - Extension store tab" }, @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Coller un lien Spotify ou rechercher par nom", + "homeSubtitle": "Paste a supported URL or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -89,9 +89,17 @@ "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, "downloadFolderOrganization": "Organisation du dossier", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "Apparence", "@appearanceTitle": { @@ -150,6 +158,14 @@ } } }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, "optionsSwitchBack": "Appuyez sur Deezer ou Spotify pour revenir à l'extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" @@ -190,6 +206,42 @@ "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Artist Tag Mode", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Choose how multiple artists are written into embedded tags.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Single joined value", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Split tags for FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, "optionsConcurrentDownloads": "Concurrent Downloads", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" @@ -211,11 +263,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "Extension Store", + "optionsExtensionStore": "Extension Repo", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "Show Store tab in navigation", + "optionsExtensionStoreSubtitle": "Show Repo tab in navigation", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -292,7 +344,7 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsDisabled": "Disabled", + "extensionsDisabled": "Désactivée", "@extensionsDisabled": { "description": "Extension status - inactive" }, @@ -305,7 +357,7 @@ } } }, - "extensionsAuthor": "by {author}", + "extensionsAuthor": "par {author}", "@extensionsAuthor": { "description": "Extension author credit", "placeholders": { @@ -318,7 +370,7 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "Magasin d'extension", + "storeTitle": "Extension Repo", "@storeTitle": { "description": "Store screen title" }, @@ -326,31 +378,31 @@ "@storeSearch": { "description": "Store search placeholder" }, - "storeInstall": "Install", + "storeInstall": "Installer", "@storeInstall": { "description": "Install extension button" }, - "storeInstalled": "Installed", + "storeInstalled": "Installé", "@storeInstalled": { "description": "Already installed badge" }, - "storeUpdate": "Update", + "storeUpdate": "Mettre à jour", "@storeUpdate": { "description": "Update available button" }, - "aboutTitle": "About", + "aboutTitle": "À propos de", "@aboutTitle": { "description": "About page title" }, - "aboutContributors": "Contributors", + "aboutContributors": "Contributeurs", "@aboutContributors": { "description": "Section for contributors" }, - "aboutMobileDeveloper": "Mobile version developer", + "aboutMobileDeveloper": "Développeur de la version mobile", "@aboutMobileDeveloper": { "description": "Role description for mobile dev" }, - "aboutOriginalCreator": "Creator of the original SpotiFLAC", + "aboutOriginalCreator": "Créateur de SpotiFLAC original", "@aboutOriginalCreator": { "description": "Role description for original creator" }, @@ -402,7 +454,7 @@ "@aboutTelegramChannel": { "description": "Link to Telegram channel" }, - "aboutTelegramChannelSubtitle": "Announcements and updates", + "aboutTelegramChannelSubtitle": "Annonces et mises à jour", "@aboutTelegramChannelSubtitle": { "description": "Subtitle for Telegram channel" }, @@ -603,11 +655,11 @@ "@setupStorageDescription": { "description": "Explanation for storage permission" }, - "setupNotificationGranted": "Notification Permission Granted!", + "setupNotificationGranted": "Autorisation de notifications accordée!", "@setupNotificationGranted": { "description": "Success message for notification permission" }, - "setupNotificationEnable": "Enable Notifications", + "setupNotificationEnable": "Activer les notifications", "@setupNotificationEnable": { "description": "Button to enable notifications" }, @@ -619,11 +671,11 @@ "@setupFolderDescription": { "description": "Explanation for folder selection" }, - "setupSelectFolder": "Select Folder", + "setupSelectFolder": "Sélectionner un dossier", "@setupSelectFolder": { "description": "Button to select folder" }, - "setupEnableNotifications": "Enable Notifications", + "setupEnableNotifications": "Activer les notifications", "@setupEnableNotifications": { "description": "Button to enable notifications" }, @@ -631,35 +683,35 @@ "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" }, - "setupSkipForNow": "Skip for now", + "setupSkipForNow": "Ignorer pour le moment", "@setupSkipForNow": { "description": "Skip button text" }, - "setupNext": "Next", + "setupNext": "Suivant", "@setupNext": { "description": "Next button text" }, - "setupGetStarted": "Get Started", + "setupGetStarted": "Démarrer", "@setupGetStarted": { "description": "Final setup button" }, - "setupAllowAccessToManageFiles": "Please enable \"Allow access to manage all files\" in the next screen.", + "setupAllowAccessToManageFiles": "Veuillez activer \"Autoriser l'accès à tous les fichiers\" sur l'écran suivant.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, - "dialogCancel": "Cancel", + "dialogCancel": "Annuler", "@dialogCancel": { "description": "Dialog button - cancel action" }, - "dialogSave": "Save", + "dialogSave": "Sauvegarder", "@dialogSave": { "description": "Dialog button - save changes" }, - "dialogDelete": "Delete", + "dialogDelete": "Supprimer", "@dialogDelete": { "description": "Dialog button - delete item" }, - "dialogRetry": "Retry", + "dialogRetry": "Réessayer", "@dialogRetry": { "description": "Dialog button - retry action" }, @@ -675,6 +727,10 @@ "@dialogImport": { "description": "Dialog button - import data" }, + "dialogDownload": "Télécharger", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, "dialogDiscard": "Discard", "@dialogDiscard": { "description": "Dialog button - discard changes" @@ -683,11 +739,11 @@ "@dialogRemove": { "description": "Dialog button - remove item" }, - "dialogUninstall": "Uninstall", + "dialogUninstall": "Désinstaller", "@dialogUninstall": { "description": "Dialog button - uninstall extension" }, - "dialogDiscardChanges": "Discard Changes?", + "dialogDiscardChanges": "Ignorer les modifications ?", "@dialogDiscardChanges": { "description": "Dialog title - unsaved changes warning" }, @@ -999,6 +1055,46 @@ "@searchPlaylists": { "description": "Search result category - playlists" }, + "searchSortTitle": "Sort Results", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Default", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Title (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Title (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artist (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artist (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duration (Shortest)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duration (Longest)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Release Date (Oldest)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Release Date (Newest)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, "tooltipPlay": "Play", "@tooltipPlay": { "description": "Tooltip - play button" @@ -1119,6 +1215,18 @@ "@providerPriorityInfo": { "description": "Info tip about fallback behavior" }, + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { "description": "Label for built-in providers (Tidal/Qobuz)" @@ -1479,6 +1587,14 @@ "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, "trackLyricsTimeout": "Request timed out. Try again later.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" @@ -1574,7 +1690,59 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "extensionDefaultProvider": "Default (Deezer/Spotify)", + "storeAddRepoTitle": "Add Extension Repository", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "e.g. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Add Repository", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Change repository", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Extension Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Current repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "New Repository URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionDefaultProvider": "Default (Deezer)", "@extensionDefaultProvider": { "description": "Default search provider option" }, @@ -1713,6 +1881,14 @@ "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, + "extensionsFallbackTitle": "Fallback Extensions", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, "extensionsNoDownloadProvider": "No extensions with download provider", "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" @@ -1773,6 +1949,46 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, + "downloadLossy320": "Lossy 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Lossy Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Best compatibility, ~10MB per track", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Best quality Opus, ~8MB per track", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, "qualityNote": "Actual quality depends on track availability from the service", "@qualityNote": { "description": "Note about quality availability" @@ -1897,6 +2113,14 @@ "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, "downloadedAlbumDeleteSelected": "Delete Selected", "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" @@ -2178,6 +2402,30 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, "libraryActions": "Actions", "@libraryActions": { "description": "Section header for library actions" @@ -2235,6 +2483,15 @@ } } }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -2252,6 +2509,10 @@ "@libraryScanning": { "description": "Status during scan" }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, "libraryScanProgress": "{progress}% of {total} files", "@libraryScanProgress": { "description": "Scan progress display", @@ -2349,6 +2610,30 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -2361,6 +2646,22 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -2447,7 +2748,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "tutorialExtensionsTip1": "Browse the Repo tab to discover useful extensions", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -2713,6 +3014,38 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, "trackEditMetadata": "Edit Metadata", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" @@ -2755,6 +3088,47 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, + "queueFlacAction": "Queue FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, "trackSaveFailed": "Failed: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", @@ -2768,7 +3142,7 @@ "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "trackConvertFormatSubtitle": "Convert to MP3, Opus, ALAC, or FLAC", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, @@ -2803,6 +3177,22 @@ } } }, + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, "trackConvertConverting": "Converting audio...", "@trackConvertConverting": { "description": "Snackbar while converting" @@ -3154,6 +3544,18 @@ } } }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, "selectionBatchConvertProgress": "Converting {current} of {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", @@ -3197,5 +3599,915 @@ "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" + }, + "lyricsProvidersTitle": "Lyrics Providers", + "@lyricsProvidersTitle": { + "description": "Title for the lyrics provider priority page" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Donate", + "@settingsDonate": { + "description": "Settings menu item - donate" + }, + "settingsDonateSubtitle": "Support SpotiFLAC-Mobile development", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose storage mode for downloaded files.", + "@downloadLocationSubtitle": { + "description": "Subtitle text in Android download location bottom sheet" + }, + "storageModeAppFolder": "App folder (non-SAF)", + "@storageModeAppFolder": { + "description": "Storage mode option - use legacy app folder" + }, + "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "SAF folder", + "@storageModeSaf": { + "description": "Storage mode option - use Android SAF picker" + }, + "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Customize how your files are named.", + "@downloadFilenameDescription": { + "description": "Description text in filename format bottom sheet" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is enabled" + }, + "downloadSeparateSinglesDisabled": "All files in same structure", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is disabled" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting title for SongLink country region" + }, + "downloadNetworkCompatibilityMode": "Network compatibility mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting title for network compatibility toggle" + }, + "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is enabled" + }, + "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is disabled" + }, + "downloadSelectServiceToEnable": "Select a built-in service to enable", + "@downloadSelectServiceToEnable": { + "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", + "@downloadSelectTidalQobuz": { + "description": "Info hint when non-Tidal/Qobuz service is selected" + }, + "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Toggle title for including Netease translated lyrics" + }, + "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is enabled" + }, + "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is disabled" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Toggle title for including Netease romanized lyrics" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is enabled" + }, + "downloadNeteaseIncludeRomanizationDisabled": "Disabled", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is disabled" + }, + "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", + "@downloadAppleQqMultiPerson": { + "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + }, + "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person word-by-word is enabled" + }, + "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person word-by-word is disabled" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting title for Musixmatch language preference" + }, + "downloadMusixmatchLanguageAuto": "Auto (original)", + "@downloadMusixmatchLanguageAuto": { + "description": "Option label when Musixmatch uses original language" + }, + "downloadFilterContributing": "Filter contributing artists in Album Artist", + "@downloadFilterContributing": { + "description": "Toggle title for filtering contributing artists in Album Artist metadata" + }, + "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is enabled" + }, + "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is disabled" + }, + "downloadProvidersNoneEnabled": "None enabled", + "@downloadProvidersNoneEnabled": { + "description": "Subtitle for lyrics providers setting when no providers are enabled" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for the Musixmatch language code text field" + }, + "downloadMusixmatchLanguageHint": "auto / en / es / ja", + "@downloadMusixmatchLanguageHint": { + "description": "Hint text for the Musixmatch language code field" + }, + "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in the Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to reset Musixmatch language to automatic" + }, + "downloadNetworkAnySubtitle": "WiFi + Mobile Data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for 'Any' network mode option" + }, + "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for 'WiFi only' network mode option" + }, + "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", + "@downloadSongLinkRegionDesc": { + "description": "Description in the SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_hi.arb b/lib/l10n/arb/app_hi.arb index af06bd66..355c5c7e 100644 --- a/lib/l10n/arb/app_hi.arb +++ b/lib/l10n/arb/app_hi.arb @@ -17,7 +17,7 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "Store", + "navStore": "Repo", "@navStore": { "description": "Bottom navigation - Extension store tab" }, @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Paste a Spotify link or search by name", + "homeSubtitle": "Paste a supported URL or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -89,9 +89,17 @@ "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, "downloadFolderOrganization": "Folder Organization", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "दिखावट", "@appearanceTitle": { @@ -150,6 +158,14 @@ } } }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" @@ -190,6 +206,42 @@ "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Artist Tag Mode", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Choose how multiple artists are written into embedded tags.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Single joined value", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Split tags for FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, "optionsConcurrentDownloads": "Concurrent Downloads", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" @@ -211,11 +263,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "Extension Store", + "optionsExtensionStore": "Extension Repo", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "Show Store tab in navigation", + "optionsExtensionStoreSubtitle": "Show Repo tab in navigation", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -318,7 +370,7 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "Extension Store", + "storeTitle": "Extension Repo", "@storeTitle": { "description": "Store screen title" }, @@ -675,6 +727,10 @@ "@dialogImport": { "description": "Dialog button - import data" }, + "dialogDownload": "Download", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, "dialogDiscard": "Discard", "@dialogDiscard": { "description": "Dialog button - discard changes" @@ -999,6 +1055,46 @@ "@searchPlaylists": { "description": "Search result category - playlists" }, + "searchSortTitle": "Sort Results", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Default", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Title (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Title (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artist (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artist (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duration (Shortest)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duration (Longest)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Release Date (Oldest)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Release Date (Newest)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, "tooltipPlay": "Play", "@tooltipPlay": { "description": "Tooltip - play button" @@ -1119,6 +1215,18 @@ "@providerPriorityInfo": { "description": "Info tip about fallback behavior" }, + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { "description": "Label for built-in providers (Tidal/Qobuz)" @@ -1479,6 +1587,14 @@ "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, "trackLyricsTimeout": "Request timed out. Try again later.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" @@ -1574,7 +1690,59 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "extensionDefaultProvider": "Default (Deezer/Spotify)", + "storeAddRepoTitle": "Add Extension Repository", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "e.g. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Add Repository", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Change repository", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Extension Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Current repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "New Repository URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionDefaultProvider": "Default (Deezer)", "@extensionDefaultProvider": { "description": "Default search provider option" }, @@ -1713,6 +1881,14 @@ "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, + "extensionsFallbackTitle": "Fallback Extensions", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, "extensionsNoDownloadProvider": "No extensions with download provider", "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" @@ -1773,6 +1949,46 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, + "downloadLossy320": "Lossy 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Lossy Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Best compatibility, ~10MB per track", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Best quality Opus, ~8MB per track", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, "qualityNote": "Actual quality depends on track availability from the service", "@qualityNote": { "description": "Note about quality availability" @@ -1897,6 +2113,14 @@ "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, "downloadedAlbumDeleteSelected": "Delete Selected", "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" @@ -2178,6 +2402,30 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, "libraryActions": "Actions", "@libraryActions": { "description": "Section header for library actions" @@ -2235,6 +2483,15 @@ } } }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -2252,6 +2509,10 @@ "@libraryScanning": { "description": "Status during scan" }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, "libraryScanProgress": "{progress}% of {total} files", "@libraryScanProgress": { "description": "Scan progress display", @@ -2349,6 +2610,30 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -2361,6 +2646,22 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -2447,7 +2748,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "tutorialExtensionsTip1": "Browse the Repo tab to discover useful extensions", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -2713,6 +3014,38 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, "trackEditMetadata": "Edit Metadata", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" @@ -2755,6 +3088,47 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, + "queueFlacAction": "Queue FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, "trackSaveFailed": "Failed: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", @@ -2768,7 +3142,7 @@ "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "trackConvertFormatSubtitle": "Convert to MP3, Opus, ALAC, or FLAC", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, @@ -2803,6 +3177,22 @@ } } }, + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, "trackConvertConverting": "Converting audio...", "@trackConvertConverting": { "description": "Snackbar while converting" @@ -3154,6 +3544,18 @@ } } }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, "selectionBatchConvertProgress": "Converting {current} of {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", @@ -3197,5 +3599,915 @@ "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" + }, + "lyricsProvidersTitle": "Lyrics Providers", + "@lyricsProvidersTitle": { + "description": "Title for the lyrics provider priority page" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Donate", + "@settingsDonate": { + "description": "Settings menu item - donate" + }, + "settingsDonateSubtitle": "Support SpotiFLAC-Mobile development", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose storage mode for downloaded files.", + "@downloadLocationSubtitle": { + "description": "Subtitle text in Android download location bottom sheet" + }, + "storageModeAppFolder": "App folder (non-SAF)", + "@storageModeAppFolder": { + "description": "Storage mode option - use legacy app folder" + }, + "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "SAF folder", + "@storageModeSaf": { + "description": "Storage mode option - use Android SAF picker" + }, + "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Customize how your files are named.", + "@downloadFilenameDescription": { + "description": "Description text in filename format bottom sheet" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is enabled" + }, + "downloadSeparateSinglesDisabled": "All files in same structure", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is disabled" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting title for SongLink country region" + }, + "downloadNetworkCompatibilityMode": "Network compatibility mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting title for network compatibility toggle" + }, + "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is enabled" + }, + "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is disabled" + }, + "downloadSelectServiceToEnable": "Select a built-in service to enable", + "@downloadSelectServiceToEnable": { + "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", + "@downloadSelectTidalQobuz": { + "description": "Info hint when non-Tidal/Qobuz service is selected" + }, + "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Toggle title for including Netease translated lyrics" + }, + "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is enabled" + }, + "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is disabled" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Toggle title for including Netease romanized lyrics" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is enabled" + }, + "downloadNeteaseIncludeRomanizationDisabled": "Disabled", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is disabled" + }, + "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", + "@downloadAppleQqMultiPerson": { + "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + }, + "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person word-by-word is enabled" + }, + "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person word-by-word is disabled" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting title for Musixmatch language preference" + }, + "downloadMusixmatchLanguageAuto": "Auto (original)", + "@downloadMusixmatchLanguageAuto": { + "description": "Option label when Musixmatch uses original language" + }, + "downloadFilterContributing": "Filter contributing artists in Album Artist", + "@downloadFilterContributing": { + "description": "Toggle title for filtering contributing artists in Album Artist metadata" + }, + "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is enabled" + }, + "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is disabled" + }, + "downloadProvidersNoneEnabled": "None enabled", + "@downloadProvidersNoneEnabled": { + "description": "Subtitle for lyrics providers setting when no providers are enabled" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for the Musixmatch language code text field" + }, + "downloadMusixmatchLanguageHint": "auto / en / es / ja", + "@downloadMusixmatchLanguageHint": { + "description": "Hint text for the Musixmatch language code field" + }, + "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in the Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to reset Musixmatch language to automatic" + }, + "downloadNetworkAnySubtitle": "WiFi + Mobile Data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for 'Any' network mode option" + }, + "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for 'WiFi only' network mode option" + }, + "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", + "@downloadSongLinkRegionDesc": { + "description": "Description in the SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index e54cd15f..667c7d8f 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Tempel URL yang didukung atau cari berdasarkan nama", + "homeSubtitle": "Paste a supported URL or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -89,9 +89,17 @@ "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, "downloadFolderOrganization": "Organisasi Folder", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "Tampilan", "@appearanceTitle": { @@ -150,11 +158,11 @@ } } }, - "optionsDefaultSearchTab": "Tab Pencarian Default", + "optionsDefaultSearchTab": "Default Search Tab", "@optionsDefaultSearchTab": { "description": "Title for the preferred default search tab setting" }, - "optionsDefaultSearchTabSubtitle": "Pilih tab yang dibuka lebih dulu untuk hasil pencarian baru.", + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, @@ -198,6 +206,42 @@ "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Artist Tag Mode", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Choose how multiple artists are written into embedded tags.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Single joined value", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Split tags for FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, "optionsConcurrentDownloads": "Unduhan Bersamaan", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" @@ -219,11 +263,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "Repo Ekstensi", + "optionsExtensionStore": "Extension Repo", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "Tampilkan tab Repo di navigasi", + "optionsExtensionStoreSubtitle": "Show Repo tab in navigation", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -326,14 +370,10 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "Repo Ekstensi", + "storeTitle": "Extension Repo", "@storeTitle": { "description": "Store screen title" }, - "storeLoadError": "Gagal memuat repo", - "@storeLoadError": { - "description": "Error heading when the store cannot be loaded" - }, "storeSearch": "Cari ekstensi...", "@storeSearch": { "description": "Store search placeholder" @@ -687,6 +727,10 @@ "@dialogImport": { "description": "Dialog button - import data" }, + "dialogDownload": "Download", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, "dialogDiscard": "Buang", "@dialogDiscard": { "description": "Dialog button - discard changes" @@ -913,15 +957,15 @@ "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorUrlNotRecognized": "Link tidak dikenali", + "errorUrlNotRecognized": "Tautan 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": "Tautan ini tidak didukung. Pastikan URL sudah benar dan ekstensi yang kompatibel telah terpasang.", "@errorUrlNotRecognizedMessage": { "description": "Error message - URL not recognized explanation" }, - "errorUrlFetchFailed": "Gagal memuat konten dari link ini. Silakan coba lagi.", + "errorUrlFetchFailed": "Konten dari tautan ini gagal dimuat. Silakan coba lagi.", "@errorUrlFetchFailed": { "description": "Error message - generic URL fetch failure" }, @@ -1011,6 +1055,46 @@ "@searchPlaylists": { "description": "Search result category - playlists" }, + "searchSortTitle": "Sort Results", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Default", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Title (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Title (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artist (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artist (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duration (Shortest)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duration (Longest)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Release Date (Oldest)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Release Date (Newest)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, "tooltipPlay": "Putar", "@tooltipPlay": { "description": "Tooltip - play button" @@ -1131,15 +1215,15 @@ "@providerPriorityInfo": { "description": "Info tip about fallback behavior" }, - "providerPriorityFallbackExtensionsTitle": "Fallback Ekstensi", + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", "@providerPriorityFallbackExtensionsTitle": { "description": "Section title for choosing which download extensions can be used as fallback providers" }, - "providerPriorityFallbackExtensionsDescription": "Pilih ekstensi unduhan terpasang mana yang boleh dipakai saat fallback otomatis. Provider bawaan tetap mengikuti urutan prioritas di atas.", + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", "@providerPriorityFallbackExtensionsDescription": { "description": "Section description for extension fallback selection" }, - "providerPriorityFallbackExtensionsHint": "Hanya ekstensi aktif dengan kemampuan download provider yang ditampilkan di sini.", + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", "@providerPriorityFallbackExtensionsHint": { "description": "Hint below the extension fallback selection list" }, @@ -1503,6 +1587,14 @@ "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, "trackLyricsTimeout": "Permintaan timeout. Coba lagi nanti.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" @@ -1598,7 +1690,59 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "extensionDefaultProvider": "Bawaan (Deezer/Spotify)", + "storeAddRepoTitle": "Add Extension Repository", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "e.g. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Add Repository", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Change repository", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Extension Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Current repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "New Repository URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionDefaultProvider": "Default (Deezer)", "@extensionDefaultProvider": { "description": "Default search provider option" }, @@ -1741,7 +1885,7 @@ "@extensionsFallbackTitle": { "description": "Setting and page title for choosing which download extensions can be used during fallback" }, - "extensionsFallbackSubtitle": "Pilih ekstensi unduhan terpasang yang boleh dipakai saat fallback", + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", "@extensionsFallbackSubtitle": { "description": "Subtitle for download fallback extensions menu" }, @@ -1805,6 +1949,46 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, + "downloadLossy320": "Lossy 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Lossy Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Best compatibility, ~10MB per track", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Best quality Opus, ~8MB per track", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, "qualityNote": "Kualitas sebenarnya tergantung ketersediaan lagu dari layanan", "@qualityNote": { "description": "Note about quality availability" @@ -1829,22 +2013,6 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadCreatePlaylistSourceFolder": "Buat folder sumber playlist", - "@downloadCreatePlaylistSourceFolder": { - "description": "Setting title for adding a playlist folder prefix before the normal organization structure" - }, - "downloadCreatePlaylistSourceFolderEnabled": "Unduhan dari playlist memakai Playlist/ lalu struktur folder normal Anda.", - "@downloadCreatePlaylistSourceFolderEnabled": { - "description": "Subtitle when playlist source folder prefix is enabled" - }, - "downloadCreatePlaylistSourceFolderDisabled": "Unduhan dari playlist hanya memakai struktur folder normal.", - "@downloadCreatePlaylistSourceFolderDisabled": { - "description": "Subtitle when playlist source folder prefix is disabled" - }, - "downloadCreatePlaylistSourceFolderRedundant": "Mode Berdasarkan Playlist sudah menaruh unduhan ke dalam folder playlist.", - "@downloadCreatePlaylistSourceFolderRedundant": { - "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" - }, "downloadUsePrimaryArtistOnly": "Hanya artis utama untuk folder", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" @@ -1945,6 +2113,14 @@ "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, "downloadedAlbumDeleteSelected": "Hapus yang Dipilih", "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" @@ -2226,6 +2402,30 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, "libraryActions": "Actions", "@libraryActions": { "description": "Section header for library actions" @@ -2283,6 +2483,15 @@ } } }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -2300,6 +2509,10 @@ "@libraryScanning": { "description": "Status during scan" }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, "libraryScanProgress": "{progress}% of {total} files", "@libraryScanProgress": { "description": "Scan progress display", @@ -2397,6 +2610,30 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -2409,6 +2646,22 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -2495,7 +2748,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Buka tab Repo untuk menemukan ekstensi yang berguna", + "tutorialExtensionsTip1": "Browse the Repo tab to discover useful extensions", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -2761,6 +3014,38 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, "trackEditMetadata": "Edit Metadata", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" @@ -2803,11 +3088,11 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, - "queueFlacAction": "Antrekan FLAC", + "queueFlacAction": "Queue FLAC", "@queueFlacAction": { "description": "Action/button label for queueing FLAC redownloads for local tracks" }, - "queueFlacConfirmMessage": "Cari kecocokan online untuk track yang dipilih lalu antrekan download FLAC.\n\nFile yang sudah ada tidak akan diubah atau dihapus.\n\nHanya kecocokan dengan keyakinan tinggi yang akan diantrikan otomatis.\n\n{count} dipilih", + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", "@queueFlacConfirmMessage": { "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", "placeholders": { @@ -2816,7 +3101,7 @@ } } }, - "queueFlacFindingProgress": "Mencari kecocokan FLAC... ({current}/{total})", + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", "@queueFlacFindingProgress": { "description": "Snackbar while resolving remote matches for local FLAC redownloads", "placeholders": { @@ -2828,11 +3113,11 @@ } } }, - "queueFlacNoReliableMatches": "Tidak ada kecocokan online yang cukup meyakinkan untuk pilihan ini", + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", "@queueFlacNoReliableMatches": { "description": "Snackbar when no safe FLAC redownload matches were found" }, - "queueFlacQueuedWithSkipped": "Menambahkan {addedCount} track ke antrean, melewati {skippedCount}", + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", "@queueFlacQueuedWithSkipped": { "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", "placeholders": { @@ -2857,7 +3142,7 @@ "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Konversi ke MP3, Opus, ALAC, atau FLAC", + "trackConvertFormatSubtitle": "Convert to MP3, Opus, ALAC, or FLAC", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, @@ -2892,7 +3177,7 @@ } } }, - "trackConvertConfirmMessageLossless": "Konversi dari {sourceFormat} ke {targetFormat}? (Lossless — tanpa kehilangan kualitas)\n\nFile asli akan dihapus setelah konversi.", + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", "@trackConvertConfirmMessageLossless": { "description": "Confirmation dialog message for lossless-to-lossless conversion", "placeholders": { @@ -2904,7 +3189,7 @@ } } }, - "trackConvertLosslessHint": "Konversi lossless — tanpa kehilangan kualitas", + "trackConvertLosslessHint": "Lossless conversion — no quality loss", "@trackConvertLosslessHint": { "description": "Hint shown when converting between lossless formats" }, @@ -3259,6 +3544,18 @@ } } }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, "selectionBatchConvertProgress": "Converting {current} of {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", @@ -3302,5 +3599,915 @@ "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" + }, + "lyricsProvidersTitle": "Lyrics Providers", + "@lyricsProvidersTitle": { + "description": "Title for the lyrics provider priority page" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Donate", + "@settingsDonate": { + "description": "Settings menu item - donate" + }, + "settingsDonateSubtitle": "Support SpotiFLAC-Mobile development", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose storage mode for downloaded files.", + "@downloadLocationSubtitle": { + "description": "Subtitle text in Android download location bottom sheet" + }, + "storageModeAppFolder": "App folder (non-SAF)", + "@storageModeAppFolder": { + "description": "Storage mode option - use legacy app folder" + }, + "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "SAF folder", + "@storageModeSaf": { + "description": "Storage mode option - use Android SAF picker" + }, + "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Customize how your files are named.", + "@downloadFilenameDescription": { + "description": "Description text in filename format bottom sheet" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is enabled" + }, + "downloadSeparateSinglesDisabled": "All files in same structure", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is disabled" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting title for SongLink country region" + }, + "downloadNetworkCompatibilityMode": "Network compatibility mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting title for network compatibility toggle" + }, + "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is enabled" + }, + "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is disabled" + }, + "downloadSelectServiceToEnable": "Select a built-in service to enable", + "@downloadSelectServiceToEnable": { + "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", + "@downloadSelectTidalQobuz": { + "description": "Info hint when non-Tidal/Qobuz service is selected" + }, + "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Toggle title for including Netease translated lyrics" + }, + "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is enabled" + }, + "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is disabled" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Toggle title for including Netease romanized lyrics" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is enabled" + }, + "downloadNeteaseIncludeRomanizationDisabled": "Disabled", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is disabled" + }, + "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", + "@downloadAppleQqMultiPerson": { + "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + }, + "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person word-by-word is enabled" + }, + "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person word-by-word is disabled" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting title for Musixmatch language preference" + }, + "downloadMusixmatchLanguageAuto": "Auto (original)", + "@downloadMusixmatchLanguageAuto": { + "description": "Option label when Musixmatch uses original language" + }, + "downloadFilterContributing": "Filter contributing artists in Album Artist", + "@downloadFilterContributing": { + "description": "Toggle title for filtering contributing artists in Album Artist metadata" + }, + "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is enabled" + }, + "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is disabled" + }, + "downloadProvidersNoneEnabled": "None enabled", + "@downloadProvidersNoneEnabled": { + "description": "Subtitle for lyrics providers setting when no providers are enabled" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for the Musixmatch language code text field" + }, + "downloadMusixmatchLanguageHint": "auto / en / es / ja", + "@downloadMusixmatchLanguageHint": { + "description": "Hint text for the Musixmatch language code field" + }, + "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in the Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to reset Musixmatch language to automatic" + }, + "downloadNetworkAnySubtitle": "WiFi + Mobile Data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for 'Any' network mode option" + }, + "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for 'WiFi only' network mode option" + }, + "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", + "@downloadSongLinkRegionDesc": { + "description": "Description in the SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" } -} +} \ No newline at end of file diff --git a/lib/l10n/arb/app_ja.arb b/lib/l10n/arb/app_ja.arb index 7dd086a6..c265d491 100644 --- a/lib/l10n/arb/app_ja.arb +++ b/lib/l10n/arb/app_ja.arb @@ -17,7 +17,7 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "ストア", + "navStore": "Repo", "@navStore": { "description": "Bottom navigation - Extension store tab" }, @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Spotify のリンクを貼り付けるか、名前で検索します", + "homeSubtitle": "Paste a supported URL or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -89,9 +89,17 @@ "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, "downloadFolderOrganization": "フォルダ構成", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "外観", "@appearanceTitle": { @@ -150,6 +158,14 @@ } } }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" @@ -190,6 +206,42 @@ "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Artist Tag Mode", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Choose how multiple artists are written into embedded tags.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Single joined value", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Split tags for FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, "optionsConcurrentDownloads": "同時ダウンロード", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" @@ -211,11 +263,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "拡張ストア", + "optionsExtensionStore": "Extension Repo", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "ナビゲーションにストアタブを表示", + "optionsExtensionStoreSubtitle": "Show Repo tab in navigation", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -318,7 +370,7 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "拡張ストア", + "storeTitle": "Extension Repo", "@storeTitle": { "description": "Store screen title" }, @@ -675,6 +727,10 @@ "@dialogImport": { "description": "Dialog button - import data" }, + "dialogDownload": "Download", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, "dialogDiscard": "破棄", "@dialogDiscard": { "description": "Dialog button - discard changes" @@ -999,6 +1055,46 @@ "@searchPlaylists": { "description": "Search result category - playlists" }, + "searchSortTitle": "Sort Results", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Default", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Title (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Title (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artist (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artist (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duration (Shortest)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duration (Longest)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Release Date (Oldest)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Release Date (Newest)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, "tooltipPlay": "再生", "@tooltipPlay": { "description": "Tooltip - play button" @@ -1119,6 +1215,18 @@ "@providerPriorityInfo": { "description": "Info tip about fallback behavior" }, + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, "providerBuiltIn": "内蔵", "@providerBuiltIn": { "description": "Label for built-in providers (Tidal/Qobuz)" @@ -1479,6 +1587,14 @@ "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, "trackLyricsTimeout": "リクエストがタイムアウトしました。後ほどお試しください。", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" @@ -1574,7 +1690,59 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "extensionDefaultProvider": "デフォルト (Deezer/Spotify)", + "storeAddRepoTitle": "Add Extension Repository", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "e.g. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Add Repository", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Change repository", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Extension Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Current repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "New Repository URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionDefaultProvider": "Default (Deezer)", "@extensionDefaultProvider": { "description": "Default search provider option" }, @@ -1713,6 +1881,14 @@ "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, + "extensionsFallbackTitle": "Fallback Extensions", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, "extensionsNoDownloadProvider": "ダウンロードプロバイダーの拡張はありません", "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" @@ -1773,6 +1949,46 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, + "downloadLossy320": "Lossy 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Lossy Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Best compatibility, ~10MB per track", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Best quality Opus, ~8MB per track", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, "qualityNote": "実際の品質はサービスからのトラックの可用性に依存します", "@qualityNote": { "description": "Note about quality availability" @@ -1897,6 +2113,14 @@ "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, "downloadedAlbumDeleteSelected": "選択済みを削除", "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" @@ -2178,6 +2402,30 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, "libraryActions": "アクション", "@libraryActions": { "description": "Section header for library actions" @@ -2235,6 +2483,15 @@ } } }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "最終スキャン: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -2252,6 +2509,10 @@ "@libraryScanning": { "description": "Status during scan" }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, "libraryScanProgress": "{progress}% of {total} files", "@libraryScanProgress": { "description": "Scan progress display", @@ -2349,6 +2610,30 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -2361,6 +2646,22 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -2447,7 +2748,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "tutorialExtensionsTip1": "Browse the Repo tab to discover useful extensions", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -2713,6 +3014,38 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, "trackEditMetadata": "メタデータを編集", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" @@ -2755,6 +3088,47 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, + "queueFlacAction": "Queue FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, "trackSaveFailed": "失敗: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", @@ -2768,7 +3142,7 @@ "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "MP3 または Opus に変換", + "trackConvertFormatSubtitle": "Convert to MP3, Opus, ALAC, or FLAC", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, @@ -2803,6 +3177,22 @@ } } }, + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, "trackConvertConverting": "オーディオを変換中...", "@trackConvertConverting": { "description": "Snackbar while converting" @@ -3154,6 +3544,18 @@ } } }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, "selectionBatchConvertProgress": "Converting {current} of {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", @@ -3197,5 +3599,915 @@ "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" + }, + "lyricsProvidersTitle": "Lyrics Providers", + "@lyricsProvidersTitle": { + "description": "Title for the lyrics provider priority page" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Donate", + "@settingsDonate": { + "description": "Settings menu item - donate" + }, + "settingsDonateSubtitle": "Support SpotiFLAC-Mobile development", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose storage mode for downloaded files.", + "@downloadLocationSubtitle": { + "description": "Subtitle text in Android download location bottom sheet" + }, + "storageModeAppFolder": "App folder (non-SAF)", + "@storageModeAppFolder": { + "description": "Storage mode option - use legacy app folder" + }, + "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "SAF folder", + "@storageModeSaf": { + "description": "Storage mode option - use Android SAF picker" + }, + "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Customize how your files are named.", + "@downloadFilenameDescription": { + "description": "Description text in filename format bottom sheet" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is enabled" + }, + "downloadSeparateSinglesDisabled": "All files in same structure", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is disabled" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting title for SongLink country region" + }, + "downloadNetworkCompatibilityMode": "Network compatibility mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting title for network compatibility toggle" + }, + "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is enabled" + }, + "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is disabled" + }, + "downloadSelectServiceToEnable": "Select a built-in service to enable", + "@downloadSelectServiceToEnable": { + "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", + "@downloadSelectTidalQobuz": { + "description": "Info hint when non-Tidal/Qobuz service is selected" + }, + "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Toggle title for including Netease translated lyrics" + }, + "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is enabled" + }, + "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is disabled" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Toggle title for including Netease romanized lyrics" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is enabled" + }, + "downloadNeteaseIncludeRomanizationDisabled": "Disabled", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is disabled" + }, + "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", + "@downloadAppleQqMultiPerson": { + "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + }, + "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person word-by-word is enabled" + }, + "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person word-by-word is disabled" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting title for Musixmatch language preference" + }, + "downloadMusixmatchLanguageAuto": "Auto (original)", + "@downloadMusixmatchLanguageAuto": { + "description": "Option label when Musixmatch uses original language" + }, + "downloadFilterContributing": "Filter contributing artists in Album Artist", + "@downloadFilterContributing": { + "description": "Toggle title for filtering contributing artists in Album Artist metadata" + }, + "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is enabled" + }, + "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is disabled" + }, + "downloadProvidersNoneEnabled": "None enabled", + "@downloadProvidersNoneEnabled": { + "description": "Subtitle for lyrics providers setting when no providers are enabled" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for the Musixmatch language code text field" + }, + "downloadMusixmatchLanguageHint": "auto / en / es / ja", + "@downloadMusixmatchLanguageHint": { + "description": "Hint text for the Musixmatch language code field" + }, + "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in the Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to reset Musixmatch language to automatic" + }, + "downloadNetworkAnySubtitle": "WiFi + Mobile Data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for 'Any' network mode option" + }, + "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for 'WiFi only' network mode option" + }, + "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", + "@downloadSongLinkRegionDesc": { + "description": "Description in the SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_ko.arb b/lib/l10n/arb/app_ko.arb index 7565a8b6..862b39cd 100644 --- a/lib/l10n/arb/app_ko.arb +++ b/lib/l10n/arb/app_ko.arb @@ -17,7 +17,7 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "Store", + "navStore": "Repo", "@navStore": { "description": "Bottom navigation - Extension store tab" }, @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Spotify URL을 붙여 넣거나 검색", + "homeSubtitle": "Paste a supported URL or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -89,9 +89,17 @@ "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, "downloadFolderOrganization": "폴더 분류 형식", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "외관", "@appearanceTitle": { @@ -150,6 +158,14 @@ } } }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, "optionsSwitchBack": "Deezer 또는 Spotify를 탭하여 확장 기능에서 다시 전환하세요.", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" @@ -190,6 +206,42 @@ "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Artist Tag Mode", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Choose how multiple artists are written into embedded tags.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Single joined value", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Split tags for FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, "optionsConcurrentDownloads": "동시 다운로드", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" @@ -211,11 +263,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "확장 기능 스토어", + "optionsExtensionStore": "Extension Repo", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "탐색 메뉴에 스토어 탭 표시", + "optionsExtensionStoreSubtitle": "Show Repo tab in navigation", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -318,7 +370,7 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "확장 기능 스토어", + "storeTitle": "Extension Repo", "@storeTitle": { "description": "Store screen title" }, @@ -675,6 +727,10 @@ "@dialogImport": { "description": "Dialog button - import data" }, + "dialogDownload": "Download", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, "dialogDiscard": "취소", "@dialogDiscard": { "description": "Dialog button - discard changes" @@ -999,6 +1055,46 @@ "@searchPlaylists": { "description": "Search result category - playlists" }, + "searchSortTitle": "Sort Results", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Default", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Title (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Title (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artist (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artist (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duration (Shortest)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duration (Longest)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Release Date (Oldest)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Release Date (Newest)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, "tooltipPlay": "재생", "@tooltipPlay": { "description": "Tooltip - play button" @@ -1119,6 +1215,18 @@ "@providerPriorityInfo": { "description": "Info tip about fallback behavior" }, + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { "description": "Label for built-in providers (Tidal/Qobuz)" @@ -1479,6 +1587,14 @@ "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, "trackLyricsTimeout": "Request timed out. Try again later.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" @@ -1574,7 +1690,59 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "extensionDefaultProvider": "Default (Deezer/Spotify)", + "storeAddRepoTitle": "Add Extension Repository", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "e.g. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Add Repository", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Change repository", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Extension Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Current repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "New Repository URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionDefaultProvider": "Default (Deezer)", "@extensionDefaultProvider": { "description": "Default search provider option" }, @@ -1713,6 +1881,14 @@ "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, + "extensionsFallbackTitle": "Fallback Extensions", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, "extensionsNoDownloadProvider": "No extensions with download provider", "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" @@ -1773,6 +1949,46 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, + "downloadLossy320": "Lossy 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Lossy Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Best compatibility, ~10MB per track", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Best quality Opus, ~8MB per track", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, "qualityNote": "Actual quality depends on track availability from the service", "@qualityNote": { "description": "Note about quality availability" @@ -1897,6 +2113,14 @@ "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, "downloadedAlbumDeleteSelected": "Delete Selected", "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" @@ -2178,6 +2402,30 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, "libraryActions": "Actions", "@libraryActions": { "description": "Section header for library actions" @@ -2235,6 +2483,15 @@ } } }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -2252,6 +2509,10 @@ "@libraryScanning": { "description": "Status during scan" }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, "libraryScanProgress": "{progress}% of {total} files", "@libraryScanProgress": { "description": "Scan progress display", @@ -2349,6 +2610,30 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -2361,6 +2646,22 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -2447,7 +2748,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "tutorialExtensionsTip1": "Browse the Repo tab to discover useful extensions", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -2713,6 +3014,38 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, "trackEditMetadata": "Edit Metadata", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" @@ -2755,6 +3088,47 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, + "queueFlacAction": "Queue FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, "trackSaveFailed": "Failed: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", @@ -2768,7 +3142,7 @@ "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "trackConvertFormatSubtitle": "Convert to MP3, Opus, ALAC, or FLAC", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, @@ -2803,6 +3177,22 @@ } } }, + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, "trackConvertConverting": "Converting audio...", "@trackConvertConverting": { "description": "Snackbar while converting" @@ -3154,6 +3544,18 @@ } } }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, "selectionBatchConvertProgress": "Converting {current} of {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", @@ -3197,5 +3599,915 @@ "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" + }, + "lyricsProvidersTitle": "Lyrics Providers", + "@lyricsProvidersTitle": { + "description": "Title for the lyrics provider priority page" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Donate", + "@settingsDonate": { + "description": "Settings menu item - donate" + }, + "settingsDonateSubtitle": "Support SpotiFLAC-Mobile development", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose storage mode for downloaded files.", + "@downloadLocationSubtitle": { + "description": "Subtitle text in Android download location bottom sheet" + }, + "storageModeAppFolder": "App folder (non-SAF)", + "@storageModeAppFolder": { + "description": "Storage mode option - use legacy app folder" + }, + "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "SAF folder", + "@storageModeSaf": { + "description": "Storage mode option - use Android SAF picker" + }, + "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Customize how your files are named.", + "@downloadFilenameDescription": { + "description": "Description text in filename format bottom sheet" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is enabled" + }, + "downloadSeparateSinglesDisabled": "All files in same structure", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is disabled" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting title for SongLink country region" + }, + "downloadNetworkCompatibilityMode": "Network compatibility mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting title for network compatibility toggle" + }, + "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is enabled" + }, + "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is disabled" + }, + "downloadSelectServiceToEnable": "Select a built-in service to enable", + "@downloadSelectServiceToEnable": { + "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", + "@downloadSelectTidalQobuz": { + "description": "Info hint when non-Tidal/Qobuz service is selected" + }, + "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Toggle title for including Netease translated lyrics" + }, + "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is enabled" + }, + "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is disabled" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Toggle title for including Netease romanized lyrics" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is enabled" + }, + "downloadNeteaseIncludeRomanizationDisabled": "Disabled", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is disabled" + }, + "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", + "@downloadAppleQqMultiPerson": { + "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + }, + "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person word-by-word is enabled" + }, + "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person word-by-word is disabled" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting title for Musixmatch language preference" + }, + "downloadMusixmatchLanguageAuto": "Auto (original)", + "@downloadMusixmatchLanguageAuto": { + "description": "Option label when Musixmatch uses original language" + }, + "downloadFilterContributing": "Filter contributing artists in Album Artist", + "@downloadFilterContributing": { + "description": "Toggle title for filtering contributing artists in Album Artist metadata" + }, + "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is enabled" + }, + "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is disabled" + }, + "downloadProvidersNoneEnabled": "None enabled", + "@downloadProvidersNoneEnabled": { + "description": "Subtitle for lyrics providers setting when no providers are enabled" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for the Musixmatch language code text field" + }, + "downloadMusixmatchLanguageHint": "auto / en / es / ja", + "@downloadMusixmatchLanguageHint": { + "description": "Hint text for the Musixmatch language code field" + }, + "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in the Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to reset Musixmatch language to automatic" + }, + "downloadNetworkAnySubtitle": "WiFi + Mobile Data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for 'Any' network mode option" + }, + "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for 'WiFi only' network mode option" + }, + "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", + "@downloadSongLinkRegionDesc": { + "description": "Description in the SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_nl.arb b/lib/l10n/arb/app_nl.arb index e7031f0e..5e4be112 100644 --- a/lib/l10n/arb/app_nl.arb +++ b/lib/l10n/arb/app_nl.arb @@ -17,7 +17,7 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "Store", + "navStore": "Repo", "@navStore": { "description": "Bottom navigation - Extension store tab" }, @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Paste a Spotify link or search by name", + "homeSubtitle": "Paste a supported URL or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -89,9 +89,17 @@ "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, "downloadFolderOrganization": "Folder Organization", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "Appearance", "@appearanceTitle": { @@ -150,6 +158,14 @@ } } }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" @@ -190,6 +206,42 @@ "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Artist Tag Mode", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Choose how multiple artists are written into embedded tags.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Single joined value", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Split tags for FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, "optionsConcurrentDownloads": "Concurrent Downloads", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" @@ -211,11 +263,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "Extension Store", + "optionsExtensionStore": "Extension Repo", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "Show Store tab in navigation", + "optionsExtensionStoreSubtitle": "Show Repo tab in navigation", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -318,7 +370,7 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "Extension Store", + "storeTitle": "Extension Repo", "@storeTitle": { "description": "Store screen title" }, @@ -675,6 +727,10 @@ "@dialogImport": { "description": "Dialog button - import data" }, + "dialogDownload": "Download", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, "dialogDiscard": "Discard", "@dialogDiscard": { "description": "Dialog button - discard changes" @@ -999,6 +1055,46 @@ "@searchPlaylists": { "description": "Search result category - playlists" }, + "searchSortTitle": "Sort Results", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Default", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Title (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Title (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artist (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artist (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duration (Shortest)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duration (Longest)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Release Date (Oldest)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Release Date (Newest)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, "tooltipPlay": "Play", "@tooltipPlay": { "description": "Tooltip - play button" @@ -1119,6 +1215,18 @@ "@providerPriorityInfo": { "description": "Info tip about fallback behavior" }, + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { "description": "Label for built-in providers (Tidal/Qobuz)" @@ -1479,6 +1587,14 @@ "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, "trackLyricsTimeout": "Request timed out. Try again later.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" @@ -1574,7 +1690,59 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "extensionDefaultProvider": "Default (Deezer/Spotify)", + "storeAddRepoTitle": "Add Extension Repository", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "e.g. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Add Repository", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Change repository", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Extension Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Current repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "New Repository URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionDefaultProvider": "Default (Deezer)", "@extensionDefaultProvider": { "description": "Default search provider option" }, @@ -1713,6 +1881,14 @@ "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, + "extensionsFallbackTitle": "Fallback Extensions", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, "extensionsNoDownloadProvider": "No extensions with download provider", "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" @@ -1773,6 +1949,46 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, + "downloadLossy320": "Lossy 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Lossy Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Best compatibility, ~10MB per track", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Best quality Opus, ~8MB per track", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, "qualityNote": "Actual quality depends on track availability from the service", "@qualityNote": { "description": "Note about quality availability" @@ -1897,6 +2113,14 @@ "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, "downloadedAlbumDeleteSelected": "Delete Selected", "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" @@ -2178,6 +2402,30 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, "libraryActions": "Actions", "@libraryActions": { "description": "Section header for library actions" @@ -2235,6 +2483,15 @@ } } }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -2252,6 +2509,10 @@ "@libraryScanning": { "description": "Status during scan" }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, "libraryScanProgress": "{progress}% of {total} files", "@libraryScanProgress": { "description": "Scan progress display", @@ -2349,6 +2610,30 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -2361,6 +2646,22 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -2447,7 +2748,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "tutorialExtensionsTip1": "Browse the Repo tab to discover useful extensions", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -2713,6 +3014,38 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, "trackEditMetadata": "Edit Metadata", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" @@ -2755,6 +3088,47 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, + "queueFlacAction": "Queue FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, "trackSaveFailed": "Failed: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", @@ -2768,7 +3142,7 @@ "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "trackConvertFormatSubtitle": "Convert to MP3, Opus, ALAC, or FLAC", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, @@ -2803,6 +3177,22 @@ } } }, + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, "trackConvertConverting": "Converting audio...", "@trackConvertConverting": { "description": "Snackbar while converting" @@ -3154,6 +3544,18 @@ } } }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, "selectionBatchConvertProgress": "Converting {current} of {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", @@ -3197,5 +3599,915 @@ "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" + }, + "lyricsProvidersTitle": "Lyrics Providers", + "@lyricsProvidersTitle": { + "description": "Title for the lyrics provider priority page" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Donate", + "@settingsDonate": { + "description": "Settings menu item - donate" + }, + "settingsDonateSubtitle": "Support SpotiFLAC-Mobile development", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose storage mode for downloaded files.", + "@downloadLocationSubtitle": { + "description": "Subtitle text in Android download location bottom sheet" + }, + "storageModeAppFolder": "App folder (non-SAF)", + "@storageModeAppFolder": { + "description": "Storage mode option - use legacy app folder" + }, + "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "SAF folder", + "@storageModeSaf": { + "description": "Storage mode option - use Android SAF picker" + }, + "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Customize how your files are named.", + "@downloadFilenameDescription": { + "description": "Description text in filename format bottom sheet" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is enabled" + }, + "downloadSeparateSinglesDisabled": "All files in same structure", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is disabled" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting title for SongLink country region" + }, + "downloadNetworkCompatibilityMode": "Network compatibility mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting title for network compatibility toggle" + }, + "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is enabled" + }, + "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is disabled" + }, + "downloadSelectServiceToEnable": "Select a built-in service to enable", + "@downloadSelectServiceToEnable": { + "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", + "@downloadSelectTidalQobuz": { + "description": "Info hint when non-Tidal/Qobuz service is selected" + }, + "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Toggle title for including Netease translated lyrics" + }, + "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is enabled" + }, + "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is disabled" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Toggle title for including Netease romanized lyrics" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is enabled" + }, + "downloadNeteaseIncludeRomanizationDisabled": "Disabled", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is disabled" + }, + "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", + "@downloadAppleQqMultiPerson": { + "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + }, + "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person word-by-word is enabled" + }, + "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person word-by-word is disabled" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting title for Musixmatch language preference" + }, + "downloadMusixmatchLanguageAuto": "Auto (original)", + "@downloadMusixmatchLanguageAuto": { + "description": "Option label when Musixmatch uses original language" + }, + "downloadFilterContributing": "Filter contributing artists in Album Artist", + "@downloadFilterContributing": { + "description": "Toggle title for filtering contributing artists in Album Artist metadata" + }, + "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is enabled" + }, + "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is disabled" + }, + "downloadProvidersNoneEnabled": "None enabled", + "@downloadProvidersNoneEnabled": { + "description": "Subtitle for lyrics providers setting when no providers are enabled" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for the Musixmatch language code text field" + }, + "downloadMusixmatchLanguageHint": "auto / en / es / ja", + "@downloadMusixmatchLanguageHint": { + "description": "Hint text for the Musixmatch language code field" + }, + "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in the Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to reset Musixmatch language to automatic" + }, + "downloadNetworkAnySubtitle": "WiFi + Mobile Data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for 'Any' network mode option" + }, + "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for 'WiFi only' network mode option" + }, + "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", + "@downloadSongLinkRegionDesc": { + "description": "Description in the SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_pt_PT.arb b/lib/l10n/arb/app_pt_PT.arb index e5eb9c09..3cf494eb 100644 --- a/lib/l10n/arb/app_pt_PT.arb +++ b/lib/l10n/arb/app_pt_PT.arb @@ -17,7 +17,7 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "Loja", + "navStore": "Repo", "@navStore": { "description": "Bottom navigation - Extension store tab" }, @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Cole um link do Spotify ou procure por nome", + "homeSubtitle": "Paste a supported URL or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -89,9 +89,17 @@ "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, "downloadFolderOrganization": "Organização de Pastas", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "Aparência", "@appearanceTitle": { @@ -150,6 +158,14 @@ } } }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, "optionsSwitchBack": "Toque no Deezer ou Spotify para alternar de volta da extensão", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" @@ -190,6 +206,42 @@ "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Artist Tag Mode", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Choose how multiple artists are written into embedded tags.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Single joined value", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Split tags for FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, "optionsConcurrentDownloads": "Downloads Simultâneos", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" @@ -211,11 +263,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "Loja de Extensões", + "optionsExtensionStore": "Extension Repo", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "Mostrar aba da Loja na navegação", + "optionsExtensionStoreSubtitle": "Show Repo tab in navigation", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -318,7 +370,7 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "Loja de Extensões", + "storeTitle": "Extension Repo", "@storeTitle": { "description": "Store screen title" }, @@ -675,6 +727,10 @@ "@dialogImport": { "description": "Dialog button - import data" }, + "dialogDownload": "Download", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, "dialogDiscard": "Descartar", "@dialogDiscard": { "description": "Dialog button - discard changes" @@ -732,7 +788,7 @@ "@dialogDeleteSelectedTitle": { "description": "Dialog title - delete selected items" }, - "dialogDeleteSelectedMessage": "Apagar {count} {count, plural, one {faixa} other{faixas}} do histórico?\n\nIsso também apagará os arquivos do armazenamento.", + "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": { @@ -811,7 +867,7 @@ "@snackbarCredentialsCleared": { "description": "Snackbar - Spotify credentials removed" }, - "snackbarDeletedTracks": "{count} {count, plural, one {faixa apagada} other{faixas apagadas}}", + "snackbarDeletedTracks": "{count} {count, plural, one {}=1{faixa apagada} other{faixas apagadas}}", "@snackbarDeletedTracks": { "description": "Snackbar - tracks deleted", "placeholders": { @@ -999,6 +1055,46 @@ "@searchPlaylists": { "description": "Search result category - playlists" }, + "searchSortTitle": "Sort Results", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Default", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Title (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Title (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artist (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artist (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duration (Shortest)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duration (Longest)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Release Date (Oldest)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Release Date (Newest)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, "tooltipPlay": "Reproduzir", "@tooltipPlay": { "description": "Tooltip - play button" @@ -1119,6 +1215,18 @@ "@providerPriorityInfo": { "description": "Info tip about fallback behavior" }, + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, "providerBuiltIn": "Embutido", "@providerBuiltIn": { "description": "Label for built-in providers (Tidal/Qobuz)" @@ -1374,7 +1482,7 @@ } } }, - "tracksCount": "{count, plural, one {1 faixa} other{{count} faixas}}", + "tracksCount": "{count, plural, one {}=1{1 faixa} other{{count} faixas}}", "@tracksCount": { "description": "Track count display", "placeholders": { @@ -1479,6 +1587,14 @@ "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, "trackLyricsTimeout": "A solicitação expirou. Tente novamente mais tarde.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" @@ -1574,7 +1690,59 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "extensionDefaultProvider": "Padrão (Deezer/Spotify)", + "storeAddRepoTitle": "Add Extension Repository", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "e.g. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Add Repository", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Change repository", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Extension Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Current repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "New Repository URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionDefaultProvider": "Default (Deezer)", "@extensionDefaultProvider": { "description": "Default search provider option" }, @@ -1713,6 +1881,14 @@ "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, + "extensionsFallbackTitle": "Fallback Extensions", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, "extensionsNoDownloadProvider": "Nenhuma extensão com provedor de download", "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" @@ -1773,6 +1949,46 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, + "downloadLossy320": "Lossy 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Lossy Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Best compatibility, ~10MB per track", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Best quality Opus, ~8MB per track", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, "qualityNote": "A qualidade real depende da faixa que estiver disponível no serviço", "@qualityNote": { "description": "Note about quality availability" @@ -1897,11 +2113,19 @@ "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, "downloadedAlbumDeleteSelected": "Apagar Selecionados", "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" }, - "downloadedAlbumDeleteMessage": "Excluir {count} {count, plural, one {faixa} other{faixas}} deste álbum?\n\nIsso também excluirá os arquivos do armazenamento.", + "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": { @@ -1927,7 +2151,7 @@ "@downloadedAlbumTapToSelect": { "description": "Selection hint" }, - "downloadedAlbumDeleteCount": "Apagar {count} {count, plural, one {faixa} other{faixas}}", + "downloadedAlbumDeleteCount": "Apagar {count} {count, plural, one {}=1{faixa} other{faixas}}", "@downloadedAlbumDeleteCount": { "description": "Delete button text with count", "placeholders": { @@ -2178,6 +2402,30 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, "libraryActions": "Actions", "@libraryActions": { "description": "Section header for library actions" @@ -2235,6 +2483,15 @@ } } }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -2252,6 +2509,10 @@ "@libraryScanning": { "description": "Status during scan" }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, "libraryScanProgress": "{progress}% of {total} files", "@libraryScanProgress": { "description": "Scan progress display", @@ -2349,6 +2610,30 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -2361,6 +2646,22 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -2447,7 +2748,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "tutorialExtensionsTip1": "Browse the Repo tab to discover useful extensions", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -2713,6 +3014,38 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, "trackEditMetadata": "Edit Metadata", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" @@ -2755,6 +3088,47 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, + "queueFlacAction": "Queue FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, "trackSaveFailed": "Failed: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", @@ -2768,7 +3142,7 @@ "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "trackConvertFormatSubtitle": "Convert to MP3, Opus, ALAC, or FLAC", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, @@ -2803,6 +3177,22 @@ } } }, + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, "trackConvertConverting": "Converting audio...", "@trackConvertConverting": { "description": "Snackbar while converting" @@ -3154,6 +3544,18 @@ } } }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, "selectionBatchConvertProgress": "Converting {current} of {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", @@ -3197,5 +3599,915 @@ "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" + }, + "lyricsProvidersTitle": "Lyrics Providers", + "@lyricsProvidersTitle": { + "description": "Title for the lyrics provider priority page" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Donate", + "@settingsDonate": { + "description": "Settings menu item - donate" + }, + "settingsDonateSubtitle": "Support SpotiFLAC-Mobile development", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose storage mode for downloaded files.", + "@downloadLocationSubtitle": { + "description": "Subtitle text in Android download location bottom sheet" + }, + "storageModeAppFolder": "App folder (non-SAF)", + "@storageModeAppFolder": { + "description": "Storage mode option - use legacy app folder" + }, + "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "SAF folder", + "@storageModeSaf": { + "description": "Storage mode option - use Android SAF picker" + }, + "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Customize how your files are named.", + "@downloadFilenameDescription": { + "description": "Description text in filename format bottom sheet" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is enabled" + }, + "downloadSeparateSinglesDisabled": "All files in same structure", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is disabled" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting title for SongLink country region" + }, + "downloadNetworkCompatibilityMode": "Network compatibility mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting title for network compatibility toggle" + }, + "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is enabled" + }, + "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is disabled" + }, + "downloadSelectServiceToEnable": "Select a built-in service to enable", + "@downloadSelectServiceToEnable": { + "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", + "@downloadSelectTidalQobuz": { + "description": "Info hint when non-Tidal/Qobuz service is selected" + }, + "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Toggle title for including Netease translated lyrics" + }, + "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is enabled" + }, + "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is disabled" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Toggle title for including Netease romanized lyrics" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is enabled" + }, + "downloadNeteaseIncludeRomanizationDisabled": "Disabled", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is disabled" + }, + "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", + "@downloadAppleQqMultiPerson": { + "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + }, + "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person word-by-word is enabled" + }, + "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person word-by-word is disabled" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting title for Musixmatch language preference" + }, + "downloadMusixmatchLanguageAuto": "Auto (original)", + "@downloadMusixmatchLanguageAuto": { + "description": "Option label when Musixmatch uses original language" + }, + "downloadFilterContributing": "Filter contributing artists in Album Artist", + "@downloadFilterContributing": { + "description": "Toggle title for filtering contributing artists in Album Artist metadata" + }, + "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is enabled" + }, + "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is disabled" + }, + "downloadProvidersNoneEnabled": "None enabled", + "@downloadProvidersNoneEnabled": { + "description": "Subtitle for lyrics providers setting when no providers are enabled" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for the Musixmatch language code text field" + }, + "downloadMusixmatchLanguageHint": "auto / en / es / ja", + "@downloadMusixmatchLanguageHint": { + "description": "Hint text for the Musixmatch language code field" + }, + "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in the Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to reset Musixmatch language to automatic" + }, + "downloadNetworkAnySubtitle": "WiFi + Mobile Data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for 'Any' network mode option" + }, + "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for 'WiFi only' network mode option" + }, + "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", + "@downloadSongLinkRegionDesc": { + "description": "Description in the SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" } -} \ No newline at end of file +} diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 4d8901f8..c3d650fa 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -17,7 +17,7 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "Магазин", + "navStore": "Repo", "@navStore": { "description": "Bottom navigation - Extension store tab" }, @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Вставьте ссылку Spotify или ищите по названию", + "homeSubtitle": "Paste a supported URL or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -89,9 +89,17 @@ "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, "downloadFolderOrganization": "Организация папок", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "Внешний вид", "@appearanceTitle": { @@ -150,6 +158,14 @@ } } }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, "optionsSwitchBack": "Нажмите Deezer или Spotify для возврата с расширения", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" @@ -190,6 +206,42 @@ "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Artist Tag Mode", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Choose how multiple artists are written into embedded tags.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Single joined value", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Split tags for FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, "optionsConcurrentDownloads": "Одновременные загрузки", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" @@ -211,11 +263,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "Магазин расширений", + "optionsExtensionStore": "Extension Repo", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "Показывать вкладку Магазин в гл. меню", + "optionsExtensionStoreSubtitle": "Show Repo tab in navigation", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -318,7 +370,7 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "Магазин расширений", + "storeTitle": "Extension Repo", "@storeTitle": { "description": "Store screen title" }, @@ -675,6 +727,10 @@ "@dialogImport": { "description": "Dialog button - import data" }, + "dialogDownload": "Download", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, "dialogDiscard": "Отменить", "@dialogDiscard": { "description": "Dialog button - discard changes" @@ -732,7 +788,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": { @@ -811,7 +867,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": { @@ -999,6 +1055,46 @@ "@searchPlaylists": { "description": "Search result category - playlists" }, + "searchSortTitle": "Sort Results", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Default", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Title (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Title (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artist (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artist (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duration (Shortest)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duration (Longest)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Release Date (Oldest)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Release Date (Newest)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, "tooltipPlay": "Воспроизвести", "@tooltipPlay": { "description": "Tooltip - play button" @@ -1119,6 +1215,18 @@ "@providerPriorityInfo": { "description": "Info tip about fallback behavior" }, + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, "providerBuiltIn": "Встроенные", "@providerBuiltIn": { "description": "Label for built-in providers (Tidal/Qobuz)" @@ -1374,7 +1482,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": { @@ -1479,6 +1587,14 @@ "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, "trackLyricsTimeout": "Время ожидания запроса истекло. Повторите попытку позже.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" @@ -1574,7 +1690,59 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "extensionDefaultProvider": "По умолчанию (Deezer/Spotify)", + "storeAddRepoTitle": "Add Extension Repository", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "e.g. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Add Repository", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Change repository", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Extension Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Current repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "New Repository URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionDefaultProvider": "Default (Deezer)", "@extensionDefaultProvider": { "description": "Default search provider option" }, @@ -1713,6 +1881,14 @@ "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, + "extensionsFallbackTitle": "Fallback Extensions", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, "extensionsNoDownloadProvider": "Нет расширений с провайдером загрузки", "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" @@ -1773,6 +1949,46 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, + "downloadLossy320": "Lossy 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Lossy Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Best compatibility, ~10MB per track", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Best quality Opus, ~8MB per track", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, "qualityNote": "Фактическое качество зависит от доступности треков в сервисе", "@qualityNote": { "description": "Note about quality availability" @@ -1897,11 +2113,19 @@ "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, "downloadedAlbumDeleteSelected": "Удалить выбранные", "@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": { @@ -1927,7 +2151,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": { @@ -2178,6 +2402,30 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, "libraryActions": "Действия", "@libraryActions": { "description": "Section header for library actions" @@ -2226,7 +2474,7 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksUnit": "{count, plural, one {трек} few {трека} many {треков} other{треков}}", + "libraryTracksUnit": "{count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", "placeholders": { @@ -2235,6 +2483,15 @@ } } }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Последнее сканирование: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -2252,6 +2509,10 @@ "@libraryScanning": { "description": "Status during scan" }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, "libraryScanProgress": "{progress}% из {total} файлов", "@libraryScanProgress": { "description": "Scan progress display", @@ -2349,6 +2610,30 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, "libraryFilterSort": "Сортировка", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -2361,11 +2646,27 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, "timeJustNow": "Только что", "@timeJustNow": { "description": "Relative time - less than a minute ago" }, - "timeMinutesAgo": "{count, plural, one {{count} минуту} few {{count} минуты} many {{count} минут} other {{count} минут}} назад", + "timeMinutesAgo": "{count, plural, one {{count} минуту} few {{count} минуты} many {{count} минут} =1 {1 минуту} other {{count} минут}} назад", "@timeMinutesAgo": { "description": "Relative time - minutes ago", "placeholders": { @@ -2374,7 +2675,7 @@ } } }, - "timeHoursAgo": "{count, plural, one {{count} час} few {{count} часа} many {{count} часов} other {{count} часов}} назад", + "timeHoursAgo": "{count, plural, one {{count} час} few {{count} часа} many {{count} часов} =1 {1 час} other {{count} часов}} назад", "@timeHoursAgo": { "description": "Relative time - hours ago", "placeholders": { @@ -2447,7 +2748,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Просмотрите вкладку Магазина, чтобы найти полезные расширения", + "tutorialExtensionsTip1": "Browse the Repo tab to discover useful extensions", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -2713,6 +3014,38 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, "trackEditMetadata": "Редактировать метаданные", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" @@ -2755,6 +3088,47 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, + "queueFlacAction": "Queue FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, "trackSaveFailed": "Ошибка: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", @@ -2768,7 +3142,7 @@ "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Конвертировать в MP3 или Opus", + "trackConvertFormatSubtitle": "Convert to MP3, Opus, ALAC, or FLAC", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, @@ -2803,6 +3177,22 @@ } } }, + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, "trackConvertConverting": "Конвертация аудио...", "@trackConvertConverting": { "description": "Snackbar while converting" @@ -2944,7 +3334,7 @@ "@collectionNoPlaylistsSubtitle": { "description": "Empty state subtitle when user has no playlists" }, - "collectionPlaylistTracks": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} other {{count} треков}}", + "collectionPlaylistTracks": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}", "@collectionPlaylistTracks": { "description": "Track count label for custom playlists", "placeholders": { @@ -3109,7 +3499,7 @@ "@collectionPlaylistRemoveCover": { "description": "Bottom sheet action to remove custom cover image from a playlist" }, - "selectionShareCount": "Отправить {count} {count, plural, one {трек} few {трека} many {треков} other{треков}}", + "selectionShareCount": "Отправить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", "@selectionShareCount": { "description": "Share button text with count in selection mode", "placeholders": { @@ -3122,7 +3512,7 @@ "@selectionShareNoFiles": { "description": "Snackbar when no selected files exist on disk" }, - "selectionConvertCount": "Конвертировать {count} {count, plural, one {трек} few {трека} many {треков} other{треков}}", + "selectionConvertCount": "Конвертировать {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", "@selectionConvertCount": { "description": "Convert button text with count in selection mode", "placeholders": { @@ -3154,6 +3544,18 @@ } } }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, "selectionBatchConvertProgress": "Конвертация {current} из {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", @@ -3197,5 +3599,915 @@ "downloadUseAlbumArtistForFoldersTrackSubtitle": "Папки исполнителя используют только трек исполнителя", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" + }, + "lyricsProvidersTitle": "Lyrics Providers", + "@lyricsProvidersTitle": { + "description": "Title for the lyrics provider priority page" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Donate", + "@settingsDonate": { + "description": "Settings menu item - donate" + }, + "settingsDonateSubtitle": "Support SpotiFLAC-Mobile development", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose storage mode for downloaded files.", + "@downloadLocationSubtitle": { + "description": "Subtitle text in Android download location bottom sheet" + }, + "storageModeAppFolder": "App folder (non-SAF)", + "@storageModeAppFolder": { + "description": "Storage mode option - use legacy app folder" + }, + "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "SAF folder", + "@storageModeSaf": { + "description": "Storage mode option - use Android SAF picker" + }, + "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Customize how your files are named.", + "@downloadFilenameDescription": { + "description": "Description text in filename format bottom sheet" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is enabled" + }, + "downloadSeparateSinglesDisabled": "All files in same structure", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is disabled" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting title for SongLink country region" + }, + "downloadNetworkCompatibilityMode": "Network compatibility mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting title for network compatibility toggle" + }, + "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is enabled" + }, + "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is disabled" + }, + "downloadSelectServiceToEnable": "Select a built-in service to enable", + "@downloadSelectServiceToEnable": { + "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", + "@downloadSelectTidalQobuz": { + "description": "Info hint when non-Tidal/Qobuz service is selected" + }, + "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Toggle title for including Netease translated lyrics" + }, + "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is enabled" + }, + "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is disabled" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Toggle title for including Netease romanized lyrics" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is enabled" + }, + "downloadNeteaseIncludeRomanizationDisabled": "Disabled", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is disabled" + }, + "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", + "@downloadAppleQqMultiPerson": { + "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + }, + "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person word-by-word is enabled" + }, + "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person word-by-word is disabled" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting title for Musixmatch language preference" + }, + "downloadMusixmatchLanguageAuto": "Auto (original)", + "@downloadMusixmatchLanguageAuto": { + "description": "Option label when Musixmatch uses original language" + }, + "downloadFilterContributing": "Filter contributing artists in Album Artist", + "@downloadFilterContributing": { + "description": "Toggle title for filtering contributing artists in Album Artist metadata" + }, + "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is enabled" + }, + "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is disabled" + }, + "downloadProvidersNoneEnabled": "None enabled", + "@downloadProvidersNoneEnabled": { + "description": "Subtitle for lyrics providers setting when no providers are enabled" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for the Musixmatch language code text field" + }, + "downloadMusixmatchLanguageHint": "auto / en / es / ja", + "@downloadMusixmatchLanguageHint": { + "description": "Hint text for the Musixmatch language code field" + }, + "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in the Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to reset Musixmatch language to automatic" + }, + "downloadNetworkAnySubtitle": "WiFi + Mobile Data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for 'Any' network mode option" + }, + "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for 'WiFi only' network mode option" + }, + "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", + "@downloadSongLinkRegionDesc": { + "description": "Description in the SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_tr.arb b/lib/l10n/arb/app_tr.arb index b00e4fa2..e0aa8df7 100644 --- a/lib/l10n/arb/app_tr.arb +++ b/lib/l10n/arb/app_tr.arb @@ -5,11 +5,11 @@ "@appName": { "description": "App name - DO NOT TRANSLATE" }, - "navHome": "Ana Sayfa", + "navHome": "Ana sayfa", "@navHome": { "description": "Bottom navigation - Home tab" }, - "navLibrary": "Kitaplık", + "navLibrary": "Kitaplığın", "@navLibrary": { "description": "Bottom navigation - Library tab" }, @@ -17,23 +17,23 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "Mağaza", + "navStore": "Depo", "@navStore": { "description": "Bottom navigation - Extension store tab" }, - "homeTitle": "Ana Sayfa", + "homeTitle": "Ana sayfa", "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Bir Spotify bağlantısı yapıştırın veya şarkı arayın", + "homeSubtitle": "Desteklenen bir URL yapıştırın veya isme göre arayın", "@homeSubtitle": { "description": "Subtitle shown below search box" }, - "homeSupports": "Desteklenenler: Şarkı, Albüm, Çalma Listesi, Sanatçı bağlantıları", + "homeSupports": "Desteklenen linkler: Şarkı, Albüm, Çalma Listesi, Sanatçı linkleri", "@homeSupports": { "description": "Info text about supported URL types" }, - "homeRecent": "Son Arananlar", + "homeRecent": "En son", "@homeRecent": { "description": "Section header for recent searches" }, @@ -49,7 +49,7 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historySearchHint": "Geçmişte ara...", + "historySearchHint": "Arama geçmişi...", "@historySearchHint": { "description": "Search bar placeholder in history" }, @@ -81,17 +81,25 @@ "@downloadTitle": { "description": "Download settings page title" }, - "downloadAskQualitySubtitle": "Her indirmede kalite seçme ekranını göster", + "downloadAskQualitySubtitle": "Her indirmeden önce kalite seçim ekranını göster", "@downloadAskQualitySubtitle": { "description": "Subtitle for ask quality toggle" }, - "downloadFilenameFormat": "Dosya Adı Formatı", + "downloadFilenameFormat": "Dosya adı formatı", "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, - "downloadFolderOrganization": "Klasör Düzeni", + "downloadSingleFilenameFormat": "Single Dosya Adı Formatı", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Single ve EP'ler için dosya adı örneği. Albüm formatıyla aynı etiketleri kullanır.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, + "downloadFolderOrganization": "Dosya Organizasyonu", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "Görünüm", "@appearanceTitle": { @@ -109,15 +117,15 @@ "@appearanceThemeDark": { "description": "Dark theme" }, - "appearanceDynamicColor": "Dinamik Renkler", + "appearanceDynamicColor": "Dinamik Renk", "@appearanceDynamicColor": { "description": "Material You dynamic colors" }, - "appearanceDynamicColorSubtitle": "Uygulama renklerini duvar kağıdınızdan alır", + "appearanceDynamicColorSubtitle": "Duvar kağıdının renklerini kullan", "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, - "appearanceHistoryView": "Geçmiş Görünümü", + "appearanceHistoryView": "Geçmiş Düzeni", "@appearanceHistoryView": { "description": "Layout style for history" }, @@ -133,15 +141,15 @@ "@optionsTitle": { "description": "Options settings page title" }, - "optionsPrimaryProvider": "Ana Sağlayıcı", + "optionsPrimaryProvider": "Ana Kaynek", "@optionsPrimaryProvider": { "description": "Main search provider setting" }, - "optionsPrimaryProviderSubtitle": "Şarkı adıyla arama yaparken kullanılacak servis.", + "optionsPrimaryProviderSubtitle": "Şarkı ismi aratılırken kullanılan kaynak.", "@optionsPrimaryProviderSubtitle": { "description": "Subtitle for primary provider" }, - "optionsUsingExtension": "Şu anki eklenti: {extensionName}", + "optionsUsingExtension": "Kullanılan eklenti: {extensionName}", "@optionsUsingExtension": { "description": "Shows active extension name", "placeholders": { @@ -150,51 +158,95 @@ } } }, - "optionsSwitchBack": "Eklentiden çıkıp varsayılana dönmek için Deezer veya Spotify'a dokunun", + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, + "optionsSwitchBack": "Dahili kaynaklara dönmek için Deezer veya Spotify'a tıkla", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" }, - "optionsAutoFallback": "Otomatik Geçiş", + "optionsAutoFallback": "Diğerlerini dene", "@optionsAutoFallback": { "description": "Auto-retry with other services" }, - "optionsAutoFallbackSubtitle": "İndirme başarısız olursa otomatik olarak diğer servisleri dener", + "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": "Eklenti sağlayıcılarını kullan", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" }, - "optionsUseExtensionProvidersOn": "İndirme için önce eklentiler denenecek", + "optionsUseExtensionProvidersOn": "Eklentiler ilk denenecek", "@optionsUseExtensionProvidersOn": { "description": "Status when extension providers enabled" }, - "optionsUseExtensionProvidersOff": "Sadece yerleşik sağlayıcılar kullanılıyor", + "optionsUseExtensionProvidersOff": "Sadece dahili sağlayıcıları kullan", "@optionsUseExtensionProvidersOff": { "description": "Status when extension providers disabled" }, - "optionsEmbedLyrics": "Şarkı Sözlerini Gömer", + "optionsEmbedLyrics": "Şarkı Sözlerini Göm", "@optionsEmbedLyrics": { "description": "Embed lyrics in audio files" }, - "optionsEmbedLyricsSubtitle": "Senkronize şarkı sözlerini FLAC dosyalarının içine gömer", + "optionsEmbedLyricsSubtitle": "Senkronize şarkı sözlerini FLAC dosyalarına göm", "@optionsEmbedLyricsSubtitle": { "description": "Subtitle for embed lyrics" }, - "optionsMaxQualityCover": "En Yüksek Kalite Albüm Kapağı", + "optionsMaxQualityCover": "En Yüksek Kapak Kalitesi", "@optionsMaxQualityCover": { "description": "Download highest quality album art" }, - "optionsMaxQualityCoverSubtitle": "Albüm kapağını bulunabilen en yüksek çözünürlükte indirir", + "optionsMaxQualityCoverSubtitle": "En yüksek kalitedeki albüm kapaklarını indir", "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, - "optionsConcurrentDownloads": "Eşzamanlı İndirmeler", + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Ses yüksekliğini tara ve ReplayGain etiketlerini göm (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Devre dışı: Ses normalleştirme etiketi yok", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Sanatçı Etiketi Modu", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Birden fazla sanatçının gömülü etiketlere nasıl yazılacağını seçin.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Birleşik tek değer", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Maksimum oynatıcı uyumluluğu için 'Sanatçı A, Sanatçı B' şeklinde tek bir SANATÇI değeri yazın.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "FLAC/Opus için ayrılmış etiketler", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "FLAC ve Opus için her sanatçıya ayrı bir etiket yazın; MP3 ve M4A birleşik kalır.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, + "optionsConcurrentDownloads": "Eş Zamanlı İndirmeler", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" }, - "optionsConcurrentSequential": "Sırayla (Tek tek)", + "optionsConcurrentSequential": "Sıralı (Birer birer)", "@optionsConcurrentSequential": { "description": "Download one at a time" }, @@ -207,23 +259,23 @@ } } }, - "optionsConcurrentWarning": "Aynı anda çok fazla indirme yapmak kısıtlamaya takılmanıza neden olabilir", + "optionsConcurrentWarning": "Aynı anda birden fazla indirme sınırlamaya takılabilir", "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "Eklenti Mağazası", + "optionsExtensionStore": "Eklenti Deposu", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "Gezinme çubuğunda Mağaza sekmesini göster", + "optionsExtensionStoreSubtitle": "Gezinme menüsünde Depo sekmesini göster", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, - "optionsCheckUpdates": "Güncellemeleri Kontrol Et", + "optionsCheckUpdates": "Güncelleştirmeleri Denetle", "@optionsCheckUpdates": { "description": "Auto update check toggle" }, - "optionsCheckUpdatesSubtitle": "Yeni bir sürüm çıktığında haber ver", + "optionsCheckUpdatesSubtitle": "Yeni sürüm çıktığında bildir", "@optionsCheckUpdatesSubtitle": { "description": "Subtitle for update check" }, @@ -231,15 +283,15 @@ "@optionsUpdateChannel": { "description": "Stable vs preview releases" }, - "optionsUpdateChannelStable": "Sadece kararlı sürümler", + "optionsUpdateChannelStable": "Sadece stabil sürümler", "@optionsUpdateChannelStable": { "description": "Only stable updates" }, - "optionsUpdateChannelPreview": "Ön izleme (Beta) sürümlerini de al", + "optionsUpdateChannelPreview": "Önizleme sürümlerini al", "@optionsUpdateChannelPreview": { "description": "Include beta/preview updates" }, - "optionsUpdateChannelWarning": "Beta sürümler hatalar içerebilir veya tamamlanmamış özellikler barındırabilir", + "optionsUpdateChannelWarning": "Önizleme sürümleri hatalar veya tamamlanmamış özellikler içerebilir", "@optionsUpdateChannelWarning": { "description": "Warning about preview channel" }, @@ -247,27 +299,27 @@ "@optionsClearHistory": { "description": "Delete all download history" }, - "optionsClearHistorySubtitle": "İndirilen tüm şarkıları geçmişten siler", + "optionsClearHistorySubtitle": "İndirilen bütün şarkıları geçmişten temizle", "@optionsClearHistorySubtitle": { "description": "Subtitle for clear history" }, - "optionsDetailedLogging": "Detaylı Hata Ayıklama (Log)", + "optionsDetailedLogging": "Detaylı Günlükleme", "@optionsDetailedLogging": { "description": "Enable verbose logs for debugging" }, - "optionsDetailedLoggingOn": "Arka planda detaylı kayıt tutuluyor", + "optionsDetailedLoggingOn": "Detaylı günlük kayıt ediliyor", "@optionsDetailedLoggingOn": { "description": "Status when logging enabled" }, - "optionsDetailedLoggingOff": "Hata bildirimi yapacaksanız açın", + "optionsDetailedLoggingOff": "Hata bildirmek için aç", "@optionsDetailedLoggingOff": { "description": "Status when logging disabled" }, - "optionsSpotifyCredentials": "Spotify API Kimlik Bilgileri", + "optionsSpotifyCredentials": "Spotify Kimlik Bilgileri", "@optionsSpotifyCredentials": { "description": "Spotify API credentials setting" }, - "optionsSpotifyCredentialsConfigured": "İstemci Kimliği (Client ID): {clientId}...", + "optionsSpotifyCredentialsConfigured": "Client ID: {clientId}...", "@optionsSpotifyCredentialsConfigured": { "description": "Shows configured client ID preview", "placeholders": { @@ -276,15 +328,15 @@ } } }, - "optionsSpotifyCredentialsRequired": "Gerekli - ayarlamak için dokunun", + "optionsSpotifyCredentialsRequired": "Zorunlu - değiştirmek için tıkla", "@optionsSpotifyCredentialsRequired": { "description": "Prompt to set up credentials" }, - "optionsSpotifyWarning": "Spotify, kendi API kimlik bilgilerinizi kullanmanızı gerektirir. developer.spotify.com adresinden ücretsiz alabilirsiniz.", + "optionsSpotifyWarning": "Spotify'ın senin API kimlik bilgilerine ihtiyacı var. Onları developer.spotify.com'dan alabilirsin", "@optionsSpotifyWarning": { "description": "Info about Spotify API requirement" }, - "optionsSpotifyDeprecationWarning": "Spotify API'sindeki değişiklikler nedeniyle Spotify araması 3 Mart 2026'da kullanımdan kaldırılacak. Lütfen Deezer'a geçin.", + "optionsSpotifyDeprecationWarning": "Spotify API değişiklikleri nedeniyle Spotify araması 3 Mart 2026 tarihinde kullanımdan kaldırılacaktır. Lütfen Deezer'a geçiş yapın.", "@optionsSpotifyDeprecationWarning": { "description": "Warning about Spotify API deprecation" }, @@ -292,11 +344,11 @@ "@extensionsTitle": { "description": "Extensions page title" }, - "extensionsDisabled": "Devre dışı", + "extensionsDisabled": "Devre Dışı", "@extensionsDisabled": { "description": "Extension status - inactive" }, - "extensionsVersion": "Sürüm {version}", + "extensionsVersion": "Versiyon {version}", "@extensionsVersion": { "description": "Extension version display", "placeholders": { @@ -318,19 +370,19 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "Eklenti Mağazası", + "storeTitle": "Uzantı Deposu", "@storeTitle": { "description": "Store screen title" }, - "storeSearch": "Eklentilerde ara...", + "storeSearch": "Eklenti ara...", "@storeSearch": { "description": "Store search placeholder" }, - "storeInstall": "Yükle", + "storeInstall": "Kur", "@storeInstall": { "description": "Install extension button" }, - "storeInstalled": "Yüklü", + "storeInstalled": "Kuruldu", "@storeInstalled": { "description": "Already installed badge" }, @@ -346,15 +398,15 @@ "@aboutContributors": { "description": "Section for contributors" }, - "aboutMobileDeveloper": "Mobil sürüm geliştiricisi", + "aboutMobileDeveloper": "Mobil versiyon geliştiricisi", "@aboutMobileDeveloper": { "description": "Role description for mobile dev" }, - "aboutOriginalCreator": "Orijinal SpotiFLAC'ın yaratıcısı", + "aboutOriginalCreator": "Orijinal SpotiFLAC'ın kurucusu", "@aboutOriginalCreator": { "description": "Role description for original creator" }, - "aboutLogoArtist": "Uygulamanın harika logosunu tasarlayan yetenekli sanatçı!", + "aboutLogoArtist": "Uygulama logomuzu yaratmış yetenekli sanatçımız!", "@aboutLogoArtist": { "description": "Role description for logo artist" }, @@ -362,11 +414,11 @@ "@aboutTranslators": { "description": "Section for translators" }, - "aboutSpecialThanks": "Özel Teşekkürler", + "aboutSpecialThanks": "Özel teşekkür", "@aboutSpecialThanks": { "description": "Section for special thanks" }, - "aboutLinks": "Bağlantılar", + "aboutLinks": "Linkler", "@aboutLinks": { "description": "Section for external links" }, @@ -386,7 +438,7 @@ "@aboutReportIssue": { "description": "Link to report bugs" }, - "aboutReportIssueSubtitle": "Karşılaştığınız sorunları bize iletin", + "aboutReportIssueSubtitle": "Karşılaştığın herhangi bir problemi bildir", "@aboutReportIssueSubtitle": { "description": "Subtitle for report issue" }, @@ -394,7 +446,7 @@ "@aboutFeatureRequest": { "description": "Link to suggest features" }, - "aboutFeatureRequestSubtitle": "Uygulama için yeni özellikler önerin", + "aboutFeatureRequestSubtitle": "Uygulama için yeni özellikler isteyin", "@aboutFeatureRequestSubtitle": { "description": "Subtitle for feature request" }, @@ -406,35 +458,35 @@ "@aboutTelegramChannelSubtitle": { "description": "Subtitle for Telegram channel" }, - "aboutTelegramChat": "Telegram Topluluğu", + "aboutTelegramChat": "Telegram Grubu", "@aboutTelegramChat": { "description": "Link to Telegram chat group" }, - "aboutTelegramChatSubtitle": "Diğer kullanıcılarla sohbet edin", + "aboutTelegramChatSubtitle": "Diğer kullanıcılarla sohbet et", "@aboutTelegramChatSubtitle": { "description": "Subtitle for Telegram chat" }, - "aboutSocial": "Sosyal Medya", + "aboutSocial": "Sosyal ağlar", "@aboutSocial": { "description": "Section for social links" }, - "aboutApp": "Uygulama Bilgisi", + "aboutApp": "Uygulama", "@aboutApp": { "description": "Section for app info" }, - "aboutVersion": "Sürüm", + "aboutVersion": "Versiyon", "@aboutVersion": { "description": "Version info label" }, - "aboutBinimumDesc": "QQDL ve HiFi API'nin yaratıcısı. Bu API olmasaydı Tidal indirmeleri var olamazdı!", + "aboutBinimumDesc": "QQDL ve HiFi API'ın kurucusu. Bu API olmadan, Tidal indirmeleri olmazdı!", "@aboutBinimumDesc": { "description": "Credit description for binimum" }, - "aboutSachinsenalDesc": "Orijinal HiFi projesinin kurucusu. Tidal entegrasyonunun temel taşı!", + "aboutSachinsenalDesc": "Orijinal HiFi projesi kurucusu. Tidal entegrasyonun temeli!", "@aboutSachinsenalDesc": { "description": "Credit description for sachinsenal0x64" }, - "aboutSjdonadoDesc": "I Don't Have Spotify (IDHS) projesinin yaratıcısı. Günü kurtaran bağlantı çözümleyicimiz!", + "aboutSjdonadoDesc": "I Don't Have Spotify (IDHS) yaratıcısı. Günü kurtaran yedek bağlantı çözücü!", "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, @@ -442,7 +494,7 @@ "@aboutDabMusic": { "description": "Name of Qobuz API service - DO NOT TRANSLATE" }, - "aboutDabMusicDesc": "En iyi Qobuz yayın API'si. Hi-Res indirmeler onlar olmadan mümkün olamazdı!", + "aboutDabMusicDesc": "En iyi Qobuz streaming API'ı. Yüksek kalite indirmeler bunun sayesinde!", "@aboutDabMusicDesc": { "description": "Credit for DAB Music API" }, @@ -450,11 +502,11 @@ "@aboutSpotiSaver": { "description": "Name of SpotiSaver API service - DO NOT TRANSLATE" }, - "aboutSpotiSaverDesc": "Tidal Hi-Res FLAC altyapısı. Kayıpsız ses deneyiminin kilit parçası!", + "aboutSpotiSaverDesc": "Tidal Hi-Res FLAC akış uç noktaları. Kayıpsız ses yapbozunun kilit bir parçası!", "@aboutSpotiSaverDesc": { "description": "Credit for SpotiSaver API" }, - "aboutAppDescription": "Spotify şarkılarını Tidal ve Qobuz üzerinden kayıpsız kalitede indirin.", + "aboutAppDescription": "Spotify parçalarını Tidal ve Qobuz aracılığıyla kayıpsız kalitede indirin.", "@aboutAppDescription": { "description": "App description in header card" }, @@ -484,7 +536,7 @@ } } }, - "trackMetadataService": "Sağlayıcı", + "trackMetadataService": "Hizmet", "@trackMetadataService": { "description": "Metadata field - download service used" }, @@ -508,11 +560,11 @@ "@setupSkip": { "description": "Skip current step button" }, - "setupStorageAccessRequired": "Depolama İzni Gerekli", + "setupStorageAccessRequired": "Depolama Erişimi Gerekli", "@setupStorageAccessRequired": { "description": "Title when storage access needed" }, - "setupStorageAccessMessageAndroid11": "Android 11 ve üzeri, müzikleri seçtiğiniz klasöre kaydedebilmek için 'Tüm dosyalara erişim' izni gerektirir.", + "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" }, @@ -520,11 +572,11 @@ "@setupOpenSettings": { "description": "Button to open system settings" }, - "setupPermissionDeniedMessage": "İzin reddedildi. Devam etmek için lütfen gerekli izinleri verin.", + "setupPermissionDeniedMessage": "İzin reddedildi. Devam etmek için lütfen bütün izinleri verin.", "@setupPermissionDeniedMessage": { "description": "Error when permission denied" }, - "setupPermissionRequired": "{permissionType} İzni Gerekli", + "setupPermissionRequired": "{permissionType} İzni Zorunlu", "@setupPermissionRequired": { "description": "Generic permission required title", "placeholders": { @@ -534,7 +586,7 @@ } } }, - "setupPermissionRequiredMessage": "En iyi deneyim için {permissionType} iznine ihtiyacımız var. Bunu daha sonra Ayarlar'dan değiştirebilirsiniz.", + "setupPermissionRequiredMessage": "En iyi deneyim için {permissionType} izni zorunludur. Bunu ayarlardan daha sonra değiştirebilirsiniz.", "@setupPermissionRequiredMessage": { "description": "Generic permission required message", "placeholders": { @@ -543,11 +595,11 @@ } } }, - "setupUseDefaultFolder": "Varsayılan Klasör Kullanılsın mı?", + "setupUseDefaultFolder": "Varsayılan Klasörü Kullan?", "@setupUseDefaultFolder": { "description": "Dialog title for default folder" }, - "setupNoFolderSelected": "Hiçbir klasör seçilmedi. İndirilenler için cihazınızdaki varsayılan Müzik klasörü kullanılsın mı?", + "setupNoFolderSelected": "Klasör seçilmedi. Varsayılan \"Music\" klasörünü kullanmak ister misiniz?", "@setupNoFolderSelected": { "description": "Prompt when no folder selected" }, @@ -559,15 +611,15 @@ "@setupDownloadLocationTitle": { "description": "Download location dialog title" }, - "setupDownloadLocationIosMessage": "iOS'te indirilen dosyalar uygulamanın Belgeler klasörüne kaydedilir. Bunlara Dosyalar uygulaması üzerinden erişebilirsiniz.", + "setupDownloadLocationIosMessage": "iOS'ta indirilenler uygulamanın \"Documents\" dosyasına kaydedilir. Onlara Dosyalar uygulamasından erişebilirsiniz.", "@setupDownloadLocationIosMessage": { "description": "iOS-specific folder info" }, - "setupAppDocumentsFolder": "Uygulama Belgeleri Klasörü", + "setupAppDocumentsFolder": "Uygulama Belgeler Klasörü", "@setupAppDocumentsFolder": { "description": "iOS documents folder option" }, - "setupAppDocumentsFolderSubtitle": "Önerilen - Dosyalar uygulamasıyla erişilebilir", + "setupAppDocumentsFolderSubtitle": "Tavsiye edilen - Dosyalar uygulamasından erişilebilir", "@setupAppDocumentsFolderSubtitle": { "description": "Subtitle for documents folder" }, @@ -575,19 +627,19 @@ "@setupChooseFromFiles": { "description": "iOS file picker option" }, - "setupChooseFromFilesSubtitle": "iCloud veya başka bir konum seçin", + "setupChooseFromFilesSubtitle": "iCloud veya başka konum seç", "@setupChooseFromFilesSubtitle": { "description": "Subtitle for file picker" }, - "setupIosEmptyFolderWarning": "iOS Kısıtlaması: Boş klasörler seçilemez. Lütfen içinde en az bir dosya olan bir klasör seçin.", + "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 desteklenmiyor. Lütfen uygulamanın Belgeler klasörünü kullanın.", + "setupIcloudNotSupported": "iCloud Drive desteklenmiyor. Lütfen uygulama Belgeler klasörünü kullanın.", "@setupIcloudNotSupported": { "description": "Error when user selects iCloud Drive on iOS" }, - "setupDownloadInFlac": "Spotify müziklerini FLAC formatında indirin", + "setupDownloadInFlac": "Spotify şarkılarını FLAC olarak indirin", "@setupDownloadInFlac": { "description": "App tagline in setup" }, @@ -599,7 +651,7 @@ "@setupStorageRequired": { "description": "Title when storage permission needed" }, - "setupStorageDescription": "İndirdiğiniz şarkıları kaydedebilmemiz için SpotiFLAC'ın depolama iznine ihtiyacı var.", + "setupStorageDescription": "SpotiFLAC'ın şarkılarınızı kaydetmek için depolama iznine ihtiyacı var.", "@setupStorageDescription": { "description": "Explanation for storage permission" }, @@ -607,15 +659,15 @@ "@setupNotificationGranted": { "description": "Success message for notification permission" }, - "setupNotificationEnable": "Bildirimleri Aç", + "setupNotificationEnable": "Bildirimleri Etkinleştir", "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupFolderChoose": "İndirme Klasörü Seç", + "setupFolderChoose": "İndirilecek Klasörü Seç", "@setupFolderChoose": { "description": "Button to choose folder" }, - "setupFolderDescription": "İndirilen şarkıların nereye kaydedileceğini seçin.", + "setupFolderDescription": "İndirdiğin şarkıların kaydedileceği klasörü seç.", "@setupFolderDescription": { "description": "Explanation for folder selection" }, @@ -623,11 +675,11 @@ "@setupSelectFolder": { "description": "Button to select folder" }, - "setupEnableNotifications": "Bildirimleri Aç", + "setupEnableNotifications": "Bildirimleri Etkinleştir", "@setupEnableNotifications": { "description": "Button to enable notifications" }, - "setupNotificationBackgroundDescription": "İndirme durumları ve tamamlanan şarkılar hakkında anında bildirim alın. Bu, uygulama arka plandayken süreci takip etmenizi kolaylaştırır.", + "setupNotificationBackgroundDescription": "İndirmelerin durumu hakkında bildirim al. Bunu açmak uygulama arka plandayken indirmelerinizi takip etmenizi sağlar.", "@setupNotificationBackgroundDescription": { "description": "Detailed notification explanation" }, @@ -635,15 +687,15 @@ "@setupSkipForNow": { "description": "Skip button text" }, - "setupNext": "İleri", + "setupNext": "Sıradaki", "@setupNext": { "description": "Next button text" }, - "setupGetStarted": "Hadi Başlayalım", + "setupGetStarted": "Başla", "@setupGetStarted": { "description": "Final setup button" }, - "setupAllowAccessToManageFiles": "Lütfen sonraki ekranda \"Tüm dosyaları yönetme erişimine izin ver\" seçeneğini açın.", + "setupAllowAccessToManageFiles": "Lütfen bir sonraki ekranda \"Bütün dosyalara eriş\" iznini sağlayın.", "@setupAllowAccessToManageFiles": { "description": "Instruction for file access permission" }, @@ -659,7 +711,7 @@ "@dialogDelete": { "description": "Dialog button - delete item" }, - "dialogRetry": "Yeniden Dene", + "dialogRetry": "Yeniden dene", "@dialogRetry": { "description": "Dialog button - retry action" }, @@ -667,19 +719,19 @@ "@dialogClear": { "description": "Dialog button - clear items" }, - "dialogDone": "Bitti", + "dialogDone": "Tamamlandı", "@dialogDone": { "description": "Dialog button - action completed" }, - "dialogImport": "İçe Aktar", + "dialogImport": "İçe aktar", "@dialogImport": { "description": "Dialog button - import data" }, "dialogDownload": "İndir", "@dialogDownload": { - "description": "Dialog button - download action" + "description": "Confirm button in Download All dialog" }, - "dialogDiscard": "Değişiklikleri Sil", + "dialogDiscard": "Vazgeç", "@dialogDiscard": { "description": "Dialog button - discard changes" }, @@ -687,15 +739,15 @@ "@dialogRemove": { "description": "Dialog button - remove item" }, - "dialogUninstall": "Sil", + "dialogUninstall": "Kaldır", "@dialogUninstall": { "description": "Dialog button - uninstall extension" }, - "dialogDiscardChanges": "Değişiklikler İptal Edilsin mi?", + "dialogDiscardChanges": "Değişiklikleri İptal Et?", "@dialogDiscardChanges": { "description": "Dialog title - unsaved changes warning" }, - "dialogUnsavedChanges": "Kaydedilmemiş değişiklikleriniz var. Çıkmak istediğinize emin misiniz?", + "dialogUnsavedChanges": "Kaydedilmeyen değişiklikler mevcut. Bu değişiklikleri iptal etmek istiyor musunuz?", "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, @@ -707,15 +759,15 @@ "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" }, - "dialogRemoveExtensionMessage": "Bu eklentiyi kaldırmak istediğinize emin misiniz? Bu işlem geri alınamaz.", + "dialogRemoveExtensionMessage": "Bu eklentiyi kaldırmak istediğine emin misin? Bu işlem geri alınamaz.", "@dialogRemoveExtensionMessage": { "description": "Dialog message - uninstall confirmation" }, - "dialogUninstallExtension": "Eklentiyi Sil?", + "dialogUninstallExtension": "Eklentiyi Kaldır?", "@dialogUninstallExtension": { "description": "Dialog title - uninstall extension" }, - "dialogUninstallExtensionMessage": "{extensionName} eklentisini silmek istediğinize emin misiniz?", + "dialogUninstallExtensionMessage": "{extensionName} eklentisini kaldırmak istediğine emin misin?", "@dialogUninstallExtensionMessage": { "description": "Dialog message - uninstall specific extension", "placeholders": { @@ -728,15 +780,15 @@ "@dialogClearHistoryTitle": { "description": "Dialog title - clear download history" }, - "dialogClearHistoryMessage": "Tüm indirme geçmişinizi silmek istediğinize emin misiniz? Bu işlem geri alınamaz.", + "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çilenleri Sil", + "dialogDeleteSelectedTitle": "Seçileni Sil", "@dialogDeleteSelectedTitle": { "description": "Dialog title - delete selected items" }, - "dialogDeleteSelectedMessage": "Geçmişten {count} {count, plural, =1{şarkı} other{şarkı}} silinsin mi?\n\nBu işlem, indirilen dosyaları cihazınızdan da tamamen silecek.", + "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": { @@ -745,11 +797,11 @@ } } }, - "dialogImportPlaylistTitle": "Çalma Listesi İçe Aktar", + "dialogImportPlaylistTitle": "Çalma listesini içe aktar", "@dialogImportPlaylistTitle": { "description": "Dialog title - import CSV playlist" }, - "dialogImportPlaylistMessage": "CSV dosyasında {count} şarkı bulundu. İndirme sırasına eklensin mi?", + "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", @@ -767,7 +819,7 @@ } } }, - "snackbarAddedToQueue": "\"{trackName}\" indirme sırasına eklendi", + "snackbarAddedToQueue": "\"{trackName}\" kuyruğa eklendi", "@snackbarAddedToQueue": { "description": "Snackbar - track added to download queue", "placeholders": { @@ -776,7 +828,7 @@ } } }, - "snackbarAddedTracksToQueue": "{count} şarkı indirme sırasına eklendi", + "snackbarAddedTracksToQueue": "{count} şarkı kuyruğa eklendi", "@snackbarAddedTracksToQueue": { "description": "Snackbar - multiple tracks added to queue", "placeholders": { @@ -785,7 +837,7 @@ } } }, - "snackbarAlreadyDownloaded": "\"{trackName}\" zaten inmiş durumda", + "snackbarAlreadyDownloaded": "\"{trackName}\" zaten indirilmiş", "@snackbarAlreadyDownloaded": { "description": "Snackbar - track already exists", "placeholders": { @@ -807,15 +859,15 @@ "@snackbarHistoryCleared": { "description": "Snackbar - history deleted" }, - "snackbarCredentialsSaved": "API bilgileri kaydedildi", + "snackbarCredentialsSaved": "Kimlik bilgileri kaydedildi", "@snackbarCredentialsSaved": { "description": "Snackbar - Spotify credentials saved" }, - "snackbarCredentialsCleared": "API bilgileri silindi", + "snackbarCredentialsCleared": "Kimlik bilgileri temizlendi", "@snackbarCredentialsCleared": { "description": "Snackbar - Spotify credentials removed" }, - "snackbarDeletedTracks": "{count} {count, plural, =1{şarkı} other{şarkı}} silindi", + "snackbarDeletedTracks": "{count} {count, plural, one {}=1{şarkı} other{şarkı}} silindi", "@snackbarDeletedTracks": { "description": "Snackbar - tracks deleted", "placeholders": { @@ -824,7 +876,7 @@ } } }, - "snackbarCannotOpenFile": "Dosya açılamıyor: {error}", + "snackbarCannotOpenFile": "Dosya açılamadı: {error}", "@snackbarCannotOpenFile": { "description": "Snackbar - file open error", "placeholders": { @@ -837,11 +889,11 @@ "@snackbarFillAllFields": { "description": "Snackbar - validation error" }, - "snackbarViewQueue": "Sırayı Gör", + "snackbarViewQueue": "Kuyruğu Görüntüle", "@snackbarViewQueue": { "description": "Snackbar action - view download queue" }, - "snackbarUrlCopied": "{platform} bağlantısı panoya kopyalandı", + "snackbarUrlCopied": "{platform} Bağlantı panoya kopyalandı", "@snackbarUrlCopied": { "description": "Snackbar - URL copied", "placeholders": { @@ -855,7 +907,7 @@ "@snackbarFileNotFound": { "description": "Snackbar - file doesn't exist" }, - "snackbarSelectExtFile": "Lütfen bir .spotiflac-ext dosyası seçin", + "snackbarSelectExtFile": "Lütfen .spotiflac-ext dosyasını seçin", "@snackbarSelectExtFile": { "description": "Snackbar - wrong file type selected" }, @@ -863,11 +915,11 @@ "@snackbarProviderPrioritySaved": { "description": "Snackbar - provider order saved" }, - "snackbarMetadataProviderSaved": "Veri sağlayıcı önceliği kaydedildi", + "snackbarMetadataProviderSaved": "Meta veri sağlayıcı önceliği kaydedildi", "@snackbarMetadataProviderSaved": { "description": "Snackbar - metadata provider order saved" }, - "snackbarExtensionInstalled": "{extensionName} başarıyla yüklendi.", + "snackbarExtensionInstalled": "{extensionName} yüklendi.", "@snackbarExtensionInstalled": { "description": "Snackbar - extension installed successfully", "placeholders": { @@ -876,7 +928,7 @@ } } }, - "snackbarExtensionUpdated": "{extensionName} başarıyla güncellendi.", + "snackbarExtensionUpdated": "{extensionName} güncellendi.", "@snackbarExtensionUpdated": { "description": "Snackbar - extension updated successfully", "placeholders": { @@ -885,31 +937,31 @@ } } }, - "snackbarFailedToInstall": "Eklenti yüklenemedi", + "snackbarFailedToInstall": "Eklenti yüklenirken hata oluştu", "@snackbarFailedToInstall": { "description": "Snackbar - extension install error" }, - "snackbarFailedToUpdate": "Eklenti güncellenemedi", + "snackbarFailedToUpdate": "Eklenti güncellenirken hata oluştu", "@snackbarFailedToUpdate": { "description": "Snackbar - extension update error" }, - "errorRateLimited": "Hız Sınırına Takıldınız", + "errorRateLimited": "Aşırı istek gönderildi", "@errorRateLimited": { "description": "Error title - too many requests" }, - "errorRateLimitedMessage": "Çok fazla istek gönderdiniz. Yeniden arama yapmadan önce lütfen biraz bekleyin.", + "errorRateLimitedMessage": "Çok fazla istek. Lütfen arama yapmadan önce biraz bekleyin.", "@errorRateLimitedMessage": { "description": "Error message - rate limit explanation" }, - "errorNoTracksFound": "Şarkı bulunamadı", + "errorNoTracksFound": "Parça bulunamadı", "@errorNoTracksFound": { "description": "Error - search returned no results" }, - "errorUrlNotRecognized": "Bağlantı algılanamadı", + "errorUrlNotRecognized": "Bağlantı tanınamadı", "@errorUrlNotRecognized": { "description": "Error title - URL not handled by any extension or service" }, - "errorUrlNotRecognizedMessage": "Bu bağlantı desteklenmiyor. Bağlantının doğru olduğundan ve gerekli eklentinin yüklü olduğundan emin olun.", + "errorUrlNotRecognizedMessage": "Bu bağlantı desteklenmiyor. URL'nin doğru olduğundan ve uyumlu bir uzantının yüklü olduğundan emin olun.", "@errorUrlNotRecognizedMessage": { "description": "Error message - URL not recognized explanation" }, @@ -917,7 +969,7 @@ "@errorUrlFetchFailed": { "description": "Error message - generic URL fetch failure" }, - "errorMissingExtensionSource": "{item} yüklenemiyor: Eklenti kaynağı eksik", + "errorMissingExtensionSource": "{item} yüklenemedi: Eksik eklenti kaynağı", "@errorMissingExtensionSource": { "description": "Error - extension source not available", "placeholders": { @@ -930,11 +982,11 @@ "@actionPause": { "description": "Action button - pause download" }, - "actionResume": "Devam Et", + "actionResume": "Devam et", "@actionResume": { "description": "Action button - resume download" }, - "actionCancel": "İptal", + "actionCancel": "Vazgeç", "@actionCancel": { "description": "Action button - cancel operation" }, @@ -942,15 +994,15 @@ "@actionSelectAll": { "description": "Action button - select all items" }, - "actionDeselect": "Seçimi Kaldır", + "actionDeselect": "Seçimi kaldır", "@actionDeselect": { "description": "Action button - deselect all" }, - "actionRemoveCredentials": "API Bilgilerini Sil", + "actionRemoveCredentials": "Özellikleri kaldır", "@actionRemoveCredentials": { "description": "Action button - delete Spotify credentials" }, - "actionSaveCredentials": "API Bilgilerini Kaydet", + "actionSaveCredentials": "Özellikleri kaydet", "@actionSaveCredentials": { "description": "Action button - save Spotify credentials" }, @@ -963,15 +1015,15 @@ } } }, - "selectionAllSelected": "Tüm şarkılar seçildi", + "selectionAllSelected": "Tüm parçalar seçildi", "@selectionAllSelected": { "description": "Status - all items selected" }, - "selectionSelectToDelete": "Silinecek şarkıları seçin", + "selectionSelectToDelete": "Silinecek parçaları seçin", "@selectionSelectToDelete": { "description": "Placeholder when nothing selected" }, - "progressFetchingMetadata": "Veriler alınıyor... {current}/{total}", + "progressFetchingMetadata": "Meta verileri alınıyor... {current}/{total}", "@progressFetchingMetadata": { "description": "Progress indicator - loading track info", "placeholders": { @@ -983,7 +1035,7 @@ } } }, - "progressReadingCsv": "CSV dosyası okunuyor...", + "progressReadingCsv": "CSV okunuyor...", "@progressReadingCsv": { "description": "Progress indicator - parsing CSV file" }, @@ -1011,11 +1063,11 @@ "@searchSortDefault": { "description": "Sort option - default API order" }, - "searchSortTitleAZ": "Şarkı Adı (A-Z)", + "searchSortTitleAZ": "Başlık (A-Z)", "@searchSortTitleAZ": { "description": "Sort option - title ascending" }, - "searchSortTitleZA": "Şarkı Adı (Z-A)", + "searchSortTitleZA": "Başlık (Z-A)", "@searchSortTitleZA": { "description": "Sort option - title descending" }, @@ -1027,19 +1079,19 @@ "@searchSortArtistZA": { "description": "Sort option - artist descending" }, - "searchSortDurationShort": "Süre (Önce kısalar)", + "searchSortDurationShort": "Süre (en kısa)", "@searchSortDurationShort": { "description": "Sort option - shortest duration first" }, - "searchSortDurationLong": "Süre (Önce uzunlar)", + "searchSortDurationLong": "Süre (en uzun)", "@searchSortDurationLong": { "description": "Sort option - longest duration first" }, - "searchSortDateOldest": "Çıkış Tarihi (Önce eskiler)", + "searchSortDateOldest": "Yayın Tarihi (En eski)", "@searchSortDateOldest": { "description": "Sort option - oldest release first" }, - "searchSortDateNewest": "Çıkış Tarihi (Önce yeniler)", + "searchSortDateNewest": "Yayın Tarihi (En yeni)", "@searchSortDateNewest": { "description": "Sort option - newest release first" }, @@ -1047,7 +1099,7 @@ "@tooltipPlay": { "description": "Tooltip - play button" }, - "filenameFormat": "Dosya Adı Formatı", + "filenameFormat": "Dosya adı formatı", "@filenameFormat": { "description": "Setting title - filename pattern" }, @@ -1055,11 +1107,11 @@ "@filenameShowAdvancedTags": { "description": "Toggle label for showing advanced filename tags" }, - "filenameShowAdvancedTagsDescription": "Boşluk doldurma ve tarih formatları için gelişmiş dosya adı etiketlerini açar", + "filenameShowAdvancedTagsDescription": "Parça numarası tamamlama ve tarih desenleri için biçimlendirilmiş etiketleri etkinleştir", "@filenameShowAdvancedTagsDescription": { "description": "Description for advanced filename tag toggle" }, - "folderOrganizationNone": "Düzen yok", + "folderOrganizationNone": "Organizasyon yok", "@folderOrganizationNone": { "description": "Folder option - flat structure" }, @@ -1067,7 +1119,7 @@ "@folderOrganizationByPlaylist": { "description": "Folder option - playlist folders" }, - "folderOrganizationByPlaylistSubtitle": "Her çalma listesi için ayrı bir klasör oluşturur", + "folderOrganizationByPlaylistSubtitle": "Her çalma listesi için ayrı klasör", "@folderOrganizationByPlaylistSubtitle": { "description": "Subtitle for playlist folder option" }, @@ -1079,31 +1131,31 @@ "@folderOrganizationByAlbum": { "description": "Folder option - album folders" }, - "folderOrganizationByArtistAlbum": "Sanatçı / Albüm", + "folderOrganizationByArtistAlbum": "Sanatçı/Albüm", "@folderOrganizationByArtistAlbum": { "description": "Folder option - nested folders" }, - "folderOrganizationDescription": "İndirilen dosyaları klasörlere düzenler", + "folderOrganizationDescription": "İndirilenleri klasörlerle organize et", "@folderOrganizationDescription": { "description": "Folder organization sheet description" }, - "folderOrganizationNoneSubtitle": "Tüm dosyalar tek bir klasöre atılır", + "folderOrganizationNoneSubtitle": "Her şey indirilen dosyasına kaydedilecek", "@folderOrganizationNoneSubtitle": { "description": "Subtitle for no organization option" }, - "folderOrganizationByArtistSubtitle": "Her sanatçı için ayrı klasör oluşturur", + "folderOrganizationByArtistSubtitle": "Her sanatçı için ayrı klasör", "@folderOrganizationByArtistSubtitle": { "description": "Subtitle for artist folder option" }, - "folderOrganizationByAlbumSubtitle": "Her albüm için ayrı klasör oluşturur", + "folderOrganizationByAlbumSubtitle": "Her albüm için ayrı klasör", "@folderOrganizationByAlbumSubtitle": { "description": "Subtitle for album folder option" }, - "folderOrganizationByArtistAlbumSubtitle": "İç içe Sanatçı ve Albüm klasörleri oluşturur", + "folderOrganizationByArtistAlbumSubtitle": "Sanatçı klasörlerinin içinde Albüm klasörleri", "@folderOrganizationByArtistAlbumSubtitle": { "description": "Subtitle for nested folder option" }, - "updateAvailable": "Güncelleme Var", + "updateAvailable": "Güncelleme Mevcut", "@updateAvailable": { "description": "Update dialog title" }, @@ -1111,7 +1163,7 @@ "@updateLater": { "description": "Update button - dismiss" }, - "updateStartingDownload": "İndirme başlatılıyor...", + "updateStartingDownload": "İndirme başlıyor...", "@updateStartingDownload": { "description": "Update status - initializing" }, @@ -1119,15 +1171,15 @@ "@updateDownloadFailed": { "description": "Update error title" }, - "updateFailedMessage": "Güncelleme dosyası indirilemedi", + "updateFailedMessage": "Güncelleme indirilemedi", "@updateFailedMessage": { "description": "Update error message" }, - "updateNewVersionReady": "Uygulamanın yeni bir sürümü hazır", + "updateNewVersionReady": "Yeni bir sürüm hazır", "@updateNewVersionReady": { "description": "Update subtitle" }, - "updateCurrent": "Mevcut", + "updateCurrent": "Şimdiki", "@updateCurrent": { "description": "Label for current version" }, @@ -1139,31 +1191,43 @@ "@updateDownloading": { "description": "Update status - downloading" }, - "updateWhatsNew": "Neler Yeni?", + "updateWhatsNew": "Yenilikler", "@updateWhatsNew": { "description": "Changelog section title" }, - "updateDownloadInstall": "İndir ve Yükle", + "updateDownloadInstall": "İndir & Yükle", "@updateDownloadInstall": { "description": "Update button - download and install" }, - "updateDontRemind": "Bir daha hatırlatma", + "updateDontRemind": "Bir daha sorma", "@updateDontRemind": { "description": "Update button - skip this version" }, - "providerPriorityTitle": "Sağlayıcı Önceliği", + "providerPriorityTitle": "İndirme hizmetleri öncelik sırası", "@providerPriorityTitle": { "description": "Provider priority page title" }, - "providerPriorityDescription": "İndirme sağlayıcılarını sürükleyip sıralayın. Uygulama, şarkıları indirirken sağlayıcıları yukarıdan aşağıya doğru dener.", + "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": "Bir şarkı ilk sağlayıcıda bulunamazsa, uygulama otomatik olarak listedeki bir sonrakini dener.", + "providerPriorityInfo": "Eğer bir şarkı ilk hizmette mevcut değilse uygulama otomatik olarak bir sonrakini deneyecektir.", "@providerPriorityInfo": { "description": "Info tip about fallback behavior" }, - "providerBuiltIn": "Yerleşik", + "providerPriorityFallbackExtensionsTitle": "Uzantı Yedeği", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Otomatik yedekleme sırasında hangi yüklü indirme uzantılarının kullanılabileceğini seçin. Yerleşik sağlayıcılar hâlâ yukarıdaki öncelik sırasını takip eder.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Burada yalnızca indirme sağlayıcısı yeteneğine sahip olan ve etkinleştirilmiş uzantılar listelenir.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, + "providerBuiltIn": "Dahili", "@providerBuiltIn": { "description": "Label for built-in providers (Tidal/Qobuz)" }, @@ -1171,27 +1235,27 @@ "@providerExtension": { "description": "Label for extension-provided providers" }, - "metadataProviderPriorityTitle": "Arama Kaynağı Önceliği", + "metadataProviderPriorityTitle": "Meta Veri Önceliği", "@metadataProviderPriorityTitle": { "description": "Metadata priority page title" }, - "metadataProviderPriorityDescription": "Arama kaynaklarını sürükleyip sıralayın. Uygulama, şarkı ararken ve veri çekerken kaynakları yukarıdan aşağıya doğru dener.", + "metadataProviderPriorityDescription": "Meta veri sağlayıcılarını yeniden sıralamak için sürükleyin. Uygulama, parça ararken ve meta verileri alırken sağlayıcıları yukarıdan aşağıya doğru deneyecektir.", "@metadataProviderPriorityDescription": { "description": "Metadata priority page description" }, - "metadataProviderPriorityInfo": "Deezer'da hız sınırı yoktur, bu yüzden ana kaynak olarak kullanılması tavsiye edilir. Spotify, çok fazla istekte bulunduğunuzda kısıtlama yapabilir.", + "metadataProviderPriorityInfo": "Deezer'da istek sınırı yoktur ve birincil olarak önerilir. Spotify, çok sayıda istekten sonra hız sınırlaması uygulayabilir.", "@metadataProviderPriorityInfo": { "description": "Info tip about rate limits" }, - "metadataNoRateLimits": "Hız sınırı yok", + "metadataNoRateLimits": "İstek sınırı yok", "@metadataNoRateLimits": { "description": "Deezer provider description" }, - "metadataMayRateLimit": "Hız sınırı yapabilir", + "metadataMayRateLimit": "Hız sınırlaması uygulanabilir", "@metadataMayRateLimit": { "description": "Spotify provider description" }, - "logTitle": "Hata Kayıtları (Log)", + "logTitle": "Kayıtlar", "@logTitle": { "description": "Logs screen title" }, @@ -1199,7 +1263,7 @@ "@logCopied": { "description": "Snackbar - logs copied" }, - "logSearchHint": "Kayıtlarda ara...", + "logSearchHint": "Kayıtları Ara...", "@logSearchHint": { "description": "Log search placeholder" }, @@ -1207,7 +1271,7 @@ "@logFilterLevel": { "description": "Filter by log level" }, - "logFilterSection": "Filtrele", + "logFilterSection": "Filtre", "@logFilterSection": { "description": "Filter section title" }, @@ -1219,15 +1283,15 @@ "@logClearLogs": { "description": "Clear button tooltip" }, - "logClearLogsTitle": "Kayıtları Temizle", + "logClearLogsTitle": "Kayıtları temizle", "@logClearLogsTitle": { "description": "Clear logs dialog title" }, - "logClearLogsMessage": "Tüm hata kayıtlarını silmek istediğinize emin misiniz?", + "logClearLogsMessage": "Tüm kayıtları temizlemek istediğinize emin misiniz?", "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, - "logFilterBySeverity": "Önem derecesine göre filtrele", + "logFilterBySeverity": "Günlükleri önem derecesine göre filtrele", "@logFilterBySeverity": { "description": "Filter dialog title" }, @@ -1235,7 +1299,7 @@ "@logNoLogsYet": { "description": "Empty state title" }, - "logNoLogsYetSubtitle": "Uygulamayı kullandıkça teknik kayıtlar burada görünecek", + "logNoLogsYetSubtitle": "Uygulamayı kullandıkça günlükler burada görünecektir", "@logNoLogsYetSubtitle": { "description": "Empty state subtitle" }, @@ -1257,15 +1321,15 @@ } } }, - "credentialsTitle": "Spotify API Bilgileri", + "credentialsTitle": "Spotify Kimlik Bilgileri", "@credentialsTitle": { "description": "Credentials dialog title" }, - "credentialsDescription": "Kendi Spotify uygulamanızın kota limitlerini kullanmak için İstemci Kimliği (Client ID) ve Gizli Anahtarınızı (Client Secret) girin.", + "credentialsDescription": "Kendi Spotify uygulama kotanızı kullanmak için Client ID ve Secret girin.", "@credentialsDescription": { "description": "Credentials dialog explanation" }, - "credentialsClientId": "Client ID (İstemci Kimliği)", + "credentialsClientId": "Client ID", "@credentialsClientId": { "description": "Client ID field label - DO NOT TRANSLATE" }, @@ -1273,7 +1337,7 @@ "@credentialsClientIdHint": { "description": "Client ID placeholder" }, - "credentialsClientSecret": "Client Secret (Gizli Anahtar)", + "credentialsClientSecret": "Client Secret", "@credentialsClientSecret": { "description": "Client Secret field label - DO NOT TRANSLATE" }, @@ -1285,7 +1349,7 @@ "@channelStable": { "description": "Update channel - stable releases" }, - "channelPreview": "Beta (Ön İzleme)", + "channelPreview": "Önizleme", "@channelPreview": { "description": "Update channel - beta/preview releases" }, @@ -1293,7 +1357,7 @@ "@sectionSearchSource": { "description": "Settings section header" }, - "sectionDownload": "İndirme", + "sectionDownload": "İndir", "@sectionDownload": { "description": "Settings section header" }, @@ -1305,15 +1369,15 @@ "@sectionApp": { "description": "Settings section header" }, - "sectionData": "Veri Yönetimi", + "sectionData": "Veri", "@sectionData": { "description": "Settings section header" }, - "sectionDebug": "Hata Ayıklama", + "sectionDebug": "Hata ayıklama", "@sectionDebug": { "description": "Settings section header" }, - "sectionService": "Servisler", + "sectionService": "Servis", "@sectionService": { "description": "Settings section header" }, @@ -1325,15 +1389,15 @@ "@sectionFileSettings": { "description": "Settings section header" }, - "sectionLyrics": "Şarkı Sözleri", + "sectionLyrics": "Şarkı sözleri", "@sectionLyrics": { "description": "Settings section header" }, - "lyricsMode": "Şarkı Sözü Formatı", + "lyricsMode": "Şarkı Sözü Modu", "@lyricsMode": { "description": "Setting - how to save lyrics" }, - "lyricsModeDescription": "Şarkı sözlerinin nasıl kaydedileceğini seçin", + "lyricsModeDescription": "Şarkı sözlerinin indirmelerinizle birlikte nasıl kaydedileceğini seçin", "@lyricsModeDescription": { "description": "Lyrics mode picker description" }, @@ -1341,7 +1405,7 @@ "@lyricsModeEmbed": { "description": "Lyrics mode option - embed in audio file" }, - "lyricsModeEmbedSubtitle": "Şarkı sözleri FLAC dosyasının içine işlenir", + "lyricsModeEmbedSubtitle": "Şarkı sözleri FLAC meta verilerinin içinde saklanır", "@lyricsModeEmbedSubtitle": { "description": "Subtitle for embed option" }, @@ -1349,7 +1413,7 @@ "@lyricsModeExternal": { "description": "Lyrics mode option - separate LRC file" }, - "lyricsModeExternalSubtitle": "Bazı müzik çalarlar için şarkının yanına ayrı bir .lrc dosyası açar", + "lyricsModeExternalSubtitle": "Samsung Music gibi oynatıcılar için ayrı .lrc dosyası", "@lyricsModeExternalSubtitle": { "description": "Subtitle for external option" }, @@ -1357,11 +1421,11 @@ "@lyricsModeBoth": { "description": "Lyrics mode option - embed and external" }, - "lyricsModeBothSubtitle": "Hem dosyaya gömer hem de .lrc dosyası olarak kaydeder", + "lyricsModeBothSubtitle": "Hem göm hem de .lrc dosyası olarak kaydet", "@lyricsModeBothSubtitle": { "description": "Subtitle for both option" }, - "sectionColor": "Renkler", + "sectionColor": "Renk", "@sectionColor": { "description": "Settings section header" }, @@ -1369,7 +1433,7 @@ "@sectionTheme": { "description": "Settings section header" }, - "sectionLayout": "Tasarım", + "sectionLayout": "Düzen", "@sectionLayout": { "description": "Settings section header" }, @@ -1381,23 +1445,23 @@ "@appearanceLanguage": { "description": "Language setting title" }, - "settingsAppearanceSubtitle": "Temalar, renkler, görünümler", + "settingsAppearanceSubtitle": "Tema, renkler, görünüm", "@settingsAppearanceSubtitle": { "description": "Appearance settings description" }, - "settingsDownloadSubtitle": "İndirme servisi, ses kalitesi, dosya adı düzeni", + "settingsDownloadSubtitle": "Servis, kalite, dosya adı formatı", "@settingsDownloadSubtitle": { "description": "Download settings description" }, - "settingsOptionsSubtitle": "İndirme limitleri, şarkı sözleri, güncellemeler", + "settingsOptionsSubtitle": "Yedekleme, sözler, kapak resmi, güncellemeler", "@settingsOptionsSubtitle": { "description": "Options settings description" }, - "settingsExtensionsSubtitle": "Yeni müzik kaynakları ve eklentileri yönetin", + "settingsExtensionsSubtitle": "İndirme sağlayıcılarını yönet", "@settingsExtensionsSubtitle": { "description": "Extensions settings description" }, - "settingsLogsSubtitle": "Sorun tespiti için uygulama kayıtlarına göz atın", + "settingsLogsSubtitle": "Hata ayıklama için uygulama günlüklerini görüntüle", "@settingsLogsSubtitle": { "description": "Logs settings description" }, @@ -1405,7 +1469,7 @@ "@loadingSharedLink": { "description": "Status when opening shared URL" }, - "pressBackAgainToExit": "Çıkmak için tekrar geri dokunun", + "pressBackAgainToExit": "Çıkmak için tekrar geri basın", "@pressBackAgainToExit": { "description": "Exit confirmation message" }, @@ -1418,7 +1482,7 @@ } } }, - "tracksCount": "{count, plural, =1{1 şarkı} other{{count} şarkı}}", + "tracksCount": "{count, plural, one {}=1{1 parça} other{{count} parça}}", "@tracksCount": { "description": "Track count display", "placeholders": { @@ -1431,15 +1495,15 @@ "@trackCopyFilePath": { "description": "Action - copy file path" }, - "trackRemoveFromDevice": "Cihazdan sil", + "trackRemoveFromDevice": "Cihazdan kaldır", "@trackRemoveFromDevice": { "description": "Action - delete downloaded file" }, - "trackLoadLyrics": "Sözleri Yükle", + "trackLoadLyrics": "Şarkı Sözlerini Yükle", "@trackLoadLyrics": { "description": "Action - fetch lyrics" }, - "trackMetadata": "Şarkı Künyesi", + "trackMetadata": "Meta Veri", "@trackMetadata": { "description": "Tab title - track metadata" }, @@ -1447,7 +1511,7 @@ "@trackFileInfo": { "description": "Tab title - file information" }, - "trackLyrics": "Sözler", + "trackLyrics": "Şarkı Sözleri", "@trackLyrics": { "description": "Tab title - lyrics" }, @@ -1455,15 +1519,15 @@ "@trackFileNotFound": { "description": "Error - file doesn't exist" }, - "trackOpenInDeezer": "Deezer'da Aç", + "trackOpenInDeezer": "Deezer'da aç", "@trackOpenInDeezer": { "description": "Action - open track in Deezer app" }, - "trackOpenInSpotify": "Spotify'da Aç", + "trackOpenInSpotify": "Spotify'da aç", "@trackOpenInSpotify": { "description": "Action - open track in Spotify app" }, - "trackTrackName": "Şarkı Adı", + "trackTrackName": "Parça adı", "@trackTrackName": { "description": "Metadata label - track title" }, @@ -1471,7 +1535,7 @@ "@trackArtist": { "description": "Metadata label - artist name" }, - "trackAlbumArtist": "Albüm Sanatçısı", + "trackAlbumArtist": "Albüm sanatçısı", "@trackAlbumArtist": { "description": "Metadata label - album artist" }, @@ -1495,7 +1559,7 @@ "@trackAudioQuality": { "description": "Metadata label - audio quality" }, - "trackReleaseDate": "Çıkış tarihi", + "trackReleaseDate": "Yayın tarihi", "@trackReleaseDate": { "description": "Metadata label - release date" }, @@ -1503,7 +1567,7 @@ "@trackGenre": { "description": "Metadata label - music genre" }, - "trackLabel": "Plak Şirketi", + "trackLabel": "Etiket / Müzik Şirketi", "@trackLabel": { "description": "Metadata label - record label" }, @@ -1511,19 +1575,27 @@ "@trackCopyright": { "description": "Metadata label - copyright information" }, - "trackDownloaded": "İndirilme tarihi", + "trackDownloaded": "İndirildi", "@trackDownloaded": { "description": "Metadata label - download date" }, - "trackCopyLyrics": "Sözleri kopyala", + "trackCopyLyrics": "Şarkı sözlerini kopyala", "@trackCopyLyrics": { "description": "Action - copy lyrics to clipboard" }, - "trackLyricsNotAvailable": "Bu şarkının sözleri bulunamadı", + "trackLyricsNotAvailable": "Bu parça için şarkı sözü mevcut değil", "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, - "trackLyricsTimeout": "Zaman aşımına uğradı. Lütfen daha sonra tekrar deneyin.", + "trackLyricsNotInFile": "Bu dosyada şarkı sözü bulunamadı", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "İnternetten Getir", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, + "trackLyricsTimeout": "İstek zaman aşımına uğradı. Daha sonra tekrar deneyin.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" }, @@ -1531,15 +1603,15 @@ "@trackLyricsLoadFailed": { "description": "Message when lyrics loading fails" }, - "trackEmbedLyrics": "Şarkı Sözlerini Gömer", + "trackEmbedLyrics": "Şarkı Sözlerini Göm", "@trackEmbedLyrics": { "description": "Action - embed lyrics into audio file" }, - "trackLyricsEmbedded": "Şarkı sözleri dosyaya başarıyla eklendi", + "trackLyricsEmbedded": "Şarkı sözleri başarıyla gömüldü", "@trackLyricsEmbedded": { "description": "Snackbar - lyrics saved to file" }, - "trackInstrumental": "Enstrümantal parça (Sözsüz)", + "trackInstrumental": "Enstrümantal parça", "@trackInstrumental": { "description": "Message when track is instrumental (no lyrics)" }, @@ -1547,11 +1619,11 @@ "@trackCopiedToClipboard": { "description": "Snackbar - content copied" }, - "trackDeleteConfirmTitle": "Cihazdan silinsin mi?", + "trackDeleteConfirmTitle": "Cihazdan kaldırılsın mı?", "@trackDeleteConfirmTitle": { "description": "Delete confirmation title" }, - "trackDeleteConfirmMessage": "Bu işlem indirdiğiniz dosyayı tamamen silecek ve geçmişinizden kaldıracak.", + "trackDeleteConfirmMessage": "Bu işlem, indirilen dosyayı kalıcı olarak silecek ve geçmişinizden kaldıracaktır.", "@trackDeleteConfirmMessage": { "description": "Delete confirmation message" }, @@ -1594,19 +1666,19 @@ "@storeFilterAll": { "description": "Store filter - all extensions" }, - "storeFilterMetadata": "Şarkı Verisi", + "storeFilterMetadata": "Meta Veri", "@storeFilterMetadata": { "description": "Store filter - metadata providers" }, - "storeFilterDownload": "İndirme", + "storeFilterDownload": "İndir", "@storeFilterDownload": { "description": "Store filter - download providers" }, - "storeFilterUtility": "Araçlar", + "storeFilterUtility": "Araç", "@storeFilterUtility": { "description": "Store filter - utility extensions" }, - "storeFilterLyrics": "Şarkı Sözü", + "storeFilterLyrics": "Şarkı Sözleri", "@storeFilterLyrics": { "description": "Store filter - lyrics providers" }, @@ -1618,27 +1690,27 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "storeAddRepoTitle": "Eklenti Deposu (Repository) Ekle", + "storeAddRepoTitle": "Uzantı Deposu Ekle", "@storeAddRepoTitle": { "description": "Store setup screen - heading when no repo is configured" }, - "storeAddRepoDescription": "Eklentilere göz atmak ve yüklemek için içinde registry.json dosyası olan bir GitHub depo bağlantısı girin.", + "storeAddRepoDescription": "Uzantılara göz atmak ve yüklemek için registry.json dosyası içeren bir GitHub depo URL'si girin.", "@storeAddRepoDescription": { "description": "Store setup screen - explanatory text" }, - "storeRepoUrlLabel": "Depo Bağlantısı (URL)", + "storeRepoUrlLabel": "Depo URL'si", "@storeRepoUrlLabel": { "description": "Label for the repository URL input field" }, - "storeRepoUrlHint": "https://github.com/kullaniciadi/depo", + "storeRepoUrlHint": "https://github.com/user/repo", "@storeRepoUrlHint": { "description": "Hint/placeholder for the repository URL input field" }, - "storeRepoUrlHelper": "Örn: https://github.com/spoti/extensions-repo", + "storeRepoUrlHelper": "örn. https://github.com/user/extensions-repo", "@storeRepoUrlHelper": { "description": "Helper text below the repository URL input field" }, - "storeAddRepoButton": "Depoyu Ekle", + "storeAddRepoButton": "Depo Ekle", "@storeAddRepoButton": { "description": "Button to submit a new repository URL" }, @@ -1646,7 +1718,7 @@ "@storeChangeRepoTooltip": { "description": "Tooltip for the change-repository icon button in the app bar" }, - "storeRepoDialogTitle": "Eklenti Deposu", + "storeRepoDialogTitle": "Uzantı Deposu", "@storeRepoDialogTitle": { "description": "Title of the change/remove repository dialog" }, @@ -1654,19 +1726,19 @@ "@storeRepoDialogCurrent": { "description": "Label shown above the current repository URL in the dialog" }, - "storeNewRepoUrlLabel": "Yeni Depo Bağlantısı", + "storeNewRepoUrlLabel": "Yeni Depo URL'si", "@storeNewRepoUrlLabel": { "description": "Label for the new repository URL field inside the dialog" }, - "storeLoadError": "Mağaza yüklenemedi", + "storeLoadError": "Depo yüklenemedi", "@storeLoadError": { "description": "Error heading when the store cannot be loaded" }, - "storeEmptyNoExtensions": "Kullanılabilir eklenti yok", + "storeEmptyNoExtensions": "Uygun uzantı yok", "@storeEmptyNoExtensions": { "description": "Message when store has no extensions" }, - "storeEmptyNoResults": "Aramanıza uygun eklenti bulunamadı", + "storeEmptyNoResults": "Uzantı bulunamadı", "@storeEmptyNoResults": { "description": "Message when search/filter returns no results" }, @@ -1674,15 +1746,15 @@ "@extensionDefaultProvider": { "description": "Default search provider option" }, - "extensionDefaultProviderSubtitle": "Uygulamanın kendi aramasını kullan", + "extensionDefaultProviderSubtitle": "Yerleşik aramayı kullan", "@extensionDefaultProviderSubtitle": { "description": "Subtitle for default provider" }, - "extensionAuthor": "Geliştirici", + "extensionAuthor": "Oluşturan", "@extensionAuthor": { "description": "Extension detail - author" }, - "extensionId": "Kimlik (ID)", + "extensionId": "ID", "@extensionId": { "description": "Extension detail - unique ID" }, @@ -1690,23 +1762,23 @@ "@extensionError": { "description": "Extension detail - error message" }, - "extensionCapabilities": "Yetenekler", + "extensionCapabilities": "Özellikler", "@extensionCapabilities": { "description": "Section header - extension features" }, - "extensionMetadataProvider": "Şarkı Verisi (Metadata) Kaynağı", + "extensionMetadataProvider": "Meta Veri Sağlayıcı", "@extensionMetadataProvider": { "description": "Capability - provides metadata" }, - "extensionDownloadProvider": "İndirme Sağlayıcısı", + "extensionDownloadProvider": "İndirme Sağlayıcı", "@extensionDownloadProvider": { "description": "Capability - provides downloads" }, - "extensionLyricsProvider": "Şarkı Sözü Sağlayıcısı", + "extensionLyricsProvider": "Şarkı Sözü Sağlayıcı", "@extensionLyricsProvider": { "description": "Capability - provides lyrics" }, - "extensionUrlHandler": "Bağlantı Okuyucu", + "extensionUrlHandler": "URL İşleyici", "@extensionUrlHandler": { "description": "Capability - handles URLs" }, @@ -1714,7 +1786,7 @@ "@extensionQualityOptions": { "description": "Capability - quality selection" }, - "extensionPostProcessingHooks": "İndirme Sonrası İşlemler", + "extensionPostProcessingHooks": "Son İşlem Kancaları", "@extensionPostProcessingHooks": { "description": "Capability - post-processing" }, @@ -1726,11 +1798,11 @@ "@extensionSettings": { "description": "Section header - extension settings" }, - "extensionRemoveButton": "Eklentiyi Kaldır", + "extensionRemoveButton": "Uzantıyı Kaldır", "@extensionRemoveButton": { "description": "Button to uninstall extension" }, - "extensionUpdated": "Son Güncelleme", + "extensionUpdated": "Güncellendi", "@extensionUpdated": { "description": "Extension detail - last update" }, @@ -1738,15 +1810,15 @@ "@extensionMinAppVersion": { "description": "Extension detail - minimum app version" }, - "extensionCustomTrackMatching": "Özel Eşleştirme Algoritması", + "extensionCustomTrackMatching": "Özel Parça Eşleştirme", "@extensionCustomTrackMatching": { "description": "Capability - custom track matching algorithm" }, - "extensionPostProcessing": "İşlem Sonrası Özellikleri", + "extensionPostProcessing": "Son İşlem", "@extensionPostProcessing": { "description": "Capability - post-download processing" }, - "extensionHooksAvailable": "{count} özel kanca (hook) mevcut", + "extensionHooksAvailable": "{count} kanca kullanılabilir", "@extensionHooksAvailable": { "description": "Post-processing hooks count", "placeholders": { @@ -1755,7 +1827,7 @@ } } }, - "extensionPatternsCount": "{count} bağlantı kalıbı", + "extensionPatternsCount": "{count} desen", "@extensionPatternsCount": { "description": "URL patterns count", "placeholders": { @@ -1764,7 +1836,7 @@ } } }, - "extensionStrategy": "Eşleştirme Stratejisi: {strategy}", + "extensionStrategy": "Strateji: {strategy}", "@extensionStrategy": { "description": "Track matching strategy name", "placeholders": { @@ -1777,27 +1849,27 @@ "@extensionsProviderPrioritySection": { "description": "Section header - provider priority" }, - "extensionsInstalledSection": "Yüklü Eklentiler", + "extensionsInstalledSection": "Kurulu uzantılar", "@extensionsInstalledSection": { "description": "Section header - installed extensions" }, - "extensionsNoExtensions": "Henüz eklenti yüklenmemiş", + "extensionsNoExtensions": "Hiçbir eklenti kurulmamış", "@extensionsNoExtensions": { "description": "Empty state - no extensions" }, - "extensionsNoExtensionsSubtitle": "Yeni müzik kaynakları eklemek için .spotiflac-ext dosyalarını yükleyin", + "extensionsNoExtensionsSubtitle": "Yeni sağlayıcılar eklemek için .spotiflac-ext dosyalarını yükleyin", "@extensionsNoExtensionsSubtitle": { "description": "Empty state subtitle" }, - "extensionsInstallButton": "Eklenti Yükle", + "extensionsInstallButton": "Uzantı Yükle", "@extensionsInstallButton": { "description": "Button to install extension from file" }, - "extensionsInfoTip": "Eklentiler yeni veri ve indirme kaynakları ekleyebilir. Lütfen eklentileri sadece güvendiğiniz kaynaklardan yükleyin.", + "extensionsInfoTip": "Uzantılar yeni meta veri ve indirme sağlayıcıları ekleyebilir. Yalnızca güvenilir kaynaklardan gelen uzantıları yükleyin.", "@extensionsInfoTip": { "description": "Security warning about extensions" }, - "extensionsInstalledSuccess": "Eklenti başarıyla yüklendi", + "extensionsInstalledSuccess": "Uzantı başarıyla yüklendi", "@extensionsInstalledSuccess": { "description": "Success message after install" }, @@ -1805,35 +1877,43 @@ "@extensionsDownloadPriority": { "description": "Setting - download provider order" }, - "extensionsDownloadPrioritySubtitle": "İndirme servislerinin deneneceği sırayı belirleyin", + "extensionsDownloadPrioritySubtitle": "İndirme servisi sırasını ayarla", "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, - "extensionsNoDownloadProvider": "İndirme sağlayıcısı barındıran bir eklenti yok", + "extensionsFallbackTitle": "Yedekleme Uzantıları", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Hangi yüklü indirme uzantılarının yedekleme olarak kullanılabileceğini seçin", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, + "extensionsNoDownloadProvider": "İndirme sağlayıcısı olan uzantı yok", "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" }, - "extensionsMetadataPriority": "Arama Kaynağı Önceliği", + "extensionsMetadataPriority": "Meta Veri Önceliği", "@extensionsMetadataPriority": { "description": "Setting - metadata provider order" }, - "extensionsMetadataPrioritySubtitle": "Arama ve veri kaynaklarının sırasını belirleyin", + "extensionsMetadataPrioritySubtitle": "Arama ve meta veri kaynağı sırasını ayarla", "@extensionsMetadataPrioritySubtitle": { "description": "Subtitle for metadata priority" }, - "extensionsNoMetadataProvider": "Şarkı verisi (metadata) barındıran bir eklenti yok", + "extensionsNoMetadataProvider": "Meta veri sağlayıcısı içeren uzantı bulunamadı", "@extensionsNoMetadataProvider": { "description": "Empty state - no metadata providers" }, - "extensionsSearchProvider": "Arama Servisi", + "extensionsSearchProvider": "Arama Sağlayıcısı", "@extensionsSearchProvider": { "description": "Setting - search provider selection" }, - "extensionsNoCustomSearch": "Özel arama özelliği olan bir eklenti yok", + "extensionsNoCustomSearch": "Özel arama içeren uzantı bulunamadı", "@extensionsNoCustomSearch": { "description": "Empty state - no search providers" }, - "extensionsSearchProviderDescription": "Şarkı aramak için kullanılacak servisi seçin", + "extensionsSearchProviderDescription": "Parça aramak için hangi servisin kullanılacağını seçin", "@extensionsSearchProviderDescription": { "description": "Search provider setting description" }, @@ -1841,7 +1921,7 @@ "@extensionsCustomSearch": { "description": "Label for custom search provider" }, - "extensionsErrorLoading": "Eklenti yüklenirken hata oluştu", + "extensionsErrorLoading": "Uzantı yüklenirken hata oluştu", "@extensionsErrorLoading": { "description": "Error message when extension fails to load" }, @@ -1853,7 +1933,7 @@ "@qualityFlacLosslessSubtitle": { "description": "Technical spec for lossless" }, - "qualityHiResFlac": "Hi-Res FLAC (Yüksek Çözünürlüklü)", + "qualityHiResFlac": "Hi-Res FLAC", "@qualityHiResFlac": { "description": "Quality option - high resolution FLAC" }, @@ -1861,7 +1941,7 @@ "@qualityHiResFlacSubtitle": { "description": "Technical spec for hi-res" }, - "qualityHiResFlacMax": "Hi-Res FLAC Maksimum", + "qualityHiResFlacMax": "Hi-Res FLAC Max", "@qualityHiResFlacMax": { "description": "Quality option - maximum resolution FLAC" }, @@ -1869,19 +1949,19 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, - "downloadLossy320": "Kayıplı (Lossy) 320kbps", + "downloadLossy320": "Kayıplı 320kbps", "@downloadLossy320": { "description": "Quality option label for Tidal lossy 320kbps" }, - "downloadLossyFormat": "Kayıplı Formatı", + "downloadLossyFormat": "Kayıplı Format", "@downloadLossyFormat": { "description": "Setting title to pick output format for Tidal lossy downloads" }, - "downloadLossy320Format": "Kayıplı (Lossy) 320kbps Formatı", + "downloadLossy320Format": "Kayıplı 320kbps Formatı", "@downloadLossy320Format": { "description": "Title of the Tidal lossy format picker bottom sheet" }, - "downloadLossy320FormatDesc": "Tidal'dan 320kbps kalitesinde indirirken kullanılacak formatı seçin. Orijinal AAC yayını seçtiğiniz formata dönüştürülecektir.", + "downloadLossy320FormatDesc": "Tidal 320kbps kayıplı indirmeler için çıktı formatını seçin. Orijinal AAC akışı seçtiğiniz formata dönüştürülecektir.", "@downloadLossy320FormatDesc": { "description": "Description in the Tidal lossy format picker" }, @@ -1889,7 +1969,7 @@ "@downloadLossyMp3": { "description": "Tidal lossy format option - MP3 320kbps" }, - "downloadLossyMp3Subtitle": "En iyi uyumluluk, şarkı başı ~10MB", + "downloadLossyMp3Subtitle": "En iyi uyumluluk, parça başına ~10 Mb", "@downloadLossyMp3Subtitle": { "description": "Subtitle for MP3 320kbps Tidal lossy option" }, @@ -1897,7 +1977,7 @@ "@downloadLossyOpus256": { "description": "Tidal lossy format option - Opus 256kbps" }, - "downloadLossyOpus256Subtitle": "En iyi Opus kalitesi, şarkı başı ~8MB", + "downloadLossyOpus256Subtitle": "En iyi Opus kalitesi, parça başına ~8 Mb", "@downloadLossyOpus256Subtitle": { "description": "Subtitle for Opus 256kbps Tidal lossy option" }, @@ -1905,11 +1985,11 @@ "@downloadLossyOpus128": { "description": "Tidal lossy format option - Opus 128kbps" }, - "downloadLossyOpus128Subtitle": "En küçük boyut, şarkı başı ~4MB", + "downloadLossyOpus128Subtitle": "En küçük boyut, parça başına ~4 Mb", "@downloadLossyOpus128Subtitle": { "description": "Subtitle for Opus 128kbps Tidal lossy option" }, - "qualityNote": "Gerçek kalite, şarkının serviste hangi kalitede bulunduğuna bağlıdır.", + "qualityNote": "Gerçek kalite, parçanın servisteki uygunluğuna bağlıdır", "@qualityNote": { "description": "Note about quality availability" }, @@ -1917,47 +1997,47 @@ "@downloadAskBeforeDownload": { "description": "Setting - show quality picker" }, - "downloadDirectory": "İndirme Klasörü", + "downloadDirectory": "İndirme Dizini", "@downloadDirectory": { "description": "Setting - download folder" }, - "downloadSeparateSinglesFolder": "Single'ları Ayrı Klasöre Koy", + "downloadSeparateSinglesFolder": "Ayrı Single Klasörü", "@downloadSeparateSinglesFolder": { "description": "Setting - separate folder for singles" }, - "downloadAlbumFolderStructure": "Albüm Klasörü Düzeni", + "downloadAlbumFolderStructure": "Albüm Klasör Yapısı", "@downloadAlbumFolderStructure": { "description": "Setting - album folder organization" }, - "downloadUseAlbumArtistForFolders": "Klasörler için Albüm Sanatçısını Kullan", + "downloadUseAlbumArtistForFolders": "Klasörler için Albüm Sanatçısı'nı kullan", "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, - "downloadUsePrimaryArtistOnly": "Klasörlerde Sadece Ana Sanatçı (Düetleri Gizle)", + "downloadUsePrimaryArtistOnly": "Klasörler için yalnızca birincil sanatçıyı kullan", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" }, - "downloadUsePrimaryArtistOnlyEnabled": "Konuk sanatçılar klasör adından silinir (Örn: Justin Bieber, Quavo → Justin Bieber)", + "downloadUsePrimaryArtistOnlyEnabled": "Düet sanatçıları klasör adından kaldırılır (örn. Justin Bieber, Quavo → Justin Bieber)", "@downloadUsePrimaryArtistOnlyEnabled": { "description": "Subtitle when primary artist only is enabled" }, - "downloadUsePrimaryArtistOnlyDisabled": "Klasör adına tüm sanatçılar yazılır", + "downloadUsePrimaryArtistOnlyDisabled": "Klasör adı için tam sanatçı dizesi kullanılır", "@downloadUsePrimaryArtistOnlyDisabled": { "description": "Subtitle when primary artist only is disabled" }, - "downloadSelectQuality": "Kaliteyi Seçin", + "downloadSelectQuality": "Kalite seçin", "@downloadSelectQuality": { "description": "Dialog title - choose audio quality" }, - "downloadFrom": "İndirme Kaynağı:", + "downloadFrom": "İndirme Kaynağı", "@downloadFrom": { "description": "Label - download source" }, - "appearanceAmoledDark": "AMOLED Koyu (Tam Siyah)", + "appearanceAmoledDark": "AMOLED Koyu", "@appearanceAmoledDark": { "description": "Theme option - pure black" }, - "appearanceAmoledDarkSubtitle": "Tamamen siyah arka plan (OLED ekranlar için)", + "appearanceAmoledDarkSubtitle": "Saf siyah arka plan", "@appearanceAmoledDarkSubtitle": { "description": "Subtitle for AMOLED dark" }, @@ -1965,31 +2045,31 @@ "@queueClearAll": { "description": "Button - clear all queue items" }, - "queueClearAllMessage": "Tüm indirme sırasını temizlemek istediğinize emin misiniz?", + "queueClearAllMessage": "Tüm indirmeleri temizlemek istediğinizden emin misiniz?", "@queueClearAllMessage": { "description": "Clear queue confirmation" }, - "settingsAutoExportFailed": "Başarısız İndirmeleri Otomatik Dışa Aktar", + "settingsAutoExportFailed": "Başarısız indirmeleri otomatik dışa aktar", "@settingsAutoExportFailed": { "description": "Setting toggle for auto-export" }, - "settingsAutoExportFailedSubtitle": "İndirilemeyen şarkıların listesini TXT dosyası olarak kaydeder", + "settingsAutoExportFailedSubtitle": "Başarısız indirmeleri otomatik olarak TXT dosyasına kaydet", "@settingsAutoExportFailedSubtitle": { "description": "Subtitle for auto-export setting" }, - "settingsDownloadNetwork": "İndirme İçin Kullanılacak Ağ", + "settingsDownloadNetwork": "İndirme Ağı", "@settingsDownloadNetwork": { "description": "Setting for network type preference" }, - "settingsDownloadNetworkAny": "Wi-Fi + Mobil Veri", + "settingsDownloadNetworkAny": "WiFi + Mobil Veri", "@settingsDownloadNetworkAny": { "description": "Network option - use any connection" }, - "settingsDownloadNetworkWifiOnly": "Sadece Wi-Fi", + "settingsDownloadNetworkWifiOnly": "Yalnızca WiFi", "@settingsDownloadNetworkWifiOnly": { "description": "Network option - only use WiFi" }, - "settingsDownloadNetworkSubtitle": "Sadece Wi-Fi seçildiğinde, mobil verideyken indirmeler otomatik duraklatılır.", + "settingsDownloadNetworkSubtitle": "İndirmeler için hangi ağın kullanılacağını seçin. Yalnızca WiFi olarak ayarlandığında, mobil veriye geçildiğinde indirmeler duraklatılır.", "@settingsDownloadNetworkSubtitle": { "description": "Subtitle explaining network preference" }, @@ -2009,7 +2089,7 @@ "@albumFolderArtistYearAlbumSubtitle": { "description": "Folder structure example" }, - "albumFolderAlbumOnly": "Sadece Albüm", + "albumFolderAlbumOnly": "Yalnızca Albüm", "@albumFolderAlbumOnly": { "description": "Album folder option" }, @@ -2025,19 +2105,19 @@ "@albumFolderYearAlbumSubtitle": { "description": "Folder structure example" }, - "albumFolderArtistAlbumSingles": "Sanatçı / Albüm + Single'lar", + "albumFolderArtistAlbumSingles": "Sanatçı / Albüm + Singlelar", "@albumFolderArtistAlbumSingles": { "description": "Album folder option with singles inside artist" }, - "albumFolderArtistAlbumSinglesSubtitle": "Sanatçı/Albüm/ ve Sanatçı/Single'lar/", + "albumFolderArtistAlbumSinglesSubtitle": "Sanatçı/Albüm/ ve Sanatçı/Singlelar/", "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, - "albumFolderArtistAlbumFlat": "Sanatçı / Albüm (Single'lar ayrı klasörsüz)", + "albumFolderArtistAlbumFlat": "Sanatçı / Albüm (Singlelar alt klasörsüz)", "@albumFolderArtistAlbumFlat": { "description": "Album folder option with singles directly in artist folder" }, - "albumFolderArtistAlbumFlatSubtitle": "Sanatçı/Albüm/ ve Sanatçı/sarki.flac", + "albumFolderArtistAlbumFlatSubtitle": "Sanatçı/Albüm/ ve Sanatçı/şarkı.flac", "@albumFolderArtistAlbumFlatSubtitle": { "description": "Folder structure example for flat singles" }, @@ -2045,7 +2125,7 @@ "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" }, - "downloadedAlbumDeleteMessage": "Bu albümden {count} {count, plural, =1{şarkı} other{şarkı}} silinsin mi?\n\nBu işlem, dosyaları cihazınızdan da tamamen silecek.", + "downloadedAlbumDeleteMessage": "Bu albümden {count} {count, plural, one {}=1{parça} other{parça}} parça silinsin mi?\n\nBu işlem dosyaları depolama alanından da kalıcı olarak silecektir.", "@downloadedAlbumDeleteMessage": { "description": "Delete confirmation with count", "placeholders": { @@ -2063,15 +2143,15 @@ } } }, - "downloadedAlbumAllSelected": "Tüm şarkılar seçildi", + "downloadedAlbumAllSelected": "Tüm parçalar seçildi", "@downloadedAlbumAllSelected": { "description": "Status - all items selected" }, - "downloadedAlbumTapToSelect": "Seçmek için şarkılara dokunun", + "downloadedAlbumTapToSelect": "Seçmek için parçalara dokunun", "@downloadedAlbumTapToSelect": { "description": "Selection hint" }, - "downloadedAlbumDeleteCount": "{count} {count, plural, =1{Şarkıyı} other{Şarkıyı}} Sil", + "downloadedAlbumDeleteCount": "{count} {count, plural, one {}=1{parçayı} other{parçayı}} sil", "@downloadedAlbumDeleteCount": { "description": "Delete button text with count", "placeholders": { @@ -2080,7 +2160,7 @@ } } }, - "downloadedAlbumSelectToDelete": "Silinecek şarkıları seçin", + "downloadedAlbumSelectToDelete": "Silinecek parçaları seçin", "@downloadedAlbumSelectToDelete": { "description": "Placeholder when nothing selected" }, @@ -2110,7 +2190,7 @@ "@recentTypePlaylist": { "description": "Recent access item type - playlist" }, - "recentEmpty": "Henüz yeni bir arama yok", + "recentEmpty": "Henüz son kullanılan öğe yok", "@recentEmpty": { "description": "Empty state text for recent access list" }, @@ -2128,7 +2208,7 @@ } } }, - "discographyDownload": "Tüm Diskografiyi İndir", + "discographyDownload": "Diskografiyi İndir", "@discographyDownload": { "description": "Button - download artist discography" }, @@ -2136,7 +2216,7 @@ "@discographyDownloadAll": { "description": "Option - download entire discography" }, - "discographyDownloadAllSubtitle": "{albumCount} albüm/single üzerinden toplam {count} şarkı", + "discographyDownloadAllSubtitle": "{albumCount} yayından {count} parça", "@discographyDownloadAllSubtitle": { "description": "Subtitle showing total tracks and albums", "placeholders": { @@ -2148,11 +2228,11 @@ } } }, - "discographyAlbumsOnly": "Sadece Albümler", + "discographyAlbumsOnly": "Yalnızca Albümler", "@discographyAlbumsOnly": { "description": "Option - download only albums" }, - "discographyAlbumsOnlySubtitle": "{albumCount} albümden toplam {count} şarkı", + "discographyAlbumsOnlySubtitle": "{albumCount} albümden {count} parça", "@discographyAlbumsOnlySubtitle": { "description": "Subtitle showing album tracks count", "placeholders": { @@ -2164,11 +2244,11 @@ } } }, - "discographySinglesOnly": "Sadece Single'lar ve EP'ler", + "discographySinglesOnly": "Yalnızca Single'lar ve EP'ler", "@discographySinglesOnly": { "description": "Option - download only singles" }, - "discographySinglesOnlySubtitle": "{albumCount} single üzerinden toplam {count} şarkı", + "discographySinglesOnlySubtitle": "{albumCount} tekliden {count} parça", "@discographySinglesOnlySubtitle": { "description": "Subtitle showing singles tracks count", "placeholders": { @@ -2184,15 +2264,15 @@ "@discographySelectAlbums": { "description": "Option - manually select albums to download" }, - "discographySelectAlbumsSubtitle": "İstediğiniz albümleri veya single'ları kendiniz seçin", + "discographySelectAlbumsSubtitle": "Belirli albümleri veya single'ları seçin", "@discographySelectAlbumsSubtitle": { "description": "Subtitle for select albums option" }, - "discographyFetchingTracks": "Şarkılar alınıyor...", + "discographyFetchingTracks": "Parçalar getiriliyor...", "@discographyFetchingTracks": { "description": "Progress - fetching album tracks" }, - "discographyFetchingAlbum": "Alınıyor: {current} / {total}...", + "discographyFetchingAlbum": "{total} üzerinden {current} getiriliyor...", "@discographyFetchingAlbum": { "description": "Progress - fetching specific album", "placeholders": { @@ -2217,7 +2297,7 @@ "@discographyDownloadSelected": { "description": "Button - download selected albums" }, - "discographyAddedToQueue": "{count} şarkı indirme sırasına eklendi", + "discographyAddedToQueue": "{count} parça kuyruğa eklendi", "@discographyAddedToQueue": { "description": "Snackbar - tracks added from discography", "placeholders": { @@ -2226,7 +2306,7 @@ } } }, - "discographySkippedDownloaded": "{added} şarkı eklendi, zaten inmiş olan {skipped} şarkı atlandı", + "discographySkippedDownloaded": "{added} eklendi, {skipped} zaten indirilmiş", "@discographySkippedDownloaded": { "description": "Snackbar - with skipped tracks count", "placeholders": { @@ -2238,11 +2318,11 @@ } } }, - "discographyNoAlbums": "Bu sanatçıya ait albüm bulunamadı", + "discographyNoAlbums": "Kullanılabilir albüm yok", "@discographyNoAlbums": { "description": "Error - no albums found for artist" }, - "discographyFailedToFetch": "Bazı albümler yüklenemedi", + "discographyFailedToFetch": "Bazı albümler getirilemedi", "@discographyFailedToFetch": { "description": "Error - some albums failed to load" }, @@ -2254,23 +2334,23 @@ "@allFilesAccess": { "description": "Toggle for MANAGE_EXTERNAL_STORAGE permission" }, - "allFilesAccessEnabledSubtitle": "Cihazdaki herhangi bir klasöre yazabilir", + "allFilesAccessEnabledSubtitle": "Herhangi bir klasöre yazabilir", "@allFilesAccessEnabledSubtitle": { "description": "Subtitle when all files access is enabled" }, - "allFilesAccessDisabledSubtitle": "Sadece medya klasörleriyle sınırlı", + "allFilesAccessDisabledSubtitle": "Yalnızca medya klasörleriyle sınırlı", "@allFilesAccessDisabledSubtitle": { "description": "Subtitle when all files access is disabled" }, - "allFilesAccessDescription": "Özel klasörlere kaydederken yazma hatası alıyorsanız bunu açın. Android 13 ve sonrasında bazı klasörlere erişim varsayılan olarak kısıtlanmıştır.", + "allFilesAccessDescription": "Özel klasörlere kaydederken yazma hatalarıyla karşılaşırsanız bunu etkinleştirin. Android 13 ve üzeri, varsayılan olarak belirli dizinlere erişimi kısıtlar.", "@allFilesAccessDescription": { "description": "Description explaining when to enable all files access" }, - "allFilesAccessDeniedMessage": "İzin reddedildi. Lütfen sistem ayarlarından 'Tüm dosyalara erişim' iznini manuel olarak verin.", + "allFilesAccessDeniedMessage": "İzin reddedildi. Lütfen sistem ayarlarından 'Tüm dosyalara erişim' iznini manuel olarak etkinleştirin.", "@allFilesAccessDeniedMessage": { "description": "Message when permission is permanently denied" }, - "allFilesAccessDisabledMessage": "Tüm Dosyalara Erişim devre dışı. Uygulama sınırlı depolama izniyle çalışacak.", + "allFilesAccessDisabledMessage": "Tüm Dosyalara Erişim devre dışı bırakıldı. Uygulama kısıtlı depolama erişimi kullanacak.", "@allFilesAccessDisabledMessage": { "description": "Snackbar message when user disables all files access" }, @@ -2278,15 +2358,15 @@ "@settingsLocalLibrary": { "description": "Settings menu item - local library" }, - "settingsLocalLibrarySubtitle": "Telefonunuzdaki müzikleri tarayıp kopyaları bulun", + "settingsLocalLibrarySubtitle": "Müziği tara ve kopyaları tespit et", "@settingsLocalLibrarySubtitle": { "description": "Subtitle for local library settings" }, - "settingsCache": "Önbellek ve Depolama", + "settingsCache": "Depolama ve Önbellek", "@settingsCache": { "description": "Settings menu item - cache management" }, - "settingsCacheSubtitle": "Boyutu görüntüleyin ve gereksiz dosyaları temizleyin", + "settingsCacheSubtitle": "Boyutu görüntüle ve önbelleğe alınmış verileri temizle", "@settingsCacheSubtitle": { "description": "Subtitle for cache management menu" }, @@ -2298,15 +2378,15 @@ "@libraryScanSettings": { "description": "Section header for scan settings" }, - "libraryEnableLocalLibrary": "Yerel Kitaplık Taramasını Aç", + "libraryEnableLocalLibrary": "Yerel Kitaplığı Etkinleştir", "@libraryEnableLocalLibrary": { "description": "Toggle to enable library scanning" }, - "libraryEnableLocalLibrarySubtitle": "İndirme yaparken elinizde olan şarkıları takip eder", + "libraryEnableLocalLibrarySubtitle": "Mevcut müziğinizi tarayın ve takip edin", "@libraryEnableLocalLibrarySubtitle": { "description": "Subtitle for enable toggle" }, - "libraryFolder": "Taranacak Klasör", + "libraryFolder": "Kitaplık Klasörü", "@libraryFolder": { "description": "Folder selection setting" }, @@ -2314,11 +2394,11 @@ "@libraryFolderHint": { "description": "Placeholder when no folder selected" }, - "libraryShowDuplicateIndicator": "Kopya İndikatörünü Göster", + "libraryShowDuplicateIndicator": "Kopya Belirtecini Göster", "@libraryShowDuplicateIndicator": { "description": "Toggle for duplicate indicator in search" }, - "libraryShowDuplicateIndicatorSubtitle": "Zaten indirmiş olduğunuz şarkıların yanında belirteç gösterir", + "libraryShowDuplicateIndicatorSubtitle": "Mevcut parçalar aranırken göster", "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, @@ -2326,7 +2406,7 @@ "@libraryAutoScan": { "description": "Setting for automatic library scanning" }, - "libraryAutoScanSubtitle": "Cihazınıza yeni eklenen müzikleri otomatik olarak bulur", + "libraryAutoScanSubtitle": "Kitaplığınızı yeni dosyalar için otomatik olarak tarayın", "@libraryAutoScanSubtitle": { "description": "Subtitle for auto scan setting" }, @@ -2334,19 +2414,19 @@ "@libraryAutoScanOff": { "description": "Auto scan disabled" }, - "libraryAutoScanOnOpen": "Uygulama her açıldığında", + "libraryAutoScanOnOpen": "Her uygulama açılışında", "@libraryAutoScanOnOpen": { "description": "Auto scan when app opens" }, - "libraryAutoScanDaily": "Günde bir", + "libraryAutoScanDaily": "Günlük", "@libraryAutoScanDaily": { "description": "Auto scan once per day" }, - "libraryAutoScanWeekly": "Haftada bir", + "libraryAutoScanWeekly": "Haftalık", "@libraryAutoScanWeekly": { "description": "Auto scan once per week" }, - "libraryActions": "İşlemler", + "libraryActions": "Eylemler", "@libraryActions": { "description": "Section header for library actions" }, @@ -2354,11 +2434,11 @@ "@libraryScan": { "description": "Button to start library scan" }, - "libraryScanSubtitle": "Klasördeki müzik dosyalarını tarar", + "libraryScanSubtitle": "Ses dosyaları için tara", "@libraryScanSubtitle": { "description": "Subtitle for scan button" }, - "libraryScanSelectFolderFirst": "Lütfen önce taranacak bir klasör seçin", + "libraryScanSelectFolderFirst": "Önce bir klasör seçin", "@libraryScanSelectFolderFirst": { "description": "Message when trying to scan without folder" }, @@ -2366,35 +2446,35 @@ "@libraryCleanupMissingFiles": { "description": "Button to remove entries for missing files" }, - "libraryCleanupMissingFilesSubtitle": "Artık cihazınızda olmayan dosyaların kayıtlarını kaldırır", + "libraryCleanupMissingFilesSubtitle": "Eski dosya kalıntılarını temizleyin", "@libraryCleanupMissingFilesSubtitle": { "description": "Subtitle for cleanup button" }, - "libraryClear": "Kitaplığı Temizle", + "libraryClear": "Kitaplığı temizle", "@libraryClear": { "description": "Button to clear all library entries" }, - "libraryClearSubtitle": "Taranmış tüm şarkı kayıtlarını sıfırlar", + "libraryClearSubtitle": "Taranan tüm parçaları sil", "@libraryClearSubtitle": { "description": "Subtitle for clear button" }, - "libraryClearConfirmTitle": "Kitaplık Temizlensin mi?", + "libraryClearConfirmTitle": "Kütüphaneyi temizle", "@libraryClearConfirmTitle": { "description": "Dialog title for clear confirmation" }, - "libraryClearConfirmMessage": "Uygulamanın kaydettiği tüm taranmış şarkı verileri silinecek. (Gerçek müzik dosyalarınız SİLİNMEYECEK).", + "libraryClearConfirmMessage": "Bu işlem, kitaplığınızdaki tüm taranmış parçaları siler. Asıl müzik dosyalarınız silinmez.", "@libraryClearConfirmMessage": { "description": "Dialog message for clear confirmation" }, - "libraryAbout": "Yerel Kitaplık Hakkında", + "libraryAbout": "Yerel Kütüphane Hakkında", "@libraryAbout": { "description": "Section header for about info" }, - "libraryAboutDescription": "İndirme yaparken kopyaları (zaten inmiş olanları) tespit etmek için mevcut müzik arşivinizi tarar. FLAC, M4A, MP3, Opus ve OGG formatlarını destekler. Bilgiler şarkı dosyalarının kendi etiketlerinden (ID3 tag vb.) okunur.", + "libraryAboutDescription": "İndirme işlemi sırasında mevcut müzik koleksiyonunuzu tarayarak yinelenen dosyaları tespit eder. FLAC, M4A, MP3, Opus ve OGG formatlarını destekler. Varsa, meta veriler dosya etiketlerinden okunur.", "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksUnit": "{count, plural, =1{şarkı} other{şarkı}}", + "libraryTracksUnit": "{count, plural, =1{track} other{tracks}}", "@libraryTracksUnit": { "description": "Unit label for tracks count (without the number itself)", "placeholders": { @@ -2403,7 +2483,16 @@ } } }, - "libraryLastScanned": "Son tarama: {time}", + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", "placeholders": { @@ -2412,15 +2501,19 @@ } } }, - "libraryLastScannedNever": "Hiç taranmadı", + "libraryLastScannedNever": "Never", "@libraryLastScannedNever": { "description": "Shown when library has never been scanned" }, - "libraryScanning": "Taranıyor...", + "libraryScanning": "Scanning...", "@libraryScanning": { "description": "Status during scan" }, - "libraryScanProgress": "%{progress} (Toplam {total} dosya)", + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, + "libraryScanProgress": "{progress}% of {total} files", "@libraryScanProgress": { "description": "Scan progress display", "placeholders": { @@ -2432,11 +2525,11 @@ } } }, - "libraryInLibrary": "Cihazda Var", + "libraryInLibrary": "In Library", "@libraryInLibrary": { "description": "Badge shown on tracks that exist in local library" }, - "libraryRemovedMissingFiles": "Cihazda olmayan {count} dosyanın kaydı temizlendi", + "libraryRemovedMissingFiles": "Removed {count} missing files from library", "@libraryRemovedMissingFiles": { "description": "Snackbar after cleanup", "placeholders": { @@ -2445,39 +2538,39 @@ } } }, - "libraryCleared": "Kitaplık kayıtları temizlendi", + "libraryCleared": "Library cleared", "@libraryCleared": { "description": "Snackbar after clearing library" }, - "libraryStorageAccessRequired": "Depolama İzni Gerekli", + "libraryStorageAccessRequired": "Storage Access Required", "@libraryStorageAccessRequired": { "description": "Dialog title for storage permission" }, - "libraryStorageAccessMessage": "Müzik kitaplığınızı taramak için SpotiFLAC'ın depolama iznine ihtiyacı var. Lütfen ayarlardan izin verin.", + "libraryStorageAccessMessage": "SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.", "@libraryStorageAccessMessage": { "description": "Dialog message for storage permission" }, - "libraryFolderNotExist": "Seçilen klasör artık mevcut değil", + "libraryFolderNotExist": "Selected folder does not exist", "@libraryFolderNotExist": { "description": "Error when folder doesn't exist" }, - "librarySourceDownloaded": "İndirildi", + "librarySourceDownloaded": "Downloaded", "@librarySourceDownloaded": { "description": "Badge for tracks downloaded via SpotiFLAC" }, - "librarySourceLocal": "Cihazdan", + "librarySourceLocal": "Local", "@librarySourceLocal": { "description": "Badge for tracks from local library scan" }, - "libraryFilterAll": "Tümü", + "libraryFilterAll": "All", "@libraryFilterAll": { "description": "Filter chip - show all library items" }, - "libraryFilterDownloaded": "Uygulama İle İndirilenler", + "libraryFilterDownloaded": "Downloaded", "@libraryFilterDownloaded": { "description": "Filter chip - show only downloaded items" }, - "libraryFilterLocal": "Yerel Dosyalar", + "libraryFilterLocal": "Local", "@libraryFilterLocal": { "description": "Filter chip - show only local library items" }, @@ -2485,31 +2578,31 @@ "@libraryFilterTitle": { "description": "Filter bottom sheet title" }, - "libraryFilterReset": "Sıfırla", + "libraryFilterReset": "Reset", "@libraryFilterReset": { "description": "Reset all filters button" }, - "libraryFilterApply": "Uygula", + "libraryFilterApply": "Apply", "@libraryFilterApply": { "description": "Apply filters button" }, - "libraryFilterSource": "Kaynak", + "libraryFilterSource": "Source", "@libraryFilterSource": { "description": "Filter section - source type" }, - "libraryFilterQuality": "Kalite", + "libraryFilterQuality": "Quality", "@libraryFilterQuality": { "description": "Filter section - audio quality" }, - "libraryFilterQualityHiRes": "Hi-Res (24-bit)", + "libraryFilterQualityHiRes": "Hi-Res (24bit)", "@libraryFilterQualityHiRes": { "description": "Filter option - high resolution audio" }, - "libraryFilterQualityCD": "CD Kalitesi (16-bit)", + "libraryFilterQualityCD": "CD (16bit)", "@libraryFilterQualityCD": { "description": "Filter option - CD quality audio" }, - "libraryFilterQualityLossy": "Kayıplı (Lossy)", + "libraryFilterQualityLossy": "Lossy", "@libraryFilterQualityLossy": { "description": "Filter option - lossy compressed audio" }, @@ -2517,23 +2610,63 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, - "libraryFilterSort": "Sıralama", + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, + "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" }, - "libraryFilterSortLatest": "En Yeniler", + "libraryFilterSortLatest": "Latest", "@libraryFilterSortLatest": { "description": "Sort option - newest first" }, - "libraryFilterSortOldest": "En Eskiler", + "libraryFilterSortOldest": "Oldest", "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, - "timeJustNow": "Az önce", + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, + "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" }, - "timeMinutesAgo": "{count, plural, =1{1 dakika önce} other{{count} dakika önce}}", + "timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}", "@timeMinutesAgo": { "description": "Relative time - minutes ago", "placeholders": { @@ -2542,7 +2675,7 @@ } } }, - "timeHoursAgo": "{count, plural, =1{1 saat önce} other{{count} saat önce}}", + "timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}", "@timeHoursAgo": { "description": "Relative time - hours ago", "placeholders": { @@ -2551,123 +2684,123 @@ } } }, - "tutorialWelcomeTitle": "SpotiFLAC'a Hoş Geldiniz!", + "tutorialWelcomeTitle": "Welcome to SpotiFLAC!", "@tutorialWelcomeTitle": { "description": "Tutorial welcome page title" }, - "tutorialWelcomeDesc": "En sevdiğiniz müzikleri kayıpsız kalitede nasıl indireceğinizi öğrenelim. Bu kısa rehber size temelleri gösterecek.", + "tutorialWelcomeDesc": "En sevdiğiniz müzikleri kayıpsız kalitede nasıl indirebileceğinizi öğrenelim. Bu kısa eğitim size temel bilgileri gösterecek.", "@tutorialWelcomeDesc": { "description": "Tutorial welcome page description" }, - "tutorialWelcomeTip1": "Müzikleri bulmak için bir Spotify ya da Deezer bağlantısı yapıştırabilir veya adıyla arayabilirsiniz", + "tutorialWelcomeTip1": "Spotify, Deezer'dan müzik indirin veya desteklenen herhangi bir URL'yi yapıştırın", "@tutorialWelcomeTip1": { "description": "Tutorial welcome tip 1" }, - "tutorialWelcomeTip2": "Şarkıları Tidal, Qobuz veya Deezer altyapısıyla en yüksek kalitede (FLAC) indirin", + "tutorialWelcomeTip2": "Tidal, Qobuz veya Deezer'dan FLAC kalitesinde ses alın", "@tutorialWelcomeTip2": { "description": "Tutorial welcome tip 2" }, - "tutorialWelcomeTip3": "Albüm kapağı, şarkı sözleri ve tüm şarkı verileri dosyanın içine otomatik olarak gömülür", + "tutorialWelcomeTip3": "Otomatik meta veri, kapak resmi ve şarkı sözü gömme", "@tutorialWelcomeTip3": { "description": "Tutorial welcome tip 3" }, - "tutorialSearchTitle": "Müzik Bulmak Çok Kolay", + "tutorialSearchTitle": "Müzik Bulma", "@tutorialSearchTitle": { "description": "Tutorial search page title" }, - "tutorialSearchDesc": "İstediğiniz müzikleri bulmanın iki basit yolu var.", + "tutorialSearchDesc": "İndirmek istediğiniz müziği bulmanın iki kolay yolu vardır.", "@tutorialSearchDesc": { "description": "Tutorial search page description" }, - "tutorialDownloadTitle": "Müzikleri İndirme", + "tutorialDownloadTitle": "Müzik İndirme", "@tutorialDownloadTitle": { "description": "Tutorial download page title" }, - "tutorialDownloadDesc": "Şarkıları indirmek hızlı ve pürüzsüzdür. İşte süreç böyle işliyor:", + "tutorialDownloadDesc": "Müzik indirmek basit ve hızlıdır. İşte nasıl çalıştığı.", "@tutorialDownloadDesc": { "description": "Tutorial download page description" }, - "tutorialLibraryTitle": "Kişisel Kitaplığınız", + "tutorialLibraryTitle": "Kitaplığınız", "@tutorialLibraryTitle": { "description": "Tutorial library page title" }, - "tutorialLibraryDesc": "İndirdiğiniz tüm müzikler Kitaplık sekmesinde düzenli bir şekilde tutulur.", + "tutorialLibraryDesc": "İndirdiğiniz tüm müzikler Kitaplık sekmesinde düzenlenir.", "@tutorialLibraryDesc": { "description": "Tutorial library page description" }, - "tutorialLibraryTip1": "İndirme ilerlemenizi ve sırayı Kitaplık sekmesinden takip edin", + "tutorialLibraryTip1": "Kitaplık sekmesinden indirme ilerlemesini ve kuyruğu görüntüleyin", "@tutorialLibraryTip1": { "description": "Tutorial library tip 1" }, - "tutorialLibraryTip2": "İndirdiğiniz şarkıyı favori müzik çalarınızda açmak için üzerine dokunun", + "tutorialLibraryTip2": "Müzik çalarınızla oynatmak için herhangi bir parçaya dokunun", "@tutorialLibraryTip2": { "description": "Tutorial library tip 2" }, - "tutorialLibraryTip3": "Daha rahat göz atmak için liste ve ızgara görünümleri arasında geçiş yapın", + "tutorialLibraryTip3": "Daha iyi göz atmak için liste ve ızgara görünümü arasında geçiş yapın", "@tutorialLibraryTip3": { "description": "Tutorial library tip 3" }, - "tutorialExtensionsTitle": "Eklentilerle Güçlendirin", + "tutorialExtensionsTitle": "Uzantılar", "@tutorialExtensionsTitle": { "description": "Tutorial extensions page title" }, - "tutorialExtensionsDesc": "Topluluğun geliştirdiği eklentilerle uygulamanın sınırlarını aşın.", + "tutorialExtensionsDesc": "Topluluk uzantılarıyla uygulamanın yeteneklerini artırın.", "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "İlginizi çekebilecek eklentileri keşfetmek için Mağaza sekmesine göz atın", + "tutorialExtensionsTip1": "Faydalı uzantıları keşfetmek için Depo sekmesine göz atın", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, - "tutorialExtensionsTip2": "Uygulamaya yepyeni indirme ve arama kaynakları ekleyin", + "tutorialExtensionsTip2": "Yeni indirme sağlayıcıları veya arama kaynakları ekleyin", "@tutorialExtensionsTip2": { "description": "Tutorial extensions tip 2" }, - "tutorialExtensionsTip3": "Farklı şarkı sözü sağlayıcıları ve yepyeni özellikler kazanın", + "tutorialExtensionsTip3": "Şarkı sözleri, gelişmiş meta veriler ve daha fazla özellik edinin", "@tutorialExtensionsTip3": { "description": "Tutorial extensions tip 3" }, - "tutorialSettingsTitle": "Deneyiminizi Kişiselleştirin", + "tutorialSettingsTitle": "Deneyiminizi Özelleştirin", "@tutorialSettingsTitle": { "description": "Tutorial settings page title" }, - "tutorialSettingsDesc": "Uygulamanın nasıl davranacağını Ayarlar menüsünden zevkinize göre özelleştirin.", + "tutorialSettingsDesc": "Uygulamayı Ayarlar'dan tercihlerinize göre kişiselleştirin.", "@tutorialSettingsDesc": { "description": "Tutorial settings page description" }, - "tutorialSettingsTip1": "İndirme konumunu ve klasörleme biçimini değiştirin", + "tutorialSettingsTip1": "İndirme konumunu ve klasör düzenini değiştirin", "@tutorialSettingsTip1": { "description": "Tutorial settings tip 1" }, - "tutorialSettingsTip2": "Varsayılan ses kalitesini ve indirme formatınızı belirleyin", + "tutorialSettingsTip2": "Varsayılan ses kalitesi ve format tercihlerini ayarlayın", "@tutorialSettingsTip2": { "description": "Tutorial settings tip 2" }, - "tutorialSettingsTip3": "Temayı, renkleri ve uygulamanın görünümünü ayarlayın", + "tutorialSettingsTip3": "Customize app theme and appearance", "@tutorialSettingsTip3": { "description": "Tutorial settings tip 3" }, - "tutorialReadyMessage": "İşte bu kadar! Artık favori müziklerinizi indirmeye hazırsınız.", + "tutorialReadyMessage": "You're all set! Start downloading your favorite music now.", "@tutorialReadyMessage": { "description": "Tutorial completion message" }, - "libraryForceFullScan": "Tam Taramaya Zorla", + "libraryForceFullScan": "Force Full Scan", "@libraryForceFullScan": { "description": "Button to force a complete rescan of library" }, - "libraryForceFullScanSubtitle": "Önbelleği yoksayarak klasördeki tüm dosyaları baştan tarar", + "libraryForceFullScanSubtitle": "Rescan all files, ignoring cache", "@libraryForceFullScanSubtitle": { "description": "Subtitle for force full scan button" }, - "cleanupOrphanedDownloads": "Geçersiz İndirmeleri Temizle", + "cleanupOrphanedDownloads": "Cleanup Orphaned Downloads", "@cleanupOrphanedDownloads": { "description": "Button to remove history entries for deleted files" }, - "cleanupOrphanedDownloadsSubtitle": "Cihazdan silinmiş dosyalara ait eski geçmiş kayıtlarını kaldırır", + "cleanupOrphanedDownloadsSubtitle": "Remove history entries for files that no longer exist", "@cleanupOrphanedDownloadsSubtitle": { "description": "Subtitle for orphaned cleanup button" }, - "cleanupOrphanedDownloadsResult": "Geçmişten {count} geçersiz kayıt kaldırıldı", + "cleanupOrphanedDownloadsResult": "Removed {count} orphaned entries from history", "@cleanupOrphanedDownloadsResult": { "description": "Snackbar after orphan cleanup", "placeholders": { @@ -2676,23 +2809,23 @@ } } }, - "cleanupOrphanedDownloadsNone": "Temizlenecek geçersiz kayıt bulunamadı", + "cleanupOrphanedDownloadsNone": "No orphaned entries found", "@cleanupOrphanedDownloadsNone": { "description": "Snackbar when no orphans found" }, - "cacheTitle": "Önbellek ve Depolama", + "cacheTitle": "Storage & Cache", "@cacheTitle": { "description": "Cache management page title" }, - "cacheSummaryTitle": "Önbellek Özeti", + "cacheSummaryTitle": "Cache overview", "@cacheSummaryTitle": { "description": "Heading for cache summary card" }, - "cacheSummarySubtitle": "Önbelleği temizlemek indirdiğiniz müzik dosyalarını SİLMEZ.", + "cacheSummarySubtitle": "Clearing cache will not remove downloaded music files.", "@cacheSummarySubtitle": { "description": "Helper text for cache summary card" }, - "cacheEstimatedTotal": "Tahmini önbellek kullanımı: {size}", + "cacheEstimatedTotal": "Estimated cache usage: {size}", "@cacheEstimatedTotal": { "description": "Total cache size shown in summary", "placeholders": { @@ -2701,71 +2834,71 @@ } } }, - "cacheSectionStorage": "Önbelleğe Alınan Veriler", + "cacheSectionStorage": "Cached Data", "@cacheSectionStorage": { "description": "Section header for cache entries" }, - "cacheSectionMaintenance": "Bakım ve Temizlik", + "cacheSectionMaintenance": "Maintenance", "@cacheSectionMaintenance": { "description": "Section header for cleanup actions" }, - "cacheAppDirectory": "Uygulama Önbelleği", + "cacheAppDirectory": "App cache directory", "@cacheAppDirectory": { "description": "Cache item title for app cache directory" }, - "cacheAppDirectoryDesc": "İnternet yanıtları, küçük resimler ve uygulamanın tuttuğu geçici dosyalar.", + "cacheAppDirectoryDesc": "HTTP responses, WebView data, and other temporary app data.", "@cacheAppDirectoryDesc": { "description": "Description of what app cache directory contains" }, - "cacheTempDirectory": "Geçici Klasör", + "cacheTempDirectory": "Temporary directory", "@cacheTempDirectory": { "description": "Cache item title for temporary files directory" }, - "cacheTempDirectoryDesc": "İndirme ve ses dönüştürme işlemleri sırasında oluşan artık dosyalar.", + "cacheTempDirectoryDesc": "Temporary files from downloads and audio conversion.", "@cacheTempDirectoryDesc": { "description": "Description of what temporary directory contains" }, - "cacheCoverImage": "Kapak Resmi Önbelleği", + "cacheCoverImage": "Cover image cache", "@cacheCoverImage": { "description": "Cache item title for persistent cover images" }, - "cacheCoverImageDesc": "Önceden yüklenmiş albüm kapakları. Silinirse tekrar görüntülediğinizde yeniden indirilir.", + "cacheCoverImageDesc": "Downloaded album and track cover art. Will re-download when viewed.", "@cacheCoverImageDesc": { "description": "Description of what cover image cache contains" }, - "cacheLibraryCover": "Kitaplık Kapağı Önbelleği", + "cacheLibraryCover": "Library cover cache", "@cacheLibraryCover": { "description": "Cache item title for local library cover art images" }, - "cacheLibraryCoverDesc": "Yerel müzik dosyalarınızdan çıkarılmış kapaklar. Silinirse sonraki taramada yeniden oluşturulur.", + "cacheLibraryCoverDesc": "Cover art extracted from local music files. Will re-extract on next scan.", "@cacheLibraryCoverDesc": { "description": "Description of what library cover cache contains" }, - "cacheExploreFeed": "Keşfet Akışı Önbelleği", + "cacheExploreFeed": "Explore feed cache", "@cacheExploreFeed": { "description": "Cache item title for explore home feed cache" }, - "cacheExploreFeedDesc": "Keşfet sekmesindeki (yeni çıkanlar vb.) içerikler. Silerseniz sayfayı açtığınızda yenilenir.", + "cacheExploreFeedDesc": "Explore tab content (new releases, trending). Will refresh on next visit.", "@cacheExploreFeedDesc": { "description": "Description of what explore feed cache contains" }, - "cacheTrackLookup": "Şarkı Kimliği Önbelleği", + "cacheTrackLookup": "Track lookup cache", "@cacheTrackLookup": { "description": "Cache item title for track ID lookup cache" }, - "cacheTrackLookupDesc": "Spotify/Deezer ID eşleşmeleri. Temizlerseniz ilk birkaç aramanız biraz yavaşlayabilir.", + "cacheTrackLookupDesc": "Spotify/Deezer track ID lookups. Clearing may slow next few searches.", "@cacheTrackLookupDesc": { "description": "Description of what track lookup cache contains" }, - "cacheCleanupUnusedDesc": "Artık cihazınızda var olmayan dosyaların geçmiş kayıtlarını ve kitaplık verilerini temizler.", + "cacheCleanupUnusedDesc": "Remove orphaned download history and library entries for missing files.", "@cacheCleanupUnusedDesc": { "description": "Description of what cleanup unused data does" }, - "cacheNoData": "Veri yok", + "cacheNoData": "No cached data", "@cacheNoData": { "description": "Label when cache category has no data" }, - "cacheSizeWithFiles": "{size} ({count} dosya)", + "cacheSizeWithFiles": "{size} in {count} files", "@cacheSizeWithFiles": { "description": "Cache size and file count", "placeholders": { @@ -2786,7 +2919,7 @@ } } }, - "cacheEntries": "{count} kayıt", + "cacheEntries": "{count} entries", "@cacheEntries": { "description": "Track cache entry count", "placeholders": { @@ -2795,7 +2928,7 @@ } } }, - "cacheClearSuccess": "Temizlendi: {target}", + "cacheClearSuccess": "Cleared: {target}", "@cacheClearSuccess": { "description": "Snackbar after clearing selected cache", "placeholders": { @@ -2804,11 +2937,11 @@ } } }, - "cacheClearConfirmTitle": "Önbelleği Temizle?", + "cacheClearConfirmTitle": "Clear cache?", "@cacheClearConfirmTitle": { "description": "Dialog title before clearing one cache category" }, - "cacheClearConfirmMessage": "Sadece \"{target}\" için olan önbellek silinecek. İndirdiğiniz hiçbir müzik dosyasına dokunulmayacak.", + "cacheClearConfirmMessage": "This will clear cached data for {target}. Downloaded music files will not be deleted.", "@cacheClearConfirmMessage": { "description": "Dialog message before clearing selected cache", "placeholders": { @@ -2817,27 +2950,27 @@ } } }, - "cacheClearAllConfirmTitle": "Tüm Önbelleği Temizle?", + "cacheClearAllConfirmTitle": "Clear all cache?", "@cacheClearAllConfirmTitle": { "description": "Dialog title before clearing all caches" }, - "cacheClearAllConfirmMessage": "Bu sayfadaki tüm önbellek kategorileri temizlenecek. İndirdiğiniz müzik dosyaları kesinlikle SİLİNMEYECEK.", + "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": "Tüm Önbelleği Temizle", + "cacheClearAll": "Clear all cache", "@cacheClearAll": { "description": "Button label to clear all caches" }, - "cacheCleanupUnused": "Gereksiz Dosyaları Temizle", + "cacheCleanupUnused": "Cleanup unused data", "@cacheCleanupUnused": { "description": "Action title for cleaning unused entries" }, - "cacheCleanupUnusedSubtitle": "Cihazda olmayan dosyalara ait geçmiş ve kitaplık kayıtlarını kaldırır", + "cacheCleanupUnusedSubtitle": "Remove orphaned download history and missing library entries", "@cacheCleanupUnusedSubtitle": { "description": "Subtitle for cleanup unused data action" }, - "cacheCleanupResult": "Temizlik Bitti: {downloadCount} geçersiz geçmiş, {libraryCount} eksik kitaplık kaydı kaldırıldı", + "cacheCleanupResult": "Cleanup completed: {downloadCount} orphaned downloads, {libraryCount} missing library entries", "@cacheCleanupResult": { "description": "Snackbar after unused data cleanup", "placeholders": { @@ -2849,43 +2982,75 @@ } } }, - "cacheRefreshStats": "Boyutları Yenile", + "cacheRefreshStats": "Refresh stats", "@cacheRefreshStats": { "description": "Button label to refresh cache statistics" }, - "trackSaveCoverArt": "Albüm Kapağını Kaydet", + "trackSaveCoverArt": "Save Cover Art", "@trackSaveCoverArt": { "description": "Menu action - save album cover art as file" }, - "trackSaveCoverArtSubtitle": "Albüm kapağını resim (.jpg) dosyası olarak dışa aktar", + "trackSaveCoverArtSubtitle": "Save album art as .jpg file", "@trackSaveCoverArtSubtitle": { "description": "Subtitle for save cover art action" }, - "trackSaveLyrics": "Şarkı Sözlerini Kaydet (.lrc)", + "trackSaveLyrics": "Save Lyrics (.lrc)", "@trackSaveLyrics": { "description": "Menu action - save lyrics as .lrc file" }, - "trackSaveLyricsSubtitle": "Şarkı sözlerini çekip .lrc dosyası olarak kaydeder", + "trackSaveLyricsSubtitle": "Fetch and save lyrics as .lrc file", "@trackSaveLyricsSubtitle": { "description": "Subtitle for save lyrics action" }, - "trackSaveLyricsProgress": "Şarkı sözleri kaydediliyor...", + "trackSaveLyricsProgress": "Saving lyrics...", "@trackSaveLyricsProgress": { "description": "Snackbar while saving lyrics to file" }, - "trackReEnrich": "Bilgileri İnternetten Güncelle (Re-enrich)", + "trackReEnrich": "Re-enrich", "@trackReEnrich": { "description": "Menu action - re-embed metadata into audio file" }, - "trackReEnrichOnlineSubtitle": "İnternetten şarkı verilerini (metadata) bulup dosyaya yeniden işler", + "trackReEnrichOnlineSubtitle": "Search metadata online and embed into file", "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, - "trackEditMetadata": "Şarkı Bilgilerini Düzenle", + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, + "trackEditMetadata": "Edit Metadata", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" }, - "trackCoverSaved": "Kapak resmi \"{fileName}\" adıyla kaydedildi", + "trackCoverSaved": "Cover art saved to {fileName}", "@trackCoverSaved": { "description": "Snackbar after cover art saved", "placeholders": { @@ -2894,11 +3059,11 @@ } } }, - "trackCoverNoSource": "Geçerli bir kapak resmi kaynağı bulunamadı", + "trackCoverNoSource": "No cover art source available", "@trackCoverNoSource": { "description": "Snackbar when no cover art URL or embedded cover" }, - "trackLyricsSaved": "Sözler \"{fileName}\" adıyla kaydedildi", + "trackLyricsSaved": "Lyrics saved to {fileName}", "@trackLyricsSaved": { "description": "Snackbar after lyrics saved", "placeholders": { @@ -2907,27 +3072,27 @@ } } }, - "trackReEnrichProgress": "Şarkı bilgileri (metadata) güncelleniyor...", + "trackReEnrichProgress": "Re-enriching metadata...", "@trackReEnrichProgress": { "description": "Snackbar while re-enriching metadata" }, - "trackReEnrichSearching": "İnternette şarkı bilgisi (metadata) aranıyor...", + "trackReEnrichSearching": "Searching metadata online...", "@trackReEnrichSearching": { "description": "Snackbar while searching metadata from internet for local items" }, - "trackReEnrichSuccess": "Şarkı bilgileri dosyaya başarıyla işlendi", + "trackReEnrichSuccess": "Metadata re-enriched successfully", "@trackReEnrichSuccess": { "description": "Snackbar after successful re-enrichment" }, - "trackReEnrichFfmpegFailed": "Sözleri (veya verileri) dosyaya yazarken hata oluştu", + "trackReEnrichFfmpegFailed": "FFmpeg metadata embed failed", "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, - "queueFlacAction": "Bunu FLAC Olarak İndir", + "queueFlacAction": "Queue FLAC", "@queueFlacAction": { "description": "Action/button label for queueing FLAC redownloads for local tracks" }, - "queueFlacConfirmMessage": "Seçilen şarkılar için internette FLAC eşleşmesi aranacak ve indirme sırasına eklenecek.\n\nMevcut dosyalarınıza dokunulmayacak veya silinmeyecek.\n\nSadece yüksek oranda eşleşenler otomatik olarak sıraya eklenir.\n\n{count} şarkı seçildi", + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", "@queueFlacConfirmMessage": { "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", "placeholders": { @@ -2936,7 +3101,7 @@ } } }, - "queueFlacFindingProgress": "FLAC eşleşmeleri aranıyor... ({current}/{total})", + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", "@queueFlacFindingProgress": { "description": "Snackbar while resolving remote matches for local FLAC redownloads", "placeholders": { @@ -2948,11 +3113,11 @@ } } }, - "queueFlacNoReliableMatches": "Seçiminiz için internette güvenilir bir eşleşme bulunamadı", + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", "@queueFlacNoReliableMatches": { "description": "Snackbar when no safe FLAC redownload matches were found" }, - "queueFlacQueuedWithSkipped": "{addedCount} şarkı sıraya eklendi, {skippedCount} şarkı eşleşmediği için atlandı", + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", "@queueFlacQueuedWithSkipped": { "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", "placeholders": { @@ -2964,7 +3129,7 @@ } } }, - "trackSaveFailed": "İşlem başarısız: {error}", + "trackSaveFailed": "Failed: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", "placeholders": { @@ -2973,31 +3138,31 @@ } } }, - "trackConvertFormat": "Ses Formatını Dönüştür", + "trackConvertFormat": "Convert Format", "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Dosyayı MP3, Opus, ALAC veya FLAC formatına çevirin", + "trackConvertFormatSubtitle": "Convert to MP3, Opus, ALAC, or FLAC", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, - "trackConvertTitle": "Sesi Dönüştür", + "trackConvertTitle": "Convert Audio", "@trackConvertTitle": { "description": "Title of convert bottom sheet" }, - "trackConvertTargetFormat": "Hedef Format", + "trackConvertTargetFormat": "Target Format", "@trackConvertTargetFormat": { "description": "Label for format selection" }, - "trackConvertBitrate": "Bit Hızı (Kalite)", + "trackConvertBitrate": "Bitrate", "@trackConvertBitrate": { "description": "Label for bitrate selection" }, - "trackConvertConfirmTitle": "Dönüşümü Onayla", + "trackConvertConfirmTitle": "Confirm Conversion", "@trackConvertConfirmTitle": { "description": "Confirmation dialog title" }, - "trackConvertConfirmMessage": "{sourceFormat} formatından {targetFormat} formatına ({bitrate}) dönüştürülsün mü?\n\nDönüşüm bittikten sonra orijinal dosya tamamen silinecektir.", + "trackConvertConfirmMessage": "Convert from {sourceFormat} to {targetFormat} at {bitrate}?\n\nThe original file will be deleted after conversion.", "@trackConvertConfirmMessage": { "description": "Confirmation dialog message", "placeholders": { @@ -3012,7 +3177,7 @@ } } }, - "trackConvertConfirmMessageLossless": "{sourceFormat} formatından {targetFormat} formatına dönüştürülsün mü? (Kayıpsız format, kalite kaybı yaşanmaz)\n\nDönüşüm bittikten sonra orijinal dosya tamamen silinecektir.", + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", "@trackConvertConfirmMessageLossless": { "description": "Confirmation dialog message for lossless-to-lossless conversion", "placeholders": { @@ -3024,15 +3189,15 @@ } } }, - "trackConvertLosslessHint": "Kayıpsız bir formata dönüştürülüyor (Kalite düşüşü olmaz)", + "trackConvertLosslessHint": "Lossless conversion — no quality loss", "@trackConvertLosslessHint": { "description": "Hint shown when converting between lossless formats" }, - "trackConvertConverting": "Ses dönüştürülüyor...", + "trackConvertConverting": "Converting audio...", "@trackConvertConverting": { "description": "Snackbar while converting" }, - "trackConvertSuccess": "Dosya başarıyla {format} formatına çevrildi", + "trackConvertSuccess": "Converted to {format} successfully", "@trackConvertSuccess": { "description": "Snackbar after successful conversion", "placeholders": { @@ -3041,19 +3206,19 @@ } } }, - "trackConvertFailed": "Dönüşüm işlemi başarısız oldu", + "trackConvertFailed": "Conversion failed", "@trackConvertFailed": { "description": "Snackbar when conversion fails" }, - "cueSplitTitle": "CUE Dosyasını Parçalara Böl", + "cueSplitTitle": "Split CUE Sheet", "@cueSplitTitle": { "description": "Title for CUE split bottom sheet" }, - "cueSplitSubtitle": "Tek parça olan CUE+FLAC dosyasını ayrı şarkılara böler", + "cueSplitSubtitle": "Split CUE+FLAC into individual tracks", "@cueSplitSubtitle": { "description": "Subtitle for CUE split menu item" }, - "cueSplitAlbum": "Albüm: {album}", + "cueSplitAlbum": "Album: {album}", "@cueSplitAlbum": { "description": "Album name in CUE split sheet", "placeholders": { @@ -3062,7 +3227,7 @@ } } }, - "cueSplitArtist": "Sanatçı: {artist}", + "cueSplitArtist": "Artist: {artist}", "@cueSplitArtist": { "description": "Artist name in CUE split sheet", "placeholders": { @@ -3071,7 +3236,7 @@ } } }, - "cueSplitTrackCount": "{count} şarkı var", + "cueSplitTrackCount": "{count} tracks", "@cueSplitTrackCount": { "description": "Number of tracks in CUE sheet", "placeholders": { @@ -3080,11 +3245,11 @@ } } }, - "cueSplitConfirmTitle": "CUE Dosyasını Böl", + "cueSplitConfirmTitle": "Split CUE Album", "@cueSplitConfirmTitle": { "description": "CUE split confirmation dialog title" }, - "cueSplitConfirmMessage": "\"{album}\" albümünü {count} ayrı FLAC dosyasına bölmek istiyor musunuz?\n\nYeni dosyalar orijinal dosyanın bulunduğu klasöre kaydedilecektir.", + "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": { @@ -3096,7 +3261,7 @@ } } }, - "cueSplitSplitting": "CUE dosyası ayrıştırılıyor... ({current}/{total})", + "cueSplitSplitting": "Splitting CUE sheet... ({current}/{total})", "@cueSplitSplitting": { "description": "Snackbar while splitting CUE", "placeholders": { @@ -3108,7 +3273,7 @@ } } }, - "cueSplitSuccess": "Dosya başarıyla {count} şarkıya bölündü", + "cueSplitSuccess": "Split into {count} tracks successfully", "@cueSplitSuccess": { "description": "Snackbar after successful CUE split", "placeholders": { @@ -3117,59 +3282,59 @@ } } }, - "cueSplitFailed": "CUE bölme işlemi başarısız", + "cueSplitFailed": "CUE split failed", "@cueSplitFailed": { "description": "Snackbar when CUE split fails" }, - "cueSplitNoAudioFile": "Bu CUE ile eşleşen bir ses dosyası bulunamadı", + "cueSplitNoAudioFile": "Audio file not found for this CUE sheet", "@cueSplitNoAudioFile": { "description": "Error when CUE audio file is missing" }, - "cueSplitButton": "Şarkılara Böl", + "cueSplitButton": "Split into Tracks", "@cueSplitButton": { "description": "Button text to start CUE splitting" }, - "actionCreate": "Oluştur", + "actionCreate": "Create", "@actionCreate": { "description": "Generic action button - create" }, - "collectionFoldersTitle": "Klasörlerim", + "collectionFoldersTitle": "My folders", "@collectionFoldersTitle": { "description": "Library section title for custom folders" }, - "collectionWishlist": "İstek Listesi", + "collectionWishlist": "Wishlist", "@collectionWishlist": { "description": "Custom folder for saved tracks to download later" }, - "collectionLoved": "Favoriler", + "collectionLoved": "Loved", "@collectionLoved": { "description": "Custom folder for favorite tracks" }, - "collectionPlaylists": "Çalma Listeleri", + "collectionPlaylists": "Playlists", "@collectionPlaylists": { "description": "Custom user playlists folder" }, - "collectionPlaylist": "Çalma Listesi", + "collectionPlaylist": "Playlist", "@collectionPlaylist": { "description": "Single playlist label" }, - "collectionAddToPlaylist": "Çalma listesine ekle", + "collectionAddToPlaylist": "Add to playlist", "@collectionAddToPlaylist": { "description": "Action to add a track to user playlist" }, - "collectionCreatePlaylist": "Yeni çalma listesi oluştur", + "collectionCreatePlaylist": "Create playlist", "@collectionCreatePlaylist": { "description": "Action to create a new playlist" }, - "collectionNoPlaylistsYet": "Henüz listeniz yok", + "collectionNoPlaylistsYet": "No playlists yet", "@collectionNoPlaylistsYet": { "description": "Empty state title when user has no playlists" }, - "collectionNoPlaylistsSubtitle": "Müziklerinizi kategorize etmek için bir çalma listesi oluşturun", + "collectionNoPlaylistsSubtitle": "Create a playlist to start categorizing tracks", "@collectionNoPlaylistsSubtitle": { "description": "Empty state subtitle when user has no playlists" }, - "collectionPlaylistTracks": "{count, plural, =1{1 şarkı} other{{count} şarkı}}", + "collectionPlaylistTracks": "{count, plural, =1{1 track} other{{count} tracks}}", "@collectionPlaylistTracks": { "description": "Track count label for custom playlists", "placeholders": { @@ -3178,7 +3343,7 @@ } } }, - "collectionAddedToPlaylist": "\"{playlistName}\" listesine eklendi", + "collectionAddedToPlaylist": "Added to \"{playlistName}\"", "@collectionAddedToPlaylist": { "description": "Snackbar after adding track to playlist", "placeholders": { @@ -3187,7 +3352,7 @@ } } }, - "collectionAlreadyInPlaylist": "Zaten \"{playlistName}\" listesinde var", + "collectionAlreadyInPlaylist": "Already in \"{playlistName}\"", "@collectionAlreadyInPlaylist": { "description": "Snackbar when track already exists in playlist", "placeholders": { @@ -3196,27 +3361,27 @@ } } }, - "collectionPlaylistCreated": "Çalma listesi oluşturuldu", + "collectionPlaylistCreated": "Playlist created", "@collectionPlaylistCreated": { "description": "Snackbar after creating playlist" }, - "collectionPlaylistNameHint": "Çalma listesi adı", + "collectionPlaylistNameHint": "Playlist name", "@collectionPlaylistNameHint": { "description": "Hint text for playlist name input" }, - "collectionPlaylistNameRequired": "Lütfen liste için bir isim girin", + "collectionPlaylistNameRequired": "Playlist name is required", "@collectionPlaylistNameRequired": { "description": "Validation error for empty playlist name" }, - "collectionRenamePlaylist": "Yeniden adlandır", + "collectionRenamePlaylist": "Rename playlist", "@collectionRenamePlaylist": { "description": "Action to rename playlist" }, - "collectionDeletePlaylist": "Listeyi sil", + "collectionDeletePlaylist": "Delete playlist", "@collectionDeletePlaylist": { "description": "Action to delete playlist" }, - "collectionDeletePlaylistMessage": "\"{playlistName}\" listesini ve içindeki tüm şarkıları silmek istiyor musunuz?", + "collectionDeletePlaylistMessage": "Delete \"{playlistName}\" and all tracks inside it?", "@collectionDeletePlaylistMessage": { "description": "Confirmation message for deleting playlist", "placeholders": { @@ -3225,47 +3390,47 @@ } } }, - "collectionPlaylistDeleted": "Çalma listesi silindi", + "collectionPlaylistDeleted": "Playlist deleted", "@collectionPlaylistDeleted": { "description": "Snackbar after deleting playlist" }, - "collectionPlaylistRenamed": "Çalma listesi adı değiştirildi", + "collectionPlaylistRenamed": "Playlist renamed", "@collectionPlaylistRenamed": { "description": "Snackbar after renaming playlist" }, - "collectionWishlistEmptyTitle": "İstek Listeniz boş", + "collectionWishlistEmptyTitle": "Wishlist is empty", "@collectionWishlistEmptyTitle": { "description": "Wishlist empty state title" }, - "collectionWishlistEmptySubtitle": "Daha sonra indirmek istediğiniz şarkıların yanındaki (+) simgesine dokunun", + "collectionWishlistEmptySubtitle": "Tap + on tracks to save what you want to download later", "@collectionWishlistEmptySubtitle": { "description": "Wishlist empty state subtitle" }, - "collectionLovedEmptyTitle": "Favori klasörünüz boş", + "collectionLovedEmptyTitle": "Loved folder is empty", "@collectionLovedEmptyTitle": { "description": "Loved empty state title" }, - "collectionLovedEmptySubtitle": "Sevdiğiniz şarkıları burada toplamak için kalp ikonuna dokunun", + "collectionLovedEmptySubtitle": "Tap love on tracks to keep your favorites", "@collectionLovedEmptySubtitle": { "description": "Loved empty state subtitle" }, - "collectionPlaylistEmptyTitle": "Bu çalma listesi boş", + "collectionPlaylistEmptyTitle": "Playlist is empty", "@collectionPlaylistEmptyTitle": { "description": "Playlist empty state title" }, - "collectionPlaylistEmptySubtitle": "Buraya eklemek için istediğiniz şarkının üzerindeki (+) butonuna basılı tutun", + "collectionPlaylistEmptySubtitle": "Long-press + on any track to add it here", "@collectionPlaylistEmptySubtitle": { "description": "Playlist empty state subtitle" }, - "collectionRemoveFromPlaylist": "Çalma listesinden çıkar", + "collectionRemoveFromPlaylist": "Remove from playlist", "@collectionRemoveFromPlaylist": { "description": "Tooltip for removing track from playlist" }, - "collectionRemoveFromFolder": "Klasörden çıkar", + "collectionRemoveFromFolder": "Remove from folder", "@collectionRemoveFromFolder": { "description": "Tooltip for removing track from wishlist/loved folder" }, - "collectionRemoved": "\"{trackName}\" listeden çıkarıldı", + "collectionRemoved": "\"{trackName}\" removed", "@collectionRemoved": { "description": "Snackbar after removing a track from a collection", "placeholders": { @@ -3274,7 +3439,7 @@ } } }, - "collectionAddedToLoved": "\"{trackName}\" Favoriler klasörüne eklendi", + "collectionAddedToLoved": "\"{trackName}\" added to Loved", "@collectionAddedToLoved": { "description": "Snackbar after adding track to loved folder", "placeholders": { @@ -3283,7 +3448,7 @@ } } }, - "collectionRemovedFromLoved": "\"{trackName}\" Favorilerinizden çıkarıldı", + "collectionRemovedFromLoved": "\"{trackName}\" removed from Loved", "@collectionRemovedFromLoved": { "description": "Snackbar after removing track from loved folder", "placeholders": { @@ -3292,7 +3457,7 @@ } } }, - "collectionAddedToWishlist": "\"{trackName}\" İstek Listenize eklendi", + "collectionAddedToWishlist": "\"{trackName}\" added to Wishlist", "@collectionAddedToWishlist": { "description": "Snackbar after adding track to wishlist", "placeholders": { @@ -3301,7 +3466,7 @@ } } }, - "collectionRemovedFromWishlist": "\"{trackName}\" İstek Listenizden çıkarıldı", + "collectionRemovedFromWishlist": "\"{trackName}\" removed from Wishlist", "@collectionRemovedFromWishlist": { "description": "Snackbar after removing track from wishlist", "placeholders": { @@ -3310,31 +3475,31 @@ } } }, - "trackOptionAddToLoved": "Favorilere Ekle", + "trackOptionAddToLoved": "Add to Loved", "@trackOptionAddToLoved": { "description": "Bottom sheet action label - add track to loved folder" }, - "trackOptionRemoveFromLoved": "Favorilerden Çıkar", + "trackOptionRemoveFromLoved": "Remove from Loved", "@trackOptionRemoveFromLoved": { "description": "Bottom sheet action label - remove track from loved folder" }, - "trackOptionAddToWishlist": "İstek Listesine Ekle", + "trackOptionAddToWishlist": "Add to Wishlist", "@trackOptionAddToWishlist": { "description": "Bottom sheet action label - add track to wishlist" }, - "trackOptionRemoveFromWishlist": "İstek Listesinden Çıkar", + "trackOptionRemoveFromWishlist": "Remove from Wishlist", "@trackOptionRemoveFromWishlist": { "description": "Bottom sheet action label - remove track from wishlist" }, - "collectionPlaylistChangeCover": "Kapak resmini değiştir", + "collectionPlaylistChangeCover": "Change cover image", "@collectionPlaylistChangeCover": { "description": "Bottom sheet action to pick a custom cover image for a playlist" }, - "collectionPlaylistRemoveCover": "Kapak resmini kaldır", + "collectionPlaylistRemoveCover": "Remove cover image", "@collectionPlaylistRemoveCover": { "description": "Bottom sheet action to remove custom cover image from a playlist" }, - "selectionShareCount": "{count} {count, plural, =1{şarkıyı} other{şarkıyı}} paylaş", + "selectionShareCount": "Share {count} {count, plural, =1{track} other{tracks}}", "@selectionShareCount": { "description": "Share button text with count in selection mode", "placeholders": { @@ -3343,11 +3508,11 @@ } } }, - "selectionShareNoFiles": "Paylaşılabilir bir dosya bulunamadı", + "selectionShareNoFiles": "No shareable files found", "@selectionShareNoFiles": { "description": "Snackbar when no selected files exist on disk" }, - "selectionConvertCount": "{count} {count, plural, =1{şarkıyı} other{şarkıyı}} dönüştür", + "selectionConvertCount": "Convert {count} {count, plural, =1{track} other{tracks}}", "@selectionConvertCount": { "description": "Convert button text with count in selection mode", "placeholders": { @@ -3356,15 +3521,15 @@ } } }, - "selectionConvertNoConvertible": "Dönüştürülebilir formatta bir şarkı seçilmedi", + "selectionConvertNoConvertible": "No convertible tracks selected", "@selectionConvertNoConvertible": { "description": "Snackbar when no selected tracks support conversion" }, - "selectionBatchConvertConfirmTitle": "Toplu Dönüştürme", + "selectionBatchConvertConfirmTitle": "Batch Convert", "@selectionBatchConvertConfirmTitle": { "description": "Confirmation dialog title for batch conversion" }, - "selectionBatchConvertConfirmMessage": "{count} {count, plural, =1{şarkıyı} other{şarkıyı}} {format} formatına ({bitrate}) dönüştürmek istiyor musunuz?\n\nDönüşüm işlemi bittikten sonra orijinal dosyalar tamamen silinecektir.", + "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": { @@ -3379,7 +3544,7 @@ } } }, - "selectionBatchConvertConfirmMessageLossless": "{count} {count, plural, =1{şarkıyı} other{şarkıyı}} {format} formatına dönüştürmek istiyor musunuz? (Kayıpsız işlem — kalite kaybı olmaz)\n\nDönüşüm işlemi bittikten sonra orijinal dosyalar tamamen silinecektir.", + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", "@selectionBatchConvertConfirmMessageLossless": { "description": "Confirmation dialog message for lossless batch conversion", "placeholders": { @@ -3391,7 +3556,7 @@ } } }, - "selectionBatchConvertProgress": "Dönüştürülüyor: {current} / {total}...", + "selectionBatchConvertProgress": "Converting {current} of {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", "placeholders": { @@ -3403,7 +3568,7 @@ } } }, - "selectionBatchConvertSuccess": "{total} şarkıdan {success} tanesi {format} formatına dönüştürüldü", + "selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}", "@selectionBatchConvertSuccess": { "description": "Snackbar after batch conversion completes", "placeholders": { @@ -3418,7 +3583,7 @@ } } }, - "downloadedAlbumDownloadedCount": "{count} tanesi indirildi", + "downloadedAlbumDownloadedCount": "{count} downloaded", "@downloadedAlbumDownloadedCount": { "description": "Downloaded tracks count badge", "placeholders": { @@ -3427,27 +3592,27 @@ } } }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Sanatçı klasörleri için Albüm Sanatçısı adı kullanılır", + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { "description": "Subtitle when Album Artist is used for folder naming" }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Sanatçı klasörleri için sadece Şarkı Sanatçısı adı kullanılır", + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" }, - "lyricsProvidersTitle": "Şarkı Sözü Sağlayıcıları", + "lyricsProvidersTitle": "Lyrics Providers", "@lyricsProvidersTitle": { "description": "Title for the lyrics provider priority page" }, - "lyricsProvidersDescription": "Şarkı sözü kaynaklarını açıp kapatın veya sıralamalarını değiştirin. Uygulama sözleri bulana kadar sağlayıcıları yukarıdan aşağıya doğru sırayla dener.", + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", "@lyricsProvidersDescription": { "description": "Description on the lyrics provider priority page" }, - "lyricsProvidersInfoText": "Mağazadan yüklediğiniz eklentiler her zaman varsayılan yerleşik sağlayıcılardan önce çalışır. En az bir sağlayıcı her zaman açık kalmalıdır.", + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", "@lyricsProvidersInfoText": { "description": "Info tip on lyrics provider priority page" }, - "lyricsProvidersEnabledSection": "Açık ({count})", + "lyricsProvidersEnabledSection": "Enabled ({count})", "@lyricsProvidersEnabledSection": { "description": "Section header for enabled providers", "placeholders": { @@ -3456,7 +3621,7 @@ } } }, - "lyricsProvidersDisabledSection": "Kapalı ({count})", + "lyricsProvidersDisabledSection": "Disabled ({count})", "@lyricsProvidersDisabledSection": { "description": "Section header for disabled providers", "placeholders": { @@ -3465,79 +3630,75 @@ } } }, - "lyricsProvidersAtLeastOne": "En az bir sağlayıcı her zaman açık kalmalıdır", + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", "@lyricsProvidersAtLeastOne": { "description": "Snackbar when user tries to disable the last enabled provider" }, - "lyricsProvidersSaved": "Şarkı sözü sağlayıcılarının sıralaması kaydedildi", + "lyricsProvidersSaved": "Lyrics provider priority saved", "@lyricsProvidersSaved": { "description": "Snackbar after saving lyrics provider priority" }, - "lyricsProvidersDiscardContent": "Kaydedilmemiş değişiklikleriniz iptal edilecek.", + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", "@lyricsProvidersDiscardContent": { "description": "Body text of the discard-changes dialog on lyrics provider page" }, - "lyricsProviderSpotifyApiDesc": "Topluluk API'si üzerinden senkronize Spotify sözleri", - "@lyricsProviderSpotifyApiDesc": { - "description": "Description for Spotify Lyrics API provider" - }, - "lyricsProviderLrclibDesc": "Açık kaynaklı, senkronize şarkı sözü veritabanı", + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", "@lyricsProviderLrclibDesc": { "description": "Description for LRCLIB provider" }, - "lyricsProviderNeteaseDesc": "NetEase Cloud Music (Özellikle Asya müzikleri için ideal)", + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", "@lyricsProviderNeteaseDesc": { "description": "Description for Netease provider" }, - "lyricsProviderMusixmatchDesc": "En geniş şarkı sözü arşivi (Çok dilli)", + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", "@lyricsProviderMusixmatchDesc": { "description": "Description for Musixmatch provider" }, - "lyricsProviderAppleMusicDesc": "Kelime kelime akan senkronize sözler (Proxy üzerinden)", + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", "@lyricsProviderAppleMusicDesc": { "description": "Description for Apple Music provider" }, - "lyricsProviderQqMusicDesc": "QQ Music (Özellikle Çince şarkılar için, Proxy üzerinden)", + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", "@lyricsProviderQqMusicDesc": { "description": "Description for QQ Music provider" }, - "lyricsProviderExtensionDesc": "Eklenti ile sağlanan kaynak", + "lyricsProviderExtensionDesc": "Extension provider", "@lyricsProviderExtensionDesc": { "description": "Generic description for extension-based lyrics providers" }, - "safMigrationTitle": "Depolama Sistem Güncellemesi", + "safMigrationTitle": "Storage Update Required", "@safMigrationTitle": { "description": "Title of SAF migration dialog" }, - "safMigrationMessage1": "SpotiFLAC artık indirme işlemleri için Android'in yeni Depolama Erişim Çerçevesi'ni (SAF) kullanıyor. Bu sayede Android 10 ve üzeri sürümlerdeki \"izin reddedildi\" hataları ortadan kalkıyor.", + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", "@safMigrationMessage1": { "description": "First paragraph of SAF migration dialog" }, - "safMigrationMessage2": "Yeni depolama sistemine geçiş yapmak için lütfen indirme klasörünüzü tekrar seçin.", + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", "@safMigrationMessage2": { "description": "Second paragraph of SAF migration dialog" }, - "safMigrationSuccess": "İndirme klasörü başarıyla yeni (SAF) moda geçirildi", + "safMigrationSuccess": "Download folder updated to SAF mode", "@safMigrationSuccess": { "description": "Snackbar after successfully migrating to SAF" }, - "settingsDonate": "Bağış Yap", + "settingsDonate": "Donate", "@settingsDonate": { "description": "Settings menu item - donate" }, - "settingsDonateSubtitle": "SpotiFLAC-Mobile gelişimine destek olun", + "settingsDonateSubtitle": "Support SpotiFLAC-Mobile development", "@settingsDonateSubtitle": { "description": "Subtitle for donate menu item" }, - "tooltipLoveAll": "Tümünü Favorilere Ekle", + "tooltipLoveAll": "Love All", "@tooltipLoveAll": { "description": "Tooltip for the Love All button on album/playlist screens" }, - "tooltipAddToPlaylist": "Çalma Listesine Ekle", + "tooltipAddToPlaylist": "Add to Playlist", "@tooltipAddToPlaylist": { "description": "Tooltip for the Add to Playlist button" }, - "snackbarRemovedTracksFromLoved": "{count} şarkı Favoriler'den çıkarıldı", + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", "@snackbarRemovedTracksFromLoved": { "description": "Snackbar after removing multiple tracks from Loved folder", "placeholders": { @@ -3546,7 +3707,7 @@ } } }, - "snackbarAddedTracksToLoved": "{count} şarkı Favoriler'e eklendi", + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", "@snackbarAddedTracksToLoved": { "description": "Snackbar after adding multiple tracks to Loved folder", "placeholders": { @@ -3555,11 +3716,11 @@ } } }, - "dialogDownloadAllTitle": "Tümünü İndir", + "dialogDownloadAllTitle": "Download All", "@dialogDownloadAllTitle": { - "description": "Title of the Download All confirmation dialog" + "description": "Dialog title for bulk download confirmation" }, - "dialogDownloadAllMessage": "Bu listedeki {count} şarkı indirilsin mi?", + "dialogDownloadAllMessage": "Download {count} tracks?", "@dialogDownloadAllMessage": { "description": "Body of the Download All confirmation dialog", "placeholders": { @@ -3568,39 +3729,35 @@ } } }, - "dialogDownload": "İndir", - "@dialogDownload": { - "description": "Confirm button in Download All dialog" - }, - "homeSkipAlreadyDownloaded": "Daha önce inmiş olan şarkıları atla", + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", "@homeSkipAlreadyDownloaded": { "description": "Checkbox label in import dialog to skip already-downloaded songs" }, - "homeGoToAlbum": "Albüme Git", + "homeGoToAlbum": "Go to Album", "@homeGoToAlbum": { "description": "Context menu item to navigate to the album page" }, - "homeAlbumInfoUnavailable": "Albüm bilgisine ulaşılamıyor", + "homeAlbumInfoUnavailable": "Album info not available", "@homeAlbumInfoUnavailable": { "description": "Snackbar when album info cannot be loaded" }, - "snackbarLoadingCueSheet": "CUE dosyası yükleniyor...", + "snackbarLoadingCueSheet": "Loading CUE sheet...", "@snackbarLoadingCueSheet": { "description": "Snackbar while loading a CUE sheet file" }, - "snackbarMetadataSaved": "Şarkı verileri dosyaya başarıyla kaydedildi", + "snackbarMetadataSaved": "Metadata saved successfully", "@snackbarMetadataSaved": { "description": "Snackbar after successfully saving track metadata" }, - "snackbarFailedToEmbedLyrics": "Şarkı sözleri dosyaya eklenemedi", + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", "@snackbarFailedToEmbedLyrics": { "description": "Snackbar when lyrics embedding fails" }, - "snackbarFailedToWriteStorage": "Değişiklikler asıl dosyaya yazılamadı", + "snackbarFailedToWriteStorage": "Failed to write back to storage", "@snackbarFailedToWriteStorage": { "description": "Snackbar when writing metadata back to file fails" }, - "snackbarError": "Hata: {error}", + "snackbarError": "Error: {error}", "@snackbarError": { "description": "Generic error snackbar with error detail", "placeholders": { @@ -3609,199 +3766,195 @@ } } }, - "snackbarNoActionDefined": "Bu buton için henüz bir işlev tanımlanmamış", + "snackbarNoActionDefined": "No action defined for this button", "@snackbarNoActionDefined": { "description": "Snackbar when an extension button has no action configured" }, - "noTracksFoundForAlbum": "Bu albümün içinde hiç şarkı bulunamadı", + "noTracksFoundForAlbum": "No tracks found for this album", "@noTracksFoundForAlbum": { "description": "Empty state message when an album has no tracks" }, - "downloadLocationSubtitle": "İndirdiğiniz dosyaların cihazınızda nasıl tutulacağını seçin.", + "downloadLocationSubtitle": "Choose storage mode for downloaded files.", "@downloadLocationSubtitle": { "description": "Subtitle text in Android download location bottom sheet" }, - "storageModeAppFolder": "Uygulama Klasörü", + "storageModeAppFolder": "App folder (non-SAF)", "@storageModeAppFolder": { "description": "Storage mode option - use legacy app folder" }, - "storageModeAppFolderSubtitle": "Telefonunuzdaki varsayılan Müzik klasörünü kullanır", + "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", "@storageModeAppFolderSubtitle": { "description": "Subtitle for app folder storage mode" }, - "storageModeSaf": "SAF ile Özel Klasör (Önerilen)", + "storageModeSaf": "SAF folder", "@storageModeSaf": { "description": "Storage mode option - use Android SAF picker" }, - "storageModeSafSubtitle": "Android dosya seçicisi ile cihazınızdan dilediğiniz klasörü seçin", + "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", "@storageModeSafSubtitle": { "description": "Subtitle for SAF storage mode" }, - "downloadFilenameDescription": "Şarkıların cihazınızda hangi dosya adıyla kaydedileceğini özelleştirin.", + "downloadFilenameDescription": "Customize how your files are named.", "@downloadFilenameDescription": { "description": "Description text in filename format bottom sheet" }, - "downloadFilenameInsertTag": "Eklemek için dokunun:", + "downloadFilenameInsertTag": "Tap to insert tag:", "@downloadFilenameInsertTag": { "description": "Label above filename tag chips" }, - "downloadSeparateSinglesEnabled": "Sanatçı klasörünün içinde Single'ları ayrı bir klasöre ayırır", + "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", "@downloadSeparateSinglesEnabled": { "description": "Subtitle when separate singles folder is enabled" }, - "downloadSeparateSinglesDisabled": "Single'lar ile albümler aynı yerde durur", + "downloadSeparateSinglesDisabled": "All files in same structure", "@downloadSeparateSinglesDisabled": { "description": "Subtitle when separate singles folder is disabled" }, - "downloadArtistNameFilters": "Sanatçı Adı Filtreleri", + "downloadArtistNameFilters": "Artist Name Filters", "@downloadArtistNameFilters": { "description": "Setting title for artist folder filter options" }, - "downloadCreatePlaylistSourceFolder": "Çalma Listeleri İçin Ana Klasör Oluştur", + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", "@downloadCreatePlaylistSourceFolder": { "description": "Setting title for adding a playlist folder prefix before the normal organization structure" }, - "downloadCreatePlaylistSourceFolderEnabled": "Çalma listesi indirildiğinde en dışa 'Çalma Listesi Adı' isimli bir klasör oluşturur ve içini normal düzeninize göre dizer.", + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", "@downloadCreatePlaylistSourceFolderEnabled": { "description": "Subtitle when playlist source folder prefix is enabled" }, - "downloadCreatePlaylistSourceFolderDisabled": "Çalma listesindeki şarkılar da diğerleri gibi doğrudan albüm ve sanatçı klasörlerinize atılır.", + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", "@downloadCreatePlaylistSourceFolderDisabled": { "description": "Subtitle when playlist source folder prefix is disabled" }, - "downloadCreatePlaylistSourceFolderRedundant": "Klasör Düzeni zaten 'Çalma Listesine Göre' ayarlı olduğu için bu seçenek pasiftir.", + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", "@downloadCreatePlaylistSourceFolderRedundant": { "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" }, - "downloadSongLinkRegion": "SongLink Arama Bölgesi", + "downloadSongLinkRegion": "SongLink Region", "@downloadSongLinkRegion": { "description": "Setting title for SongLink country region" }, - "downloadNetworkCompatibilityMode": "Ağ Uyumluluk Modu", + "downloadNetworkCompatibilityMode": "Network compatibility mode", "@downloadNetworkCompatibilityMode": { "description": "Setting title for network compatibility toggle" }, - "downloadNetworkCompatibilityModeEnabled": "Açık: Bağlantı HTTP ile denenir ve geçersiz sertifikalar kabul edilir (Güvensiz ama çözümleyici)", + "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", "@downloadNetworkCompatibilityModeEnabled": { "description": "Subtitle when network compatibility mode is enabled" }, - "downloadNetworkCompatibilityModeDisabled": "Kapalı: Katı HTTPS kuralları uygulanır (Önerilen)", + "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", "@downloadNetworkCompatibilityModeDisabled": { "description": "Subtitle when network compatibility mode is disabled" }, - "downloadSelectServiceToEnable": "Seçenekleri açmak için yerleşik bir sağlayıcı seçin", + "downloadSelectServiceToEnable": "Select a built-in service to enable", "@downloadSelectServiceToEnable": { "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" }, - "downloadSelectTidalQobuz": "Kaliteyi ayarlamak için lütfen yukarıdan Tidal veya Qobuz seçin", + "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", "@downloadSelectTidalQobuz": { "description": "Info hint when non-Tidal/Qobuz service is selected" }, - "downloadEmbedLyricsDisabled": "Şarkı Verilerini Dosyaya Gömme ayarı kapalıyken kullanılamaz", + "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", "@downloadEmbedLyricsDisabled": { "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" }, - "downloadNeteaseIncludeTranslation": "Netease: Çevirileri Dahil Et", + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", "@downloadNeteaseIncludeTranslation": { "description": "Toggle title for including Netease translated lyrics" }, - "downloadNeteaseIncludeTranslationEnabled": "Varsa, orijinal sözlere çevirilerini ekler", + "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", "@downloadNeteaseIncludeTranslationEnabled": { "description": "Subtitle when Netease translation is enabled" }, - "downloadNeteaseIncludeTranslationDisabled": "Sadece şarkının kendi sözleri kullanılır", + "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", "@downloadNeteaseIncludeTranslationDisabled": { "description": "Subtitle when Netease translation is disabled" }, - "downloadNeteaseIncludeRomanization": "Netease: Okunuşları (Romanizasyon) Dahil Et", + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", "@downloadNeteaseIncludeRomanization": { "description": "Toggle title for including Netease romanized lyrics" }, - "downloadNeteaseIncludeRomanizationEnabled": "Varsa, Asya şarkıları için Latin alfabesi okunuşlarını ekler", + "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", "@downloadNeteaseIncludeRomanizationEnabled": { "description": "Subtitle when Netease romanization is enabled" }, - "downloadNeteaseIncludeRomanizationDisabled": "Kapalı", + "downloadNeteaseIncludeRomanizationDisabled": "Disabled", "@downloadNeteaseIncludeRomanizationDisabled": { "description": "Subtitle when Netease romanization is disabled" }, - "downloadAppleQqMultiPerson": "Apple/QQ: Çoklu Sanatçı Düzeni (Kelime kelime akan sözler)", + "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", "@downloadAppleQqMultiPerson": { "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" }, - "downloadAppleQqMultiPersonEnabled": "Gelişmiş v1/v2 ve arka plan [bg:] etiketlerini açık tutar", + "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", "@downloadAppleQqMultiPersonEnabled": { "description": "Subtitle when multi-person word-by-word is enabled" }, - "downloadAppleQqMultiPersonDisabled": "Standart kelime kelime senkronizasyon kullanır", + "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", "@downloadAppleQqMultiPersonDisabled": { "description": "Subtitle when multi-person word-by-word is disabled" }, - "downloadMusixmatchLanguage": "Musixmatch Tercih Edilen Dil", + "downloadMusixmatchLanguage": "Musixmatch Language", "@downloadMusixmatchLanguage": { "description": "Setting title for Musixmatch language preference" }, - "downloadMusixmatchLanguageAuto": "Otomatik (Orijinal Dil)", + "downloadMusixmatchLanguageAuto": "Auto (original)", "@downloadMusixmatchLanguageAuto": { "description": "Option label when Musixmatch uses original language" }, - "downloadFilterContributing": "Albüm Sanatçısı etiketinde konuk sanatçıları filtrele", + "downloadFilterContributing": "Filter contributing artists in Album Artist", "@downloadFilterContributing": { "description": "Toggle title for filtering contributing artists in Album Artist metadata" }, - "downloadFilterContributingEnabled": "Albüm Sanatçısı verisinde sadece ana sanatçı ismi tutulur", + "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", "@downloadFilterContributingEnabled": { "description": "Subtitle when contributing artist filter is enabled" }, - "downloadFilterContributingDisabled": "Tüm sanatçı isimlerini olduğu gibi korur", + "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", "@downloadFilterContributingDisabled": { "description": "Subtitle when contributing artist filter is disabled" }, - "downloadProvidersNoneEnabled": "Hiçbir sağlayıcı açık değil", + "downloadProvidersNoneEnabled": "None enabled", "@downloadProvidersNoneEnabled": { "description": "Subtitle for lyrics providers setting when no providers are enabled" }, - "downloadMusixmatchLanguageCode": "Dil Kodu", + "downloadMusixmatchLanguageCode": "Language code", "@downloadMusixmatchLanguageCode": { "description": "Label for the Musixmatch language code text field" }, - "downloadMusixmatchLanguageHint": "auto / tr / en / es", + "downloadMusixmatchLanguageHint": "auto / en / es / ja", "@downloadMusixmatchLanguageHint": { "description": "Hint text for the Musixmatch language code field" }, - "downloadMusixmatchLanguageDesc": "Tercih ettiğiniz söz dilini belirleyin (örnek: tr, en, es). Otomatik seçim için boş bırakın.", + "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", "@downloadMusixmatchLanguageDesc": { "description": "Description in the Musixmatch language picker" }, - "downloadMusixmatchAuto": "Otomatik", + "downloadMusixmatchAuto": "Auto", "@downloadMusixmatchAuto": { "description": "Button to reset Musixmatch language to automatic" }, - "downloadNetworkAnySubtitle": "Wi-Fi + Mobil Veri üzerinden indir", + "downloadNetworkAnySubtitle": "WiFi + Mobile Data", "@downloadNetworkAnySubtitle": { "description": "Subtitle for 'Any' network mode option" }, - "downloadNetworkWifiOnlySubtitle": "Wi-Fi'dan çıkarsanız indirmeler duraklatılır", + "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", "@downloadNetworkWifiOnlySubtitle": { "description": "Subtitle for 'WiFi only' network mode option" }, - "downloadSongLinkRegionDesc": "Şarkı aramalarında SongLink API'ye iletilecek ülke kodunu belirler.", + "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", "@downloadSongLinkRegionDesc": { "description": "Description in the SongLink region picker" }, - "downloadFolderOrganization": "Klasör Düzeni Seçimi", - "@downloadFolderOrganization": { - "description": "Title of the folder organization picker bottom sheet" - }, - "snackbarUnsupportedAudioFormat": "Bu ses formatı desteklenmiyor", + "snackbarUnsupportedAudioFormat": "Unsupported audio format", "@snackbarUnsupportedAudioFormat": { "description": "Snackbar when the audio format is not supported for the requested operation" }, - "cacheRefresh": "Yenile", + "cacheRefresh": "Refresh", "@cacheRefresh": { "description": "Tooltip for refresh button on cache management page" }, - "dialogDownloadPlaylistsMessage": "{playlistCount} {playlistCount, plural, =1{listeden} other{listeden}} toplam {trackCount} şarkı indirilsin mi?", + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", "@dialogDownloadPlaylistsMessage": { "description": "Dialog message for bulk playlist download confirmation", "placeholders": { @@ -3813,7 +3966,7 @@ } } }, - "bulkDownloadPlaylistsButton": "Seçili {count} {count, plural, =1{listeyi} other{listeyi}} indir", + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", "@bulkDownloadPlaylistsButton": { "description": "Button label for bulk downloading selected playlists", "placeholders": { @@ -3822,15 +3975,15 @@ } } }, - "bulkDownloadSelectPlaylists": "İndirilecek çalma listelerini seçin", + "bulkDownloadSelectPlaylists": "Select playlists to download", "@bulkDownloadSelectPlaylists": { "description": "Button label when no playlists are selected for download" }, - "snackbarSelectedPlaylistsEmpty": "Seçilen çalma listelerinde şarkı yok", + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", "@snackbarSelectedPlaylistsEmpty": { "description": "Snackbar when selected playlists contain no tracks" }, - "playlistsCount": "{count, plural, =1{1 çalma listesi} other{{count} çalma listesi}}", + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", "@playlistsCount": { "description": "Playlist count display", "placeholders": { @@ -3839,27 +3992,27 @@ } } }, - "editMetadataAutoFill": "İnternetten Otomatik Doldur", + "editMetadataAutoFill": "Auto-fill from online", "@editMetadataAutoFill": { "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" }, - "editMetadataAutoFillDesc": "İnternetteki verilerle otomatik doldurulmasını istediğiniz alanları seçin", + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", "@editMetadataAutoFillDesc": { "description": "Description for the auto-fill section" }, - "editMetadataAutoFillFetch": "Bul ve Doldur", + "editMetadataAutoFillFetch": "Fetch & Fill", "@editMetadataAutoFillFetch": { "description": "Button label to fetch online metadata and fill selected fields" }, - "editMetadataAutoFillSearching": "İnternette eşleşme aranıyor...", + "editMetadataAutoFillSearching": "Searching online...", "@editMetadataAutoFillSearching": { "description": "Snackbar shown while searching for online metadata" }, - "editMetadataAutoFillNoResults": "İnternette bu şarkıya uygun bir veri bulunamadı", + "editMetadataAutoFillNoResults": "No matching metadata found online", "@editMetadataAutoFillNoResults": { "description": "Snackbar when online metadata search returns no results" }, - "editMetadataAutoFillDone": "Şarkı verilerinden {count} {count, plural, =1{alan} other{alan}} internetten çekilerek dolduruldu", + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", "@editMetadataAutoFillDone": { "description": "Snackbar confirming how many fields were auto-filled", "placeholders": { @@ -3868,39 +4021,39 @@ } } }, - "editMetadataAutoFillNoneSelected": "Lütfen otomatik doldurulacak en az bir alan seçin", + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", "@editMetadataAutoFillNoneSelected": { "description": "Snackbar when user taps Fetch without selecting any fields" }, - "editMetadataFieldTitle": "Şarkı Adı", + "editMetadataFieldTitle": "Title", "@editMetadataFieldTitle": { "description": "Chip label for title field in auto-fill selector" }, - "editMetadataFieldArtist": "Sanatçı", + "editMetadataFieldArtist": "Artist", "@editMetadataFieldArtist": { "description": "Chip label for artist field in auto-fill selector" }, - "editMetadataFieldAlbum": "Albüm", + "editMetadataFieldAlbum": "Album", "@editMetadataFieldAlbum": { "description": "Chip label for album field in auto-fill selector" }, - "editMetadataFieldAlbumArtist": "Albüm Sanatçısı", + "editMetadataFieldAlbumArtist": "Album Artist", "@editMetadataFieldAlbumArtist": { "description": "Chip label for album artist field in auto-fill selector" }, - "editMetadataFieldDate": "Tarih", + "editMetadataFieldDate": "Date", "@editMetadataFieldDate": { "description": "Chip label for date field in auto-fill selector" }, - "editMetadataFieldTrackNum": "Şarkı Sırası", + "editMetadataFieldTrackNum": "Track #", "@editMetadataFieldTrackNum": { "description": "Chip label for track number field in auto-fill selector" }, - "editMetadataFieldDiscNum": "Disk #", + "editMetadataFieldDiscNum": "Disc #", "@editMetadataFieldDiscNum": { "description": "Chip label for disc number field in auto-fill selector" }, - "editMetadataFieldGenre": "Tür", + "editMetadataFieldGenre": "Genre", "@editMetadataFieldGenre": { "description": "Chip label for genre field in auto-fill selector" }, @@ -3908,27 +4061,27 @@ "@editMetadataFieldIsrc": { "description": "Chip label for ISRC field in auto-fill selector" }, - "editMetadataFieldLabel": "Plak Şirketi", + "editMetadataFieldLabel": "Label", "@editMetadataFieldLabel": { "description": "Chip label for label field in auto-fill selector" }, - "editMetadataFieldCopyright": "Telif Hakkı", + "editMetadataFieldCopyright": "Copyright", "@editMetadataFieldCopyright": { "description": "Chip label for copyright field in auto-fill selector" }, - "editMetadataFieldCover": "Albüm Kapağı", + "editMetadataFieldCover": "Cover Art", "@editMetadataFieldCover": { "description": "Chip label for cover art field in auto-fill selector" }, - "editMetadataSelectAll": "Tümü", + "editMetadataSelectAll": "All", "@editMetadataSelectAll": { "description": "Button to select all fields for auto-fill" }, - "editMetadataSelectEmpty": "Sadece boşlar", + "editMetadataSelectEmpty": "Empty only", "@editMetadataSelectEmpty": { "description": "Button to select only fields that are currently empty" }, - "queueDownloadingCount": "Şu An İnenler ({count})", + "queueDownloadingCount": "Downloading ({count})", "@queueDownloadingCount": { "description": "Header for active downloads section with count", "placeholders": { @@ -3937,15 +4090,15 @@ } } }, - "queueDownloadedHeader": "İnenler", + "queueDownloadedHeader": "Downloaded", "@queueDownloadedHeader": { "description": "Header label for downloaded items section in library" }, - "queueFilteringIndicator": "Filtreleniyor...", + "queueFilteringIndicator": "Filtering...", "@queueFilteringIndicator": { "description": "Shown while filter results are being computed" }, - "queueTrackCount": "{count, plural, =1{1 şarkı} other{{count} şarkı}}", + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", "@queueTrackCount": { "description": "Track count label with plural support", "placeholders": { @@ -3954,7 +4107,7 @@ } } }, - "queueAlbumCount": "{count, plural, =1{1 albüm} other{{count} albüm}}", + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", "@queueAlbumCount": { "description": "Album count label with plural support", "placeholders": { @@ -3963,92 +4116,398 @@ } } }, - "queueEmptyAlbums": "İndirilmiş bir albüm yok", + "queueEmptyAlbums": "No album downloads", "@queueEmptyAlbums": { "description": "Empty state title when no album downloads exist" }, - "queueEmptyAlbumsSubtitle": "Bir albümden birden fazla şarkı indirdiğinizde burada görünecektir.", + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", "@queueEmptyAlbumsSubtitle": { "description": "Empty state subtitle for album downloads" }, - "queueEmptySingles": "İndirilmiş bir şarkı yok", + "queueEmptySingles": "No single downloads", "@queueEmptySingles": { "description": "Empty state title when no single track downloads exist" }, - "queueEmptySinglesSubtitle": "Tek tek indirdiğiniz şarkılar burada görünecektir.", + "queueEmptySinglesSubtitle": "Single track downloads will appear here", "@queueEmptySinglesSubtitle": { "description": "Empty state subtitle for single track downloads" }, - "queueEmptyHistory": "İndirme geçmişi boş", + "queueEmptyHistory": "No download history", "@queueEmptyHistory": { "description": "Empty state title when download history is empty" }, - "queueEmptyHistorySubtitle": "İndirdiğiniz şarkılar başarıyla tamamlandığında burada görünecektir.", + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", "@queueEmptyHistorySubtitle": { "description": "Empty state subtitle for download history" }, - "selectionAllPlaylistsSelected": "Tüm listeler seçildi", + "selectionAllPlaylistsSelected": "All playlists selected", "@selectionAllPlaylistsSelected": { "description": "Shown when all playlists are selected in selection mode" }, - "selectionTapPlaylistsToSelect": "Seçmek için listelere dokunun", + "selectionTapPlaylistsToSelect": "Tap playlists to select", "@selectionTapPlaylistsToSelect": { "description": "Hint shown in playlist selection mode" }, - "selectionSelectPlaylistsToDelete": "Silinecek çalma listelerini seçin", + "selectionSelectPlaylistsToDelete": "Select playlists to delete", "@selectionSelectPlaylistsToDelete": { "description": "Hint shown when no playlists are selected for deletion" }, - "audioAnalysisTitle": "Ses Kalitesi Analizi", + "audioAnalysisTitle": "Audio Quality Analysis", "@audioAnalysisTitle": { "description": "Title for audio analysis section" }, - "audioAnalysisDescription": "Kayıpsız kalite doğrulaması için spektrum analizi yapın", + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", "@audioAnalysisDescription": { "description": "Description for audio analysis tap-to-analyze prompt" }, - "audioAnalysisAnalyzing": "Ses dosyası analiz ediliyor...", + "audioAnalysisAnalyzing": "Analyzing audio...", "@audioAnalysisAnalyzing": { "description": "Loading text while analyzing audio" }, - "audioAnalysisSampleRate": "Örnekleme Hızı (Sample Rate)", + "audioAnalysisSampleRate": "Sample Rate", "@audioAnalysisSampleRate": { "description": "Sample rate metric label" }, - "audioAnalysisBitDepth": "Bit Derinliği", + "audioAnalysisBitDepth": "Bit Depth", "@audioAnalysisBitDepth": { "description": "Bit depth metric label" }, - "audioAnalysisChannels": "Kanal", + "audioAnalysisChannels": "Channels", "@audioAnalysisChannels": { "description": "Channels metric label" }, - "audioAnalysisDuration": "Süre", + "audioAnalysisDuration": "Duration", "@audioAnalysisDuration": { "description": "Duration metric label" }, - "audioAnalysisNyquist": "Nyquist Frekansı", + "audioAnalysisNyquist": "Nyquist", "@audioAnalysisNyquist": { "description": "Nyquist frequency metric label" }, - "audioAnalysisFileSize": "Boyut", + "audioAnalysisFileSize": "Size", "@audioAnalysisFileSize": { "description": "File size metric label" }, - "audioAnalysisDynamicRange": "Dinamik Aralık", + "audioAnalysisDynamicRange": "Dynamic Range", "@audioAnalysisDynamicRange": { "description": "Dynamic range metric label" }, - "audioAnalysisPeak": "Tepe Değeri (Peak)", + "audioAnalysisPeak": "Peak", "@audioAnalysisPeak": { "description": "Peak amplitude metric label" }, - "audioAnalysisRms": "Ortalama Değer (RMS)", + "audioAnalysisRms": "RMS", "@audioAnalysisRms": { "description": "RMS level metric label" }, - "audioAnalysisSamples": "Toplam Örneklem (Samples)", + "audioAnalysisSamples": "Samples", "@audioAnalysisSamples": { "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" } -} +} \ 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 928e2b4b..5b271364 100644 --- a/lib/l10n/arb/app_zh_CN.arb +++ b/lib/l10n/arb/app_zh_CN.arb @@ -17,7 +17,7 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "商店", + "navStore": "Repo", "@navStore": { "description": "Bottom navigation - Extension store tab" }, @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "粘贴 Spotify 链接或按名称搜索", + "homeSubtitle": "Paste a supported URL or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -89,9 +89,17 @@ "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, "downloadFolderOrganization": "文件夹结构", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "外观", "@appearanceTitle": { @@ -150,6 +158,14 @@ } } }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, "optionsSwitchBack": "点击 Deezer 或 Spotify 即可从扩展程序切换回来", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" @@ -190,6 +206,42 @@ "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Artist Tag Mode", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Choose how multiple artists are written into embedded tags.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Single joined value", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Split tags for FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, "optionsConcurrentDownloads": "并行下载数", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" @@ -211,11 +263,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "扩展商店", + "optionsExtensionStore": "Extension Repo", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "在导航中显示商店标签", + "optionsExtensionStoreSubtitle": "Show Repo tab in navigation", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -318,7 +370,7 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "扩展商店", + "storeTitle": "Extension Repo", "@storeTitle": { "description": "Store screen title" }, @@ -675,6 +727,10 @@ "@dialogImport": { "description": "Dialog button - import data" }, + "dialogDownload": "Download", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, "dialogDiscard": "Discard", "@dialogDiscard": { "description": "Dialog button - discard changes" @@ -999,6 +1055,46 @@ "@searchPlaylists": { "description": "Search result category - playlists" }, + "searchSortTitle": "Sort Results", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Default", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Title (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Title (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artist (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artist (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duration (Shortest)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duration (Longest)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Release Date (Oldest)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Release Date (Newest)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, "tooltipPlay": "Play", "@tooltipPlay": { "description": "Tooltip - play button" @@ -1119,6 +1215,18 @@ "@providerPriorityInfo": { "description": "Info tip about fallback behavior" }, + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { "description": "Label for built-in providers (Tidal/Qobuz)" @@ -1479,6 +1587,14 @@ "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, "trackLyricsTimeout": "Request timed out. Try again later.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" @@ -1574,7 +1690,59 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "extensionDefaultProvider": "Default (Deezer/Spotify)", + "storeAddRepoTitle": "Add Extension Repository", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "e.g. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Add Repository", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Change repository", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Extension Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Current repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "New Repository URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionDefaultProvider": "Default (Deezer)", "@extensionDefaultProvider": { "description": "Default search provider option" }, @@ -1713,6 +1881,14 @@ "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, + "extensionsFallbackTitle": "Fallback Extensions", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, "extensionsNoDownloadProvider": "No extensions with download provider", "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" @@ -1773,6 +1949,46 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, + "downloadLossy320": "Lossy 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Lossy Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Best compatibility, ~10MB per track", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Best quality Opus, ~8MB per track", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, "qualityNote": "Actual quality depends on track availability from the service", "@qualityNote": { "description": "Note about quality availability" @@ -1897,6 +2113,14 @@ "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, "downloadedAlbumDeleteSelected": "Delete Selected", "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" @@ -2178,6 +2402,30 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, "libraryActions": "Actions", "@libraryActions": { "description": "Section header for library actions" @@ -2235,6 +2483,15 @@ } } }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -2252,6 +2509,10 @@ "@libraryScanning": { "description": "Status during scan" }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, "libraryScanProgress": "{progress}% of {total} files", "@libraryScanProgress": { "description": "Scan progress display", @@ -2349,6 +2610,30 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -2361,6 +2646,22 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -2447,7 +2748,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "tutorialExtensionsTip1": "Browse the Repo tab to discover useful extensions", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -2713,6 +3014,38 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, "trackEditMetadata": "Edit Metadata", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" @@ -2755,6 +3088,47 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, + "queueFlacAction": "Queue FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, "trackSaveFailed": "Failed: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", @@ -2768,7 +3142,7 @@ "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "trackConvertFormatSubtitle": "Convert to MP3, Opus, ALAC, or FLAC", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, @@ -2803,6 +3177,22 @@ } } }, + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, "trackConvertConverting": "Converting audio...", "@trackConvertConverting": { "description": "Snackbar while converting" @@ -3154,6 +3544,18 @@ } } }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, "selectionBatchConvertProgress": "Converting {current} of {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", @@ -3197,5 +3599,915 @@ "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" + }, + "lyricsProvidersTitle": "Lyrics Providers", + "@lyricsProvidersTitle": { + "description": "Title for the lyrics provider priority page" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Donate", + "@settingsDonate": { + "description": "Settings menu item - donate" + }, + "settingsDonateSubtitle": "Support SpotiFLAC-Mobile development", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose storage mode for downloaded files.", + "@downloadLocationSubtitle": { + "description": "Subtitle text in Android download location bottom sheet" + }, + "storageModeAppFolder": "App folder (non-SAF)", + "@storageModeAppFolder": { + "description": "Storage mode option - use legacy app folder" + }, + "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "SAF folder", + "@storageModeSaf": { + "description": "Storage mode option - use Android SAF picker" + }, + "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Customize how your files are named.", + "@downloadFilenameDescription": { + "description": "Description text in filename format bottom sheet" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is enabled" + }, + "downloadSeparateSinglesDisabled": "All files in same structure", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is disabled" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting title for SongLink country region" + }, + "downloadNetworkCompatibilityMode": "Network compatibility mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting title for network compatibility toggle" + }, + "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is enabled" + }, + "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is disabled" + }, + "downloadSelectServiceToEnable": "Select a built-in service to enable", + "@downloadSelectServiceToEnable": { + "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", + "@downloadSelectTidalQobuz": { + "description": "Info hint when non-Tidal/Qobuz service is selected" + }, + "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Toggle title for including Netease translated lyrics" + }, + "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is enabled" + }, + "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is disabled" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Toggle title for including Netease romanized lyrics" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is enabled" + }, + "downloadNeteaseIncludeRomanizationDisabled": "Disabled", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is disabled" + }, + "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", + "@downloadAppleQqMultiPerson": { + "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + }, + "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person word-by-word is enabled" + }, + "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person word-by-word is disabled" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting title for Musixmatch language preference" + }, + "downloadMusixmatchLanguageAuto": "Auto (original)", + "@downloadMusixmatchLanguageAuto": { + "description": "Option label when Musixmatch uses original language" + }, + "downloadFilterContributing": "Filter contributing artists in Album Artist", + "@downloadFilterContributing": { + "description": "Toggle title for filtering contributing artists in Album Artist metadata" + }, + "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is enabled" + }, + "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is disabled" + }, + "downloadProvidersNoneEnabled": "None enabled", + "@downloadProvidersNoneEnabled": { + "description": "Subtitle for lyrics providers setting when no providers are enabled" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for the Musixmatch language code text field" + }, + "downloadMusixmatchLanguageHint": "auto / en / es / ja", + "@downloadMusixmatchLanguageHint": { + "description": "Hint text for the Musixmatch language code field" + }, + "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in the Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to reset Musixmatch language to automatic" + }, + "downloadNetworkAnySubtitle": "WiFi + Mobile Data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for 'Any' network mode option" + }, + "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for 'WiFi only' network mode option" + }, + "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", + "@downloadSongLinkRegionDesc": { + "description": "Description in the SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" } -} \ No newline at end of file +} diff --git a/lib/l10n/arb/app_zh_TW.arb b/lib/l10n/arb/app_zh_TW.arb index 3f883b85..4d9a042a 100644 --- a/lib/l10n/arb/app_zh_TW.arb +++ b/lib/l10n/arb/app_zh_TW.arb @@ -17,7 +17,7 @@ "@navSettings": { "description": "Bottom navigation - Settings tab" }, - "navStore": "Store", + "navStore": "Repo", "@navStore": { "description": "Bottom navigation - Extension store tab" }, @@ -25,7 +25,7 @@ "@homeTitle": { "description": "Home screen title" }, - "homeSubtitle": "Paste a Spotify link or search by name", + "homeSubtitle": "Paste a supported URL or search by name", "@homeSubtitle": { "description": "Subtitle shown below search box" }, @@ -89,9 +89,17 @@ "@downloadFilenameFormat": { "description": "Setting for output filename pattern" }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, "downloadFolderOrganization": "Folder Organization", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "Appearance", "@appearanceTitle": { @@ -150,6 +158,14 @@ } } }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" @@ -190,6 +206,42 @@ "@optionsMaxQualityCoverSubtitle": { "description": "Subtitle for max quality cover" }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Artist Tag Mode", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Choose how multiple artists are written into embedded tags.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Single joined value", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Split tags for FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, "optionsConcurrentDownloads": "Concurrent Downloads", "@optionsConcurrentDownloads": { "description": "Number of parallel downloads" @@ -211,11 +263,11 @@ "@optionsConcurrentWarning": { "description": "Warning about rate limits" }, - "optionsExtensionStore": "Extension Store", + "optionsExtensionStore": "Extension Repo", "@optionsExtensionStore": { "description": "Show/hide store tab" }, - "optionsExtensionStoreSubtitle": "Show Store tab in navigation", + "optionsExtensionStoreSubtitle": "Show Repo tab in navigation", "@optionsExtensionStoreSubtitle": { "description": "Subtitle for extension store toggle" }, @@ -318,7 +370,7 @@ "@extensionsUninstall": { "description": "Uninstall extension button" }, - "storeTitle": "Extension Store", + "storeTitle": "Extension Repo", "@storeTitle": { "description": "Store screen title" }, @@ -675,6 +727,10 @@ "@dialogImport": { "description": "Dialog button - import data" }, + "dialogDownload": "Download", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, "dialogDiscard": "Discard", "@dialogDiscard": { "description": "Dialog button - discard changes" @@ -999,6 +1055,46 @@ "@searchPlaylists": { "description": "Search result category - playlists" }, + "searchSortTitle": "Sort Results", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Default", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Title (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Title (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artist (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artist (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duration (Shortest)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duration (Longest)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Release Date (Oldest)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Release Date (Newest)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, "tooltipPlay": "Play", "@tooltipPlay": { "description": "Tooltip - play button" @@ -1119,6 +1215,18 @@ "@providerPriorityInfo": { "description": "Info tip about fallback behavior" }, + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, "providerBuiltIn": "Built-in", "@providerBuiltIn": { "description": "Label for built-in providers (Tidal/Qobuz)" @@ -1479,6 +1587,14 @@ "@trackLyricsNotAvailable": { "description": "Message when lyrics not found" }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, "trackLyricsTimeout": "Request timed out. Try again later.", "@trackLyricsTimeout": { "description": "Message when lyrics request times out" @@ -1574,7 +1690,59 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, - "extensionDefaultProvider": "Default (Deezer/Spotify)", + "storeAddRepoTitle": "Add Extension Repository", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "e.g. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Add Repository", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Change repository", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Extension Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Current repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "New Repository URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionDefaultProvider": "Default (Deezer)", "@extensionDefaultProvider": { "description": "Default search provider option" }, @@ -1713,6 +1881,14 @@ "@extensionsDownloadPrioritySubtitle": { "description": "Subtitle for download priority" }, + "extensionsFallbackTitle": "Fallback Extensions", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, "extensionsNoDownloadProvider": "No extensions with download provider", "@extensionsNoDownloadProvider": { "description": "Empty state - no download providers" @@ -1773,6 +1949,46 @@ "@qualityHiResFlacMaxSubtitle": { "description": "Technical spec for hi-res max" }, + "downloadLossy320": "Lossy 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Lossy Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Best compatibility, ~10MB per track", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Best quality Opus, ~8MB per track", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, "qualityNote": "Actual quality depends on track availability from the service", "@qualityNote": { "description": "Note about quality availability" @@ -1897,6 +2113,14 @@ "@albumFolderArtistAlbumSinglesSubtitle": { "description": "Folder structure example" }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, "downloadedAlbumDeleteSelected": "Delete Selected", "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" @@ -2178,6 +2402,30 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, "libraryActions": "Actions", "@libraryActions": { "description": "Section header for library actions" @@ -2235,6 +2483,15 @@ } } }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, "libraryLastScanned": "Last scanned: {time}", "@libraryLastScanned": { "description": "Last scan time display", @@ -2252,6 +2509,10 @@ "@libraryScanning": { "description": "Status during scan" }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, "libraryScanProgress": "{progress}% of {total} files", "@libraryScanProgress": { "description": "Scan progress display", @@ -2349,6 +2610,30 @@ "@libraryFilterFormat": { "description": "Filter section - file format" }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, "libraryFilterSort": "Sort", "@libraryFilterSort": { "description": "Filter section - sort order" @@ -2361,6 +2646,22 @@ "@libraryFilterSortOldest": { "description": "Sort option - oldest first" }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, "timeJustNow": "Just now", "@timeJustNow": { "description": "Relative time - less than a minute ago" @@ -2447,7 +2748,7 @@ "@tutorialExtensionsDesc": { "description": "Tutorial extensions page description" }, - "tutorialExtensionsTip1": "Browse the Store tab to discover useful extensions", + "tutorialExtensionsTip1": "Browse the Repo tab to discover useful extensions", "@tutorialExtensionsTip1": { "description": "Tutorial extensions tip 1" }, @@ -2713,6 +3014,38 @@ "@trackReEnrichOnlineSubtitle": { "description": "Subtitle for re-enrich metadata action for local items" }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, "trackEditMetadata": "Edit Metadata", "@trackEditMetadata": { "description": "Menu action - edit embedded metadata" @@ -2755,6 +3088,47 @@ "@trackReEnrichFfmpegFailed": { "description": "Snackbar when FFmpeg embed fails for MP3/Opus" }, + "queueFlacAction": "Queue FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, "trackSaveFailed": "Failed: {error}", "@trackSaveFailed": { "description": "Snackbar when save operation fails", @@ -2768,7 +3142,7 @@ "@trackConvertFormat": { "description": "Menu item - convert audio format" }, - "trackConvertFormatSubtitle": "Convert to MP3 or Opus", + "trackConvertFormatSubtitle": "Convert to MP3, Opus, ALAC, or FLAC", "@trackConvertFormatSubtitle": { "description": "Subtitle for convert format menu item" }, @@ -2803,6 +3177,22 @@ } } }, + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, "trackConvertConverting": "Converting audio...", "@trackConvertConverting": { "description": "Snackbar while converting" @@ -3154,6 +3544,18 @@ } } }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, "selectionBatchConvertProgress": "Converting {current} of {total}...", "@selectionBatchConvertProgress": { "description": "Snackbar during batch conversion progress", @@ -3197,5 +3599,915 @@ "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { "description": "Subtitle when Track Artist is used for folder naming" + }, + "lyricsProvidersTitle": "Lyrics Providers", + "@lyricsProvidersTitle": { + "description": "Title for the lyrics provider priority page" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Donate", + "@settingsDonate": { + "description": "Settings menu item - donate" + }, + "settingsDonateSubtitle": "Support SpotiFLAC-Mobile development", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose storage mode for downloaded files.", + "@downloadLocationSubtitle": { + "description": "Subtitle text in Android download location bottom sheet" + }, + "storageModeAppFolder": "App folder (non-SAF)", + "@storageModeAppFolder": { + "description": "Storage mode option - use legacy app folder" + }, + "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "SAF folder", + "@storageModeSaf": { + "description": "Storage mode option - use Android SAF picker" + }, + "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Customize how your files are named.", + "@downloadFilenameDescription": { + "description": "Description text in filename format bottom sheet" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is enabled" + }, + "downloadSeparateSinglesDisabled": "All files in same structure", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is disabled" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting title for SongLink country region" + }, + "downloadNetworkCompatibilityMode": "Network compatibility mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting title for network compatibility toggle" + }, + "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is enabled" + }, + "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is disabled" + }, + "downloadSelectServiceToEnable": "Select a built-in service to enable", + "@downloadSelectServiceToEnable": { + "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", + "@downloadSelectTidalQobuz": { + "description": "Info hint when non-Tidal/Qobuz service is selected" + }, + "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Toggle title for including Netease translated lyrics" + }, + "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is enabled" + }, + "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is disabled" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Toggle title for including Netease romanized lyrics" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is enabled" + }, + "downloadNeteaseIncludeRomanizationDisabled": "Disabled", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is disabled" + }, + "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", + "@downloadAppleQqMultiPerson": { + "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + }, + "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person word-by-word is enabled" + }, + "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person word-by-word is disabled" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting title for Musixmatch language preference" + }, + "downloadMusixmatchLanguageAuto": "Auto (original)", + "@downloadMusixmatchLanguageAuto": { + "description": "Option label when Musixmatch uses original language" + }, + "downloadFilterContributing": "Filter contributing artists in Album Artist", + "@downloadFilterContributing": { + "description": "Toggle title for filtering contributing artists in Album Artist metadata" + }, + "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is enabled" + }, + "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is disabled" + }, + "downloadProvidersNoneEnabled": "None enabled", + "@downloadProvidersNoneEnabled": { + "description": "Subtitle for lyrics providers setting when no providers are enabled" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for the Musixmatch language code text field" + }, + "downloadMusixmatchLanguageHint": "auto / en / es / ja", + "@downloadMusixmatchLanguageHint": { + "description": "Hint text for the Musixmatch language code field" + }, + "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in the Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to reset Musixmatch language to automatic" + }, + "downloadNetworkAnySubtitle": "WiFi + Mobile Data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for 'Any' network mode option" + }, + "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for 'WiFi only' network mode option" + }, + "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", + "@downloadSongLinkRegionDesc": { + "description": "Description in the SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" } -} \ No newline at end of file +} From 86b8709ea136e6556722a5959e9b78f6ebc64344 Mon Sep 17 00:00:00 2001 From: zarzet Date: Sat, 18 Apr 2026 23:02:00 +0700 Subject: [PATCH 03/12] chore: enable Ukrainian locale on main --- lib/l10n/app_localizations.dart | 5 + lib/l10n/app_localizations_uk.dart | 3479 +++++++++++++ lib/l10n/arb/app_uk.arb | 4513 +++++++++++++++++ lib/l10n/supported_locales.dart | 2 + .../settings/appearance_settings_page.dart | 1 + 5 files changed, 8000 insertions(+) create mode 100644 lib/l10n/app_localizations_uk.dart create mode 100644 lib/l10n/arb/app_uk.arb diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e6871f7f..49c2a798 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -17,6 +17,7 @@ import 'app_localizations_nl.dart'; import 'app_localizations_pt.dart'; import 'app_localizations_ru.dart'; import 'app_localizations_tr.dart'; +import 'app_localizations_uk.dart'; import 'app_localizations_zh.dart'; // ignore_for_file: type=lint @@ -119,6 +120,7 @@ abstract class AppLocalizations { Locale('pt', 'PT'), Locale('ru'), Locale('tr'), + Locale('uk'), Locale('zh'), Locale('zh', 'CN'), Locale('zh', 'TW'), @@ -5857,6 +5859,7 @@ class _AppLocalizationsDelegate 'pt', 'ru', 'tr', + 'uk', 'zh', ].contains(locale.languageCode); @@ -5921,6 +5924,8 @@ AppLocalizations lookupAppLocalizations(Locale locale) { return AppLocalizationsRu(); case 'tr': return AppLocalizationsTr(); + case 'uk': + return AppLocalizationsUk(); case 'zh': return AppLocalizationsZh(); } diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart new file mode 100644 index 00000000..76ea914a --- /dev/null +++ b/lib/l10n/app_localizations_uk.dart @@ -0,0 +1,3479 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Ukrainian (`uk`). +class AppLocalizationsUk extends AppLocalizations { + AppLocalizationsUk([String locale = 'uk']) : super(locale); + + @override + String get appName => 'SpotiFLAC'; + + @override + String get navHome => 'Головна'; + + @override + String get navLibrary => 'Бібліотека'; + + @override + String get navSettings => 'Налаштування'; + + @override + String get navStore => 'Репозиторій'; + + @override + String get homeTitle => 'Головна'; + + @override + String get homeSubtitle => + 'Вставте URL-адресу яка підтримується, або виконайте пошук за назвою'; + + @override + String get homeSupports => + 'Підтримує: URL-адреси треків, альбомів, списків відтворення, виконавців'; + + @override + String get homeRecent => 'Нещодавні'; + + @override + String get historyFilterAll => 'Усі'; + + @override + String get historyFilterAlbums => 'Альбоми'; + + @override + String get historyFilterSingles => 'Сингли'; + + @override + String get historySearchHint => 'Історія пошуку...'; + + @override + String get settingsTitle => 'Налаштування'; + + @override + String get settingsDownload => 'Завантаження'; + + @override + String get settingsAppearance => 'Зовнішній вигляд'; + + @override + String get settingsOptions => 'Опції'; + + @override + String get settingsExtensions => 'Розширення'; + + @override + String get settingsAbout => 'Про додаток'; + + @override + String get downloadTitle => 'Завантажити'; + + @override + String get downloadAskQualitySubtitle => + 'Показувати вікно вибору якості для кожного завантаження'; + + @override + String get downloadFilenameFormat => 'Формат імені файлу'; + + @override + String get downloadSingleFilenameFormat => 'Формат імені одного файлу'; + + @override + String get downloadSingleFilenameFormatDescription => + 'Шаблон назви файлу для синглів та міні-альбомів. Використовує ті самі теги, що й формат альбому.'; + + @override + String get downloadFolderOrganization => 'Організація папок'; + + @override + String get appearanceTitle => 'Зовнішній вигляд'; + + @override + String get appearanceThemeSystem => 'Системний'; + + @override + String get appearanceThemeLight => 'Світлий'; + + @override + String get appearanceThemeDark => 'Темний'; + + @override + String get appearanceDynamicColor => 'Динамічний колір'; + + @override + String get appearanceDynamicColorSubtitle => + 'Використати кольори зі своїх шпалер'; + + @override + String get appearanceHistoryView => 'Історія переглядів'; + + @override + String get appearanceHistoryViewList => 'Список'; + + @override + String get appearanceHistoryViewGrid => 'Сітка'; + + @override + String get optionsTitle => 'Опції'; + + @override + String get optionsPrimaryProvider => 'Основний постачальник'; + + @override + String get optionsPrimaryProviderSubtitle => + 'Розширення будуть випробувані першими'; + + @override + String optionsUsingExtension(String extensionName) { + return 'Використання розширення: $extensionName'; + } + + @override + String get optionsDefaultSearchTab => 'Default Search Tab'; + + @override + String get optionsDefaultSearchTabSubtitle => + 'Choose which tab opens first for new search results.'; + + @override + String get optionsSwitchBack => + 'Натисніть Deezer або Spotify, щоб повернутися до розширення'; + + @override + String get optionsAutoFallback => 'Автоматичний резервний варіант'; + + @override + String get optionsAutoFallbackSubtitle => + 'Спробувати інші сервіси, якщо завантаження не вдається'; + + @override + String get optionsUseExtensionProviders => + 'Використати постачальників розширень'; + + @override + String get optionsUseExtensionProvidersOn => + 'Розширення будуть випробувані першими'; + + @override + String get optionsUseExtensionProvidersOff => + 'Використати лише вбудованих постачальників'; + + @override + String get optionsEmbedLyrics => 'Вбудований текст пісні'; + + @override + String get optionsEmbedLyricsSubtitle => + 'Вбудовувати синхронізовані тексти пісень у файли FLAC'; + + @override + String get optionsMaxQualityCover => 'Максимальна якість обкладинки'; + + @override + String get optionsMaxQualityCoverSubtitle => + 'Завантажити обкладинку з найвищою роздільною здатністю'; + + @override + String get optionsReplayGain => 'Нормалізація звуку'; + + @override + String get optionsReplayGainSubtitleOn => + 'Сканування гучності та вбудовування тегів нормалізації звуку (EBU R128)'; + + @override + String get optionsReplayGainSubtitleOff => + 'Вимкнено: немає тегів нормалізації гучності'; + + @override + String get optionsArtistTagMode => 'Режим тегу виконавця'; + + @override + String get optionsArtistTagModeDescription => + 'Виберіть, як будуть записуватися кілька виконавців у вбудовані теги.'; + + @override + String get optionsArtistTagModeJoined => 'Одне об\'єднане значення'; + + @override + String get optionsArtistTagModeJoinedSubtitle => + 'Для максимальної сумісності програвача напишіть одне значення ARTIST, наприклад, «Виконавець A, Виконавець B».'; + + @override + String get optionsArtistTagModeSplitVorbis => 'Розділені теги для FLAC/Opus'; + + @override + String get optionsArtistTagModeSplitVorbisSubtitle => + 'Для FLAC та Opus на кожного виконавця додати окремий тег виконавця; MP3 та M4A залишаються об’єднаними.'; + + @override + String get optionsConcurrentDownloads => 'Кількість одночасних завантажень'; + + @override + String get optionsConcurrentSequential => 'Послідовно (по одному за раз)'; + + @override + String optionsConcurrentParallel(int count) { + return '$count паралельних завантажень'; + } + + @override + String get optionsConcurrentWarning => + 'Паралельні завантаження можуть призвести до обмеження швидкості'; + + @override + String get optionsExtensionStore => 'Репозиторій розширень'; + + @override + String get optionsExtensionStoreSubtitle => + 'Показати вкладку «Репозиторій» у навігації'; + + @override + String get optionsCheckUpdates => 'Перевірити наявність оновлень'; + + @override + String get optionsCheckUpdatesSubtitle => + 'Повідомити, коли буде доступна нова версія'; + + @override + String get optionsUpdateChannel => 'Канал оновлень'; + + @override + String get optionsUpdateChannelStable => 'Тільки стабільні релізи'; + + @override + String get optionsUpdateChannelPreview => 'Отримати попередні релізи'; + + @override + String get optionsUpdateChannelWarning => + 'Тестовий реліз може містити помилки або неповні функції'; + + @override + String get optionsClearHistory => 'Очистити історію завантажень'; + + @override + String get optionsClearHistorySubtitle => + 'Видалити всі завантажені треки з історії'; + + @override + String get optionsDetailedLogging => 'Детальне журналювання'; + + @override + String get optionsDetailedLoggingOn => 'Ведеться детальний журнал'; + + @override + String get optionsDetailedLoggingOff => 'Увімкнути для звітів про помилки'; + + @override + String get optionsSpotifyCredentials => 'Облікові дані Spotify'; + + @override + String optionsSpotifyCredentialsConfigured(String clientId) { + return 'Ідентифікатор клієнта: $clientId...'; + } + + @override + String get optionsSpotifyCredentialsRequired => + 'Обов\'язковий – торкніться, щоб налаштувати'; + + @override + String get optionsSpotifyWarning => + 'Spotify вимагає ваших власних облікових даних API. Отримайте їх безкоштовно на сайті developer.spotify.com'; + + @override + String get optionsSpotifyDeprecationWarning => + 'Пошук Spotify буде припинено 3 березня 2026 року через зміни в API Spotify. Будь ласка, перейдіть на Deezer.'; + + @override + String get extensionsTitle => 'Розширення'; + + @override + String get extensionsDisabled => 'Вимкнені'; + + @override + String extensionsVersion(String version) { + return 'Версія $version'; + } + + @override + String extensionsAuthor(String author) { + return 'від $author'; + } + + @override + String get extensionsUninstall => 'Видалити'; + + @override + String get storeTitle => 'Репозиторій розширень'; + + @override + String get storeSearch => 'Розширення пошуку...'; + + @override + String get storeInstall => 'Встановити'; + + @override + String get storeInstalled => 'Встановлені'; + + @override + String get storeUpdate => 'Оновлені'; + + @override + String get aboutTitle => 'Про нас'; + + @override + String get aboutContributors => 'Автори'; + + @override + String get aboutMobileDeveloper => 'Розробник мобільної версії'; + + @override + String get aboutOriginalCreator => 'Творець оригінального SpotiFLAC'; + + @override + String get aboutLogoArtist => + 'Талановитий художник, який створив чудовий логотип нашого додатку!'; + + @override + String get aboutTranslators => 'Перекладачі'; + + @override + String get aboutSpecialThanks => 'Особлива подяка'; + + @override + String get aboutLinks => 'Посилання'; + + @override + String get aboutMobileSource => 'Мобільний вихідний код'; + + @override + String get aboutPCSource => 'Вихідний код для ПК'; + + @override + String get aboutKeepAndroidOpen => 'Keep Android Open'; + + @override + String get aboutReportIssue => 'Повідомити про проблему'; + + @override + String get aboutReportIssueSubtitle => + 'Повідомити про будь-які проблеми, з якими ви зіткнулися'; + + @override + String get aboutFeatureRequest => 'Запит на функцію'; + + @override + String get aboutFeatureRequestSubtitle => + 'Запропонувати нові функції для програми'; + + @override + String get aboutTelegramChannel => 'Телеграм-канал'; + + @override + String get aboutTelegramChannelSubtitle => 'Оголошення та оновлення'; + + @override + String get aboutTelegramChat => 'Telegram Спільнота'; + + @override + String get aboutTelegramChatSubtitle => 'Спілкуватися з іншими користувачами'; + + @override + String get aboutSocial => 'Соціальні мережі'; + + @override + String get aboutApp => 'Додаток'; + + @override + String get aboutVersion => 'Версія'; + + @override + String get aboutBinimumDesc => + 'Творець QQDL та HiFi API. Без цього API завантажень Tidal\'а не існувало б!'; + + @override + String get aboutSachinsenalDesc => + 'Оригінальний творець HiFi-проектів. Основа інтеграції Tidal!'; + + @override + String get aboutSjdonadoDesc => + 'Творець I Don\'t Have Spotify (IDHS). Резервний розв\'язувач посилань, який рятує становище!'; + + @override + String get aboutDabMusic => 'DAB Music'; + + @override + String get aboutDabMusicDesc => + 'Найкращий API для потокової передачі Qobuz. Завантаження високої роздільної здатності було б неможливим без нього!'; + + @override + String get aboutSpotiSaver => 'SpotiSaver'; + + @override + String get aboutSpotiSaverDesc => + 'Кінцеві точки потокового передавання Tidal Hi-Res FLAC. Ключовий елемент пазлу музики без втрат!'; + + @override + String get aboutAppDescription => + 'Кінцеві точки потокового передавання Tidal Hi-Res FLAC. Ключовий елемент пазлу музики без втрат.'; + + @override + String get artistAlbums => 'Альбоми'; + + @override + String get artistSingles => 'Сингли та міні-альбоми'; + + @override + String get artistCompilations => 'Збірники'; + + @override + String get artistPopular => 'Популярні'; + + @override + String artistMonthlyListeners(String count) { + return '$count слухачів щомісяця'; + } + + @override + String get trackMetadataService => 'Сервіс'; + + @override + String get trackMetadataPlay => 'Прослухати'; + + @override + String get trackMetadataShare => 'Поділитися'; + + @override + String get trackMetadataDelete => 'Видалити'; + + @override + String get setupGrantPermission => 'Надати дозвіл'; + + @override + String get setupSkip => 'Пропустити поки що'; + + @override + String get setupStorageAccessRequired => 'Потрібен доступ до сховища'; + + @override + String get setupStorageAccessMessageAndroid11 => + 'Для збереження файлів у вибрану папку завантажень для Android 11+ потрібен дозвіл «Доступ до всіх файлів».'; + + @override + String get setupOpenSettings => 'Відкрити налаштування'; + + @override + String get setupPermissionDeniedMessage => + 'Дозвіл відхилено. Будь ласка, надайте всі дозволи, щоб продовжити.'; + + @override + String setupPermissionRequired(String permissionType) { + return '$permissionType Потрібен дозвіл'; + } + + @override + String setupPermissionRequiredMessage(String permissionType) { + return '$permissionType Для найкращого досвіду потрібен дозвіл. Ви можете змінити це пізніше в налаштуваннях.'; + } + + @override + String get setupUseDefaultFolder => 'Використати папку за замовчуванням?'; + + @override + String get setupNoFolderSelected => + 'Папку не вибрано. Бажаєте використовувати папку «Музика» за замовчуванням?'; + + @override + String get setupUseDefault => 'Використовувати за замовчуванням'; + + @override + String get setupDownloadLocationTitle => 'Розташування завантаження'; + + @override + String get setupDownloadLocationIosMessage => + 'На iOS завантаження зберігаються в папці «Документи» програми. Ви можете отримати до них доступ через програму «Файли».'; + + @override + String get setupAppDocumentsFolder => 'Папка з документами програми'; + + @override + String get setupAppDocumentsFolderSubtitle => + 'Рекомендація – доступно через додаток Файли'; + + @override + String get setupChooseFromFiles => 'Вибрати з файлів'; + + @override + String get setupChooseFromFilesSubtitle => + 'Виберіть iCloud або інше місцезнаходження'; + + @override + String get setupIosEmptyFolderWarning => + 'Обмеження iOS: Не можна вибрати порожні папки. Виберіть папку, яка містить принаймні один файл.'; + + @override + String get setupIcloudNotSupported => + 'iCloud Drive не підтримується. Будь ласка, скористайтеся папкою «Документи» програми.'; + + @override + String get setupDownloadInFlac => 'Завантажити треки Spotify у форматі FLAC'; + + @override + String get setupStorageGranted => 'Дозвіл на зберігання надано!'; + + @override + String get setupStorageRequired => 'Потрібен дозвіл на збереження файлів'; + + @override + String get setupStorageDescription => + 'SpotiFLAC потребує дозволу на збереження, щоб зберегти завантажені музичні файли.'; + + @override + String get setupNotificationGranted => 'Дозвіл на сповіщення надано!'; + + @override + String get setupNotificationEnable => 'Увімкнути сповіщення'; + + @override + String get setupFolderChoose => 'Виберати папку для завантаження'; + + @override + String get setupFolderDescription => + 'Виберіть папку, де буде збережено завантажену музику.'; + + @override + String get setupSelectFolder => 'Вибрати папку'; + + @override + String get setupEnableNotifications => 'Увімкнути сповіщення'; + + @override + String get setupNotificationBackgroundDescription => + 'Отримуйте сповіщення про прогрес та завершення завантаження. Це допомагає відстежувати завантаження, коли програма працює у фоновому режимі.'; + + @override + String get setupSkipForNow => 'Пропустити поки що'; + + @override + String get setupNext => 'Далі'; + + @override + String get setupGetStarted => 'Почати'; + + @override + String get setupAllowAccessToManageFiles => + 'Будь ласка, увімкніть опцію «Дозволити доступ для керування всіма файлами» на наступному екрані.'; + + @override + String get dialogCancel => 'Скасувати'; + + @override + String get dialogSave => 'Зберегти'; + + @override + String get dialogDelete => 'Видалити'; + + @override + String get dialogRetry => 'Повторити спробу'; + + @override + String get dialogClear => 'Очистити'; + + @override + String get dialogDone => 'Готово'; + + @override + String get dialogImport => 'Імпорт'; + + @override + String get dialogDownload => 'Завантажити'; + + @override + String get dialogDiscard => 'Відхилити'; + + @override + String get dialogRemove => 'Видалити'; + + @override + String get dialogUninstall => 'Деінсталювати'; + + @override + String get dialogDiscardChanges => 'Відхилити зміни?'; + + @override + String get dialogUnsavedChanges => + 'У вас є незбережені зміни. Ви хочете їх скасувати?'; + + @override + String get dialogClearAll => 'Очистити все'; + + @override + String get dialogRemoveExtension => 'Видалити розширення'; + + @override + String get dialogRemoveExtensionMessage => + 'Ви впевнені, що хочете видалити це розширення? Цю дію неможливо скасувати.'; + + @override + String get dialogUninstallExtension => 'Видалити розширення?'; + + @override + String dialogUninstallExtensionMessage(String extensionName) { + return 'Ви впевнені, що хочете видалити $extensionName?'; + } + + @override + String get dialogClearHistoryTitle => 'Очистити історію'; + + @override + String get dialogClearHistoryMessage => + 'Ви впевнені, що хочете очистити всю історію завантажень? Цю дію неможливо скасувати.'; + + @override + String get dialogDeleteSelectedTitle => 'Видалити вибране'; + + @override + String dialogDeleteSelectedMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'треків', + one: 'трек', + ); + return 'Видалити $count $_temp0 з історії?\n\nЦе також видалить файли з пам\'яті.'; + } + + @override + String get dialogImportPlaylistTitle => 'Імпорт списку відтворення'; + + @override + String dialogImportPlaylistMessage(int count) { + return 'Знайдено $count треків у CSV. Додати їх до черги завантаження?'; + } + + @override + String csvImportTracks(int count) { + return '$count треків з CSV'; + } + + @override + String snackbarAddedToQueue(String trackName) { + return 'Додано \"$trackName\" до черги'; + } + + @override + String snackbarAddedTracksToQueue(int count) { + return 'Додано $count треків до черги'; + } + + @override + String snackbarAlreadyDownloaded(String trackName) { + return '\"$trackName\" вже завантажено'; + } + + @override + String snackbarAlreadyInLibrary(String trackName) { + return '\"$trackName\" вже є у вашій бібліотеці'; + } + + @override + String get snackbarHistoryCleared => 'Історія очищена'; + + @override + String get snackbarCredentialsSaved => 'Облікові дані збережено'; + + @override + String get snackbarCredentialsCleared => 'Облікові дані очищено'; + + @override + String snackbarDeletedTracks(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'треків', + one: 'трек', + ); + return 'Видалено $count $_temp0'; + } + + @override + String snackbarCannotOpenFile(String error) { + return 'Не вдається відкрити файл: $error'; + } + + @override + String get snackbarFillAllFields => 'Будь ласка, заповніть усі поля'; + + @override + String get snackbarViewQueue => 'Переглянути чергу'; + + @override + String snackbarUrlCopied(String platform) { + return '$platform URL-адреса скопійована в буфер обміну'; + } + + @override + String get snackbarFileNotFound => 'Файл не знайдено'; + + @override + String get snackbarSelectExtFile => + 'Будь ласка, виберіть файл .spotiflac-ext'; + + @override + String get snackbarProviderPrioritySaved => + 'Пріоритет постачальника збережено'; + + @override + String get snackbarMetadataProviderSaved => + 'Пріоритет постачальника метаданих збережено'; + + @override + String snackbarExtensionInstalled(String extensionName) { + return 'Розширення $extensionName встановлено.'; + } + + @override + String snackbarExtensionUpdated(String extensionName) { + return '$extensionName оновлено.'; + } + + @override + String get snackbarFailedToInstall => 'Не вдалося встановити розширення'; + + @override + String get snackbarFailedToUpdate => 'Не вдалося оновити розширення'; + + @override + String get errorRateLimited => 'Обмежений тариф'; + + @override + String get errorRateLimitedMessage => + 'Забагато запитів. Будь ласка, зачекайте хвилинку, перш ніж шукати знову.'; + + @override + String get errorNoTracksFound => 'Треків не знайдено'; + + @override + String get errorUrlNotRecognized => 'Посилання не розпізнано'; + + @override + String get errorUrlNotRecognizedMessage => + 'Це посилання не підтримується. Переконайтеся, що URL-адреса правильна та встановлено сумісне розширення.'; + + @override + String get errorUrlFetchFailed => + 'Не вдалося завантажити вміст за цим посиланням. Спробуйте ще раз.'; + + @override + String errorMissingExtensionSource(String item) { + return 'Не вдається завантажити $item: відсутній вихідний код розширення'; + } + + @override + String get actionPause => 'Пауза'; + + @override + String get actionResume => 'Відновити'; + + @override + String get actionCancel => 'Скасувати'; + + @override + String get actionSelectAll => 'Вибрати все'; + + @override + String get actionDeselect => 'Скасувати вибір'; + + @override + String get actionRemoveCredentials => 'Видалити облікові дані'; + + @override + String get actionSaveCredentials => 'Зберегти облікові дані'; + + @override + String selectionSelected(int count) { + return 'Вибрано $count'; + } + + @override + String get selectionAllSelected => 'Усі треки вибрано'; + + @override + String get selectionSelectToDelete => 'Виберіть треки для видалення'; + + @override + String progressFetchingMetadata(int current, int total) { + return 'Отримання метаданих... $current/$total'; + } + + @override + String get progressReadingCsv => 'Читання CSV-файлу...'; + + @override + String get searchSongs => 'Пісні'; + + @override + String get searchArtists => 'Виконавці'; + + @override + String get searchAlbums => 'Альбоми'; + + @override + String get searchPlaylists => 'Списки відтворення'; + + @override + String get searchSortTitle => 'Сортувати результати'; + + @override + String get searchSortDefault => 'За замовчуванням'; + + @override + String get searchSortTitleAZ => 'Назва (А-Я)'; + + @override + String get searchSortTitleZA => 'Назва (Я-А)'; + + @override + String get searchSortArtistAZ => 'Виконавець (А-Я)'; + + @override + String get searchSortArtistZA => 'Виконавець (Я-А)'; + + @override + String get searchSortDurationShort => 'Тривалість (найкоротша)'; + + @override + String get searchSortDurationLong => 'Тривалість (найдовша)'; + + @override + String get searchSortDateOldest => 'Дата випуску (найстаріша)'; + + @override + String get searchSortDateNewest => 'Дата випуску (найновіша)'; + + @override + String get tooltipPlay => 'Відтворити'; + + @override + String get filenameFormat => 'Формат імені файлу'; + + @override + String get filenameShowAdvancedTags => 'Показати розширені теги'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Увімкнути відформатовані теги для доповнення доріжок і шаблонів дати'; + + @override + String get folderOrganizationNone => 'Жодної організації'; + + @override + String get folderOrganizationByPlaylist => 'За списком відтворення'; + + @override + String get folderOrganizationByPlaylistSubtitle => + 'Окрема папка для кожного списку відтворення'; + + @override + String get folderOrganizationByArtist => 'За виконавцем'; + + @override + String get folderOrganizationByAlbum => 'За альбомом'; + + @override + String get folderOrganizationByArtistAlbum => 'Виконавець/Альбом'; + + @override + String get folderOrganizationDescription => + 'Упорядкувати завантажені файли в папки'; + + @override + String get folderOrganizationNoneSubtitle => 'Усі файли в папці завантажень'; + + @override + String get folderOrganizationByArtistSubtitle => + 'Окрема папка для кожного виконавця'; + + @override + String get folderOrganizationByAlbumSubtitle => + 'Окрема папка для кожного альбому'; + + @override + String get folderOrganizationByArtistAlbumSubtitle => + 'Вкладені папки для виконавця та альбому'; + + @override + String get updateAvailable => 'Доступне оновлення'; + + @override + String get updateLater => 'Пізніше'; + + @override + String get updateStartingDownload => 'Початок завантаження...'; + + @override + String get updateDownloadFailed => 'Не вдалося завантажити'; + + @override + String get updateFailedMessage => 'Не вдалося завантажити оновлення'; + + @override + String get updateNewVersionReady => 'Доступна нова версія'; + + @override + String get updateCurrent => 'Поточна'; + + @override + String get updateNew => 'Нова'; + + @override + String get updateDownloading => 'Завантаження...'; + + @override + String get updateWhatsNew => 'Що нового'; + + @override + String get updateDownloadInstall => 'Завантажити та встановити'; + + @override + String get updateDontRemind => 'Не нагадувати'; + + @override + String get providerPriorityTitle => 'Пріоритет постачальника'; + + @override + String get providerPriorityDescription => + 'Перетягніть, щоб змінити порядок постачальників завантажень. Під час завантаження треків програма використовуватиме постачальників зверху вниз.'; + + @override + String get providerPriorityInfo => + 'Якщо трек недоступний у першого провайдера, додаток автоматично спробує наступного.'; + + @override + String get providerPriorityFallbackExtensionsTitle => 'Резервне розширення'; + + @override + String get providerPriorityFallbackExtensionsDescription => + 'Виберіть, які встановлені розширення завантаження можна використовувати під час автоматичного відновлення до попереднього режиму. Вбудовані постачальники все одно дотримуються порядку пріоритетності, зазначеного вище.'; + + @override + String get providerPriorityFallbackExtensionsHint => + 'Тут перелічені лише ввімкнені розширення з можливістю завантаження через постачальника послуг.'; + + @override + String get providerBuiltIn => 'Вбудований'; + + @override + String get providerExtension => 'Розширення'; + + @override + String get metadataProviderPriorityTitle => 'Пріоритет метаданих'; + + @override + String get metadataProviderPriorityDescription => + 'Перетягніть, щоб змінити порядок постачальників метаданих. Додаток шукатиме постачальників зверху вниз під час пошуку треків та отримання метаданих.'; + + @override + String get metadataProviderPriorityInfo => + 'Deezer не має обмежень за швидкістю та рекомендований як основний сервіс. Spotify може обмежувати швидкість після великої кількості запитів.'; + + @override + String get metadataNoRateLimits => 'Без обмежень щодо швидкості'; + + @override + String get metadataMayRateLimit => 'Травневе обмеження ставок'; + + @override + String get logTitle => 'Журнали'; + + @override + String get logCopied => 'Журнали скопійовано в буфер обміну'; + + @override + String get logSearchHint => 'Пошук журналів...'; + + @override + String get logFilterLevel => 'Рівень'; + + @override + String get logFilterSection => 'Фільтр'; + + @override + String get logShareLogs => 'Журнали обміну'; + + @override + String get logClearLogs => 'Очистити журнали'; + + @override + String get logClearLogsTitle => 'Очистити Журнали'; + + @override + String get logClearLogsMessage => + 'Ви впевнені, що хочете очистити всі журнали?'; + + @override + String get logFilterBySeverity => 'Фільтрувати журнали за рівнем серйозності'; + + @override + String get logNoLogsYet => 'Журналів поки що немає'; + + @override + String get logNoLogsYetSubtitle => + 'Журнали відображатимуться тут під час використання програми'; + + @override + String logEntriesFiltered(int count) { + return 'Записи ($count filtered)'; + } + + @override + String logEntries(int count) { + return 'Записи ($count)'; + } + + @override + String get credentialsTitle => 'Облікові дані Spotify'; + + @override + String get credentialsDescription => + 'Введіть свій ідентифікатор клієнта та секретний код, щоб використовувати власну квоту програми Spotify.'; + + @override + String get credentialsClientId => 'Ідентифікатор клієнта'; + + @override + String get credentialsClientIdHint => 'Вставити ідентифікатор клієнта'; + + @override + String get credentialsClientSecret => 'Секретний код клієнта'; + + @override + String get credentialsClientSecretHint => 'Вставити секретний код клієнта'; + + @override + String get channelStable => 'Стабільний'; + + @override + String get channelPreview => 'Бета'; + + @override + String get sectionSearchSource => 'Джерело пошуку'; + + @override + String get sectionDownload => 'Завантажити'; + + @override + String get sectionPerformance => 'Продуктивність'; + + @override + String get sectionApp => 'Додаток'; + + @override + String get sectionData => 'Дані'; + + @override + String get sectionDebug => 'Налагодження'; + + @override + String get sectionService => 'Сервіс'; + + @override + String get sectionAudioQuality => 'Якість звуку'; + + @override + String get sectionFileSettings => 'Налаштування файлу'; + + @override + String get sectionLyrics => 'Тексти пісень'; + + @override + String get lyricsMode => 'Режим тексту пісні'; + + @override + String get lyricsModeDescription => + 'Виберіть, як тексти пісень зберігатимуться разом із завантаженнями пісень.'; + + @override + String get lyricsModeEmbed => 'Вбудувати у файл'; + + @override + String get lyricsModeEmbedSubtitle => + 'Тексти пісень зберігаються в метаданих FLAC'; + + @override + String get lyricsModeExternal => 'Зовнішній файл .lrc'; + + @override + String get lyricsModeExternalSubtitle => + 'Окремий файл .lrc для плеєрів, таких як Samsung Music'; + + @override + String get lyricsModeBoth => 'Обидва'; + + @override + String get lyricsModeBothSubtitle => 'Вбудувати та зберегти файл .lrc'; + + @override + String get sectionColor => 'Колір'; + + @override + String get sectionTheme => 'Тема'; + + @override + String get sectionLayout => 'Макет'; + + @override + String get sectionLanguage => 'Мова'; + + @override + String get appearanceLanguage => 'Мова програми'; + + @override + String get settingsAppearanceSubtitle => 'Тема, кольори, дисплей'; + + @override + String get settingsDownloadSubtitle => 'Сервіс, якість, формат назви файлу'; + + @override + String get settingsOptionsSubtitle => + 'Резервний варіант, тексти пісень, обкладинка, оновлення'; + + @override + String get settingsExtensionsSubtitle => + 'Керування постачальниками послуг завантаження'; + + @override + String get settingsLogsSubtitle => + 'Перегляд журналів програми для налагодження'; + + @override + String get loadingSharedLink => 'Завантаження спільного посилання...'; + + @override + String get pressBackAgainToExit => + 'Натисніть кнопку «Назад» ще раз, щоб вийти'; + + @override + String downloadAllCount(int count) { + return 'Завантажити все ($count)'; + } + + @override + String tracksCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count треків', + one: '1 трек', + ); + return '$_temp0'; + } + + @override + String get trackCopyFilePath => 'Копіювати шлях до файлу'; + + @override + String get trackRemoveFromDevice => 'Видалити з пристрою'; + + @override + String get trackLoadLyrics => 'Завантажити текст пісні'; + + @override + String get trackMetadata => 'Метадані'; + + @override + String get trackFileInfo => 'Інформація про файл'; + + @override + String get trackLyrics => 'Тексти пісень'; + + @override + String get trackFileNotFound => 'Файл не знайдено'; + + @override + String get trackOpenInDeezer => 'Відкрити в Deezer'; + + @override + String get trackOpenInSpotify => 'Відкрити в Spotify'; + + @override + String get trackTrackName => 'Назва треку'; + + @override + String get trackArtist => 'Артист'; + + @override + String get trackAlbumArtist => 'Виконавець альбому'; + + @override + String get trackAlbum => 'Альбом'; + + @override + String get trackTrackNumber => 'Номер треку'; + + @override + String get trackDiscNumber => 'Номер диска'; + + @override + String get trackDuration => 'Тривалість'; + + @override + String get trackAudioQuality => 'Якість звуку'; + + @override + String get trackReleaseDate => 'Дата випуску'; + + @override + String get trackGenre => 'Жанр'; + + @override + String get trackLabel => 'Лейбл'; + + @override + String get trackCopyright => 'Авторське право'; + + @override + String get trackDownloaded => 'Завантажено'; + + @override + String get trackCopyLyrics => 'Скопіювати тексти пісень'; + + @override + String get trackLyricsNotAvailable => + 'Текст пісні для цього треку недоступний'; + + @override + String get trackLyricsNotInFile => 'У цьому файлі не знайдено текстів пісень'; + + @override + String get trackFetchOnlineLyrics => 'Отримати з Інтернету'; + + @override + String get trackLyricsTimeout => + 'Час очікування запиту минув. Спробуйте ще раз пізніше.'; + + @override + String get trackLyricsLoadFailed => 'Не вдалося завантажити текст пісні'; + + @override + String get trackEmbedLyrics => 'Вбудувати текст пісні'; + + @override + String get trackLyricsEmbedded => 'Текст пісні успішно вбудовано в пісню'; + + @override + String get trackInstrumental => 'Інструментальний трек'; + + @override + String get trackCopiedToClipboard => 'Скопійовано в буфер обміну'; + + @override + String get trackDeleteConfirmTitle => 'Видалити з пристрою?'; + + @override + String get trackDeleteConfirmMessage => + 'Це назавжди видалить завантажений файл і вилучить його з вашої історії.'; + + @override + String get dateToday => 'Сьогодні'; + + @override + String get dateYesterday => 'Вчора'; + + @override + String dateDaysAgo(int count) { + return '$count днів тому'; + } + + @override + String dateWeeksAgo(int count) { + return '$count тижнів тому'; + } + + @override + String dateMonthsAgo(int count) { + return '$count місяців тому'; + } + + @override + String get storeFilterAll => 'Усі'; + + @override + String get storeFilterMetadata => 'Метадані'; + + @override + String get storeFilterDownload => 'Завантажити'; + + @override + String get storeFilterUtility => 'Утиліта'; + + @override + String get storeFilterLyrics => 'Тексти пісень'; + + @override + String get storeFilterIntegration => 'Інтеграція'; + + @override + String get storeClearFilters => 'Очистити фільтри'; + + @override + String get storeAddRepoTitle => 'Додати репозиторій розширень'; + + @override + String get storeAddRepoDescription => + 'Введіть URL-адресу репозиторію GitHub, яка містить файл registry.json, для перегляду та встановлення розширень.'; + + @override + String get storeRepoUrlLabel => 'URL-адреса репозиторію'; + + @override + String get storeRepoUrlHint => 'https://github.com/user/repo'; + + @override + String get storeRepoUrlHelper => + 'наприклад https://github.com/user/extensions-repo'; + + @override + String get storeAddRepoButton => 'Додати репозиторій'; + + @override + String get storeChangeRepoTooltip => 'Змінити репозиторій'; + + @override + String get storeRepoDialogTitle => 'Репозиторій розширень'; + + @override + String get storeRepoDialogCurrent => 'Поточний репозиторій:'; + + @override + String get storeNewRepoUrlLabel => 'Нова URL-адреса репозиторію'; + + @override + String get storeLoadError => 'Не вдалося завантажити репозиторій'; + + @override + String get storeEmptyNoExtensions => 'Розширень немає'; + + @override + String get storeEmptyNoResults => 'Розширень не знайдено'; + + @override + String get extensionDefaultProvider => 'За замовчуванням (Deezer)'; + + @override + String get extensionDefaultProviderSubtitle => 'Використати вбудований пошук'; + + @override + String get extensionAuthor => 'Автор'; + + @override + String get extensionId => 'Ідентифікатор'; + + @override + String get extensionError => 'Помилка'; + + @override + String get extensionCapabilities => 'Можливості'; + + @override + String get extensionMetadataProvider => 'Постачальник метаданих'; + + @override + String get extensionDownloadProvider => 'Постачальник завантажень'; + + @override + String get extensionLyricsProvider => 'Постачальник текстів пісень'; + + @override + String get extensionUrlHandler => 'Обробник URL-адрес'; + + @override + String get extensionQualityOptions => 'Варіанти якості'; + + @override + String get extensionPostProcessingHooks => 'Хуки пост-обробки'; + + @override + String get extensionPermissions => 'Дозволи'; + + @override + String get extensionSettings => 'Налаштування'; + + @override + String get extensionRemoveButton => 'Видалити розширення'; + + @override + String get extensionUpdated => 'Оновлено'; + + @override + String get extensionMinAppVersion => 'Мінімальна версія програми'; + + @override + String get extensionCustomTrackMatching => 'Підбір користувацьких треків'; + + @override + String get extensionPostProcessing => 'Післяобробка'; + + @override + String extensionHooksAvailable(int count) { + return '$count доступних хуків'; + } + + @override + String extensionPatternsCount(int count) { + return '$count шаблон(ів)'; + } + + @override + String extensionStrategy(String strategy) { + return 'Стратегія: $strategy'; + } + + @override + String get extensionsProviderPrioritySection => 'Пріоритет постачальника'; + + @override + String get extensionsInstalledSection => 'Встановлені розширення'; + + @override + String get extensionsNoExtensions => 'Розширень не встановлено'; + + @override + String get extensionsNoExtensionsSubtitle => + 'Встановіть файли .spotiflac-ext, щоб додати нових провайдерів'; + + @override + String get extensionsInstallButton => 'Встановити розширення'; + + @override + String get extensionsInfoTip => + 'Розширення можуть додавати нові метадані та завантажувати постачальників. Встановлюйте розширення лише з перевірених джерел.'; + + @override + String get extensionsInstalledSuccess => 'Розширення успішно встановлено'; + + @override + String get extensionsDownloadPriority => 'Пріоритет завантаження'; + + @override + String get extensionsDownloadPrioritySubtitle => + 'Встановити порядок завантаження'; + + @override + String get extensionsFallbackTitle => 'Резервні розширення'; + + @override + String get extensionsFallbackSubtitle => + 'Виберіть, які встановлені розширення для завантаження можна використовувати як резервні'; + + @override + String get extensionsNoDownloadProvider => + 'Без розширень із постачальником завантажень'; + + @override + String get extensionsMetadataPriority => 'Пріоритет метаданих'; + + @override + String get extensionsMetadataPrioritySubtitle => + 'Встановити порядок пошуку та джерел метаданих'; + + @override + String get extensionsNoMetadataProvider => + 'Без розширень із постачальником метаданих'; + + @override + String get extensionsSearchProvider => 'Постачальник пошуку'; + + @override + String get extensionsNoCustomSearch => + 'Без розширень із користувацьким пошуком'; + + @override + String get extensionsSearchProviderDescription => + 'Виберіть, який сервіс використовувати для пошуку треків'; + + @override + String get extensionsCustomSearch => 'Користувацький пошук'; + + @override + String get extensionsErrorLoading => 'Помилка завантаження розширення'; + + @override + String get qualityFlacLossless => 'FLAC без втрат'; + + @override + String get qualityFlacLosslessSubtitle => '16 біт / 44,1 кГц'; + + @override + String get qualityHiResFlac => 'FLAC високої роздільної здатності'; + + @override + String get qualityHiResFlacSubtitle => '24-біт / до 96 кГц'; + + @override + String get qualityHiResFlacMax => 'FLAC Max з високою роздільною здатністю'; + + @override + String get qualityHiResFlacMaxSubtitle => '24-біт / до 192 кГц'; + + @override + String get downloadLossy320 => 'Lossy (із втратами) 320 кбіт/с'; + + @override + String get downloadLossyFormat => 'Формат із втратами'; + + @override + String get downloadLossy320Format => 'Формат із втратами 320 кбіт/с'; + + @override + String get downloadLossy320FormatDesc => + 'Виберіть вихідний формат для завантажень Tidal 320 кбіт/с із втратами. Оригінальний потік AAC буде конвертовано у вибраний вами формат.'; + + @override + String get downloadLossyMp3 => 'MP3 320 кбіт/с'; + + @override + String get downloadLossyMp3Subtitle => + 'Найкраща сумісність, ~10 МБ на доріжку'; + + @override + String get downloadLossyOpus256 => 'Opus 256 кбіт/с'; + + @override + String get downloadLossyOpus256Subtitle => + 'Opus найкращої якості, ~8 МБ на трек'; + + @override + String get downloadLossyOpus128 => 'Opus 128 кбіт/с'; + + @override + String get downloadLossyOpus128Subtitle => + 'Найменший розмір, ~4 МБ на доріжку'; + + @override + String get qualityNote => + 'Фактична якість залежить від наявності треку в сервісі'; + + @override + String get downloadAskBeforeDownload => 'Запитувати перед завантаженням'; + + @override + String get downloadDirectory => 'Каталог завантажень'; + + @override + String get downloadSeparateSinglesFolder => 'Окрема папка для синглів'; + + @override + String get downloadAlbumFolderStructure => 'Структура папок альбому'; + + @override + String get downloadUseAlbumArtistForFolders => + 'Використовувати виконавця альбому для папок'; + + @override + String get downloadUsePrimaryArtistOnly => + 'Тільки основний виконавець для папок'; + + @override + String get downloadUsePrimaryArtistOnlyEnabled => + 'Вибраних виконавців видалити з назви папки (наприклад, Джастін Бібер, Quavo → Джастін Бібер)'; + + @override + String get downloadUsePrimaryArtistOnlyDisabled => + 'Повний рядок виконавця, що використовується для назви папки'; + + @override + String get downloadSelectQuality => 'Вибрати якість'; + + @override + String get downloadFrom => 'Завантажити з'; + + @override + String get appearanceAmoledDark => 'Темний AMOLED'; + + @override + String get appearanceAmoledDarkSubtitle => 'Чисто чорний фон'; + + @override + String get queueClearAll => 'Усі'; + + @override + String get queueClearAllMessage => + 'Ви впевнені, що хочете очистити всі завантаження?'; + + @override + String get settingsAutoExportFailed => + 'Автоматичний експорт невдалих завантажень'; + + @override + String get settingsAutoExportFailedSubtitle => + 'Автоматично зберігати невдалі завантаження у файл TXT'; + + @override + String get settingsDownloadNetwork => 'Мережа для завантаження'; + + @override + String get settingsDownloadNetworkAny => 'Wi-Fi + мобільний інтернет'; + + @override + String get settingsDownloadNetworkWifiOnly => 'Тільки Wi-Fi'; + + @override + String get settingsDownloadNetworkSubtitle => + 'Вибрати мережу для завантажень. Якщо встановлено значення «Тільки Wi-Fi», завантаження призупиняться через мобільні дані.'; + + @override + String get albumFolderArtistAlbum => 'Артист / Альбом'; + + @override + String get albumFolderArtistAlbumSubtitle => + 'Альбоми/Ім\'я артиста/Назва альбому/'; + + @override + String get albumFolderArtistYearAlbum => 'Артист / [Рік] Альбом'; + + @override + String get albumFolderArtistYearAlbumSubtitle => + 'Альбоми/Ім\'я Виконавця/[2005] Назва альбому/'; + + @override + String get albumFolderAlbumOnly => 'Тільки альбом'; + + @override + String get albumFolderAlbumOnlySubtitle => 'Альбоми/Назва Альбому/'; + + @override + String get albumFolderYearAlbum => '[Рік] Альбом'; + + @override + String get albumFolderYearAlbumSubtitle => 'Альбоми/[2005] Назва Альбому/'; + + @override + String get albumFolderArtistAlbumSingles => 'Виконавець / Альбом + Сингли'; + + @override + String get albumFolderArtistAlbumSinglesSubtitle => + 'Виконавець/Альбом/ та Виконавець/Сингли/'; + + @override + String get albumFolderArtistAlbumFlat => + 'Виконавець / Альбом (сингли без альбомів)'; + + @override + String get albumFolderArtistAlbumFlatSubtitle => + 'Виконавець/Альбом/ та Виконавець/пісня.flac'; + + @override + String get downloadedAlbumDeleteSelected => 'Видалити вибране'; + + @override + String downloadedAlbumDeleteMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'треків', + one: 'трек', + ); + return 'Видалити $count $_temp0 з цього альбому?\n\nЦе також призведе до видалення файлів зі сховища.'; + } + + @override + String downloadedAlbumSelectedCount(int count) { + return 'Вибрано $count'; + } + + @override + String get downloadedAlbumAllSelected => 'Усі треки вибрано'; + + @override + String get downloadedAlbumTapToSelect => 'Натисніть на треки, щоб вибрати'; + + @override + String downloadedAlbumDeleteCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'треків', + one: 'трек', + ); + return 'Видалити $count $_temp0'; + } + + @override + String get downloadedAlbumSelectToDelete => 'Виберіть треки для видалення'; + + @override + String downloadedAlbumDiscHeader(int discNumber) { + return 'Диск $discNumber'; + } + + @override + String get recentTypeArtist => 'Артист'; + + @override + String get recentTypeAlbum => 'Альбом'; + + @override + String get recentTypeSong => 'Пісня'; + + @override + String get recentTypePlaylist => 'Список відтворення'; + + @override + String get recentEmpty => 'Поки що немає нещодавніх записів'; + + @override + String get recentShowAllDownloads => 'Показати всі завантаження'; + + @override + String recentPlaylistInfo(String name) { + return 'Список відтворення: $name'; + } + + @override + String get discographyDownload => 'Завантажити дискографію'; + + @override + String get discographyDownloadAll => 'Завантажити все'; + + @override + String discographyDownloadAllSubtitle(int count, int albumCount) { + return '$count треків з $albumCount релізів'; + } + + @override + String get discographyAlbumsOnly => 'Тільки альбоми'; + + @override + String discographyAlbumsOnlySubtitle(int count, int albumCount) { + return '$count треків з $albumCount альбомів'; + } + + @override + String get discographySinglesOnly => 'Тільки сингли та міні-альбоми'; + + @override + String discographySinglesOnlySubtitle(int count, int albumCount) { + return '$count треків з $albumCount синглів'; + } + + @override + String get discographySelectAlbums => 'Вибрати альбоми...'; + + @override + String get discographySelectAlbumsSubtitle => + 'Виберіть конкретні альбоми або сингли'; + + @override + String get discographyFetchingTracks => 'Отримання треків...'; + + @override + String discographyFetchingAlbum(int current, int total) { + return 'Отримання $current з $total...'; + } + + @override + String discographySelectedCount(int count) { + return '$count вибрано'; + } + + @override + String get discographyDownloadSelected => 'Завантажити вибране'; + + @override + String discographyAddedToQueue(int count) { + return 'Додано $count треків до черги'; + } + + @override + String discographySkippedDownloaded(int added, int skipped) { + return '$added додано, $skipped вже завантажено'; + } + + @override + String get discographyNoAlbums => 'Немає доступних альбомів'; + + @override + String get discographyFailedToFetch => 'Не вдалося отримати деякі альбоми'; + + @override + String get sectionStorageAccess => 'Доступ до сховища'; + + @override + String get allFilesAccess => 'Доступ до всіх файлів'; + + @override + String get allFilesAccessEnabledSubtitle => + 'Можна записувати в будь-яку папку'; + + @override + String get allFilesAccessDisabledSubtitle => 'Обмежено лише медіа-папками'; + + @override + String get allFilesAccessDescription => + 'Увімкніть цю опцію, якщо під час збереження у власні папки виникають помилки запису. Android 13+ за замовчуванням обмежує доступ до певних каталогів.'; + + @override + String get allFilesAccessDeniedMessage => + 'У дозволі відмовлено. Будь ласка, увімкніть «Доступ до всіх файлів» вручну в налаштуваннях системи.'; + + @override + String get allFilesAccessDisabledMessage => + 'У дозволі відмовлено. Будь ласка, увімкніть «Доступ до всіх файлів» вручну в налаштуваннях системи.'; + + @override + String get settingsLocalLibrary => 'Локальна бібліотека'; + + @override + String get settingsLocalLibrarySubtitle => + 'Сканування музики та виявлення дублікатів'; + + @override + String get settingsCache => 'Сховище та Кеш'; + + @override + String get settingsCacheSubtitle => + 'Переглянути розмір і очистити кешовані дані'; + + @override + String get libraryTitle => 'Локальна бібліотека'; + + @override + String get libraryScanSettings => 'Налаштування сканування'; + + @override + String get libraryEnableLocalLibrary => 'Увімкнути локальну бібліотеку'; + + @override + String get libraryEnableLocalLibrarySubtitle => + 'Скануати та відстежити свою існуючу музику'; + + @override + String get libraryFolder => 'Папка бібліотеки'; + + @override + String get libraryFolderHint => 'Натисніть, щоб вибрати папку'; + + @override + String get libraryShowDuplicateIndicator => 'Показати індикатор дублікатів'; + + @override + String get libraryShowDuplicateIndicatorSubtitle => + 'Показувати під час пошуку існуючих треків'; + + @override + String get libraryAutoScan => 'Автоматичне сканування'; + + @override + String get libraryAutoScanSubtitle => + 'Автоматичне сканування бібліотеки на наявність нових файлів'; + + @override + String get libraryAutoScanOff => 'Вимкнено'; + + @override + String get libraryAutoScanOnOpen => 'Кожного разу коли додаток відкривається'; + + @override + String get libraryAutoScanDaily => 'Щоденно'; + + @override + String get libraryAutoScanWeekly => 'Щотижнево'; + + @override + String get libraryActions => 'Дії'; + + @override + String get libraryScan => 'Сканувати бібліотеку'; + + @override + String get libraryScanSubtitle => 'Сканувати для аудіофайлів'; + + @override + String get libraryScanSelectFolderFirst => 'Спочатку виберіть папку'; + + @override + String get libraryCleanupMissingFiles => 'Очищення відсутніх файлів'; + + @override + String get libraryCleanupMissingFilesSubtitle => + 'Видалити записи для файлів, яких більше не існує'; + + @override + String get libraryClear => 'Очистити бібліотеку'; + + @override + String get libraryClearSubtitle => 'Видалити всі скановані треки'; + + @override + String get libraryClearConfirmTitle => 'Очистити бібліотеку'; + + @override + String get libraryClearConfirmMessage => + 'Це видалить усі скановані треки з вашої бібліотеки. Ваші фактичні музичні файли не будуть видалені.'; + + @override + String get libraryAbout => 'Про локальну бібліотеку'; + + @override + String get libraryAboutDescription => + 'Сканує вашу існуючу музичну колекцію для виявлення дублікатів під час завантаження. Підтримує формати FLAC, M4A, MP3, Opus та OGG. Метадані зчитуються з тегів файлів, коли вони доступні.'; + + @override + String libraryTracksUnit(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'треків', + one: 'трек', + ); + return '$_temp0'; + } + + @override + String libraryFilesUnit(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'файлів', + one: 'file', + ); + return '$_temp0'; + } + + @override + String libraryLastScanned(String time) { + return 'Останнє сканування: $time'; + } + + @override + String get libraryLastScannedNever => 'Ніколи'; + + @override + String get libraryScanning => 'Сканування...'; + + @override + String get libraryScanFinalizing => 'Завершення роботи з бібліотекою...'; + + @override + String libraryScanProgress(String progress, int total) { + return '$progress% від $total файлів'; + } + + @override + String get libraryInLibrary => 'У бібліотеці'; + + @override + String libraryRemovedMissingFiles(int count) { + return 'Видалено $count відсутніх файлів з бібліотеки'; + } + + @override + String get libraryCleared => 'Бібліотека очищена'; + + @override + String get libraryStorageAccessRequired => 'Потрібен доступ до сховища'; + + @override + String get libraryStorageAccessMessage => + 'SpotiFLAC потрібен доступ до сховища для сканування вашої музичної бібліотеки. Надайте дозвіл у налаштуваннях.'; + + @override + String get libraryFolderNotExist => 'Вибрана папка не існує'; + + @override + String get librarySourceDownloaded => 'Завантажені'; + + @override + String get librarySourceLocal => 'Локальні'; + + @override + String get libraryFilterAll => 'Усі'; + + @override + String get libraryFilterDownloaded => 'Завантажені'; + + @override + String get libraryFilterLocal => 'Локальні'; + + @override + String get libraryFilterTitle => 'Фільтри'; + + @override + String get libraryFilterReset => 'Скинути'; + + @override + String get libraryFilterApply => 'Застосувати'; + + @override + String get libraryFilterSource => 'Джерело'; + + @override + String get libraryFilterQuality => 'Якість'; + + @override + String get libraryFilterQualityHiRes => + 'Висока роздільна здатність (24 біти)'; + + @override + String get libraryFilterQualityCD => 'CD (16-бітний)'; + + @override + String get libraryFilterQualityLossy => 'Із втратами (lossy)'; + + @override + String get libraryFilterFormat => 'Формат'; + + @override + String get libraryFilterMetadata => 'Метадані'; + + @override + String get libraryFilterMetadataComplete => 'Повні метадані'; + + @override + String get libraryFilterMetadataMissingAny => 'будь-які метадані'; + + @override + String get libraryFilterMetadataMissingYear => 'Відсутній рік'; + + @override + String get libraryFilterMetadataMissingGenre => 'Відсутній жанр'; + + @override + String get libraryFilterMetadataMissingAlbumArtist => + 'Відсутній виконавець альбому'; + + @override + String get libraryFilterSort => 'Сортувати'; + + @override + String get libraryFilterSortLatest => 'Найновіші'; + + @override + String get libraryFilterSortOldest => 'Найстаріші'; + + @override + String get libraryFilterSortAlbumAsc => 'Альбом (А-Я)'; + + @override + String get libraryFilterSortAlbumDesc => 'Альбом (Я-А)'; + + @override + String get libraryFilterSortGenreAsc => 'Жанр (А-Я)'; + + @override + String get libraryFilterSortGenreDesc => 'Жанр (Я-А)'; + + @override + String get timeJustNow => 'Щойно'; + + @override + String timeMinutesAgo(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count хвилин тому', + one: '1 minute ago', + ); + return '$_temp0'; + } + + @override + String timeHoursAgo(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count годин тому', + one: '1 hour ago', + ); + return '$_temp0'; + } + + @override + String get tutorialWelcomeTitle => 'Ласкаво просимо до SpotiFLAC!'; + + @override + String get tutorialWelcomeDesc => + 'Давайте дізнаємося, як завантажувати улюблену музику в якості без втрат. Цей короткий посібник покаже вам основи.'; + + @override + String get tutorialWelcomeTip1 => + 'Завантажуйте музику зі Spotify, Deezer або вставляйте будь-яку підтримувану URL-адресу'; + + @override + String get tutorialWelcomeTip2 => + 'Отримуйте аудіо у якості FLAC з Tidal, Qobuz або Deezer'; + + @override + String get tutorialWelcomeTip3 => + 'Автоматичне додавання метаданих, обкладинки та текстів пісень'; + + @override + String get tutorialSearchTitle => 'Пошук музики'; + + @override + String get tutorialSearchDesc => + 'Існує два простих способи знайти музику, яку ви хочете завантажити.'; + + @override + String get tutorialDownloadTitle => 'Завантаження музики'; + + @override + String get tutorialDownloadDesc => + 'Завантаження музики просте та швидке. Ось як це працює.'; + + @override + String get tutorialLibraryTitle => 'Ваша бібліотека'; + + @override + String get tutorialLibraryDesc => + 'Вся завантажена музика організована на вкладці «Бібліотека».'; + + @override + String get tutorialLibraryTip1 => + 'Перегляд стану завантаження та черги на вкладці «Бібліотека»'; + + @override + String get tutorialLibraryTip2 => + 'Торкніться будь-якої композиції, щоб відтворити її за допомогою музичного плеєра'; + + @override + String get tutorialLibraryTip3 => + 'Перемикання між списком та сіткою для кращого перегляду'; + + @override + String get tutorialExtensionsTitle => 'Розширення'; + + @override + String get tutorialExtensionsDesc => + 'Розширте можливості програми за допомогою розширень спільноти.'; + + @override + String get tutorialExtensionsTip1 => + 'Перегляньте вкладку «Репозиторій», щоб знайти корисні розширення'; + + @override + String get tutorialExtensionsTip2 => + 'Додавайте нових постачальників послуг завантаження або джерела пошуку'; + + @override + String get tutorialExtensionsTip3 => + 'Отримайте тексти пісень, розширені метадані та інші функції'; + + @override + String get tutorialSettingsTitle => 'Налаштуйте свій досвід'; + + @override + String get tutorialSettingsDesc => + 'Персоналізуйте програму в налаштуваннях відповідно до ваших уподобань.'; + + @override + String get tutorialSettingsTip1 => + 'Змініть місце завантаження та організації папок'; + + @override + String get tutorialSettingsTip2 => + 'Встановіть параметри якості звуку та формату за замовчуванням'; + + @override + String get tutorialSettingsTip3 => + 'Налаштуйте тему та зовнішній вигляд програми'; + + @override + String get tutorialReadyMessage => + 'Готово! Почніть завантажувати свою улюблену музику прямо зараз.'; + + @override + String get libraryForceFullScan => 'Примусове повне сканування'; + + @override + String get libraryForceFullScanSubtitle => + 'Пересканувати всі файли, ігноруючи кеш'; + + @override + String get cleanupOrphanedDownloads => 'Очищення застарілих завантажень'; + + @override + String get cleanupOrphanedDownloadsSubtitle => + 'Видалити записи історії для файлів, яких більше не існує'; + + @override + String cleanupOrphanedDownloadsResult(int count) { + return 'Видалено $count утрачених записів з історії'; + } + + @override + String get cleanupOrphanedDownloadsNone => 'Не знайдено утрачених записів'; + + @override + String get cacheTitle => 'Зберігання та кеш'; + + @override + String get cacheSummaryTitle => 'Огляд кешу'; + + @override + String get cacheSummarySubtitle => + 'Очищення кешу не призведе до видалення завантажених музичних файлів.'; + + @override + String cacheEstimatedTotal(String size) { + return 'Орієнтовне використання кешу: $size'; + } + + @override + String get cacheSectionStorage => 'Кешовані дані'; + + @override + String get cacheSectionMaintenance => 'Технічне обслуговування'; + + @override + String get cacheAppDirectory => 'Каталог кешу додатка'; + + @override + String get cacheAppDirectoryDesc => + 'HTTP-відповіді, дані WebView та інші тимчасові дані додатків.'; + + @override + String get cacheTempDirectory => 'Тимчасовий каталог'; + + @override + String get cacheTempDirectoryDesc => + 'Тимчасові файли із завантажень та конвертації аудіо.'; + + @override + String get cacheCoverImage => 'Кеш зображень обкладинок'; + + @override + String get cacheCoverImageDesc => + 'Завантажено обкладинку альбому та треку. Завантаження відбудеться повторно після перегляду.'; + + @override + String get cacheLibraryCover => 'Кеш бібліотеки обкладинок'; + + @override + String get cacheLibraryCoverDesc => + 'Обкладинку витягнуто з локальних музичних файлів. Буде повторно витягнуто під час наступного сканування.'; + + @override + String get cacheExploreFeed => 'Огляд кешу стрічки'; + + @override + String get cacheExploreFeedDesc => + 'Переглянути вміст вкладки (нові випуски, тренди). Оновиться під час наступного відвідування.'; + + @override + String get cacheTrackLookup => 'Відстеження кешу пошуку'; + + @override + String get cacheTrackLookupDesc => + 'Пошук ідентифікаторів треків Spotify/Deezer. Очищення може уповільнити наступні кілька пошуків.'; + + @override + String get cacheCleanupUnusedDesc => + 'Видалити історію втрачених завантажень та записи бібліотеки для відсутніх файлів.'; + + @override + String get cacheNoData => 'Кешованих даних немає'; + + @override + String cacheSizeWithFiles(String size, int count) { + return '$size у $count файлах'; + } + + @override + String cacheSizeOnly(String size) { + return '$size'; + } + + @override + String cacheEntries(int count) { + return '$count записів'; + } + + @override + String cacheClearSuccess(String target) { + return 'Очищено: $target'; + } + + @override + String get cacheClearConfirmTitle => 'Очистити кеш?'; + + @override + String cacheClearConfirmMessage(String target) { + return 'Це очистить кешовані дані для $target. Завантажені музичні файли не будуть видалені.'; + } + + @override + String get cacheClearAllConfirmTitle => 'Очистити увесь кеш?'; + + @override + String get cacheClearAllConfirmMessage => + 'Це очистить усі категорії кешу на цій сторінці. Завантажені музичні файли не будуть видалені.'; + + @override + String get cacheClearAll => 'Очистити весь кеш'; + + @override + String get cacheCleanupUnused => 'Очищення невикористаних даних'; + + @override + String get cacheCleanupUnusedSubtitle => + 'Видалити історію утрачених завантажень файлів та відсутні записи бібліотеки'; + + @override + String cacheCleanupResult(int downloadCount, int libraryCount) { + return 'Очищення завершено: $downloadCount утрачених завантажень, $libraryCount відсутніх записів бібліотеки'; + } + + @override + String get cacheRefreshStats => 'Оновити статистику'; + + @override + String get trackSaveCoverArt => 'Зберегти обкладинку'; + + @override + String get trackSaveCoverArtSubtitle => + 'Зберегти обкладинку альбому як файл .jpg'; + + @override + String get trackSaveLyrics => 'Зберегти текст пісні (.lrc)'; + + @override + String get trackSaveLyricsSubtitle => + 'Отримати та зберегти текст пісні у форматі .lrc'; + + @override + String get trackSaveLyricsProgress => 'Збереження тексту пісні...'; + + @override + String get trackReEnrich => 'Перезбагачувати'; + + @override + String get trackReEnrichOnlineSubtitle => + 'Пошук метаданих в Інтернеті та вбудовування у файл'; + + @override + String get trackReEnrichFieldsTitle => 'Поля для оновлення'; + + @override + String get trackReEnrichFieldCover => 'Обкладинка'; + + @override + String get trackReEnrichFieldLyrics => 'Тексти пісень'; + + @override + String get trackReEnrichFieldBasicTags => 'Альбом, Виконавець альбому'; + + @override + String get trackReEnrichFieldTrackInfo => 'Номер треку та диска'; + + @override + String get trackReEnrichFieldReleaseInfo => 'Дата та ISRC'; + + @override + String get trackReEnrichFieldExtra => 'Жанр, Лейбл, Авторське право'; + + @override + String get trackReEnrichSelectAll => 'Вибрати все'; + + @override + String get trackEditMetadata => 'Редагувати метадані'; + + @override + String trackCoverSaved(String fileName) { + return 'Обкладинку збережено до $fileName'; + } + + @override + String get trackCoverNoSource => 'Джерело обкладинки недоступне'; + + @override + String trackLyricsSaved(String fileName) { + return 'Текст пісні збережено в $fileName'; + } + + @override + String get trackReEnrichProgress => 'Повторне збагачення метаданих...'; + + @override + String get trackReEnrichSearching => 'Пошук метаданих в Інтернеті...'; + + @override + String get trackReEnrichSuccess => 'Метадані повторно збагачені успішно'; + + @override + String get trackReEnrichFfmpegFailed => + 'Не вдалося вбудувати метадані FFmpeg'; + + @override + String get queueFlacAction => 'Черга FLAC'; + + @override + String queueFlacConfirmMessage(int count) { + return 'Пошук онлайн-збігів для вибраних треків та додавання завантажень FLAC до черги.\n\nІснуючі файли не будуть змінені або видалені.\n\nАвтоматично додаються до черги лише збіги з високою достовірністю.\n\n$count вибрано'; + } + + @override + String queueFlacFindingProgress(int current, int total) { + return 'Пошук FLAC-збігів... ($current/$total)'; + } + + @override + String get queueFlacNoReliableMatches => + 'Не знайдено надійних онлайн-відповідей для вибраного запиту'; + + @override + String queueFlacQueuedWithSkipped(int addedCount, int skippedCount) { + return 'Додано $addedCount треків до черги, пропущено $skippedCount'; + } + + @override + String trackSaveFailed(String error) { + return 'Не вдалося: $error'; + } + + @override + String get trackConvertFormat => 'Конвертувати формат'; + + @override + String get trackConvertFormatSubtitle => + 'Конвертувати в MP3, Opus, ALAC або FLAC'; + + @override + String get trackConvertTitle => 'Конвертувати аудіо'; + + @override + String get trackConvertTargetFormat => 'Цільовий формат'; + + @override + String get trackConvertBitrate => 'Бітрейт'; + + @override + String get trackConvertConfirmTitle => 'Підтвердити конверсію'; + + @override + String trackConvertConfirmMessage( + String sourceFormat, + String targetFormat, + String bitrate, + ) { + return 'Конвертувати з $sourceFormat в $targetFormat із бітрейтом $bitrate?\n\nОригінальний файл буде видалено після конвертації.'; + } + + @override + String trackConvertConfirmMessageLossless( + String sourceFormat, + String targetFormat, + ) { + return 'Конвертувати з $sourceFormat у $targetFormat? (Lossless — без втрати якості)\n\nОригінальний файл буде видалено після конвертації.'; + } + + @override + String get trackConvertLosslessHint => + 'Lossless конвертація — без втрати якості'; + + @override + String get trackConvertConverting => 'Конвертування аудіо...'; + + @override + String trackConvertSuccess(String format) { + return 'Конвертовано в $format успішно'; + } + + @override + String get trackConvertFailed => 'Конвертація не вдалася'; + + @override + String get cueSplitTitle => 'Розділений аркуш CUE'; + + @override + String get cueSplitSubtitle => 'Розділення CUE+FLAC на окремі треки'; + + @override + String cueSplitAlbum(String album) { + return 'Альбом: $album'; + } + + @override + String cueSplitArtist(String artist) { + return 'Артист: $artist'; + } + + @override + String cueSplitTrackCount(int count) { + return '$count треків'; + } + + @override + String get cueSplitConfirmTitle => 'Розділений альбом CUE'; + + @override + String cueSplitConfirmMessage(String album, int count) { + return 'Розділити \"$album\" на $count окремих FLAC-файлів?\n\nФайли будуть збережені в одному каталозі.'; + } + + @override + String cueSplitSplitting(int current, int total) { + return 'Розділення аркуша CUE... ($current/$total)'; + } + + @override + String cueSplitSuccess(int count) { + return 'Розділено на $count треків успішно'; + } + + @override + String get cueSplitFailed => 'Розділення CUE не вдалося'; + + @override + String get cueSplitNoAudioFile => + 'Аудіофайл для цього аркуша CUE не знайдено'; + + @override + String get cueSplitButton => 'Розділити на треки'; + + @override + String get actionCreate => 'Створити'; + + @override + String get collectionFoldersTitle => 'Мої папки'; + + @override + String get collectionWishlist => 'Список бажань'; + + @override + String get collectionLoved => 'Вподобані'; + + @override + String get collectionPlaylists => 'Списки відтворення'; + + @override + String get collectionPlaylist => 'Список відтворення'; + + @override + String get collectionAddToPlaylist => 'Додати до списку відтворення'; + + @override + String get collectionCreatePlaylist => 'Створити плейлист'; + + @override + String get collectionNoPlaylistsYet => 'Поки що немає списків відтворення'; + + @override + String get collectionNoPlaylistsSubtitle => + 'Створіть список відтворення, щоб розпочати категоризацію треків'; + + @override + String collectionPlaylistTracks(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count треків', + one: '1 трек', + ); + return '$_temp0'; + } + + @override + String collectionAddedToPlaylist(String playlistName) { + return 'Додано до \"$playlistName\"'; + } + + @override + String collectionAlreadyInPlaylist(String playlistName) { + return 'Вже у списку відтворення \"$playlistName\"'; + } + + @override + String get collectionPlaylistCreated => 'Список відтворення створено'; + + @override + String get collectionPlaylistNameHint => 'Назва списку відтворення'; + + @override + String get collectionPlaylistNameRequired => + 'Потрібно вказати назву списку відтворення'; + + @override + String get collectionRenamePlaylist => 'Перейменувати список відтворення'; + + @override + String get collectionDeletePlaylist => 'Видалити список відтворення'; + + @override + String collectionDeletePlaylistMessage(String playlistName) { + return 'Видалити \"$playlistName\" та всі треки в ньому?'; + } + + @override + String get collectionPlaylistDeleted => 'Список відтворення видалено'; + + @override + String get collectionPlaylistRenamed => 'Список відтворення перейменовано'; + + @override + String get collectionWishlistEmptyTitle => 'Список бажань порожній'; + + @override + String get collectionWishlistEmptySubtitle => + 'Натисніть + на треках, щоб зберегти те, що ви хочете завантажити пізніше'; + + @override + String get collectionLovedEmptyTitle => 'Папка \"Улюблені\" порожня'; + + @override + String get collectionLovedEmptySubtitle => + 'Натисніть «Подобається» на треках, щоб зберегти у свої улюблені'; + + @override + String get collectionPlaylistEmptyTitle => 'Список відтворення порожній'; + + @override + String get collectionPlaylistEmptySubtitle => + 'Тривале натискання + на будь-якій доріжці додасть її сюди'; + + @override + String get collectionRemoveFromPlaylist => 'Видалити зі списку відтворення'; + + @override + String get collectionRemoveFromFolder => 'Видалити з папки'; + + @override + String collectionRemoved(String trackName) { + return '\"$trackName\" видалено'; + } + + @override + String collectionAddedToLoved(String trackName) { + return '\"$trackName\" додано до списку улюблених'; + } + + @override + String collectionRemovedFromLoved(String trackName) { + return '\"$trackName\" видалено з уподобань'; + } + + @override + String collectionAddedToWishlist(String trackName) { + return '\"$trackName\" додано до списку бажань'; + } + + @override + String collectionRemovedFromWishlist(String trackName) { + return '\"$trackName\" видалено зі списку бажань'; + } + + @override + String get trackOptionAddToLoved => 'Додати до улюблених'; + + @override + String get trackOptionRemoveFromLoved => 'Видалити з улюблених'; + + @override + String get trackOptionAddToWishlist => 'Додати до списку бажань'; + + @override + String get trackOptionRemoveFromWishlist => 'Видалити зі списку бажань'; + + @override + String get collectionPlaylistChangeCover => 'Змінити зображення обкладинки'; + + @override + String get collectionPlaylistRemoveCover => 'Видалити зображення обкладинки'; + + @override + String selectionShareCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'треків', + one: 'трек', + ); + return 'Поділитися $count $_temp0'; + } + + @override + String get selectionShareNoFiles => + 'Файлів для спільного доступу не знайдено'; + + @override + String selectionConvertCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'треків', + one: 'трек', + ); + return 'Конвертувати $count $_temp0'; + } + + @override + String get selectionConvertNoConvertible => + 'Трансформованих треків не вибрано'; + + @override + String get selectionBatchConvertConfirmTitle => 'Пакетне конвертування'; + + @override + String selectionBatchConvertConfirmMessage( + int count, + String format, + String bitrate, + ) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'треків', + one: 'трек', + ); + return 'Конвертувати $count $_temp0 у $format з бітрейтом $bitrate?\n\nОригінальні файли будуть видалені після конвертації.'; + } + + @override + String selectionBatchConvertConfirmMessageLossless(int count, String format) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'треків', + one: 'трек', + ); + return 'Конвертувати $count $_temp0 у $format? (Lossless — без втрати якості)\n\nОригінальні файли будуть видалені після конвертації.'; + } + + @override + String selectionBatchConvertProgress(int current, int total) { + return 'Конвертування $current з $total...'; + } + + @override + String selectionBatchConvertSuccess(int success, int total, String format) { + return 'Конвертовано $success з $total треків у $format'; + } + + @override + String downloadedAlbumDownloadedCount(int count) { + return '$count завантажено'; + } + + @override + String get downloadUseAlbumArtistForFoldersAlbumSubtitle => + 'Папки виконавців використовують \"Виконавець альбому\", коли це можливо'; + + @override + String get downloadUseAlbumArtistForFoldersTrackSubtitle => + 'Папки виконавців використовують лише виконавця доріжки'; + + @override + String get lyricsProvidersTitle => 'Постачальники текстів пісень'; + + @override + String get lyricsProvidersDescription => + 'Увімкнення, вимкнення та зміна порядку джерел текстів пісень. Постачальники перевірятимуть зверху вниз, доки не буде знайдено текст пісні.'; + + @override + String get lyricsProvidersInfoText => + 'Постачальники розширених текстів пісень завжди запускаються перед вбудованими постачальниками. Принаймні один постачальник має залишатися ввімкненим.'; + + @override + String lyricsProvidersEnabledSection(int count) { + return 'Увімкнено ($count)'; + } + + @override + String lyricsProvidersDisabledSection(int count) { + return 'Вимкнено ($count)'; + } + + @override + String get lyricsProvidersAtLeastOne => + 'Принаймні один постачальник має залишатися ввімкненим'; + + @override + String get lyricsProvidersSaved => + 'Пріоритет постачальника текстів пісень збережено'; + + @override + String get lyricsProvidersDiscardContent => + 'У вас є незбережені зміни, які буде втрачено.'; + + @override + String get lyricsProviderLrclibDesc => + 'Синхронізована база даних текстів пісень з відкритим кодом'; + + @override + String get lyricsProviderNeteaseDesc => + 'NetEase Cloud Music (добре підходить для азійських пісень)'; + + @override + String get lyricsProviderMusixmatchDesc => + 'Найбільша база даних текстів пісень (багатомовна)'; + + @override + String get lyricsProviderAppleMusicDesc => + 'Синхронізовані тексти пісень слово за словом (через проксі)'; + + @override + String get lyricsProviderQqMusicDesc => + 'QQ Music (добре для китайських пісень, через проксі)'; + + @override + String get lyricsProviderExtensionDesc => 'Постачальник розширень'; + + @override + String get safMigrationTitle => 'Потрібне оновлення сховища'; + + @override + String get safMigrationMessage1 => + 'SpotiFLAC тепер використовує Android Storage Access Framework (SAF) для завантажень. Це виправляє помилки «відмовлено в доступі» на Android 10+.'; + + @override + String get safMigrationMessage2 => + 'Будь ласка, виберіть папку завантажень ще раз, щоб перейти до нової системи зберігання.'; + + @override + String get safMigrationSuccess => 'Папку завантажень оновлено до режиму SAF'; + + @override + String get settingsDonate => 'Пожертвувати кошти'; + + @override + String get settingsDonateSubtitle => + 'Підтримка розробки SpotiFLAC для мобільних пристроїв'; + + @override + String get tooltipLoveAll => 'Уподобати всіх'; + + @override + String get tooltipAddToPlaylist => 'Додати до списку відтворення'; + + @override + String snackbarRemovedTracksFromLoved(int count) { + return 'Видалено $count треків з уподобань'; + } + + @override + String snackbarAddedTracksToLoved(int count) { + return 'Додано $count треків до списку \"Улюблені\"'; + } + + @override + String get dialogDownloadAllTitle => 'Завантажити все'; + + @override + String dialogDownloadAllMessage(int count) { + return 'Завантажити $count треків?'; + } + + @override + String get homeSkipAlreadyDownloaded => 'Пропустити вже завантажені пісні'; + + @override + String get homeGoToAlbum => 'Перейти до альбому'; + + @override + String get homeAlbumInfoUnavailable => 'Інформація про альбом недоступна'; + + @override + String get snackbarLoadingCueSheet => 'Завантаження аркуша CUE...'; + + @override + String get snackbarMetadataSaved => 'Метадані успішно збережено'; + + @override + String get snackbarFailedToEmbedLyrics => 'Не вдалося вставити текст пісні'; + + @override + String get snackbarFailedToWriteStorage => + 'Не вдалося перезаписати у сховище'; + + @override + String snackbarError(String error) { + return 'Помилка: $error'; + } + + @override + String get snackbarNoActionDefined => + 'Для цієї кнопки не визначено жодної дії'; + + @override + String get noTracksFoundForAlbum => + 'Для цього альбому не знайдено жодних треків'; + + @override + String get downloadLocationSubtitle => + 'Виберіть режим зберігання для завантажених файлів.'; + + @override + String get storageModeAppFolder => 'Папка додатку (не SAF)'; + + @override + String get storageModeAppFolderSubtitle => + 'Використовувати шлях Music/SpotiFLAC за замовчуванням'; + + @override + String get storageModeSaf => 'Папка SAF'; + + @override + String get storageModeSafSubtitle => + 'Вибрати папку через Android Storage Access Framework'; + + @override + String get downloadFilenameDescription => + 'Налаштувати спосіб іменування ваших файлів.'; + + @override + String get downloadFilenameInsertTag => 'Натисніть, щоб вставити тег:'; + + @override + String get downloadSeparateSinglesEnabled => 'Папки «Альбоми» та «Сингли»'; + + @override + String get downloadSeparateSinglesDisabled => 'Всі файли в одній структурі'; + + @override + String get downloadArtistNameFilters => 'Фільтри імені виконавця'; + + @override + String get downloadCreatePlaylistSourceFolder => + 'Створити папку джерела списку відтворення'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Завантаження списків відтворення використовує Playlist/ плюс вашу звичайну структуру папок.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Завантаження списків відтворення використовують лише звичайну структуру папок.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'За допомогою списку відтворення завантаження вже розміщуються в папці зі списком відтворення.'; + + @override + String get downloadSongLinkRegion => 'Регіон SongLink'; + + @override + String get downloadNetworkCompatibilityMode => 'Режим сумісності з мережею'; + + @override + String get downloadNetworkCompatibilityModeEnabled => + 'Увімкнено: спробувати HTTP + прийняти недійсні сертифікати TLS (небезпечно)'; + + @override + String get downloadNetworkCompatibilityModeDisabled => + 'Вимкнено: сувора перевірка сертифіката HTTPS (рекомендовано)'; + + @override + String get downloadSelectServiceToEnable => + 'Виберіть вбудовану службу, яку потрібно ввімкнути'; + + @override + String get downloadSelectTidalQobuz => + 'Виберіть Tidal або Qobuz вище, щоб налаштувати якість'; + + @override + String get downloadEmbedLyricsDisabled => + 'Вимкнено, якщо вимкнено функцію «Вбудувати метадані»'; + + @override + String get downloadNeteaseIncludeTranslation => 'Netease: Включити переклад'; + + @override + String get downloadNeteaseIncludeTranslationEnabled => + 'Додати перекладені тексти пісень, коли вони доступні'; + + @override + String get downloadNeteaseIncludeTranslationDisabled => + 'Використовувати лише оригінальні тексти пісень'; + + @override + String get downloadNeteaseIncludeRomanization => + 'Netease: Включити романізацію'; + + @override + String get downloadNeteaseIncludeRomanizationEnabled => + 'Додати романізовані тексти пісень, коли це можливо'; + + @override + String get downloadNeteaseIncludeRomanizationDisabled => 'Вимкнути'; + + @override + String get downloadAppleQqMultiPerson => + 'Apple/QQ Багатокористувацький переклад слово за словом'; + + @override + String get downloadAppleQqMultiPersonEnabled => + 'Увімкнути теги динаміка v1/v2 та [bg:]'; + + @override + String get downloadAppleQqMultiPersonDisabled => + 'Спрощене послівне форматування'; + + @override + String get downloadMusixmatchLanguage => 'Мова Musixmatch'; + + @override + String get downloadMusixmatchLanguageAuto => 'Авто (оригінал)'; + + @override + String get downloadFilterContributing => + 'Фільтрувати виконавців-учасників у розділі «Виконавець альбому»'; + + @override + String get downloadFilterContributingEnabled => + 'Метадані виконавця альбому використовують лише основного виконавця'; + + @override + String get downloadFilterContributingDisabled => + 'Зберегти повне значення метаданих виконавця альбому'; + + @override + String get downloadProvidersNoneEnabled => 'Не ввімкнено'; + + @override + String get downloadMusixmatchLanguageCode => 'Код мови'; + + @override + String get downloadMusixmatchLanguageHint => 'авто / en / es / ja'; + + @override + String get downloadMusixmatchLanguageDesc => + 'Встановити потрібний код мови (наприклад: en, es, ja). Залиште поле порожнім для автоматичного вибору.'; + + @override + String get downloadMusixmatchAuto => 'Авто'; + + @override + String get downloadNetworkAnySubtitle => 'Wi-Fi + мобільний інтернет'; + + @override + String get downloadNetworkWifiOnlySubtitle => + 'Призупинити завантаження через мобільний інтернет'; + + @override + String get downloadSongLinkRegionDesc => + 'Використовувати як userCountry для пошуку SongLink API.'; + + @override + String get snackbarUnsupportedAudioFormat => 'Непідтримуваний аудіоформат'; + + @override + String get cacheRefresh => 'Оновити'; + + @override + String dialogDownloadPlaylistsMessage(int trackCount, int playlistCount) { + String _temp0 = intl.Intl.pluralLogic( + trackCount, + locale: localeName, + other: 'треків', + one: 'трек', + ); + String _temp1 = intl.Intl.pluralLogic( + playlistCount, + locale: localeName, + other: 'плейлистів', + one: 'плейлист', + ); + return 'Завантажити $trackCount $_temp0 з $playlistCount $_temp1?'; + } + + @override + String bulkDownloadPlaylistsButton(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'плейлистів', + one: 'плейлист', + ); + return 'Завантажити $count $_temp0'; + } + + @override + String get bulkDownloadSelectPlaylists => + 'Вибрати списки відтворення для завантаження'; + + @override + String get snackbarSelectedPlaylistsEmpty => + 'Вибрані списки відтворення не містять треків'; + + @override + String playlistsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count плейлистів', + one: '1 плейлист', + ); + return '$_temp0'; + } + + @override + String get editMetadataAutoFill => 'Автоматичне заповнення з онлайн-ресурсів'; + + @override + String get editMetadataAutoFillDesc => + 'Виберіть поля для автоматичного заповнення з онлайн-метаданих'; + + @override + String get editMetadataAutoFillFetch => 'Отримання та заповнення'; + + @override + String get editMetadataAutoFillSearching => 'Пошук в Інтернеті...'; + + @override + String get editMetadataAutoFillNoResults => + 'Відповідних метаданих в Інтернеті не знайдено'; + + @override + String editMetadataAutoFillDone(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'полей', + one: 'поле', + ); + return 'Заповнено $count $_temp0 з онлайн-метаданих'; + } + + @override + String get editMetadataAutoFillNoneSelected => + 'Виберіть принаймні одне поле для автоматичного заповнення'; + + @override + String get editMetadataFieldTitle => 'Назва'; + + @override + String get editMetadataFieldArtist => 'Виконавець'; + + @override + String get editMetadataFieldAlbum => 'Альбом'; + + @override + String get editMetadataFieldAlbumArtist => 'Виконавець альбому'; + + @override + String get editMetadataFieldDate => 'Дата'; + + @override + String get editMetadataFieldTrackNum => 'Номер треку'; + + @override + String get editMetadataFieldDiscNum => 'Номер диска'; + + @override + String get editMetadataFieldGenre => 'Жанр'; + + @override + String get editMetadataFieldIsrc => 'ISRC'; + + @override + String get editMetadataFieldLabel => 'Лейбл'; + + @override + String get editMetadataFieldCopyright => 'Авторське право'; + + @override + String get editMetadataFieldCover => 'Обкладинка'; + + @override + String get editMetadataSelectAll => 'Усі'; + + @override + String get editMetadataSelectEmpty => 'Порожні (без мета даних)'; + + @override + String queueDownloadingCount(int count) { + return 'Завантаження ($count)'; + } + + @override + String get queueDownloadedHeader => 'Завантажено'; + + @override + String get queueFilteringIndicator => 'Фільтрування...'; + + @override + String queueTrackCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count треків', + one: '1 трек', + ); + return '$_temp0'; + } + + @override + String queueAlbumCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count альбомів', + one: '1 альбом', + ); + return '$_temp0'; + } + + @override + String get queueEmptyAlbums => 'Немає завантажень альбомів'; + + @override + String get queueEmptyAlbumsSubtitle => + 'Завантажте кілька треків з альбому, щоб переглянути їх тут'; + + @override + String get queueEmptySingles => 'Без окремих завантажень'; + + @override + String get queueEmptySinglesSubtitle => + 'Завантаження окремих треків з’являться тут'; + + @override + String get queueEmptyHistory => 'Немає історії завантажень'; + + @override + String get queueEmptyHistorySubtitle => 'Завантажені треки з’являться тут'; + + @override + String get selectionAllPlaylistsSelected => 'Вибрано всі списки відтворення'; + + @override + String get selectionTapPlaylistsToSelect => + 'Торкніться списків відтворення, щоб вибрати'; + + @override + String get selectionSelectPlaylistsToDelete => + 'Вибрати списки відтворення для видалення'; + + @override + String get audioAnalysisTitle => 'Аналіз якості звуку'; + + @override + String get audioAnalysisDescription => + 'Перевірити якість без втрат за допомогою спектрального аналізу'; + + @override + String get audioAnalysisAnalyzing => 'Аналіз аудіо...'; + + @override + String get audioAnalysisSampleRate => 'Частота дискретизації'; + + @override + String get audioAnalysisBitDepth => 'Глибина бітів'; + + @override + String get audioAnalysisChannels => 'Канали'; + + @override + String get audioAnalysisDuration => 'Тривалість'; + + @override + String get audioAnalysisNyquist => 'Частота Найквіста'; + + @override + String get audioAnalysisFileSize => 'Розмір'; + + @override + String get audioAnalysisDynamicRange => 'Динамічний діапазон'; + + @override + String get audioAnalysisPeak => 'Пік'; + + @override + String get audioAnalysisRms => 'RMS'; + + @override + String get audioAnalysisSamples => 'Семпли'; + + @override + String extensionsSearchWith(String providerName) { + return 'Пошук за допомогою$providerName'; + } + + @override + String get extensionsHomeFeedProvider => + 'Постачальник оновлень домашньої стрічки'; + + @override + String get extensionsHomeFeedDescription => + 'Виберіть, яке розширення відображатиме домашню стрічку на головному екрані'; + + @override + String get extensionsHomeFeedAuto => 'Авто'; + + @override + String get extensionsHomeFeedAutoSubtitle => + 'Автоматично вибирати найкращий доступний'; + + @override + String extensionsHomeFeedUse(String extensionName) { + return 'Використовувати $extensionName головну стрічку'; + } + + @override + String get extensionsNoHomeFeedExtensions => + 'Без розширень із домашньою стрічкою'; + + @override + String get sortAlphaAsc => 'А-Я'; + + @override + String get sortAlphaDesc => 'Я-А'; + + @override + String get cancelDownloadTitle => 'Скасувати завантаження?'; + + @override + String cancelDownloadContent(String trackName) { + return 'Це скасує активне завантаження треку \"$trackName\".'; + } + + @override + String get cancelDownloadKeep => 'Зберегти'; + + @override + String get metadataSaveFailedFfmpeg => + 'Не вдалося зберегти метадані через FFmpeg'; + + @override + String get metadataSaveFailedStorage => + 'Не вдалося записати метадані назад у сховище'; + + @override + String snackbarFolderPickerFailed(String error) { + return 'Не вдалося відкрити засіб вибору папок: $error'; + } + + @override + String get errorLoadAlbum => 'Не вдалося завантажити альбом'; + + @override + String get errorLoadPlaylist => 'Не вдалося завантажити список відтворення'; + + @override + String get errorLoadArtist => 'Не вдалося завантажити артиста'; + + @override + String get notifChannelDownloadName => 'Прогрес завантаження'; + + @override + String get notifChannelDownloadDesc => 'Показує прогрес завантаження треків'; + + @override + String get notifChannelLibraryScanName => 'Сканування бібліотеки'; + + @override + String get notifChannelLibraryScanDesc => + 'Показує перебіг сканування локальної бібліотеки'; + + @override + String notifDownloadingTrack(String trackName) { + return 'Завантаження $trackName'; + } + + @override + String notifFinalizingTrack(String trackName) { + return 'Фіналізація $trackName'; + } + + @override + String get notifEmbeddingMetadata => 'Вбудовування метаданих...'; + + @override + String notifAlreadyInLibraryCount(int completed, int total) { + return 'Вже в бібліотеці ($completed/$total)'; + } + + @override + String get notifAlreadyInLibrary => 'Вже в бібліотеці'; + + @override + String notifDownloadCompleteCount(int completed, int total) { + return 'Завантаження завершено ($completed/$total)'; + } + + @override + String get notifDownloadComplete => 'Завантаження завершено'; + + @override + String notifDownloadsFinished(int completed, int failed) { + return 'Завантаження завершено ($completed завершено, $failed не вдалося)'; + } + + @override + String get notifAllDownloadsComplete => 'Усі завантаження завершено'; + + @override + String notifTracksDownloadedSuccess(int count) { + return '$count треки успішно завантажено'; + } + + @override + String get notifScanningLibrary => 'Сканування локальної бібліотеки'; + + @override + String notifLibraryScanProgressWithTotal( + int scanned, + int total, + int percentage, + ) { + return '$scanned/$total файлів • $percentage%'; + } + + @override + String notifLibraryScanProgressNoTotal(int scanned, int percentage) { + return '$scanned файлів скановано • $percentage%'; + } + + @override + String get notifLibraryScanComplete => 'Сканування бібліотеки завершено'; + + @override + String notifLibraryScanCompleteBody(int count) { + return '$count треків індексовано'; + } + + @override + String notifLibraryScanExcluded(int count) { + return '$count виключені'; + } + + @override + String notifLibraryScanErrors(int count) { + return '$count помилок'; + } + + @override + String get notifLibraryScanFailed => 'Не вдалося сканувати бібліотеку'; + + @override + String get notifLibraryScanCancelled => 'Сканування бібліотеки скасовано'; + + @override + String get notifLibraryScanStopped => 'Сканування зупинено до завершення.'; + + @override + String notifDownloadingUpdate(String version) { + return 'Завантаження SpotiFLAC v$version'; + } + + @override + String notifUpdateProgress(String received, String total, int percentage) { + return '$received / $total МБ • $percentage%'; + } + + @override + String get notifUpdateReady => 'Оновлення готове'; + + @override + String notifUpdateReadyBody(String version) { + return 'SpotiFLAC v$version завантажений. Натисніть щоб установити.'; + } + + @override + String get notifUpdateFailed => 'Не вдалося оновити'; + + @override + String get notifUpdateFailedBody => + 'Не вдалося завантажити оновлення. Спробуйте пізніше.'; +} diff --git a/lib/l10n/arb/app_uk.arb b/lib/l10n/arb/app_uk.arb new file mode 100644 index 00000000..62c47453 --- /dev/null +++ b/lib/l10n/arb/app_uk.arb @@ -0,0 +1,4513 @@ +{ + "@@locale": "uk", + "@@last_modified": "2026-01-16", + "appName": "SpotiFLAC", + "@appName": { + "description": "App name - DO NOT TRANSLATE" + }, + "navHome": "Головна", + "@navHome": { + "description": "Bottom navigation - Home tab" + }, + "navLibrary": "Бібліотека", + "@navLibrary": { + "description": "Bottom navigation - Library tab" + }, + "navSettings": "Налаштування", + "@navSettings": { + "description": "Bottom navigation - Settings tab" + }, + "navStore": "Репозиторій", + "@navStore": { + "description": "Bottom navigation - Extension store tab" + }, + "homeTitle": "Головна", + "@homeTitle": { + "description": "Home screen title" + }, + "homeSubtitle": "Вставте URL-адресу яка підтримується, або виконайте пошук за назвою", + "@homeSubtitle": { + "description": "Subtitle shown below search box" + }, + "homeSupports": "Підтримує: URL-адреси треків, альбомів, списків відтворення, виконавців", + "@homeSupports": { + "description": "Info text about supported URL types" + }, + "homeRecent": "Нещодавні", + "@homeRecent": { + "description": "Section header for recent searches" + }, + "historyFilterAll": "Усі", + "@historyFilterAll": { + "description": "Filter chip - show all items" + }, + "historyFilterAlbums": "Альбоми", + "@historyFilterAlbums": { + "description": "Filter chip - show albums only" + }, + "historyFilterSingles": "Сингли", + "@historyFilterSingles": { + "description": "Filter chip - show singles only" + }, + "historySearchHint": "Історія пошуку...", + "@historySearchHint": { + "description": "Search bar placeholder in history" + }, + "settingsTitle": "Налаштування", + "@settingsTitle": { + "description": "Settings screen title" + }, + "settingsDownload": "Завантаження", + "@settingsDownload": { + "description": "Settings section - download options" + }, + "settingsAppearance": "Зовнішній вигляд", + "@settingsAppearance": { + "description": "Settings section - visual customization" + }, + "settingsOptions": "Опції", + "@settingsOptions": { + "description": "Settings section - app options" + }, + "settingsExtensions": "Розширення", + "@settingsExtensions": { + "description": "Settings section - extension management" + }, + "settingsAbout": "Про додаток", + "@settingsAbout": { + "description": "Settings section - app info" + }, + "downloadTitle": "Завантажити", + "@downloadTitle": { + "description": "Download settings page title" + }, + "downloadAskQualitySubtitle": "Показувати вікно вибору якості для кожного завантаження", + "@downloadAskQualitySubtitle": { + "description": "Subtitle for ask quality toggle" + }, + "downloadFilenameFormat": "Формат імені файлу", + "@downloadFilenameFormat": { + "description": "Setting for output filename pattern" + }, + "downloadSingleFilenameFormat": "Формат імені одного файлу", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Шаблон назви файлу для синглів та міні-альбомів. Використовує ті самі теги, що й формат альбому.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, + "downloadFolderOrganization": "Організація папок", + "@downloadFolderOrganization": { + "description": "Title of the folder organization picker bottom sheet" + }, + "appearanceTitle": "Зовнішній вигляд", + "@appearanceTitle": { + "description": "Appearance settings page title" + }, + "appearanceThemeSystem": "Системний", + "@appearanceThemeSystem": { + "description": "Follow system theme" + }, + "appearanceThemeLight": "Світлий", + "@appearanceThemeLight": { + "description": "Light theme" + }, + "appearanceThemeDark": "Темний", + "@appearanceThemeDark": { + "description": "Dark theme" + }, + "appearanceDynamicColor": "Динамічний колір", + "@appearanceDynamicColor": { + "description": "Material You dynamic colors" + }, + "appearanceDynamicColorSubtitle": "Використати кольори зі своїх шпалер", + "@appearanceDynamicColorSubtitle": { + "description": "Subtitle for dynamic color" + }, + "appearanceHistoryView": "Історія переглядів", + "@appearanceHistoryView": { + "description": "Layout style for history" + }, + "appearanceHistoryViewList": "Список", + "@appearanceHistoryViewList": { + "description": "List layout option" + }, + "appearanceHistoryViewGrid": "Сітка", + "@appearanceHistoryViewGrid": { + "description": "Grid layout option" + }, + "optionsTitle": "Опції", + "@optionsTitle": { + "description": "Options settings page title" + }, + "optionsPrimaryProvider": "Основний постачальник", + "@optionsPrimaryProvider": { + "description": "Main search provider setting" + }, + "optionsPrimaryProviderSubtitle": "Розширення будуть випробувані першими", + "@optionsPrimaryProviderSubtitle": { + "description": "Subtitle for primary provider" + }, + "optionsUsingExtension": "Використання розширення: {extensionName}", + "@optionsUsingExtension": { + "description": "Shows active extension name", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, + "optionsSwitchBack": "Натисніть Deezer або Spotify, щоб повернутися до розширення", + "@optionsSwitchBack": { + "description": "Hint to switch back to built-in providers" + }, + "optionsAutoFallback": "Автоматичний резервний варіант", + "@optionsAutoFallback": { + "description": "Auto-retry with other services" + }, + "optionsAutoFallbackSubtitle": "Спробувати інші сервіси, якщо завантаження не вдається", + "@optionsAutoFallbackSubtitle": { + "description": "Subtitle for auto fallback" + }, + "optionsUseExtensionProviders": "Використати постачальників розширень", + "@optionsUseExtensionProviders": { + "description": "Enable extension download providers" + }, + "optionsUseExtensionProvidersOn": "Розширення будуть випробувані першими", + "@optionsUseExtensionProvidersOn": { + "description": "Status when extension providers enabled" + }, + "optionsUseExtensionProvidersOff": "Використати лише вбудованих постачальників", + "@optionsUseExtensionProvidersOff": { + "description": "Status when extension providers disabled" + }, + "optionsEmbedLyrics": "Вбудований текст пісні", + "@optionsEmbedLyrics": { + "description": "Embed lyrics in audio files" + }, + "optionsEmbedLyricsSubtitle": "Вбудовувати синхронізовані тексти пісень у файли FLAC", + "@optionsEmbedLyricsSubtitle": { + "description": "Subtitle for embed lyrics" + }, + "optionsMaxQualityCover": "Максимальна якість обкладинки", + "@optionsMaxQualityCover": { + "description": "Download highest quality album art" + }, + "optionsMaxQualityCoverSubtitle": "Завантажити обкладинку з найвищою роздільною здатністю", + "@optionsMaxQualityCoverSubtitle": { + "description": "Subtitle for max quality cover" + }, + "optionsReplayGain": "Нормалізація звуку", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Сканування гучності та вбудовування тегів нормалізації звуку (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Вимкнено: немає тегів нормалізації гучності", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Режим тегу виконавця", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Виберіть, як будуть записуватися кілька виконавців у вбудовані теги.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Одне об'єднане значення", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Для максимальної сумісності програвача напишіть одне значення ARTIST, наприклад, «Виконавець A, Виконавець B».", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Розділені теги для FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Для FLAC та Opus на кожного виконавця додати окремий тег виконавця; MP3 та M4A залишаються об’єднаними.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, + "optionsConcurrentDownloads": "Кількість одночасних завантажень", + "@optionsConcurrentDownloads": { + "description": "Number of parallel downloads" + }, + "optionsConcurrentSequential": "Послідовно (по одному за раз)", + "@optionsConcurrentSequential": { + "description": "Download one at a time" + }, + "optionsConcurrentParallel": "{count} паралельних завантажень", + "@optionsConcurrentParallel": { + "description": "Multiple parallel downloads", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "optionsConcurrentWarning": "Паралельні завантаження можуть призвести до обмеження швидкості", + "@optionsConcurrentWarning": { + "description": "Warning about rate limits" + }, + "optionsExtensionStore": "Репозиторій розширень", + "@optionsExtensionStore": { + "description": "Show/hide store tab" + }, + "optionsExtensionStoreSubtitle": "Показати вкладку «Репозиторій» у навігації", + "@optionsExtensionStoreSubtitle": { + "description": "Subtitle for extension store toggle" + }, + "optionsCheckUpdates": "Перевірити наявність оновлень", + "@optionsCheckUpdates": { + "description": "Auto update check toggle" + }, + "optionsCheckUpdatesSubtitle": "Повідомити, коли буде доступна нова версія", + "@optionsCheckUpdatesSubtitle": { + "description": "Subtitle for update check" + }, + "optionsUpdateChannel": "Канал оновлень", + "@optionsUpdateChannel": { + "description": "Stable vs preview releases" + }, + "optionsUpdateChannelStable": "Тільки стабільні релізи", + "@optionsUpdateChannelStable": { + "description": "Only stable updates" + }, + "optionsUpdateChannelPreview": "Отримати попередні релізи", + "@optionsUpdateChannelPreview": { + "description": "Include beta/preview updates" + }, + "optionsUpdateChannelWarning": "Тестовий реліз може містити помилки або неповні функції", + "@optionsUpdateChannelWarning": { + "description": "Warning about preview channel" + }, + "optionsClearHistory": "Очистити історію завантажень", + "@optionsClearHistory": { + "description": "Delete all download history" + }, + "optionsClearHistorySubtitle": "Видалити всі завантажені треки з історії", + "@optionsClearHistorySubtitle": { + "description": "Subtitle for clear history" + }, + "optionsDetailedLogging": "Детальне журналювання", + "@optionsDetailedLogging": { + "description": "Enable verbose logs for debugging" + }, + "optionsDetailedLoggingOn": "Ведеться детальний журнал", + "@optionsDetailedLoggingOn": { + "description": "Status when logging enabled" + }, + "optionsDetailedLoggingOff": "Увімкнути для звітів про помилки", + "@optionsDetailedLoggingOff": { + "description": "Status when logging disabled" + }, + "optionsSpotifyCredentials": "Облікові дані Spotify", + "@optionsSpotifyCredentials": { + "description": "Spotify API credentials setting" + }, + "optionsSpotifyCredentialsConfigured": "Ідентифікатор клієнта: {clientId}...", + "@optionsSpotifyCredentialsConfigured": { + "description": "Shows configured client ID preview", + "placeholders": { + "clientId": { + "type": "String" + } + } + }, + "optionsSpotifyCredentialsRequired": "Обов'язковий – торкніться, щоб налаштувати", + "@optionsSpotifyCredentialsRequired": { + "description": "Prompt to set up credentials" + }, + "optionsSpotifyWarning": "Spotify вимагає ваших власних облікових даних API. Отримайте їх безкоштовно на сайті developer.spotify.com", + "@optionsSpotifyWarning": { + "description": "Info about Spotify API requirement" + }, + "optionsSpotifyDeprecationWarning": "Пошук Spotify буде припинено 3 березня 2026 року через зміни в API Spotify. Будь ласка, перейдіть на Deezer.", + "@optionsSpotifyDeprecationWarning": { + "description": "Warning about Spotify API deprecation" + }, + "extensionsTitle": "Розширення", + "@extensionsTitle": { + "description": "Extensions page title" + }, + "extensionsDisabled": "Вимкнені", + "@extensionsDisabled": { + "description": "Extension status - inactive" + }, + "extensionsVersion": "Версія {version}", + "@extensionsVersion": { + "description": "Extension version display", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "extensionsAuthor": "від {author}", + "@extensionsAuthor": { + "description": "Extension author credit", + "placeholders": { + "author": { + "type": "String" + } + } + }, + "extensionsUninstall": "Видалити", + "@extensionsUninstall": { + "description": "Uninstall extension button" + }, + "storeTitle": "Репозиторій розширень", + "@storeTitle": { + "description": "Store screen title" + }, + "storeSearch": "Розширення пошуку...", + "@storeSearch": { + "description": "Store search placeholder" + }, + "storeInstall": "Встановити", + "@storeInstall": { + "description": "Install extension button" + }, + "storeInstalled": "Встановлені", + "@storeInstalled": { + "description": "Already installed badge" + }, + "storeUpdate": "Оновлені", + "@storeUpdate": { + "description": "Update available button" + }, + "aboutTitle": "Про нас", + "@aboutTitle": { + "description": "About page title" + }, + "aboutContributors": "Автори", + "@aboutContributors": { + "description": "Section for contributors" + }, + "aboutMobileDeveloper": "Розробник мобільної версії", + "@aboutMobileDeveloper": { + "description": "Role description for mobile dev" + }, + "aboutOriginalCreator": "Творець оригінального SpotiFLAC", + "@aboutOriginalCreator": { + "description": "Role description for original creator" + }, + "aboutLogoArtist": "Талановитий художник, який створив чудовий логотип нашого додатку!", + "@aboutLogoArtist": { + "description": "Role description for logo artist" + }, + "aboutTranslators": "Перекладачі", + "@aboutTranslators": { + "description": "Section for translators" + }, + "aboutSpecialThanks": "Особлива подяка", + "@aboutSpecialThanks": { + "description": "Section for special thanks" + }, + "aboutLinks": "Посилання", + "@aboutLinks": { + "description": "Section for external links" + }, + "aboutMobileSource": "Мобільний вихідний код", + "@aboutMobileSource": { + "description": "Link to mobile GitHub repo" + }, + "aboutPCSource": "Вихідний код для ПК", + "@aboutPCSource": { + "description": "Link to PC GitHub repo" + }, + "aboutKeepAndroidOpen": "Keep Android Open", + "@aboutKeepAndroidOpen": { + "description": "Link to Keep Android Open campaign website" + }, + "aboutReportIssue": "Повідомити про проблему", + "@aboutReportIssue": { + "description": "Link to report bugs" + }, + "aboutReportIssueSubtitle": "Повідомити про будь-які проблеми, з якими ви зіткнулися", + "@aboutReportIssueSubtitle": { + "description": "Subtitle for report issue" + }, + "aboutFeatureRequest": "Запит на функцію", + "@aboutFeatureRequest": { + "description": "Link to suggest features" + }, + "aboutFeatureRequestSubtitle": "Запропонувати нові функції для програми", + "@aboutFeatureRequestSubtitle": { + "description": "Subtitle for feature request" + }, + "aboutTelegramChannel": "Телеграм-канал", + "@aboutTelegramChannel": { + "description": "Link to Telegram channel" + }, + "aboutTelegramChannelSubtitle": "Оголошення та оновлення", + "@aboutTelegramChannelSubtitle": { + "description": "Subtitle for Telegram channel" + }, + "aboutTelegramChat": "Telegram Спільнота", + "@aboutTelegramChat": { + "description": "Link to Telegram chat group" + }, + "aboutTelegramChatSubtitle": "Спілкуватися з іншими користувачами", + "@aboutTelegramChatSubtitle": { + "description": "Subtitle for Telegram chat" + }, + "aboutSocial": "Соціальні мережі", + "@aboutSocial": { + "description": "Section for social links" + }, + "aboutApp": "Додаток", + "@aboutApp": { + "description": "Section for app info" + }, + "aboutVersion": "Версія", + "@aboutVersion": { + "description": "Version info label" + }, + "aboutBinimumDesc": "Творець QQDL та HiFi API. Без цього API завантажень Tidal'а не існувало б!", + "@aboutBinimumDesc": { + "description": "Credit description for binimum" + }, + "aboutSachinsenalDesc": "Оригінальний творець HiFi-проектів. Основа інтеграції Tidal!", + "@aboutSachinsenalDesc": { + "description": "Credit description for sachinsenal0x64" + }, + "aboutSjdonadoDesc": "Творець I Don't Have Spotify (IDHS). Резервний розв'язувач посилань, який рятує становище!", + "@aboutSjdonadoDesc": { + "description": "Credit description for sjdonado" + }, + "aboutDabMusic": "DAB Music", + "@aboutDabMusic": { + "description": "Name of Qobuz API service - DO NOT TRANSLATE" + }, + "aboutDabMusicDesc": "Найкращий API для потокової передачі Qobuz. Завантаження високої роздільної здатності було б неможливим без нього!", + "@aboutDabMusicDesc": { + "description": "Credit for DAB Music API" + }, + "aboutSpotiSaver": "SpotiSaver", + "@aboutSpotiSaver": { + "description": "Name of SpotiSaver API service - DO NOT TRANSLATE" + }, + "aboutSpotiSaverDesc": "Кінцеві точки потокового передавання Tidal Hi-Res FLAC. Ключовий елемент пазлу музики без втрат!", + "@aboutSpotiSaverDesc": { + "description": "Credit for SpotiSaver API" + }, + "aboutAppDescription": "Кінцеві точки потокового передавання Tidal Hi-Res FLAC. Ключовий елемент пазлу музики без втрат.", + "@aboutAppDescription": { + "description": "App description in header card" + }, + "artistAlbums": "Альбоми", + "@artistAlbums": { + "description": "Section header for artist albums" + }, + "artistSingles": "Сингли та міні-альбоми", + "@artistSingles": { + "description": "Section header for singles/EPs" + }, + "artistCompilations": "Збірники", + "@artistCompilations": { + "description": "Section header for compilations" + }, + "artistPopular": "Популярні", + "@artistPopular": { + "description": "Section header for popular/top tracks" + }, + "artistMonthlyListeners": "{count} слухачів щомісяця", + "@artistMonthlyListeners": { + "description": "Monthly listener count display", + "placeholders": { + "count": { + "type": "String", + "description": "Formatted listener count" + } + } + }, + "trackMetadataService": "Сервіс", + "@trackMetadataService": { + "description": "Metadata field - download service used" + }, + "trackMetadataPlay": "Прослухати", + "@trackMetadataPlay": { + "description": "Action button - play track" + }, + "trackMetadataShare": "Поділитися", + "@trackMetadataShare": { + "description": "Action button - share track" + }, + "trackMetadataDelete": "Видалити", + "@trackMetadataDelete": { + "description": "Action button - delete track" + }, + "setupGrantPermission": "Надати дозвіл", + "@setupGrantPermission": { + "description": "Button to request permission" + }, + "setupSkip": "Пропустити поки що", + "@setupSkip": { + "description": "Skip current step button" + }, + "setupStorageAccessRequired": "Потрібен доступ до сховища", + "@setupStorageAccessRequired": { + "description": "Title when storage access needed" + }, + "setupStorageAccessMessageAndroid11": "Для збереження файлів у вибрану папку завантажень для Android 11+ потрібен дозвіл «Доступ до всіх файлів».", + "@setupStorageAccessMessageAndroid11": { + "description": "Android 11+ specific explanation" + }, + "setupOpenSettings": "Відкрити налаштування", + "@setupOpenSettings": { + "description": "Button to open system settings" + }, + "setupPermissionDeniedMessage": "Дозвіл відхилено. Будь ласка, надайте всі дозволи, щоб продовжити.", + "@setupPermissionDeniedMessage": { + "description": "Error when permission denied" + }, + "setupPermissionRequired": "{permissionType} Потрібен дозвіл", + "@setupPermissionRequired": { + "description": "Generic permission required title", + "placeholders": { + "permissionType": { + "type": "String", + "description": "Type of permission (Storage/Notification)" + } + } + }, + "setupPermissionRequiredMessage": "{permissionType} Для найкращого досвіду потрібен дозвіл. Ви можете змінити це пізніше в налаштуваннях.", + "@setupPermissionRequiredMessage": { + "description": "Generic permission required message", + "placeholders": { + "permissionType": { + "type": "String" + } + } + }, + "setupUseDefaultFolder": "Використати папку за замовчуванням?", + "@setupUseDefaultFolder": { + "description": "Dialog title for default folder" + }, + "setupNoFolderSelected": "Папку не вибрано. Бажаєте використовувати папку «Музика» за замовчуванням?", + "@setupNoFolderSelected": { + "description": "Prompt when no folder selected" + }, + "setupUseDefault": "Використовувати за замовчуванням", + "@setupUseDefault": { + "description": "Button to use default folder" + }, + "setupDownloadLocationTitle": "Розташування завантаження", + "@setupDownloadLocationTitle": { + "description": "Download location dialog title" + }, + "setupDownloadLocationIosMessage": "На iOS завантаження зберігаються в папці «Документи» програми. Ви можете отримати до них доступ через програму «Файли».", + "@setupDownloadLocationIosMessage": { + "description": "iOS-specific folder info" + }, + "setupAppDocumentsFolder": "Папка з документами програми", + "@setupAppDocumentsFolder": { + "description": "iOS documents folder option" + }, + "setupAppDocumentsFolderSubtitle": "Рекомендація – доступно через додаток Файли", + "@setupAppDocumentsFolderSubtitle": { + "description": "Subtitle for documents folder" + }, + "setupChooseFromFiles": "Вибрати з файлів", + "@setupChooseFromFiles": { + "description": "iOS file picker option" + }, + "setupChooseFromFilesSubtitle": "Виберіть iCloud або інше місцезнаходження", + "@setupChooseFromFilesSubtitle": { + "description": "Subtitle for file picker" + }, + "setupIosEmptyFolderWarning": "Обмеження iOS: Не можна вибрати порожні папки. Виберіть папку, яка містить принаймні один файл.", + "@setupIosEmptyFolderWarning": { + "description": "iOS folder selection warning" + }, + "setupIcloudNotSupported": "iCloud Drive не підтримується. Будь ласка, скористайтеся папкою «Документи» програми.", + "@setupIcloudNotSupported": { + "description": "Error when user selects iCloud Drive on iOS" + }, + "setupDownloadInFlac": "Завантажити треки Spotify у форматі FLAC", + "@setupDownloadInFlac": { + "description": "App tagline in setup" + }, + "setupStorageGranted": "Дозвіл на зберігання надано!", + "@setupStorageGranted": { + "description": "Success message for storage permission" + }, + "setupStorageRequired": "Потрібен дозвіл на збереження файлів", + "@setupStorageRequired": { + "description": "Title when storage permission needed" + }, + "setupStorageDescription": "SpotiFLAC потребує дозволу на збереження, щоб зберегти завантажені музичні файли.", + "@setupStorageDescription": { + "description": "Explanation for storage permission" + }, + "setupNotificationGranted": "Дозвіл на сповіщення надано!", + "@setupNotificationGranted": { + "description": "Success message for notification permission" + }, + "setupNotificationEnable": "Увімкнути сповіщення", + "@setupNotificationEnable": { + "description": "Button to enable notifications" + }, + "setupFolderChoose": "Виберати папку для завантаження", + "@setupFolderChoose": { + "description": "Button to choose folder" + }, + "setupFolderDescription": "Виберіть папку, де буде збережено завантажену музику.", + "@setupFolderDescription": { + "description": "Explanation for folder selection" + }, + "setupSelectFolder": "Вибрати папку", + "@setupSelectFolder": { + "description": "Button to select folder" + }, + "setupEnableNotifications": "Увімкнути сповіщення", + "@setupEnableNotifications": { + "description": "Button to enable notifications" + }, + "setupNotificationBackgroundDescription": "Отримуйте сповіщення про прогрес та завершення завантаження. Це допомагає відстежувати завантаження, коли програма працює у фоновому режимі.", + "@setupNotificationBackgroundDescription": { + "description": "Detailed notification explanation" + }, + "setupSkipForNow": "Пропустити поки що", + "@setupSkipForNow": { + "description": "Skip button text" + }, + "setupNext": "Далі", + "@setupNext": { + "description": "Next button text" + }, + "setupGetStarted": "Почати", + "@setupGetStarted": { + "description": "Final setup button" + }, + "setupAllowAccessToManageFiles": "Будь ласка, увімкніть опцію «Дозволити доступ для керування всіма файлами» на наступному екрані.", + "@setupAllowAccessToManageFiles": { + "description": "Instruction for file access permission" + }, + "dialogCancel": "Скасувати", + "@dialogCancel": { + "description": "Dialog button - cancel action" + }, + "dialogSave": "Зберегти", + "@dialogSave": { + "description": "Dialog button - save changes" + }, + "dialogDelete": "Видалити", + "@dialogDelete": { + "description": "Dialog button - delete item" + }, + "dialogRetry": "Повторити спробу", + "@dialogRetry": { + "description": "Dialog button - retry action" + }, + "dialogClear": "Очистити", + "@dialogClear": { + "description": "Dialog button - clear items" + }, + "dialogDone": "Готово", + "@dialogDone": { + "description": "Dialog button - action completed" + }, + "dialogImport": "Імпорт", + "@dialogImport": { + "description": "Dialog button - import data" + }, + "dialogDownload": "Завантажити", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, + "dialogDiscard": "Відхилити", + "@dialogDiscard": { + "description": "Dialog button - discard changes" + }, + "dialogRemove": "Видалити", + "@dialogRemove": { + "description": "Dialog button - remove item" + }, + "dialogUninstall": "Деінсталювати", + "@dialogUninstall": { + "description": "Dialog button - uninstall extension" + }, + "dialogDiscardChanges": "Відхилити зміни?", + "@dialogDiscardChanges": { + "description": "Dialog title - unsaved changes warning" + }, + "dialogUnsavedChanges": "У вас є незбережені зміни. Ви хочете їх скасувати?", + "@dialogUnsavedChanges": { + "description": "Dialog message - unsaved changes" + }, + "dialogClearAll": "Очистити все", + "@dialogClearAll": { + "description": "Dialog title - clear all items" + }, + "dialogRemoveExtension": "Видалити розширення", + "@dialogRemoveExtension": { + "description": "Dialog title - uninstall extension" + }, + "dialogRemoveExtensionMessage": "Ви впевнені, що хочете видалити це розширення? Цю дію неможливо скасувати.", + "@dialogRemoveExtensionMessage": { + "description": "Dialog message - uninstall confirmation" + }, + "dialogUninstallExtension": "Видалити розширення?", + "@dialogUninstallExtension": { + "description": "Dialog title - uninstall extension" + }, + "dialogUninstallExtensionMessage": "Ви впевнені, що хочете видалити {extensionName}?", + "@dialogUninstallExtensionMessage": { + "description": "Dialog message - uninstall specific extension", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "dialogClearHistoryTitle": "Очистити історію", + "@dialogClearHistoryTitle": { + "description": "Dialog title - clear download history" + }, + "dialogClearHistoryMessage": "Ви впевнені, що хочете очистити всю історію завантажень? Цю дію неможливо скасувати.", + "@dialogClearHistoryMessage": { + "description": "Dialog message - clear history confirmation" + }, + "dialogDeleteSelectedTitle": "Видалити вибране", + "@dialogDeleteSelectedTitle": { + "description": "Dialog title - delete selected items" + }, + "dialogDeleteSelectedMessage": "Видалити {count} {count, plural, =1{трек} other{треків}} з історії?\n\nЦе також видалить файли з пам'яті.", + "@dialogDeleteSelectedMessage": { + "description": "Dialog message - delete selected tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogImportPlaylistTitle": "Імпорт списку відтворення", + "@dialogImportPlaylistTitle": { + "description": "Dialog title - import CSV playlist" + }, + "dialogImportPlaylistMessage": "Знайдено {count} треків у CSV. Додати їх до черги завантаження?", + "csvImportTracks": "{count} треків з 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}\" до черги", + "@snackbarAddedToQueue": { + "description": "Snackbar - track added to download queue", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarAddedTracksToQueue": "Додано {count} треків до черги", + "@snackbarAddedTracksToQueue": { + "description": "Snackbar - multiple tracks added to queue", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAlreadyDownloaded": "\"{trackName}\" вже завантажено", + "@snackbarAlreadyDownloaded": { + "description": "Snackbar - track already exists", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarAlreadyInLibrary": "\"{trackName}\" вже є у вашій бібліотеці", + "@snackbarAlreadyInLibrary": { + "description": "Snackbar - track already exists in local library", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "snackbarHistoryCleared": "Історія очищена", + "@snackbarHistoryCleared": { + "description": "Snackbar - history deleted" + }, + "snackbarCredentialsSaved": "Облікові дані збережено", + "@snackbarCredentialsSaved": { + "description": "Snackbar - Spotify credentials saved" + }, + "snackbarCredentialsCleared": "Облікові дані очищено", + "@snackbarCredentialsCleared": { + "description": "Snackbar - Spotify credentials removed" + }, + "snackbarDeletedTracks": "Видалено {count} {count, plural, =1{трек} other{треків}}", + "@snackbarDeletedTracks": { + "description": "Snackbar - tracks deleted", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarCannotOpenFile": "Не вдається відкрити файл: {error}", + "@snackbarCannotOpenFile": { + "description": "Snackbar - file open error", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarFillAllFields": "Будь ласка, заповніть усі поля", + "@snackbarFillAllFields": { + "description": "Snackbar - validation error" + }, + "snackbarViewQueue": "Переглянути чергу", + "@snackbarViewQueue": { + "description": "Snackbar action - view download queue" + }, + "snackbarUrlCopied": "{platform} URL-адреса скопійована в буфер обміну", + "@snackbarUrlCopied": { + "description": "Snackbar - URL copied", + "placeholders": { + "platform": { + "type": "String", + "description": "Platform name (Spotify/Deezer)" + } + } + }, + "snackbarFileNotFound": "Файл не знайдено", + "@snackbarFileNotFound": { + "description": "Snackbar - file doesn't exist" + }, + "snackbarSelectExtFile": "Будь ласка, виберіть файл .spotiflac-ext", + "@snackbarSelectExtFile": { + "description": "Snackbar - wrong file type selected" + }, + "snackbarProviderPrioritySaved": "Пріоритет постачальника збережено", + "@snackbarProviderPrioritySaved": { + "description": "Snackbar - provider order saved" + }, + "snackbarMetadataProviderSaved": "Пріоритет постачальника метаданих збережено", + "@snackbarMetadataProviderSaved": { + "description": "Snackbar - metadata provider order saved" + }, + "snackbarExtensionInstalled": "Розширення {extensionName} встановлено.", + "@snackbarExtensionInstalled": { + "description": "Snackbar - extension installed successfully", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "snackbarExtensionUpdated": "{extensionName} оновлено.", + "@snackbarExtensionUpdated": { + "description": "Snackbar - extension updated successfully", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "snackbarFailedToInstall": "Не вдалося встановити розширення", + "@snackbarFailedToInstall": { + "description": "Snackbar - extension install error" + }, + "snackbarFailedToUpdate": "Не вдалося оновити розширення", + "@snackbarFailedToUpdate": { + "description": "Snackbar - extension update error" + }, + "errorRateLimited": "Обмежений тариф", + "@errorRateLimited": { + "description": "Error title - too many requests" + }, + "errorRateLimitedMessage": "Забагато запитів. Будь ласка, зачекайте хвилинку, перш ніж шукати знову.", + "@errorRateLimitedMessage": { + "description": "Error message - rate limit explanation" + }, + "errorNoTracksFound": "Треків не знайдено", + "@errorNoTracksFound": { + "description": "Error - search returned no results" + }, + "errorUrlNotRecognized": "Посилання не розпізнано", + "@errorUrlNotRecognized": { + "description": "Error title - URL not handled by any extension or service" + }, + "errorUrlNotRecognizedMessage": "Це посилання не підтримується. Переконайтеся, що URL-адреса правильна та встановлено сумісне розширення.", + "@errorUrlNotRecognizedMessage": { + "description": "Error message - URL not recognized explanation" + }, + "errorUrlFetchFailed": "Не вдалося завантажити вміст за цим посиланням. Спробуйте ще раз.", + "@errorUrlFetchFailed": { + "description": "Error message - generic URL fetch failure" + }, + "errorMissingExtensionSource": "Не вдається завантажити {item}: відсутній вихідний код розширення", + "@errorMissingExtensionSource": { + "description": "Error - extension source not available", + "placeholders": { + "item": { + "type": "String" + } + } + }, + "actionPause": "Пауза", + "@actionPause": { + "description": "Action button - pause download" + }, + "actionResume": "Відновити", + "@actionResume": { + "description": "Action button - resume download" + }, + "actionCancel": "Скасувати", + "@actionCancel": { + "description": "Action button - cancel operation" + }, + "actionSelectAll": "Вибрати все", + "@actionSelectAll": { + "description": "Action button - select all items" + }, + "actionDeselect": "Скасувати вибір", + "@actionDeselect": { + "description": "Action button - deselect all" + }, + "actionRemoveCredentials": "Видалити облікові дані", + "@actionRemoveCredentials": { + "description": "Action button - delete Spotify credentials" + }, + "actionSaveCredentials": "Зберегти облікові дані", + "@actionSaveCredentials": { + "description": "Action button - save Spotify credentials" + }, + "selectionSelected": "Вибрано {count}", + "@selectionSelected": { + "description": "Selection count indicator", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionAllSelected": "Усі треки вибрано", + "@selectionAllSelected": { + "description": "Status - all items selected" + }, + "selectionSelectToDelete": "Виберіть треки для видалення", + "@selectionSelectToDelete": { + "description": "Placeholder when nothing selected" + }, + "progressFetchingMetadata": "Отримання метаданих... {current}/{total}", + "@progressFetchingMetadata": { + "description": "Progress indicator - loading track info", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "progressReadingCsv": "Читання CSV-файлу...", + "@progressReadingCsv": { + "description": "Progress indicator - parsing CSV file" + }, + "searchSongs": "Пісні", + "@searchSongs": { + "description": "Search result category - songs" + }, + "searchArtists": "Виконавці", + "@searchArtists": { + "description": "Search result category - artists" + }, + "searchAlbums": "Альбоми", + "@searchAlbums": { + "description": "Search result category - albums" + }, + "searchPlaylists": "Списки відтворення", + "@searchPlaylists": { + "description": "Search result category - playlists" + }, + "searchSortTitle": "Сортувати результати", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "За замовчуванням", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Назва (А-Я)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Назва (Я-А)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Виконавець (А-Я)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Виконавець (Я-А)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Тривалість (найкоротша)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Тривалість (найдовша)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Дата випуску (найстаріша)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Дата випуску (найновіша)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, + "tooltipPlay": "Відтворити", + "@tooltipPlay": { + "description": "Tooltip - play button" + }, + "filenameFormat": "Формат імені файлу", + "@filenameFormat": { + "description": "Setting title - filename pattern" + }, + "filenameShowAdvancedTags": "Показати розширені теги", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Увімкнути відформатовані теги для доповнення доріжок і шаблонів дати", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, + "folderOrganizationNone": "Жодної організації", + "@folderOrganizationNone": { + "description": "Folder option - flat structure" + }, + "folderOrganizationByPlaylist": "За списком відтворення", + "@folderOrganizationByPlaylist": { + "description": "Folder option - playlist folders" + }, + "folderOrganizationByPlaylistSubtitle": "Окрема папка для кожного списку відтворення", + "@folderOrganizationByPlaylistSubtitle": { + "description": "Subtitle for playlist folder option" + }, + "folderOrganizationByArtist": "За виконавцем", + "@folderOrganizationByArtist": { + "description": "Folder option - artist folders" + }, + "folderOrganizationByAlbum": "За альбомом", + "@folderOrganizationByAlbum": { + "description": "Folder option - album folders" + }, + "folderOrganizationByArtistAlbum": "Виконавець/Альбом", + "@folderOrganizationByArtistAlbum": { + "description": "Folder option - nested folders" + }, + "folderOrganizationDescription": "Упорядкувати завантажені файли в папки", + "@folderOrganizationDescription": { + "description": "Folder organization sheet description" + }, + "folderOrganizationNoneSubtitle": "Усі файли в папці завантажень", + "@folderOrganizationNoneSubtitle": { + "description": "Subtitle for no organization option" + }, + "folderOrganizationByArtistSubtitle": "Окрема папка для кожного виконавця", + "@folderOrganizationByArtistSubtitle": { + "description": "Subtitle for artist folder option" + }, + "folderOrganizationByAlbumSubtitle": "Окрема папка для кожного альбому", + "@folderOrganizationByAlbumSubtitle": { + "description": "Subtitle for album folder option" + }, + "folderOrganizationByArtistAlbumSubtitle": "Вкладені папки для виконавця та альбому", + "@folderOrganizationByArtistAlbumSubtitle": { + "description": "Subtitle for nested folder option" + }, + "updateAvailable": "Доступне оновлення", + "@updateAvailable": { + "description": "Update dialog title" + }, + "updateLater": "Пізніше", + "@updateLater": { + "description": "Update button - dismiss" + }, + "updateStartingDownload": "Початок завантаження...", + "@updateStartingDownload": { + "description": "Update status - initializing" + }, + "updateDownloadFailed": "Не вдалося завантажити", + "@updateDownloadFailed": { + "description": "Update error title" + }, + "updateFailedMessage": "Не вдалося завантажити оновлення", + "@updateFailedMessage": { + "description": "Update error message" + }, + "updateNewVersionReady": "Доступна нова версія", + "@updateNewVersionReady": { + "description": "Update subtitle" + }, + "updateCurrent": "Поточна", + "@updateCurrent": { + "description": "Label for current version" + }, + "updateNew": "Нова", + "@updateNew": { + "description": "Label for new version" + }, + "updateDownloading": "Завантаження...", + "@updateDownloading": { + "description": "Update status - downloading" + }, + "updateWhatsNew": "Що нового", + "@updateWhatsNew": { + "description": "Changelog section title" + }, + "updateDownloadInstall": "Завантажити та встановити", + "@updateDownloadInstall": { + "description": "Update button - download and install" + }, + "updateDontRemind": "Не нагадувати", + "@updateDontRemind": { + "description": "Update button - skip this version" + }, + "providerPriorityTitle": "Пріоритет постачальника", + "@providerPriorityTitle": { + "description": "Provider priority page title" + }, + "providerPriorityDescription": "Перетягніть, щоб змінити порядок постачальників завантажень. Під час завантаження треків програма використовуватиме постачальників зверху вниз.", + "@providerPriorityDescription": { + "description": "Provider priority page description" + }, + "providerPriorityInfo": "Якщо трек недоступний у першого провайдера, додаток автоматично спробує наступного.", + "@providerPriorityInfo": { + "description": "Info tip about fallback behavior" + }, + "providerPriorityFallbackExtensionsTitle": "Резервне розширення", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Виберіть, які встановлені розширення завантаження можна використовувати під час автоматичного відновлення до попереднього режиму. Вбудовані постачальники все одно дотримуються порядку пріоритетності, зазначеного вище.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Тут перелічені лише ввімкнені розширення з можливістю завантаження через постачальника послуг.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, + "providerBuiltIn": "Вбудований", + "@providerBuiltIn": { + "description": "Label for built-in providers (Tidal/Qobuz)" + }, + "providerExtension": "Розширення", + "@providerExtension": { + "description": "Label for extension-provided providers" + }, + "metadataProviderPriorityTitle": "Пріоритет метаданих", + "@metadataProviderPriorityTitle": { + "description": "Metadata priority page title" + }, + "metadataProviderPriorityDescription": "Перетягніть, щоб змінити порядок постачальників метаданих. Додаток шукатиме постачальників зверху вниз під час пошуку треків та отримання метаданих.", + "@metadataProviderPriorityDescription": { + "description": "Metadata priority page description" + }, + "metadataProviderPriorityInfo": "Deezer не має обмежень за швидкістю та рекомендований як основний сервіс. Spotify може обмежувати швидкість після великої кількості запитів.", + "@metadataProviderPriorityInfo": { + "description": "Info tip about rate limits" + }, + "metadataNoRateLimits": "Без обмежень щодо швидкості", + "@metadataNoRateLimits": { + "description": "Deezer provider description" + }, + "metadataMayRateLimit": "Травневе обмеження ставок", + "@metadataMayRateLimit": { + "description": "Spotify provider description" + }, + "logTitle": "Журнали", + "@logTitle": { + "description": "Logs screen title" + }, + "logCopied": "Журнали скопійовано в буфер обміну", + "@logCopied": { + "description": "Snackbar - logs copied" + }, + "logSearchHint": "Пошук журналів...", + "@logSearchHint": { + "description": "Log search placeholder" + }, + "logFilterLevel": "Рівень", + "@logFilterLevel": { + "description": "Filter by log level" + }, + "logFilterSection": "Фільтр", + "@logFilterSection": { + "description": "Filter section title" + }, + "logShareLogs": "Журнали обміну", + "@logShareLogs": { + "description": "Share button tooltip" + }, + "logClearLogs": "Очистити журнали", + "@logClearLogs": { + "description": "Clear button tooltip" + }, + "logClearLogsTitle": "Очистити Журнали", + "@logClearLogsTitle": { + "description": "Clear logs dialog title" + }, + "logClearLogsMessage": "Ви впевнені, що хочете очистити всі журнали?", + "@logClearLogsMessage": { + "description": "Clear logs confirmation message" + }, + "logFilterBySeverity": "Фільтрувати журнали за рівнем серйозності", + "@logFilterBySeverity": { + "description": "Filter dialog title" + }, + "logNoLogsYet": "Журналів поки що немає", + "@logNoLogsYet": { + "description": "Empty state title" + }, + "logNoLogsYetSubtitle": "Журнали відображатимуться тут під час використання програми", + "@logNoLogsYetSubtitle": { + "description": "Empty state subtitle" + }, + "logEntriesFiltered": "Записи ({count} filtered)", + "@logEntriesFiltered": { + "description": "Log count with filter active", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "logEntries": "Записи ({count})", + "@logEntries": { + "description": "Total log count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "credentialsTitle": "Облікові дані Spotify", + "@credentialsTitle": { + "description": "Credentials dialog title" + }, + "credentialsDescription": "Введіть свій ідентифікатор клієнта та секретний код, щоб використовувати власну квоту програми Spotify.", + "@credentialsDescription": { + "description": "Credentials dialog explanation" + }, + "credentialsClientId": "Ідентифікатор клієнта", + "@credentialsClientId": { + "description": "Client ID field label - DO NOT TRANSLATE" + }, + "credentialsClientIdHint": "Вставити ідентифікатор клієнта", + "@credentialsClientIdHint": { + "description": "Client ID placeholder" + }, + "credentialsClientSecret": "Секретний код клієнта", + "@credentialsClientSecret": { + "description": "Client Secret field label - DO NOT TRANSLATE" + }, + "credentialsClientSecretHint": "Вставити секретний код клієнта", + "@credentialsClientSecretHint": { + "description": "Client Secret placeholder" + }, + "channelStable": "Стабільний", + "@channelStable": { + "description": "Update channel - stable releases" + }, + "channelPreview": "Бета", + "@channelPreview": { + "description": "Update channel - beta/preview releases" + }, + "sectionSearchSource": "Джерело пошуку", + "@sectionSearchSource": { + "description": "Settings section header" + }, + "sectionDownload": "Завантажити", + "@sectionDownload": { + "description": "Settings section header" + }, + "sectionPerformance": "Продуктивність", + "@sectionPerformance": { + "description": "Settings section header" + }, + "sectionApp": "Додаток", + "@sectionApp": { + "description": "Settings section header" + }, + "sectionData": "Дані", + "@sectionData": { + "description": "Settings section header" + }, + "sectionDebug": "Налагодження", + "@sectionDebug": { + "description": "Settings section header" + }, + "sectionService": "Сервіс", + "@sectionService": { + "description": "Settings section header" + }, + "sectionAudioQuality": "Якість звуку", + "@sectionAudioQuality": { + "description": "Settings section header" + }, + "sectionFileSettings": "Налаштування файлу", + "@sectionFileSettings": { + "description": "Settings section header" + }, + "sectionLyrics": "Тексти пісень", + "@sectionLyrics": { + "description": "Settings section header" + }, + "lyricsMode": "Режим тексту пісні", + "@lyricsMode": { + "description": "Setting - how to save lyrics" + }, + "lyricsModeDescription": "Виберіть, як тексти пісень зберігатимуться разом із завантаженнями пісень.", + "@lyricsModeDescription": { + "description": "Lyrics mode picker description" + }, + "lyricsModeEmbed": "Вбудувати у файл", + "@lyricsModeEmbed": { + "description": "Lyrics mode option - embed in audio file" + }, + "lyricsModeEmbedSubtitle": "Тексти пісень зберігаються в метаданих FLAC", + "@lyricsModeEmbedSubtitle": { + "description": "Subtitle for embed option" + }, + "lyricsModeExternal": "Зовнішній файл .lrc", + "@lyricsModeExternal": { + "description": "Lyrics mode option - separate LRC file" + }, + "lyricsModeExternalSubtitle": "Окремий файл .lrc для плеєрів, таких як Samsung Music", + "@lyricsModeExternalSubtitle": { + "description": "Subtitle for external option" + }, + "lyricsModeBoth": "Обидва", + "@lyricsModeBoth": { + "description": "Lyrics mode option - embed and external" + }, + "lyricsModeBothSubtitle": "Вбудувати та зберегти файл .lrc", + "@lyricsModeBothSubtitle": { + "description": "Subtitle for both option" + }, + "sectionColor": "Колір", + "@sectionColor": { + "description": "Settings section header" + }, + "sectionTheme": "Тема", + "@sectionTheme": { + "description": "Settings section header" + }, + "sectionLayout": "Макет", + "@sectionLayout": { + "description": "Settings section header" + }, + "sectionLanguage": "Мова", + "@sectionLanguage": { + "description": "Settings section header for language" + }, + "appearanceLanguage": "Мова програми", + "@appearanceLanguage": { + "description": "Language setting title" + }, + "settingsAppearanceSubtitle": "Тема, кольори, дисплей", + "@settingsAppearanceSubtitle": { + "description": "Appearance settings description" + }, + "settingsDownloadSubtitle": "Сервіс, якість, формат назви файлу", + "@settingsDownloadSubtitle": { + "description": "Download settings description" + }, + "settingsOptionsSubtitle": "Резервний варіант, тексти пісень, обкладинка, оновлення", + "@settingsOptionsSubtitle": { + "description": "Options settings description" + }, + "settingsExtensionsSubtitle": "Керування постачальниками послуг завантаження", + "@settingsExtensionsSubtitle": { + "description": "Extensions settings description" + }, + "settingsLogsSubtitle": "Перегляд журналів програми для налагодження", + "@settingsLogsSubtitle": { + "description": "Logs settings description" + }, + "loadingSharedLink": "Завантаження спільного посилання...", + "@loadingSharedLink": { + "description": "Status when opening shared URL" + }, + "pressBackAgainToExit": "Натисніть кнопку «Назад» ще раз, щоб вийти", + "@pressBackAgainToExit": { + "description": "Exit confirmation message" + }, + "downloadAllCount": "Завантажити все ({count})", + "@downloadAllCount": { + "description": "Download all button with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "tracksCount": "{count, plural, =1{1 трек} other{{count} треків}}", + "@tracksCount": { + "description": "Track count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "trackCopyFilePath": "Копіювати шлях до файлу", + "@trackCopyFilePath": { + "description": "Action - copy file path" + }, + "trackRemoveFromDevice": "Видалити з пристрою", + "@trackRemoveFromDevice": { + "description": "Action - delete downloaded file" + }, + "trackLoadLyrics": "Завантажити текст пісні", + "@trackLoadLyrics": { + "description": "Action - fetch lyrics" + }, + "trackMetadata": "Метадані", + "@trackMetadata": { + "description": "Tab title - track metadata" + }, + "trackFileInfo": "Інформація про файл", + "@trackFileInfo": { + "description": "Tab title - file information" + }, + "trackLyrics": "Тексти пісень", + "@trackLyrics": { + "description": "Tab title - lyrics" + }, + "trackFileNotFound": "Файл не знайдено", + "@trackFileNotFound": { + "description": "Error - file doesn't exist" + }, + "trackOpenInDeezer": "Відкрити в Deezer", + "@trackOpenInDeezer": { + "description": "Action - open track in Deezer app" + }, + "trackOpenInSpotify": "Відкрити в Spotify", + "@trackOpenInSpotify": { + "description": "Action - open track in Spotify app" + }, + "trackTrackName": "Назва треку", + "@trackTrackName": { + "description": "Metadata label - track title" + }, + "trackArtist": "Артист", + "@trackArtist": { + "description": "Metadata label - artist name" + }, + "trackAlbumArtist": "Виконавець альбому", + "@trackAlbumArtist": { + "description": "Metadata label - album artist" + }, + "trackAlbum": "Альбом", + "@trackAlbum": { + "description": "Metadata label - album name" + }, + "trackTrackNumber": "Номер треку", + "@trackTrackNumber": { + "description": "Metadata label - track number" + }, + "trackDiscNumber": "Номер диска", + "@trackDiscNumber": { + "description": "Metadata label - disc number" + }, + "trackDuration": "Тривалість", + "@trackDuration": { + "description": "Metadata label - track length" + }, + "trackAudioQuality": "Якість звуку", + "@trackAudioQuality": { + "description": "Metadata label - audio quality" + }, + "trackReleaseDate": "Дата випуску", + "@trackReleaseDate": { + "description": "Metadata label - release date" + }, + "trackGenre": "Жанр", + "@trackGenre": { + "description": "Metadata label - music genre" + }, + "trackLabel": "Лейбл", + "@trackLabel": { + "description": "Metadata label - record label" + }, + "trackCopyright": "Авторське право", + "@trackCopyright": { + "description": "Metadata label - copyright information" + }, + "trackDownloaded": "Завантажено", + "@trackDownloaded": { + "description": "Metadata label - download date" + }, + "trackCopyLyrics": "Скопіювати тексти пісень", + "@trackCopyLyrics": { + "description": "Action - copy lyrics to clipboard" + }, + "trackLyricsNotAvailable": "Текст пісні для цього треку недоступний", + "@trackLyricsNotAvailable": { + "description": "Message when lyrics not found" + }, + "trackLyricsNotInFile": "У цьому файлі не знайдено текстів пісень", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Отримати з Інтернету", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, + "trackLyricsTimeout": "Час очікування запиту минув. Спробуйте ще раз пізніше.", + "@trackLyricsTimeout": { + "description": "Message when lyrics request times out" + }, + "trackLyricsLoadFailed": "Не вдалося завантажити текст пісні", + "@trackLyricsLoadFailed": { + "description": "Message when lyrics loading fails" + }, + "trackEmbedLyrics": "Вбудувати текст пісні", + "@trackEmbedLyrics": { + "description": "Action - embed lyrics into audio file" + }, + "trackLyricsEmbedded": "Текст пісні успішно вбудовано в пісню", + "@trackLyricsEmbedded": { + "description": "Snackbar - lyrics saved to file" + }, + "trackInstrumental": "Інструментальний трек", + "@trackInstrumental": { + "description": "Message when track is instrumental (no lyrics)" + }, + "trackCopiedToClipboard": "Скопійовано в буфер обміну", + "@trackCopiedToClipboard": { + "description": "Snackbar - content copied" + }, + "trackDeleteConfirmTitle": "Видалити з пристрою?", + "@trackDeleteConfirmTitle": { + "description": "Delete confirmation title" + }, + "trackDeleteConfirmMessage": "Це назавжди видалить завантажений файл і вилучить його з вашої історії.", + "@trackDeleteConfirmMessage": { + "description": "Delete confirmation message" + }, + "dateToday": "Сьогодні", + "@dateToday": { + "description": "Relative date - today" + }, + "dateYesterday": "Вчора", + "@dateYesterday": { + "description": "Relative date - yesterday" + }, + "dateDaysAgo": "{count} днів тому", + "@dateDaysAgo": { + "description": "Relative date - days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dateWeeksAgo": "{count} тижнів тому", + "@dateWeeksAgo": { + "description": "Relative date - weeks ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dateMonthsAgo": "{count} місяців тому", + "@dateMonthsAgo": { + "description": "Relative date - months ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "storeFilterAll": "Усі", + "@storeFilterAll": { + "description": "Store filter - all extensions" + }, + "storeFilterMetadata": "Метадані", + "@storeFilterMetadata": { + "description": "Store filter - metadata providers" + }, + "storeFilterDownload": "Завантажити", + "@storeFilterDownload": { + "description": "Store filter - download providers" + }, + "storeFilterUtility": "Утиліта", + "@storeFilterUtility": { + "description": "Store filter - utility extensions" + }, + "storeFilterLyrics": "Тексти пісень", + "@storeFilterLyrics": { + "description": "Store filter - lyrics providers" + }, + "storeFilterIntegration": "Інтеграція", + "@storeFilterIntegration": { + "description": "Store filter - integrations" + }, + "storeClearFilters": "Очистити фільтри", + "@storeClearFilters": { + "description": "Button to clear all filters" + }, + "storeAddRepoTitle": "Додати репозиторій розширень", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Введіть URL-адресу репозиторію GitHub, яка містить файл registry.json, для перегляду та встановлення розширень.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "URL-адреса репозиторію", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "наприклад https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Додати репозиторій", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Змінити репозиторій", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Репозиторій розширень", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Поточний репозиторій:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "Нова URL-адреса репозиторію", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Не вдалося завантажити репозиторій", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "Розширень немає", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "Розширень не знайдено", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionDefaultProvider": "За замовчуванням (Deezer)", + "@extensionDefaultProvider": { + "description": "Default search provider option" + }, + "extensionDefaultProviderSubtitle": "Використати вбудований пошук", + "@extensionDefaultProviderSubtitle": { + "description": "Subtitle for default provider" + }, + "extensionAuthor": "Автор", + "@extensionAuthor": { + "description": "Extension detail - author" + }, + "extensionId": "Ідентифікатор", + "@extensionId": { + "description": "Extension detail - unique ID" + }, + "extensionError": "Помилка", + "@extensionError": { + "description": "Extension detail - error message" + }, + "extensionCapabilities": "Можливості", + "@extensionCapabilities": { + "description": "Section header - extension features" + }, + "extensionMetadataProvider": "Постачальник метаданих", + "@extensionMetadataProvider": { + "description": "Capability - provides metadata" + }, + "extensionDownloadProvider": "Постачальник завантажень", + "@extensionDownloadProvider": { + "description": "Capability - provides downloads" + }, + "extensionLyricsProvider": "Постачальник текстів пісень", + "@extensionLyricsProvider": { + "description": "Capability - provides lyrics" + }, + "extensionUrlHandler": "Обробник URL-адрес", + "@extensionUrlHandler": { + "description": "Capability - handles URLs" + }, + "extensionQualityOptions": "Варіанти якості", + "@extensionQualityOptions": { + "description": "Capability - quality selection" + }, + "extensionPostProcessingHooks": "Хуки пост-обробки", + "@extensionPostProcessingHooks": { + "description": "Capability - post-processing" + }, + "extensionPermissions": "Дозволи", + "@extensionPermissions": { + "description": "Section header - required permissions" + }, + "extensionSettings": "Налаштування", + "@extensionSettings": { + "description": "Section header - extension settings" + }, + "extensionRemoveButton": "Видалити розширення", + "@extensionRemoveButton": { + "description": "Button to uninstall extension" + }, + "extensionUpdated": "Оновлено", + "@extensionUpdated": { + "description": "Extension detail - last update" + }, + "extensionMinAppVersion": "Мінімальна версія програми", + "@extensionMinAppVersion": { + "description": "Extension detail - minimum app version" + }, + "extensionCustomTrackMatching": "Підбір користувацьких треків", + "@extensionCustomTrackMatching": { + "description": "Capability - custom track matching algorithm" + }, + "extensionPostProcessing": "Післяобробка", + "@extensionPostProcessing": { + "description": "Capability - post-download processing" + }, + "extensionHooksAvailable": "{count} доступних хуків", + "@extensionHooksAvailable": { + "description": "Post-processing hooks count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "extensionPatternsCount": "{count} шаблон(ів)", + "@extensionPatternsCount": { + "description": "URL patterns count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "extensionStrategy": "Стратегія: {strategy}", + "@extensionStrategy": { + "description": "Track matching strategy name", + "placeholders": { + "strategy": { + "type": "String" + } + } + }, + "extensionsProviderPrioritySection": "Пріоритет постачальника", + "@extensionsProviderPrioritySection": { + "description": "Section header - provider priority" + }, + "extensionsInstalledSection": "Встановлені розширення", + "@extensionsInstalledSection": { + "description": "Section header - installed extensions" + }, + "extensionsNoExtensions": "Розширень не встановлено", + "@extensionsNoExtensions": { + "description": "Empty state - no extensions" + }, + "extensionsNoExtensionsSubtitle": "Встановіть файли .spotiflac-ext, щоб додати нових провайдерів", + "@extensionsNoExtensionsSubtitle": { + "description": "Empty state subtitle" + }, + "extensionsInstallButton": "Встановити розширення", + "@extensionsInstallButton": { + "description": "Button to install extension from file" + }, + "extensionsInfoTip": "Розширення можуть додавати нові метадані та завантажувати постачальників. Встановлюйте розширення лише з перевірених джерел.", + "@extensionsInfoTip": { + "description": "Security warning about extensions" + }, + "extensionsInstalledSuccess": "Розширення успішно встановлено", + "@extensionsInstalledSuccess": { + "description": "Success message after install" + }, + "extensionsDownloadPriority": "Пріоритет завантаження", + "@extensionsDownloadPriority": { + "description": "Setting - download provider order" + }, + "extensionsDownloadPrioritySubtitle": "Встановити порядок завантаження", + "@extensionsDownloadPrioritySubtitle": { + "description": "Subtitle for download priority" + }, + "extensionsFallbackTitle": "Резервні розширення", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Виберіть, які встановлені розширення для завантаження можна використовувати як резервні", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, + "extensionsNoDownloadProvider": "Без розширень із постачальником завантажень", + "@extensionsNoDownloadProvider": { + "description": "Empty state - no download providers" + }, + "extensionsMetadataPriority": "Пріоритет метаданих", + "@extensionsMetadataPriority": { + "description": "Setting - metadata provider order" + }, + "extensionsMetadataPrioritySubtitle": "Встановити порядок пошуку та джерел метаданих", + "@extensionsMetadataPrioritySubtitle": { + "description": "Subtitle for metadata priority" + }, + "extensionsNoMetadataProvider": "Без розширень із постачальником метаданих", + "@extensionsNoMetadataProvider": { + "description": "Empty state - no metadata providers" + }, + "extensionsSearchProvider": "Постачальник пошуку", + "@extensionsSearchProvider": { + "description": "Setting - search provider selection" + }, + "extensionsNoCustomSearch": "Без розширень із користувацьким пошуком", + "@extensionsNoCustomSearch": { + "description": "Empty state - no search providers" + }, + "extensionsSearchProviderDescription": "Виберіть, який сервіс використовувати для пошуку треків", + "@extensionsSearchProviderDescription": { + "description": "Search provider setting description" + }, + "extensionsCustomSearch": "Користувацький пошук", + "@extensionsCustomSearch": { + "description": "Label for custom search provider" + }, + "extensionsErrorLoading": "Помилка завантаження розширення", + "@extensionsErrorLoading": { + "description": "Error message when extension fails to load" + }, + "qualityFlacLossless": "FLAC без втрат", + "@qualityFlacLossless": { + "description": "Quality option - CD quality FLAC" + }, + "qualityFlacLosslessSubtitle": "16 біт / 44,1 кГц", + "@qualityFlacLosslessSubtitle": { + "description": "Technical spec for lossless" + }, + "qualityHiResFlac": "FLAC високої роздільної здатності", + "@qualityHiResFlac": { + "description": "Quality option - high resolution FLAC" + }, + "qualityHiResFlacSubtitle": "24-біт / до 96 кГц", + "@qualityHiResFlacSubtitle": { + "description": "Technical spec for hi-res" + }, + "qualityHiResFlacMax": "FLAC Max з високою роздільною здатністю", + "@qualityHiResFlacMax": { + "description": "Quality option - maximum resolution FLAC" + }, + "qualityHiResFlacMaxSubtitle": "24-біт / до 192 кГц", + "@qualityHiResFlacMaxSubtitle": { + "description": "Technical spec for hi-res max" + }, + "downloadLossy320": "Lossy (із втратами) 320 кбіт/с", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Формат із втратами", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Формат із втратами 320 кбіт/с", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Виберіть вихідний формат для завантажень Tidal 320 кбіт/с із втратами. Оригінальний потік AAC буде конвертовано у вибраний вами формат.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320 кбіт/с", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Найкраща сумісність, ~10 МБ на доріжку", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256 кбіт/с", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Opus найкращої якості, ~8 МБ на трек", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128 кбіт/с", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Найменший розмір, ~4 МБ на доріжку", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, + "qualityNote": "Фактична якість залежить від наявності треку в сервісі", + "@qualityNote": { + "description": "Note about quality availability" + }, + "downloadAskBeforeDownload": "Запитувати перед завантаженням", + "@downloadAskBeforeDownload": { + "description": "Setting - show quality picker" + }, + "downloadDirectory": "Каталог завантажень", + "@downloadDirectory": { + "description": "Setting - download folder" + }, + "downloadSeparateSinglesFolder": "Окрема папка для синглів", + "@downloadSeparateSinglesFolder": { + "description": "Setting - separate folder for singles" + }, + "downloadAlbumFolderStructure": "Структура папок альбому", + "@downloadAlbumFolderStructure": { + "description": "Setting - album folder organization" + }, + "downloadUseAlbumArtistForFolders": "Використовувати виконавця альбому для папок", + "@downloadUseAlbumArtistForFolders": { + "description": "Setting - choose whether artist folders use Album Artist or Track Artist" + }, + "downloadUsePrimaryArtistOnly": "Тільки основний виконавець для папок", + "@downloadUsePrimaryArtistOnly": { + "description": "Setting - strip featured artists from folder name" + }, + "downloadUsePrimaryArtistOnlyEnabled": "Вибраних виконавців видалити з назви папки (наприклад, Джастін Бібер, Quavo → Джастін Бібер)", + "@downloadUsePrimaryArtistOnlyEnabled": { + "description": "Subtitle when primary artist only is enabled" + }, + "downloadUsePrimaryArtistOnlyDisabled": "Повний рядок виконавця, що використовується для назви папки", + "@downloadUsePrimaryArtistOnlyDisabled": { + "description": "Subtitle when primary artist only is disabled" + }, + "downloadSelectQuality": "Вибрати якість", + "@downloadSelectQuality": { + "description": "Dialog title - choose audio quality" + }, + "downloadFrom": "Завантажити з", + "@downloadFrom": { + "description": "Label - download source" + }, + "appearanceAmoledDark": "Темний AMOLED", + "@appearanceAmoledDark": { + "description": "Theme option - pure black" + }, + "appearanceAmoledDarkSubtitle": "Чисто чорний фон", + "@appearanceAmoledDarkSubtitle": { + "description": "Subtitle for AMOLED dark" + }, + "queueClearAll": "Усі", + "@queueClearAll": { + "description": "Button - clear all queue items" + }, + "queueClearAllMessage": "Ви впевнені, що хочете очистити всі завантаження?", + "@queueClearAllMessage": { + "description": "Clear queue confirmation" + }, + "settingsAutoExportFailed": "Автоматичний експорт невдалих завантажень", + "@settingsAutoExportFailed": { + "description": "Setting toggle for auto-export" + }, + "settingsAutoExportFailedSubtitle": "Автоматично зберігати невдалі завантаження у файл TXT", + "@settingsAutoExportFailedSubtitle": { + "description": "Subtitle for auto-export setting" + }, + "settingsDownloadNetwork": "Мережа для завантаження", + "@settingsDownloadNetwork": { + "description": "Setting for network type preference" + }, + "settingsDownloadNetworkAny": "Wi-Fi + мобільний інтернет", + "@settingsDownloadNetworkAny": { + "description": "Network option - use any connection" + }, + "settingsDownloadNetworkWifiOnly": "Тільки Wi-Fi", + "@settingsDownloadNetworkWifiOnly": { + "description": "Network option - only use WiFi" + }, + "settingsDownloadNetworkSubtitle": "Вибрати мережу для завантажень. Якщо встановлено значення «Тільки Wi-Fi», завантаження призупиняться через мобільні дані.", + "@settingsDownloadNetworkSubtitle": { + "description": "Subtitle explaining network preference" + }, + "albumFolderArtistAlbum": "Артист / Альбом", + "@albumFolderArtistAlbum": { + "description": "Album folder option" + }, + "albumFolderArtistAlbumSubtitle": "Альбоми/Ім'я артиста/Назва альбому/", + "@albumFolderArtistAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistYearAlbum": "Артист / [Рік] Альбом", + "@albumFolderArtistYearAlbum": { + "description": "Album folder option with year" + }, + "albumFolderArtistYearAlbumSubtitle": "Альбоми/Ім'я Виконавця/[2005] Назва альбому/", + "@albumFolderArtistYearAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderAlbumOnly": "Тільки альбом", + "@albumFolderAlbumOnly": { + "description": "Album folder option" + }, + "albumFolderAlbumOnlySubtitle": "Альбоми/Назва Альбому/", + "@albumFolderAlbumOnlySubtitle": { + "description": "Folder structure example" + }, + "albumFolderYearAlbum": "[Рік] Альбом", + "@albumFolderYearAlbum": { + "description": "Album folder option with year" + }, + "albumFolderYearAlbumSubtitle": "Альбоми/[2005] Назва Альбому/", + "@albumFolderYearAlbumSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistAlbumSingles": "Виконавець / Альбом + Сингли", + "@albumFolderArtistAlbumSingles": { + "description": "Album folder option with singles inside artist" + }, + "albumFolderArtistAlbumSinglesSubtitle": "Виконавець/Альбом/ та Виконавець/Сингли/", + "@albumFolderArtistAlbumSinglesSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistAlbumFlat": "Виконавець / Альбом (сингли без альбомів)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Виконавець/Альбом/ та Виконавець/пісня.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, + "downloadedAlbumDeleteSelected": "Видалити вибране", + "@downloadedAlbumDeleteSelected": { + "description": "Button - delete selected tracks" + }, + "downloadedAlbumDeleteMessage": "Видалити {count} {count, plural, =1{трек} other{треків}} з цього альбому?\n\nЦе також призведе до видалення файлів зі сховища.", + "@downloadedAlbumDeleteMessage": { + "description": "Delete confirmation with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumSelectedCount": "Вибрано {count}", + "@downloadedAlbumSelectedCount": { + "description": "Selection count indicator", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumAllSelected": "Усі треки вибрано", + "@downloadedAlbumAllSelected": { + "description": "Status - all items selected" + }, + "downloadedAlbumTapToSelect": "Натисніть на треки, щоб вибрати", + "@downloadedAlbumTapToSelect": { + "description": "Selection hint" + }, + "downloadedAlbumDeleteCount": "Видалити {count} {count, plural, =1{трек} other{треків}}", + "@downloadedAlbumDeleteCount": { + "description": "Delete button text with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedAlbumSelectToDelete": "Виберіть треки для видалення", + "@downloadedAlbumSelectToDelete": { + "description": "Placeholder when nothing selected" + }, + "downloadedAlbumDiscHeader": "Диск {discNumber}", + "@downloadedAlbumDiscHeader": { + "description": "Header for disc separator in multi-disc albums", + "placeholders": { + "discNumber": { + "type": "int", + "example": "1" + } + } + }, + "recentTypeArtist": "Артист", + "@recentTypeArtist": { + "description": "Recent access item type - artist" + }, + "recentTypeAlbum": "Альбом", + "@recentTypeAlbum": { + "description": "Recent access item type - album" + }, + "recentTypeSong": "Пісня", + "@recentTypeSong": { + "description": "Recent access item type - song/track" + }, + "recentTypePlaylist": "Список відтворення", + "@recentTypePlaylist": { + "description": "Recent access item type - playlist" + }, + "recentEmpty": "Поки що немає нещодавніх записів", + "@recentEmpty": { + "description": "Empty state text for recent access list" + }, + "recentShowAllDownloads": "Показати всі завантаження", + "@recentShowAllDownloads": { + "description": "Button label to unhide hidden downloads in recent access" + }, + "recentPlaylistInfo": "Список відтворення: {name}", + "@recentPlaylistInfo": { + "description": "Snackbar message when tapping playlist in recent access", + "placeholders": { + "name": { + "type": "String", + "description": "Playlist name" + } + } + }, + "discographyDownload": "Завантажити дискографію", + "@discographyDownload": { + "description": "Button - download artist discography" + }, + "discographyDownloadAll": "Завантажити все", + "@discographyDownloadAll": { + "description": "Option - download entire discography" + }, + "discographyDownloadAllSubtitle": "{count} треків з {albumCount} релізів", + "@discographyDownloadAllSubtitle": { + "description": "Subtitle showing total tracks and albums", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographyAlbumsOnly": "Тільки альбоми", + "@discographyAlbumsOnly": { + "description": "Option - download only albums" + }, + "discographyAlbumsOnlySubtitle": "{count} треків з {albumCount} альбомів", + "@discographyAlbumsOnlySubtitle": { + "description": "Subtitle showing album tracks count", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographySinglesOnly": "Тільки сингли та міні-альбоми", + "@discographySinglesOnly": { + "description": "Option - download only singles" + }, + "discographySinglesOnlySubtitle": "{count} треків з {albumCount} синглів", + "@discographySinglesOnlySubtitle": { + "description": "Subtitle showing singles tracks count", + "placeholders": { + "count": { + "type": "int" + }, + "albumCount": { + "type": "int" + } + } + }, + "discographySelectAlbums": "Вибрати альбоми...", + "@discographySelectAlbums": { + "description": "Option - manually select albums to download" + }, + "discographySelectAlbumsSubtitle": "Виберіть конкретні альбоми або сингли", + "@discographySelectAlbumsSubtitle": { + "description": "Subtitle for select albums option" + }, + "discographyFetchingTracks": "Отримання треків...", + "@discographyFetchingTracks": { + "description": "Progress - fetching album tracks" + }, + "discographyFetchingAlbum": "Отримання {current} з {total}...", + "@discographyFetchingAlbum": { + "description": "Progress - fetching specific album", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "discographySelectedCount": "{count} вибрано", + "@discographySelectedCount": { + "description": "Selection count badge", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discographyDownloadSelected": "Завантажити вибране", + "@discographyDownloadSelected": { + "description": "Button - download selected albums" + }, + "discographyAddedToQueue": "Додано {count} треків до черги", + "@discographyAddedToQueue": { + "description": "Snackbar - tracks added from discography", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discographySkippedDownloaded": "{added} додано, {skipped} вже завантажено", + "@discographySkippedDownloaded": { + "description": "Snackbar - with skipped tracks count", + "placeholders": { + "added": { + "type": "int" + }, + "skipped": { + "type": "int" + } + } + }, + "discographyNoAlbums": "Немає доступних альбомів", + "@discographyNoAlbums": { + "description": "Error - no albums found for artist" + }, + "discographyFailedToFetch": "Не вдалося отримати деякі альбоми", + "@discographyFailedToFetch": { + "description": "Error - some albums failed to load" + }, + "sectionStorageAccess": "Доступ до сховища", + "@sectionStorageAccess": { + "description": "Section header for storage access settings" + }, + "allFilesAccess": "Доступ до всіх файлів", + "@allFilesAccess": { + "description": "Toggle for MANAGE_EXTERNAL_STORAGE permission" + }, + "allFilesAccessEnabledSubtitle": "Можна записувати в будь-яку папку", + "@allFilesAccessEnabledSubtitle": { + "description": "Subtitle when all files access is enabled" + }, + "allFilesAccessDisabledSubtitle": "Обмежено лише медіа-папками", + "@allFilesAccessDisabledSubtitle": { + "description": "Subtitle when all files access is disabled" + }, + "allFilesAccessDescription": "Увімкніть цю опцію, якщо під час збереження у власні папки виникають помилки запису. Android 13+ за замовчуванням обмежує доступ до певних каталогів.", + "@allFilesAccessDescription": { + "description": "Description explaining when to enable all files access" + }, + "allFilesAccessDeniedMessage": "У дозволі відмовлено. Будь ласка, увімкніть «Доступ до всіх файлів» вручну в налаштуваннях системи.", + "@allFilesAccessDeniedMessage": { + "description": "Message when permission is permanently denied" + }, + "allFilesAccessDisabledMessage": "У дозволі відмовлено. Будь ласка, увімкніть «Доступ до всіх файлів» вручну в налаштуваннях системи.", + "@allFilesAccessDisabledMessage": { + "description": "Snackbar message when user disables all files access" + }, + "settingsLocalLibrary": "Локальна бібліотека", + "@settingsLocalLibrary": { + "description": "Settings menu item - local library" + }, + "settingsLocalLibrarySubtitle": "Сканування музики та виявлення дублікатів", + "@settingsLocalLibrarySubtitle": { + "description": "Subtitle for local library settings" + }, + "settingsCache": "Сховище та Кеш", + "@settingsCache": { + "description": "Settings menu item - cache management" + }, + "settingsCacheSubtitle": "Переглянути розмір і очистити кешовані дані", + "@settingsCacheSubtitle": { + "description": "Subtitle for cache management menu" + }, + "libraryTitle": "Локальна бібліотека", + "@libraryTitle": { + "description": "Library settings page title" + }, + "libraryScanSettings": "Налаштування сканування", + "@libraryScanSettings": { + "description": "Section header for scan settings" + }, + "libraryEnableLocalLibrary": "Увімкнути локальну бібліотеку", + "@libraryEnableLocalLibrary": { + "description": "Toggle to enable library scanning" + }, + "libraryEnableLocalLibrarySubtitle": "Скануати та відстежити свою існуючу музику", + "@libraryEnableLocalLibrarySubtitle": { + "description": "Subtitle for enable toggle" + }, + "libraryFolder": "Папка бібліотеки", + "@libraryFolder": { + "description": "Folder selection setting" + }, + "libraryFolderHint": "Натисніть, щоб вибрати папку", + "@libraryFolderHint": { + "description": "Placeholder when no folder selected" + }, + "libraryShowDuplicateIndicator": "Показати індикатор дублікатів", + "@libraryShowDuplicateIndicator": { + "description": "Toggle for duplicate indicator in search" + }, + "libraryShowDuplicateIndicatorSubtitle": "Показувати під час пошуку існуючих треків", + "@libraryShowDuplicateIndicatorSubtitle": { + "description": "Subtitle for duplicate indicator toggle" + }, + "libraryAutoScan": "Автоматичне сканування", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Автоматичне сканування бібліотеки на наявність нових файлів", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Вимкнено", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Кожного разу коли додаток відкривається", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Щоденно", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Щотижнево", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, + "libraryActions": "Дії", + "@libraryActions": { + "description": "Section header for library actions" + }, + "libraryScan": "Сканувати бібліотеку", + "@libraryScan": { + "description": "Button to start library scan" + }, + "libraryScanSubtitle": "Сканувати для аудіофайлів", + "@libraryScanSubtitle": { + "description": "Subtitle for scan button" + }, + "libraryScanSelectFolderFirst": "Спочатку виберіть папку", + "@libraryScanSelectFolderFirst": { + "description": "Message when trying to scan without folder" + }, + "libraryCleanupMissingFiles": "Очищення відсутніх файлів", + "@libraryCleanupMissingFiles": { + "description": "Button to remove entries for missing files" + }, + "libraryCleanupMissingFilesSubtitle": "Видалити записи для файлів, яких більше не існує", + "@libraryCleanupMissingFilesSubtitle": { + "description": "Subtitle for cleanup button" + }, + "libraryClear": "Очистити бібліотеку", + "@libraryClear": { + "description": "Button to clear all library entries" + }, + "libraryClearSubtitle": "Видалити всі скановані треки", + "@libraryClearSubtitle": { + "description": "Subtitle for clear button" + }, + "libraryClearConfirmTitle": "Очистити бібліотеку", + "@libraryClearConfirmTitle": { + "description": "Dialog title for clear confirmation" + }, + "libraryClearConfirmMessage": "Це видалить усі скановані треки з вашої бібліотеки. Ваші фактичні музичні файли не будуть видалені.", + "@libraryClearConfirmMessage": { + "description": "Dialog message for clear confirmation" + }, + "libraryAbout": "Про локальну бібліотеку", + "@libraryAbout": { + "description": "Section header for about info" + }, + "libraryAboutDescription": "Сканує вашу існуючу музичну колекцію для виявлення дублікатів під час завантаження. Підтримує формати FLAC, M4A, MP3, Opus та OGG. Метадані зчитуються з тегів файлів, коли вони доступні.", + "@libraryAboutDescription": { + "description": "Description of local library feature" + }, + "libraryTracksUnit": "{count, plural, =1{трек} other{треків}}", + "@libraryTracksUnit": { + "description": "Unit label for tracks count (without the number itself)", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "libraryFilesUnit": "{count, plural, =1{file} other{файлів}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "libraryLastScanned": "Останнє сканування: {time}", + "@libraryLastScanned": { + "description": "Last scan time display", + "placeholders": { + "time": { + "type": "String" + } + } + }, + "libraryLastScannedNever": "Ніколи", + "@libraryLastScannedNever": { + "description": "Shown when library has never been scanned" + }, + "libraryScanning": "Сканування...", + "@libraryScanning": { + "description": "Status during scan" + }, + "libraryScanFinalizing": "Завершення роботи з бібліотекою...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, + "libraryScanProgress": "{progress}% від {total} файлів", + "@libraryScanProgress": { + "description": "Scan progress display", + "placeholders": { + "progress": { + "type": "String" + }, + "total": { + "type": "int" + } + } + }, + "libraryInLibrary": "У бібліотеці", + "@libraryInLibrary": { + "description": "Badge shown on tracks that exist in local library" + }, + "libraryRemovedMissingFiles": "Видалено {count} відсутніх файлів з бібліотеки", + "@libraryRemovedMissingFiles": { + "description": "Snackbar after cleanup", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "libraryCleared": "Бібліотека очищена", + "@libraryCleared": { + "description": "Snackbar after clearing library" + }, + "libraryStorageAccessRequired": "Потрібен доступ до сховища", + "@libraryStorageAccessRequired": { + "description": "Dialog title for storage permission" + }, + "libraryStorageAccessMessage": "SpotiFLAC потрібен доступ до сховища для сканування вашої музичної бібліотеки. Надайте дозвіл у налаштуваннях.", + "@libraryStorageAccessMessage": { + "description": "Dialog message for storage permission" + }, + "libraryFolderNotExist": "Вибрана папка не існує", + "@libraryFolderNotExist": { + "description": "Error when folder doesn't exist" + }, + "librarySourceDownloaded": "Завантажені", + "@librarySourceDownloaded": { + "description": "Badge for tracks downloaded via SpotiFLAC" + }, + "librarySourceLocal": "Локальні", + "@librarySourceLocal": { + "description": "Badge for tracks from local library scan" + }, + "libraryFilterAll": "Усі", + "@libraryFilterAll": { + "description": "Filter chip - show all library items" + }, + "libraryFilterDownloaded": "Завантажені", + "@libraryFilterDownloaded": { + "description": "Filter chip - show only downloaded items" + }, + "libraryFilterLocal": "Локальні", + "@libraryFilterLocal": { + "description": "Filter chip - show only local library items" + }, + "libraryFilterTitle": "Фільтри", + "@libraryFilterTitle": { + "description": "Filter bottom sheet title" + }, + "libraryFilterReset": "Скинути", + "@libraryFilterReset": { + "description": "Reset all filters button" + }, + "libraryFilterApply": "Застосувати", + "@libraryFilterApply": { + "description": "Apply filters button" + }, + "libraryFilterSource": "Джерело", + "@libraryFilterSource": { + "description": "Filter section - source type" + }, + "libraryFilterQuality": "Якість", + "@libraryFilterQuality": { + "description": "Filter section - audio quality" + }, + "libraryFilterQualityHiRes": "Висока роздільна здатність (24 біти)", + "@libraryFilterQualityHiRes": { + "description": "Filter option - high resolution audio" + }, + "libraryFilterQualityCD": "CD (16-бітний)", + "@libraryFilterQualityCD": { + "description": "Filter option - CD quality audio" + }, + "libraryFilterQualityLossy": "Із втратами (lossy)", + "@libraryFilterQualityLossy": { + "description": "Filter option - lossy compressed audio" + }, + "libraryFilterFormat": "Формат", + "@libraryFilterFormat": { + "description": "Filter section - file format" + }, + "libraryFilterMetadata": "Метадані", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Повні метадані", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "будь-які метадані", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Відсутній рік", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Відсутній жанр", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Відсутній виконавець альбому", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, + "libraryFilterSort": "Сортувати", + "@libraryFilterSort": { + "description": "Filter section - sort order" + }, + "libraryFilterSortLatest": "Найновіші", + "@libraryFilterSortLatest": { + "description": "Sort option - newest first" + }, + "libraryFilterSortOldest": "Найстаріші", + "@libraryFilterSortOldest": { + "description": "Sort option - oldest first" + }, + "libraryFilterSortAlbumAsc": "Альбом (А-Я)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Альбом (Я-А)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Жанр (А-Я)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Жанр (Я-А)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, + "timeJustNow": "Щойно", + "@timeJustNow": { + "description": "Relative time - less than a minute ago" + }, + "timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} хвилин тому}}", + "@timeMinutesAgo": { + "description": "Relative time - minutes ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} годин тому}}", + "@timeHoursAgo": { + "description": "Relative time - hours ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "tutorialWelcomeTitle": "Ласкаво просимо до SpotiFLAC!", + "@tutorialWelcomeTitle": { + "description": "Tutorial welcome page title" + }, + "tutorialWelcomeDesc": "Давайте дізнаємося, як завантажувати улюблену музику в якості без втрат. Цей короткий посібник покаже вам основи.", + "@tutorialWelcomeDesc": { + "description": "Tutorial welcome page description" + }, + "tutorialWelcomeTip1": "Завантажуйте музику зі Spotify, Deezer або вставляйте будь-яку підтримувану URL-адресу", + "@tutorialWelcomeTip1": { + "description": "Tutorial welcome tip 1" + }, + "tutorialWelcomeTip2": "Отримуйте аудіо у якості FLAC з Tidal, Qobuz або Deezer", + "@tutorialWelcomeTip2": { + "description": "Tutorial welcome tip 2" + }, + "tutorialWelcomeTip3": "Автоматичне додавання метаданих, обкладинки та текстів пісень", + "@tutorialWelcomeTip3": { + "description": "Tutorial welcome tip 3" + }, + "tutorialSearchTitle": "Пошук музики", + "@tutorialSearchTitle": { + "description": "Tutorial search page title" + }, + "tutorialSearchDesc": "Існує два простих способи знайти музику, яку ви хочете завантажити.", + "@tutorialSearchDesc": { + "description": "Tutorial search page description" + }, + "tutorialDownloadTitle": "Завантаження музики", + "@tutorialDownloadTitle": { + "description": "Tutorial download page title" + }, + "tutorialDownloadDesc": "Завантаження музики просте та швидке. Ось як це працює.", + "@tutorialDownloadDesc": { + "description": "Tutorial download page description" + }, + "tutorialLibraryTitle": "Ваша бібліотека", + "@tutorialLibraryTitle": { + "description": "Tutorial library page title" + }, + "tutorialLibraryDesc": "Вся завантажена музика організована на вкладці «Бібліотека».", + "@tutorialLibraryDesc": { + "description": "Tutorial library page description" + }, + "tutorialLibraryTip1": "Перегляд стану завантаження та черги на вкладці «Бібліотека»", + "@tutorialLibraryTip1": { + "description": "Tutorial library tip 1" + }, + "tutorialLibraryTip2": "Торкніться будь-якої композиції, щоб відтворити її за допомогою музичного плеєра", + "@tutorialLibraryTip2": { + "description": "Tutorial library tip 2" + }, + "tutorialLibraryTip3": "Перемикання між списком та сіткою для кращого перегляду", + "@tutorialLibraryTip3": { + "description": "Tutorial library tip 3" + }, + "tutorialExtensionsTitle": "Розширення", + "@tutorialExtensionsTitle": { + "description": "Tutorial extensions page title" + }, + "tutorialExtensionsDesc": "Розширте можливості програми за допомогою розширень спільноти.", + "@tutorialExtensionsDesc": { + "description": "Tutorial extensions page description" + }, + "tutorialExtensionsTip1": "Перегляньте вкладку «Репозиторій», щоб знайти корисні розширення", + "@tutorialExtensionsTip1": { + "description": "Tutorial extensions tip 1" + }, + "tutorialExtensionsTip2": "Додавайте нових постачальників послуг завантаження або джерела пошуку", + "@tutorialExtensionsTip2": { + "description": "Tutorial extensions tip 2" + }, + "tutorialExtensionsTip3": "Отримайте тексти пісень, розширені метадані та інші функції", + "@tutorialExtensionsTip3": { + "description": "Tutorial extensions tip 3" + }, + "tutorialSettingsTitle": "Налаштуйте свій досвід", + "@tutorialSettingsTitle": { + "description": "Tutorial settings page title" + }, + "tutorialSettingsDesc": "Персоналізуйте програму в налаштуваннях відповідно до ваших уподобань.", + "@tutorialSettingsDesc": { + "description": "Tutorial settings page description" + }, + "tutorialSettingsTip1": "Змініть місце завантаження та організації папок", + "@tutorialSettingsTip1": { + "description": "Tutorial settings tip 1" + }, + "tutorialSettingsTip2": "Встановіть параметри якості звуку та формату за замовчуванням", + "@tutorialSettingsTip2": { + "description": "Tutorial settings tip 2" + }, + "tutorialSettingsTip3": "Налаштуйте тему та зовнішній вигляд програми", + "@tutorialSettingsTip3": { + "description": "Tutorial settings tip 3" + }, + "tutorialReadyMessage": "Готово! Почніть завантажувати свою улюблену музику прямо зараз.", + "@tutorialReadyMessage": { + "description": "Tutorial completion message" + }, + "libraryForceFullScan": "Примусове повне сканування", + "@libraryForceFullScan": { + "description": "Button to force a complete rescan of library" + }, + "libraryForceFullScanSubtitle": "Пересканувати всі файли, ігноруючи кеш", + "@libraryForceFullScanSubtitle": { + "description": "Subtitle for force full scan button" + }, + "cleanupOrphanedDownloads": "Очищення застарілих завантажень", + "@cleanupOrphanedDownloads": { + "description": "Button to remove history entries for deleted files" + }, + "cleanupOrphanedDownloadsSubtitle": "Видалити записи історії для файлів, яких більше не існує", + "@cleanupOrphanedDownloadsSubtitle": { + "description": "Subtitle for orphaned cleanup button" + }, + "cleanupOrphanedDownloadsResult": "Видалено {count} утрачених записів з історії", + "@cleanupOrphanedDownloadsResult": { + "description": "Snackbar after orphan cleanup", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cleanupOrphanedDownloadsNone": "Не знайдено утрачених записів", + "@cleanupOrphanedDownloadsNone": { + "description": "Snackbar when no orphans found" + }, + "cacheTitle": "Зберігання та кеш", + "@cacheTitle": { + "description": "Cache management page title" + }, + "cacheSummaryTitle": "Огляд кешу", + "@cacheSummaryTitle": { + "description": "Heading for cache summary card" + }, + "cacheSummarySubtitle": "Очищення кешу не призведе до видалення завантажених музичних файлів.", + "@cacheSummarySubtitle": { + "description": "Helper text for cache summary card" + }, + "cacheEstimatedTotal": "Орієнтовне використання кешу: {size}", + "@cacheEstimatedTotal": { + "description": "Total cache size shown in summary", + "placeholders": { + "size": { + "type": "String" + } + } + }, + "cacheSectionStorage": "Кешовані дані", + "@cacheSectionStorage": { + "description": "Section header for cache entries" + }, + "cacheSectionMaintenance": "Технічне обслуговування", + "@cacheSectionMaintenance": { + "description": "Section header for cleanup actions" + }, + "cacheAppDirectory": "Каталог кешу додатка", + "@cacheAppDirectory": { + "description": "Cache item title for app cache directory" + }, + "cacheAppDirectoryDesc": "HTTP-відповіді, дані WebView та інші тимчасові дані додатків.", + "@cacheAppDirectoryDesc": { + "description": "Description of what app cache directory contains" + }, + "cacheTempDirectory": "Тимчасовий каталог", + "@cacheTempDirectory": { + "description": "Cache item title for temporary files directory" + }, + "cacheTempDirectoryDesc": "Тимчасові файли із завантажень та конвертації аудіо.", + "@cacheTempDirectoryDesc": { + "description": "Description of what temporary directory contains" + }, + "cacheCoverImage": "Кеш зображень обкладинок", + "@cacheCoverImage": { + "description": "Cache item title for persistent cover images" + }, + "cacheCoverImageDesc": "Завантажено обкладинку альбому та треку. Завантаження відбудеться повторно після перегляду.", + "@cacheCoverImageDesc": { + "description": "Description of what cover image cache contains" + }, + "cacheLibraryCover": "Кеш бібліотеки обкладинок", + "@cacheLibraryCover": { + "description": "Cache item title for local library cover art images" + }, + "cacheLibraryCoverDesc": "Обкладинку витягнуто з локальних музичних файлів. Буде повторно витягнуто під час наступного сканування.", + "@cacheLibraryCoverDesc": { + "description": "Description of what library cover cache contains" + }, + "cacheExploreFeed": "Огляд кешу стрічки", + "@cacheExploreFeed": { + "description": "Cache item title for explore home feed cache" + }, + "cacheExploreFeedDesc": "Переглянути вміст вкладки (нові випуски, тренди). Оновиться під час наступного відвідування.", + "@cacheExploreFeedDesc": { + "description": "Description of what explore feed cache contains" + }, + "cacheTrackLookup": "Відстеження кешу пошуку", + "@cacheTrackLookup": { + "description": "Cache item title for track ID lookup cache" + }, + "cacheTrackLookupDesc": "Пошук ідентифікаторів треків Spotify/Deezer. Очищення може уповільнити наступні кілька пошуків.", + "@cacheTrackLookupDesc": { + "description": "Description of what track lookup cache contains" + }, + "cacheCleanupUnusedDesc": "Видалити історію втрачених завантажень та записи бібліотеки для відсутніх файлів.", + "@cacheCleanupUnusedDesc": { + "description": "Description of what cleanup unused data does" + }, + "cacheNoData": "Кешованих даних немає", + "@cacheNoData": { + "description": "Label when cache category has no data" + }, + "cacheSizeWithFiles": "{size} у {count} файлах", + "@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} записів", + "@cacheEntries": { + "description": "Track cache entry count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cacheClearSuccess": "Очищено: {target}", + "@cacheClearSuccess": { + "description": "Snackbar after clearing selected cache", + "placeholders": { + "target": { + "type": "String" + } + } + }, + "cacheClearConfirmTitle": "Очистити кеш?", + "@cacheClearConfirmTitle": { + "description": "Dialog title before clearing one cache category" + }, + "cacheClearConfirmMessage": "Це очистить кешовані дані для {target}. Завантажені музичні файли не будуть видалені.", + "@cacheClearConfirmMessage": { + "description": "Dialog message before clearing selected cache", + "placeholders": { + "target": { + "type": "String" + } + } + }, + "cacheClearAllConfirmTitle": "Очистити увесь кеш?", + "@cacheClearAllConfirmTitle": { + "description": "Dialog title before clearing all caches" + }, + "cacheClearAllConfirmMessage": "Це очистить усі категорії кешу на цій сторінці. Завантажені музичні файли не будуть видалені.", + "@cacheClearAllConfirmMessage": { + "description": "Dialog message before clearing all caches" + }, + "cacheClearAll": "Очистити весь кеш", + "@cacheClearAll": { + "description": "Button label to clear all caches" + }, + "cacheCleanupUnused": "Очищення невикористаних даних", + "@cacheCleanupUnused": { + "description": "Action title for cleaning unused entries" + }, + "cacheCleanupUnusedSubtitle": "Видалити історію утрачених завантажень файлів та відсутні записи бібліотеки", + "@cacheCleanupUnusedSubtitle": { + "description": "Subtitle for cleanup unused data action" + }, + "cacheCleanupResult": "Очищення завершено: {downloadCount} утрачених завантажень, {libraryCount} відсутніх записів бібліотеки", + "@cacheCleanupResult": { + "description": "Snackbar after unused data cleanup", + "placeholders": { + "downloadCount": { + "type": "int" + }, + "libraryCount": { + "type": "int" + } + } + }, + "cacheRefreshStats": "Оновити статистику", + "@cacheRefreshStats": { + "description": "Button label to refresh cache statistics" + }, + "trackSaveCoverArt": "Зберегти обкладинку", + "@trackSaveCoverArt": { + "description": "Menu action - save album cover art as file" + }, + "trackSaveCoverArtSubtitle": "Зберегти обкладинку альбому як файл .jpg", + "@trackSaveCoverArtSubtitle": { + "description": "Subtitle for save cover art action" + }, + "trackSaveLyrics": "Зберегти текст пісні (.lrc)", + "@trackSaveLyrics": { + "description": "Menu action - save lyrics as .lrc file" + }, + "trackSaveLyricsSubtitle": "Отримати та зберегти текст пісні у форматі .lrc", + "@trackSaveLyricsSubtitle": { + "description": "Subtitle for save lyrics action" + }, + "trackSaveLyricsProgress": "Збереження тексту пісні...", + "@trackSaveLyricsProgress": { + "description": "Snackbar while saving lyrics to file" + }, + "trackReEnrich": "Перезбагачувати", + "@trackReEnrich": { + "description": "Menu action - re-embed metadata into audio file" + }, + "trackReEnrichOnlineSubtitle": "Пошук метаданих в Інтернеті та вбудовування у файл", + "@trackReEnrichOnlineSubtitle": { + "description": "Subtitle for re-enrich metadata action for local items" + }, + "trackReEnrichFieldsTitle": "Поля для оновлення", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Обкладинка", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Тексти пісень", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Альбом, Виконавець альбому", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Номер треку та диска", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Дата та ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Жанр, Лейбл, Авторське право", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Вибрати все", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, + "trackEditMetadata": "Редагувати метадані", + "@trackEditMetadata": { + "description": "Menu action - edit embedded metadata" + }, + "trackCoverSaved": "Обкладинку збережено до {fileName}", + "@trackCoverSaved": { + "description": "Snackbar after cover art saved", + "placeholders": { + "fileName": { + "type": "String" + } + } + }, + "trackCoverNoSource": "Джерело обкладинки недоступне", + "@trackCoverNoSource": { + "description": "Snackbar when no cover art URL or embedded cover" + }, + "trackLyricsSaved": "Текст пісні збережено в {fileName}", + "@trackLyricsSaved": { + "description": "Snackbar after lyrics saved", + "placeholders": { + "fileName": { + "type": "String" + } + } + }, + "trackReEnrichProgress": "Повторне збагачення метаданих...", + "@trackReEnrichProgress": { + "description": "Snackbar while re-enriching metadata" + }, + "trackReEnrichSearching": "Пошук метаданих в Інтернеті...", + "@trackReEnrichSearching": { + "description": "Snackbar while searching metadata from internet for local items" + }, + "trackReEnrichSuccess": "Метадані повторно збагачені успішно", + "@trackReEnrichSuccess": { + "description": "Snackbar after successful re-enrichment" + }, + "trackReEnrichFfmpegFailed": "Не вдалося вбудувати метадані FFmpeg", + "@trackReEnrichFfmpegFailed": { + "description": "Snackbar when FFmpeg embed fails for MP3/Opus" + }, + "queueFlacAction": "Черга FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Пошук онлайн-збігів для вибраних треків та додавання завантажень FLAC до черги.\n\nІснуючі файли не будуть змінені або видалені.\n\nАвтоматично додаються до черги лише збіги з високою достовірністю.\n\n{count} вибрано", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Пошук FLAC-збігів... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "Не знайдено надійних онлайн-відповідей для вибраного запиту", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Додано {addedCount} треків до черги, пропущено {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, + "trackSaveFailed": "Не вдалося: {error}", + "@trackSaveFailed": { + "description": "Snackbar when save operation fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "trackConvertFormat": "Конвертувати формат", + "@trackConvertFormat": { + "description": "Menu item - convert audio format" + }, + "trackConvertFormatSubtitle": "Конвертувати в MP3, Opus, ALAC або FLAC", + "@trackConvertFormatSubtitle": { + "description": "Subtitle for convert format menu item" + }, + "trackConvertTitle": "Конвертувати аудіо", + "@trackConvertTitle": { + "description": "Title of convert bottom sheet" + }, + "trackConvertTargetFormat": "Цільовий формат", + "@trackConvertTargetFormat": { + "description": "Label for format selection" + }, + "trackConvertBitrate": "Бітрейт", + "@trackConvertBitrate": { + "description": "Label for bitrate selection" + }, + "trackConvertConfirmTitle": "Підтвердити конверсію", + "@trackConvertConfirmTitle": { + "description": "Confirmation dialog title" + }, + "trackConvertConfirmMessage": "Конвертувати з {sourceFormat} в {targetFormat} із бітрейтом {bitrate}?\n\nОригінальний файл буде видалено після конвертації.", + "@trackConvertConfirmMessage": { + "description": "Confirmation dialog message", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "trackConvertConfirmMessageLossless": "Конвертувати з {sourceFormat} у {targetFormat}? (Lossless — без втрати якості)\n\nОригінальний файл буде видалено після конвертації.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless конвертація — без втрати якості", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, + "trackConvertConverting": "Конвертування аудіо...", + "@trackConvertConverting": { + "description": "Snackbar while converting" + }, + "trackConvertSuccess": "Конвертовано в {format} успішно", + "@trackConvertSuccess": { + "description": "Snackbar after successful conversion", + "placeholders": { + "format": { + "type": "String" + } + } + }, + "trackConvertFailed": "Конвертація не вдалася", + "@trackConvertFailed": { + "description": "Snackbar when conversion fails" + }, + "cueSplitTitle": "Розділений аркуш CUE", + "@cueSplitTitle": { + "description": "Title for CUE split bottom sheet" + }, + "cueSplitSubtitle": "Розділення CUE+FLAC на окремі треки", + "@cueSplitSubtitle": { + "description": "Subtitle for CUE split menu item" + }, + "cueSplitAlbum": "Альбом: {album}", + "@cueSplitAlbum": { + "description": "Album name in CUE split sheet", + "placeholders": { + "album": { + "type": "String" + } + } + }, + "cueSplitArtist": "Артист: {artist}", + "@cueSplitArtist": { + "description": "Artist name in CUE split sheet", + "placeholders": { + "artist": { + "type": "String" + } + } + }, + "cueSplitTrackCount": "{count} треків", + "@cueSplitTrackCount": { + "description": "Number of tracks in CUE sheet", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cueSplitConfirmTitle": "Розділений альбом CUE", + "@cueSplitConfirmTitle": { + "description": "CUE split confirmation dialog title" + }, + "cueSplitConfirmMessage": "Розділити \"{album}\" на {count} окремих FLAC-файлів?\n\nФайли будуть збережені в одному каталозі.", + "@cueSplitConfirmMessage": { + "description": "CUE split confirmation dialog message", + "placeholders": { + "album": { + "type": "String" + }, + "count": { + "type": "int" + } + } + }, + "cueSplitSplitting": "Розділення аркуша CUE... ({current}/{total})", + "@cueSplitSplitting": { + "description": "Snackbar while splitting CUE", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "cueSplitSuccess": "Розділено на {count} треків успішно", + "@cueSplitSuccess": { + "description": "Snackbar after successful CUE split", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "cueSplitFailed": "Розділення CUE не вдалося", + "@cueSplitFailed": { + "description": "Snackbar when CUE split fails" + }, + "cueSplitNoAudioFile": "Аудіофайл для цього аркуша CUE не знайдено", + "@cueSplitNoAudioFile": { + "description": "Error when CUE audio file is missing" + }, + "cueSplitButton": "Розділити на треки", + "@cueSplitButton": { + "description": "Button text to start CUE splitting" + }, + "actionCreate": "Створити", + "@actionCreate": { + "description": "Generic action button - create" + }, + "collectionFoldersTitle": "Мої папки", + "@collectionFoldersTitle": { + "description": "Library section title for custom folders" + }, + "collectionWishlist": "Список бажань", + "@collectionWishlist": { + "description": "Custom folder for saved tracks to download later" + }, + "collectionLoved": "Вподобані", + "@collectionLoved": { + "description": "Custom folder for favorite tracks" + }, + "collectionPlaylists": "Списки відтворення", + "@collectionPlaylists": { + "description": "Custom user playlists folder" + }, + "collectionPlaylist": "Список відтворення", + "@collectionPlaylist": { + "description": "Single playlist label" + }, + "collectionAddToPlaylist": "Додати до списку відтворення", + "@collectionAddToPlaylist": { + "description": "Action to add a track to user playlist" + }, + "collectionCreatePlaylist": "Створити плейлист", + "@collectionCreatePlaylist": { + "description": "Action to create a new playlist" + }, + "collectionNoPlaylistsYet": "Поки що немає списків відтворення", + "@collectionNoPlaylistsYet": { + "description": "Empty state title when user has no playlists" + }, + "collectionNoPlaylistsSubtitle": "Створіть список відтворення, щоб розпочати категоризацію треків", + "@collectionNoPlaylistsSubtitle": { + "description": "Empty state subtitle when user has no playlists" + }, + "collectionPlaylistTracks": "{count, plural, =1{1 трек} other{{count} треків}}", + "@collectionPlaylistTracks": { + "description": "Track count label for custom playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "collectionAddedToPlaylist": "Додано до \"{playlistName}\"", + "@collectionAddedToPlaylist": { + "description": "Snackbar after adding track to playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionAlreadyInPlaylist": "Вже у списку відтворення \"{playlistName}\"", + "@collectionAlreadyInPlaylist": { + "description": "Snackbar when track already exists in playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistCreated": "Список відтворення створено", + "@collectionPlaylistCreated": { + "description": "Snackbar after creating playlist" + }, + "collectionPlaylistNameHint": "Назва списку відтворення", + "@collectionPlaylistNameHint": { + "description": "Hint text for playlist name input" + }, + "collectionPlaylistNameRequired": "Потрібно вказати назву списку відтворення", + "@collectionPlaylistNameRequired": { + "description": "Validation error for empty playlist name" + }, + "collectionRenamePlaylist": "Перейменувати список відтворення", + "@collectionRenamePlaylist": { + "description": "Action to rename playlist" + }, + "collectionDeletePlaylist": "Видалити список відтворення", + "@collectionDeletePlaylist": { + "description": "Action to delete playlist" + }, + "collectionDeletePlaylistMessage": "Видалити \"{playlistName}\" та всі треки в ньому?", + "@collectionDeletePlaylistMessage": { + "description": "Confirmation message for deleting playlist", + "placeholders": { + "playlistName": { + "type": "String" + } + } + }, + "collectionPlaylistDeleted": "Список відтворення видалено", + "@collectionPlaylistDeleted": { + "description": "Snackbar after deleting playlist" + }, + "collectionPlaylistRenamed": "Список відтворення перейменовано", + "@collectionPlaylistRenamed": { + "description": "Snackbar after renaming playlist" + }, + "collectionWishlistEmptyTitle": "Список бажань порожній", + "@collectionWishlistEmptyTitle": { + "description": "Wishlist empty state title" + }, + "collectionWishlistEmptySubtitle": "Натисніть + на треках, щоб зберегти те, що ви хочете завантажити пізніше", + "@collectionWishlistEmptySubtitle": { + "description": "Wishlist empty state subtitle" + }, + "collectionLovedEmptyTitle": "Папка \"Улюблені\" порожня", + "@collectionLovedEmptyTitle": { + "description": "Loved empty state title" + }, + "collectionLovedEmptySubtitle": "Натисніть «Подобається» на треках, щоб зберегти у свої улюблені", + "@collectionLovedEmptySubtitle": { + "description": "Loved empty state subtitle" + }, + "collectionPlaylistEmptyTitle": "Список відтворення порожній", + "@collectionPlaylistEmptyTitle": { + "description": "Playlist empty state title" + }, + "collectionPlaylistEmptySubtitle": "Тривале натискання + на будь-якій доріжці додасть її сюди", + "@collectionPlaylistEmptySubtitle": { + "description": "Playlist empty state subtitle" + }, + "collectionRemoveFromPlaylist": "Видалити зі списку відтворення", + "@collectionRemoveFromPlaylist": { + "description": "Tooltip for removing track from playlist" + }, + "collectionRemoveFromFolder": "Видалити з папки", + "@collectionRemoveFromFolder": { + "description": "Tooltip for removing track from wishlist/loved folder" + }, + "collectionRemoved": "\"{trackName}\" видалено", + "@collectionRemoved": { + "description": "Snackbar after removing a track from a collection", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToLoved": "\"{trackName}\" додано до списку улюблених", + "@collectionAddedToLoved": { + "description": "Snackbar after adding track to loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromLoved": "\"{trackName}\" видалено з уподобань", + "@collectionRemovedFromLoved": { + "description": "Snackbar after removing track from loved folder", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionAddedToWishlist": "\"{trackName}\" додано до списку бажань", + "@collectionAddedToWishlist": { + "description": "Snackbar after adding track to wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "collectionRemovedFromWishlist": "\"{trackName}\" видалено зі списку бажань", + "@collectionRemovedFromWishlist": { + "description": "Snackbar after removing track from wishlist", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "trackOptionAddToLoved": "Додати до улюблених", + "@trackOptionAddToLoved": { + "description": "Bottom sheet action label - add track to loved folder" + }, + "trackOptionRemoveFromLoved": "Видалити з улюблених", + "@trackOptionRemoveFromLoved": { + "description": "Bottom sheet action label - remove track from loved folder" + }, + "trackOptionAddToWishlist": "Додати до списку бажань", + "@trackOptionAddToWishlist": { + "description": "Bottom sheet action label - add track to wishlist" + }, + "trackOptionRemoveFromWishlist": "Видалити зі списку бажань", + "@trackOptionRemoveFromWishlist": { + "description": "Bottom sheet action label - remove track from wishlist" + }, + "collectionPlaylistChangeCover": "Змінити зображення обкладинки", + "@collectionPlaylistChangeCover": { + "description": "Bottom sheet action to pick a custom cover image for a playlist" + }, + "collectionPlaylistRemoveCover": "Видалити зображення обкладинки", + "@collectionPlaylistRemoveCover": { + "description": "Bottom sheet action to remove custom cover image from a playlist" + }, + "selectionShareCount": "Поділитися {count} {count, plural, =1{трек} other{треків}}", + "@selectionShareCount": { + "description": "Share button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionShareNoFiles": "Файлів для спільного доступу не знайдено", + "@selectionShareNoFiles": { + "description": "Snackbar when no selected files exist on disk" + }, + "selectionConvertCount": "Конвертувати {count} {count, plural, =1{трек} other{треків}}", + "@selectionConvertCount": { + "description": "Convert button text with count in selection mode", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectionConvertNoConvertible": "Трансформованих треків не вибрано", + "@selectionConvertNoConvertible": { + "description": "Snackbar when no selected tracks support conversion" + }, + "selectionBatchConvertConfirmTitle": "Пакетне конвертування", + "@selectionBatchConvertConfirmTitle": { + "description": "Confirmation dialog title for batch conversion" + }, + "selectionBatchConvertConfirmMessage": "Конвертувати {count} {count, plural, =1{трек} other{треків}} у {format} з бітрейтом {bitrate}?\n\nОригінальні файли будуть видалені після конвертації.", + "@selectionBatchConvertConfirmMessage": { + "description": "Confirmation dialog message for batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + }, + "bitrate": { + "type": "String" + } + } + }, + "selectionBatchConvertConfirmMessageLossless": "Конвертувати {count} {count, plural, =1{трек} other{треків}} у {format}? (Lossless — без втрати якості)\n\nОригінальні файли будуть видалені після конвертації.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "selectionBatchConvertProgress": "Конвертування {current} з {total}...", + "@selectionBatchConvertProgress": { + "description": "Snackbar during batch conversion progress", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "selectionBatchConvertSuccess": "Конвертовано {success} з {total} треків у {format}", + "@selectionBatchConvertSuccess": { + "description": "Snackbar after batch conversion completes", + "placeholders": { + "success": { + "type": "int" + }, + "total": { + "type": "int" + }, + "format": { + "type": "String" + } + } + }, + "downloadedAlbumDownloadedCount": "{count} завантажено", + "@downloadedAlbumDownloadedCount": { + "description": "Downloaded tracks count badge", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Папки виконавців використовують \"Виконавець альбому\", коли це можливо", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when Album Artist is used for folder naming" + }, + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Папки виконавців використовують лише виконавця доріжки", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when Track Artist is used for folder naming" + }, + "lyricsProvidersTitle": "Постачальники текстів пісень", + "@lyricsProvidersTitle": { + "description": "Title for the lyrics provider priority page" + }, + "lyricsProvidersDescription": "Увімкнення, вимкнення та зміна порядку джерел текстів пісень. Постачальники перевірятимуть зверху вниз, доки не буде знайдено текст пісні.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Постачальники розширених текстів пісень завжди запускаються перед вбудованими постачальниками. Принаймні один постачальник має залишатися ввімкненим.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Увімкнено ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Вимкнено ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "Принаймні один постачальник має залишатися ввімкненим", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Пріоритет постачальника текстів пісень збережено", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "У вас є незбережені зміни, які буде втрачено.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Синхронізована база даних текстів пісень з відкритим кодом", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (добре підходить для азійських пісень)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Найбільша база даних текстів пісень (багатомовна)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Синхронізовані тексти пісень слово за словом (через проксі)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (добре для китайських пісень, через проксі)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Постачальник розширень", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Потрібне оновлення сховища", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC тепер використовує Android Storage Access Framework (SAF) для завантажень. Це виправляє помилки «відмовлено в доступі» на Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Будь ласка, виберіть папку завантажень ще раз, щоб перейти до нової системи зберігання.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Папку завантажень оновлено до режиму SAF", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Пожертвувати кошти", + "@settingsDonate": { + "description": "Settings menu item - donate" + }, + "settingsDonateSubtitle": "Підтримка розробки SpotiFLAC для мобільних пристроїв", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Уподобати всіх", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Додати до списку відтворення", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Видалено {count} треків з уподобань", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Додано {count} треків до списку \"Улюблені\"", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Завантажити все", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Завантажити {count} треків?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Пропустити вже завантажені пісні", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Перейти до альбому", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Інформація про альбом недоступна", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Завантаження аркуша CUE...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Метадані успішно збережено", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Не вдалося вставити текст пісні", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Не вдалося перезаписати у сховище", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Помилка: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "Для цієї кнопки не визначено жодної дії", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "Для цього альбому не знайдено жодних треків", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Виберіть режим зберігання для завантажених файлів.", + "@downloadLocationSubtitle": { + "description": "Subtitle text in Android download location bottom sheet" + }, + "storageModeAppFolder": "Папка додатку (не SAF)", + "@storageModeAppFolder": { + "description": "Storage mode option - use legacy app folder" + }, + "storageModeAppFolderSubtitle": "Використовувати шлях Music/SpotiFLAC за замовчуванням", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "Папка SAF", + "@storageModeSaf": { + "description": "Storage mode option - use Android SAF picker" + }, + "storageModeSafSubtitle": "Вибрати папку через Android Storage Access Framework", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Налаштувати спосіб іменування ваших файлів.", + "@downloadFilenameDescription": { + "description": "Description text in filename format bottom sheet" + }, + "downloadFilenameInsertTag": "Натисніть, щоб вставити тег:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Папки «Альбоми» та «Сингли»", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is enabled" + }, + "downloadSeparateSinglesDisabled": "Всі файли в одній структурі", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is disabled" + }, + "downloadArtistNameFilters": "Фільтри імені виконавця", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Створити папку джерела списку відтворення", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Завантаження списків відтворення використовує Playlist/ плюс вашу звичайну структуру папок.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Завантаження списків відтворення використовують лише звичайну структуру папок.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "За допомогою списку відтворення завантаження вже розміщуються в папці зі списком відтворення.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, + "downloadSongLinkRegion": "Регіон SongLink", + "@downloadSongLinkRegion": { + "description": "Setting title for SongLink country region" + }, + "downloadNetworkCompatibilityMode": "Режим сумісності з мережею", + "@downloadNetworkCompatibilityMode": { + "description": "Setting title for network compatibility toggle" + }, + "downloadNetworkCompatibilityModeEnabled": "Увімкнено: спробувати HTTP + прийняти недійсні сертифікати TLS (небезпечно)", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is enabled" + }, + "downloadNetworkCompatibilityModeDisabled": "Вимкнено: сувора перевірка сертифіката HTTPS (рекомендовано)", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is disabled" + }, + "downloadSelectServiceToEnable": "Виберіть вбудовану службу, яку потрібно ввімкнути", + "@downloadSelectServiceToEnable": { + "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + }, + "downloadSelectTidalQobuz": "Виберіть Tidal або Qobuz вище, щоб налаштувати якість", + "@downloadSelectTidalQobuz": { + "description": "Info hint when non-Tidal/Qobuz service is selected" + }, + "downloadEmbedLyricsDisabled": "Вимкнено, якщо вимкнено функцію «Вбудувати метадані»", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + }, + "downloadNeteaseIncludeTranslation": "Netease: Включити переклад", + "@downloadNeteaseIncludeTranslation": { + "description": "Toggle title for including Netease translated lyrics" + }, + "downloadNeteaseIncludeTranslationEnabled": "Додати перекладені тексти пісень, коли вони доступні", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is enabled" + }, + "downloadNeteaseIncludeTranslationDisabled": "Використовувати лише оригінальні тексти пісень", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is disabled" + }, + "downloadNeteaseIncludeRomanization": "Netease: Включити романізацію", + "@downloadNeteaseIncludeRomanization": { + "description": "Toggle title for including Netease romanized lyrics" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Додати романізовані тексти пісень, коли це можливо", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is enabled" + }, + "downloadNeteaseIncludeRomanizationDisabled": "Вимкнути", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is disabled" + }, + "downloadAppleQqMultiPerson": "Apple/QQ Багатокористувацький переклад слово за словом", + "@downloadAppleQqMultiPerson": { + "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + }, + "downloadAppleQqMultiPersonEnabled": "Увімкнути теги динаміка v1/v2 та [bg:]", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person word-by-word is enabled" + }, + "downloadAppleQqMultiPersonDisabled": "Спрощене послівне форматування", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person word-by-word is disabled" + }, + "downloadMusixmatchLanguage": "Мова Musixmatch", + "@downloadMusixmatchLanguage": { + "description": "Setting title for Musixmatch language preference" + }, + "downloadMusixmatchLanguageAuto": "Авто (оригінал)", + "@downloadMusixmatchLanguageAuto": { + "description": "Option label when Musixmatch uses original language" + }, + "downloadFilterContributing": "Фільтрувати виконавців-учасників у розділі «Виконавець альбому»", + "@downloadFilterContributing": { + "description": "Toggle title for filtering contributing artists in Album Artist metadata" + }, + "downloadFilterContributingEnabled": "Метадані виконавця альбому використовують лише основного виконавця", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is enabled" + }, + "downloadFilterContributingDisabled": "Зберегти повне значення метаданих виконавця альбому", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is disabled" + }, + "downloadProvidersNoneEnabled": "Не ввімкнено", + "@downloadProvidersNoneEnabled": { + "description": "Subtitle for lyrics providers setting when no providers are enabled" + }, + "downloadMusixmatchLanguageCode": "Код мови", + "@downloadMusixmatchLanguageCode": { + "description": "Label for the Musixmatch language code text field" + }, + "downloadMusixmatchLanguageHint": "авто / en / es / ja", + "@downloadMusixmatchLanguageHint": { + "description": "Hint text for the Musixmatch language code field" + }, + "downloadMusixmatchLanguageDesc": "Встановити потрібний код мови (наприклад: en, es, ja). Залиште поле порожнім для автоматичного вибору.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in the Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Авто", + "@downloadMusixmatchAuto": { + "description": "Button to reset Musixmatch language to automatic" + }, + "downloadNetworkAnySubtitle": "Wi-Fi + мобільний інтернет", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for 'Any' network mode option" + }, + "downloadNetworkWifiOnlySubtitle": "Призупинити завантаження через мобільний інтернет", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for 'WiFi only' network mode option" + }, + "downloadSongLinkRegionDesc": "Використовувати як userCountry для пошуку SongLink API.", + "@downloadSongLinkRegionDesc": { + "description": "Description in the SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Непідтримуваний аудіоформат", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Оновити", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Завантажити {trackCount} {trackCount, plural, =1{трек} other{треків}} з {playlistCount} {playlistCount, plural, =1{плейлист} other{плейлистів}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Завантажити {count} {count, plural, =1{плейлист} other{плейлистів}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Вибрати списки відтворення для завантаження", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Вибрані списки відтворення не містять треків", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 плейлист} other{{count} плейлистів}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Автоматичне заповнення з онлайн-ресурсів", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Виберіть поля для автоматичного заповнення з онлайн-метаданих", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Отримання та заповнення", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Пошук в Інтернеті...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "Відповідних метаданих в Інтернеті не знайдено", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Заповнено {count} {count, plural, =1{поле} other{полей}} з онлайн-метаданих", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Виберіть принаймні одне поле для автоматичного заповнення", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Назва", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Виконавець", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Альбом", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Виконавець альбому", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Дата", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Номер треку", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Номер диска", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Жанр", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Лейбл", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Авторське право", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Обкладинка", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "Усі", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Порожні (без мета даних)", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Завантаження ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Завантажено", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Фільтрування...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 трек} other{{count} треків}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 альбом} other{{count} альбомів}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "Немає завантажень альбомів", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Завантажте кілька треків з альбому, щоб переглянути їх тут", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "Без окремих завантажень", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Завантаження окремих треків з’являться тут", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "Немає історії завантажень", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Завантажені треки з’являться тут", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "Вибрано всі списки відтворення", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Торкніться списків відтворення, щоб вибрати", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Вибрати списки відтворення для видалення", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Аналіз якості звуку", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Перевірити якість без втрат за допомогою спектрального аналізу", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Аналіз аудіо...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Частота дискретизації", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Глибина бітів", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Канали", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Тривалість", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Частота Найквіста", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Розмір", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Динамічний діапазон", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Пік", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Семпли", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Пошук за допомогою{providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Постачальник оновлень домашньої стрічки", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Виберіть, яке розширення відображатиме домашню стрічку на головному екрані", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Авто", + "@extensionsHomeFeedAuto": { + "description": "Extensions page - home feed provider option: auto" + }, + "extensionsHomeFeedAutoSubtitle": "Автоматично вибирати найкращий доступний", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Використовувати {extensionName} головну стрічку", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "Без розширень із домашньою стрічкою", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "А-Я", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Я-А", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Скасувати завантаження?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "Це скасує активне завантаження треку \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Зберегти", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Не вдалося зберегти метадані через FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Не вдалося записати метадані назад у сховище", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Не вдалося відкрити засіб вибору папок: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Не вдалося завантажити альбом", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Не вдалося завантажити список відтворення", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Не вдалося завантажити артиста", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Прогрес завантаження", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Показує прогрес завантаження треків", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Сканування бібліотеки", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Показує перебіг сканування локальної бібліотеки", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Завантаження {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Фіналізація {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Вбудовування метаданих...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Вже в бібліотеці ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Вже в бібліотеці", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Завантаження завершено ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Завантаження завершено", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Завантаження завершено ({completed} завершено, {failed} не вдалося)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "Усі завантаження завершено", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} треки успішно завантажено", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Сканування локальної бібліотеки", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} файлів • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} файлів скановано • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Сканування бібліотеки завершено", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} треків індексовано", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} виключені", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} помилок", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Не вдалося сканувати бібліотеку", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Сканування бібліотеки скасовано", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Сканування зупинено до завершення.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Завантаження SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} МБ • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Оновлення готове", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} завантажений. Натисніть щоб установити.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Не вдалося оновити", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Не вдалося завантажити оновлення. Спробуйте пізніше.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" + } +} \ No newline at end of file diff --git a/lib/l10n/supported_locales.dart b/lib/l10n/supported_locales.dart index 30ae57cb..27d223d5 100644 --- a/lib/l10n/supported_locales.dart +++ b/lib/l10n/supported_locales.dart @@ -20,6 +20,7 @@ const List filteredSupportedLocales = [ Locale('pt', 'PT'), Locale('ja'), Locale('tr'), + Locale('uk'), ]; /// Set of locale codes for quick lookup. @@ -31,4 +32,5 @@ const Set filteredLocaleCodes = { 'pt_PT', 'ja', 'tr', + 'uk', }; diff --git a/lib/screens/settings/appearance_settings_page.dart b/lib/screens/settings/appearance_settings_page.dart index 75d93e6e..77ee8f87 100644 --- a/lib/screens/settings/appearance_settings_page.dart +++ b/lib/screens/settings/appearance_settings_page.dart @@ -735,6 +735,7 @@ class _LanguageSelector extends StatelessWidget { ('pt_PT', 'Português (Brasil)', Icons.language), ('ru', 'Русский', Icons.language), ('tr', 'Türkçe', Icons.language), + ('uk', 'Українська', Icons.language), ('zh', '简体中文', Icons.language), ('zh_CN', '简体中文 (中国)', Icons.language), ('zh_TW', '繁體中文', Icons.language), From 6c3d92cee4eb75f003ebe1e848c54e681e4ce461 Mon Sep 17 00:00:00 2001 From: zarzet Date: Sat, 18 Apr 2026 23:39:38 +0700 Subject: [PATCH 04/12] fix: map missing Crowdin locale variants --- crowdin.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crowdin.yml b/crowdin.yml index d19e6faf..91aeac6c 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -6,6 +6,7 @@ files: # Short codes for single-variant languages de: de es: es + es-ES: es_ES fr: fr hi: hi id: id @@ -13,7 +14,11 @@ files: ko: ko nl: nl pt: pt + pt-PT: pt_PT ru: ru + tr: tr + uk: uk + zh: zh # Full codes for Chinese variants zh-CN: zh_CN zh-TW: zh_TW From ad8ac3bd2ba45828f902a94096a7b5466209ecb1 Mon Sep 17 00:00:00 2001 From: Amonoman Date: Mon, 27 Apr 2026 17:12:43 +0200 Subject: [PATCH 05/12] Update every icon except banner --- .../drawable-hdpi/ic_launcher_foreground.png | Bin 2891 -> 3582 bytes .../drawable-mdpi/ic_launcher_foreground.png | Bin 1884 -> 2464 bytes .../drawable-xhdpi/ic_launcher_foreground.png | Bin 3716 -> 4566 bytes .../ic_launcher_foreground.png | Bin 5828 -> 6802 bytes .../ic_launcher_foreground.png | Bin 8262 -> 9343 bytes .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 954 -> 1590 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 647 -> 1106 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 1371 -> 2077 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 2015 -> 2879 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 2809 -> 3794 bytes .../Icon-App-1024x1024@1x.png | Bin 43156 -> 18082 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 429 -> 603 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 905 -> 966 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 1324 -> 1331 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 624 -> 783 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 1340 -> 1265 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 2073 -> 1928 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 905 -> 966 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 1896 -> 1704 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 2976 -> 2449 bytes .../AppIcon.appiconset/Icon-App-50x50@1x.png | Bin 1122 -> 1116 bytes .../AppIcon.appiconset/Icon-App-50x50@2x.png | Bin 2421 -> 2052 bytes .../AppIcon.appiconset/Icon-App-57x57@1x.png | Bin 1296 -> 1279 bytes .../AppIcon.appiconset/Icon-App-57x57@2x.png | Bin 2865 -> 2338 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 2976 -> 2449 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 4704 -> 3556 bytes .../AppIcon.appiconset/Icon-App-72x72@1x.png | Bin 1692 -> 1590 bytes .../AppIcon.appiconset/Icon-App-72x72@2x.png | Bin 3674 -> 2879 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 1821 -> 1616 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 3943 -> 3010 bytes .../Icon-App-83.5x83.5@2x.png | Bin 4384 -> 3352 bytes site/icon.png | Bin 72121 -> 73162 bytes 32 files changed, 0 insertions(+), 0 deletions(-) diff --git a/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png index fd5e939d530b8dcd42f25c2ef6ccbf857867be77..872d3c357a28371cdf4b1ae608a29151933bc586 100644 GIT binary patch literal 3582 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 }A?=qN~&gwE>1c}8z21PJO0mMc1 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 gPQw0Q)J<+QQTS0uqOC4*&oF literal 1884 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 literal 3716 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 E0IH<^ssI20 diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png index 590c5cdae7a157a5984c520f929f865fbc39265d..0c3289e2bf49aefcc15a6e1f265fd337b5a7b15d 100644 GIT binary patch literal 6802 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 NC3$sFiLAxP{{pFs=CA+& literal 8262 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 diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index ee6d88cc2c7eaaf50445c3b176cb29eeb98b86e4..ab7a9c81bed7b78aaf3b6dd598219026321f702a 100644 GIT binary patch literal 1590 zcmZXUc{mh!7{^DZOg7DoTp@-IMjMTDV{?qbj0{#q8g_^r;}}di+Nsd2t7uS;$Pp<= zuH0vOXx3RKYK&vDEM^*wFm7hY?(?+!?Eaqb@B97yp7(wKe1GxwwpJ42^5OshKmvnC zIS3QIdqMkzesTV#l`!nDUcz``u>cJrg8)Dg001Z?VF*p+ACC3?#>OI&-*~r8*q9=m z+|9pULaV|$4+sNDLSim}goh0BjN7e~9ITLl@&QFQ0I-*WL780$&z{d3^;5j47}>5< z-+H`x#3g|KNEcI#2r#d$g-J;h*VHw_BcQpRY674Dh$l;%Q5{ZNbc#WV;IzxdgUpPf zlw~h#c3}1RIQMF(_sm+QuTKComD9%+U+O@@ya%>svPc8jq#1)fRMeRCi*b=_hRLwK z|JLK<&#j2thM*e+v7g~q-#qt(FN zePDs>&E|92Xu}5!I4WR?4edMP%TX);^cdId9UKrQ`;`{`Dqz+mZ!d>Pxk&yVr{*Ik{suB$`-bcL#F|!^tA6v(k9ZFnA?7;;TwF z`Qf5f`%$MOc+U7eQ(lyQ+KKCK8Pk1`uQ$!xh80)_s1UEXp$_+&sv06c(6Tad2m*4A zgyd~9`agV@dIT!koIvWLPsRb^e+%Sj|K7YwvJhpMLmalI`3Vs&4O~T)Xq8Ve6sP&rO_1T2V~T@LaS@5!Qt1H zw+P11?}F;Q10WJ~air|AUj4vsOMRDYq^t7;d?>kY)Ue1u7BV?cX>&iJ_0jpPc5LHW z?DjOiXVDLzL+k$NqnS=kA4YDU*gEC3e*pY3h`~IM)pRUAwR1SgK1@yijua~TzoM9c zIfIzi8ifL_4(7*mJ-V_tZ>N=e(}Eod7mSU>492K?0-#MWkEZ*5q^UW!+#9!q*G%7* zrH9yFLMr^m1mj%D4M4r8ht_oPtrjIhOMVza3*Y5j90fU6w1H_iOqA2*sjKb7(krm3 z85}W{AV)&@I)O3yM3nby1hSEyH|P(^;#%;M+z%E}_lu4!qkkN;&MxVnjpr#p*<85e zlV+>q2; z(i>UX(ZxYZo-2#cx!09Aud=iSygX*g)O=0ghmR~k5JxJ$*l!V%Yr(U5O7w#xCO#~W zHqBzPq1_)t+H~LjW@YV&L^hgM5JJf88CKfJf%+W1c^PDo^u)Ri&|^wULo zX)#<3fpjLpYK9td{Yo?=(=^Kl{&tnPaPxq6PIzQnz4Gx0AEOn%_FPv?yd}4y&kOj69vEqj#F7*>D zQg9lPzzDYSBE%rDs-a>j;9*jCs-@(O=o!a2nNo38vt5y8ujwr9Q9-WHATvH6vdPs! z2mZb(-C&{M5`P-_p|0F*^!dIIb($0vYI!VfQP5w?vqN|nN1m@nh@~9!j;X}wyxQ?S z+{j$enlu$1^n)7dB161Dlvk(pf@HP@Z~6pXCGg)`HbLWdNva1VDwzJQ_zEL5`)x4= z64wxwoLF!iTR8R8&W>c)>g}ka_Vg*ikI&(7WxS~!4b4?RoqKet+sL4E>vgHo(16P` zGPxt{4CGU2tXy53iJs+^w*X7LWIufyXJfq}uQ_1-kU65r=V_`|=#xvfLZew=ZQv6Y zrEszoR&r3A1yo}!jMlran5aBb*Z8){|A#e&1W`t>IN#Q0_v6M`*rLjjZqa`N7+%>; delta 943 zcmV;g15o_747vx98Gix*007#LBoF`q1A$3IK~#90?U~DOOi>uXzwe$q(;245qYHyn zN2R6cqF4}*2$fNqfgRSFfDk;x6XQcfRkJoAbEmnDC!s{_6r%p?}5vh8FW1TFh@~F~6b3 z{Dv0$doc4PkFt3sh7i2817-zMCm%tQdyvp8u>uCgQ9y#iASAzekWh~?zoAeJV-YNg zil$!2R%3mP2ENjVF-6%GikT;?a8;3Y_L*oO%xc1Aa|35Kh!5lGQ;$_;SJbaffr)?+ z3OF=Hf|-euseenZ$C%%cDQ2uHV!KMMYi%^>Zl%I`A@j%@{`8}G)i0|UsbV|h#-=64 z>mlBda21Ga7aCiOVNSse6$8_-H-qRt(8$CXQfraf*-S!Zd5){S=F^ocr$+y;u~zSNng)H5ov7^!b8$@8%KL4V$XxzJ-Q4~Tv}v#>s!OfMm=DonenX4-4K3yp zj7J0KH}eMxe&@C0AK2b9R$G?7&`90GDhg0077amRkR7Z56Zx&K?5wrr#^KfW-{yxD zZL3s?iGS7sezna!T50ZI2?54$|9o!o{YG%@pd6)ujX&%OBht@RtcA%O_S(SJ&N?A3KRTOkBaZv;!EFE-1mM@ldD zq2R_vSQx|wVH)`QXRB48Fh=YX8}Z_kbNuXpu`XtxNg4=3!c6Ji0xh0@^emU&d3c7r z)mom|M4EDsu_?j1nJq}tk}|+#iix%_EEZuk%S5Nc>CKd|JIT_lgXo1#DkAtBI2!eplM6ElrGi$N803^F$S^jA$x;Tbp zG`^kboi80Ka=iX~pKX~@nT&^*kU4+2f>-9M5X}`+cQvtccd=+MNIKkA(_6=UO(%kl zLwZHUl)T6dj4Tr;2LyLzga#exU3tHM=FaWh?Nbtx4f&JZ&(@n>`?&M@pXYO`&+RN{ zSzvi!LUWmd1|KJn6#G1fCH48*(L%3#g&9J^>l;?R>YlmJ)nS>t$GrKjeO=ahjNQI9&3I3^@ zdwfzl*QhmBwDE7@=Hj{%+&vci&gcot1v~ll%1*em#%!?r=YH zI^dU+J!k#D4EySyrKdszLYQWLPu#|CvZFp!I&}L*-sQn}3Ia-{w`a9ib@1kkT?*JW z$5~GD=vlD|pRd_%oUOZI^DNh=+0I@J1^aa$1ijE(qxnWZuRW8m;>PJ01`j42u#;dq zBe%KtWpUoC#t&wNAroROU7vKgemv}3@O`>eWl_(S)#`YZLbzBA7wBJfz8W#3jC=iI;F%S77x9GnAx{js{J$(@}k6Lysf8 zFW=Z7s!>}eEA=pTBgdf}ZKgBR#b)f-(XnOG+pC$;0imIm7>$s<9Z-aWc|GbrBB1kpEFMpV&3s2^5x8W&SUqjdi`7rivQ106TM+C zE-+d7$g`jaSzht?mh9Ta+Uxf|SwCH3cLme#Am{w+w!$B_vY%0xKN>a9?Y7?8)B8*Q zEnK6qKKGq!Mrtnisk@)+o?JUk{vWrG>|N(yws3p&lzad3zPoSO zTD7n~y!*rcn%|O>0uR;O2gkE)S?iF!B$K6@b<#Jfs&ZfX4^y4a&E;D4E1r{K3DY`z+*j27 z)jn5suV35Fyzt`EA8}&a@2LE{qS7XMS6cd?@s?wnS2AOEdq15lzwpJcB1hF58j6lf zk~`cV-8;SD(hGa(7jM;ABzh0cZDngv)Li_p&?Di3g^EFjOa;?-#uf$rZ;2O9G=nm? Mr>mdKI;Vst0BX70VE_OC delta 633 zcmV-<0*3w42!{ob8Gix*007uvZqNV#0!~RpK~#90?bgq06G0RQ@HaENHC834wFN~I zB3eX{c&nsHK!Wt-!M{QD=)sF3hDHnat{2ZLD7|Uy{!hVmBx66n5@pcv1Vp04CY)$|&O8_!U0Dm$|hk|G-N>nDEd~E+! zE`bIPkZoHD11zHW9QE|*sEO|ey5|6b+d2{~QD`HHt7po9=&wb^>7 zbC5(t%e!E`(|<7tOEqynhaZ+r+UCmIgB*VO?Kub}im@5%az<(tlnN-t*>h%Py`~ijPnokve za}w5sA0Pl&6Z}S2O(*El9(^p$Eb~kj>Mj41TnDMQlH%d?`bey6d^oEw5`exotGv-k zkM*$gaenE!w;&P;lfLc^rMkwiP8hWI?I6)G5Tj3?iy*IKAH)T0SFJRXkOG>t`|-C8 zlDQH)9dZ><3(d#HAR&dNqRk`9E0DOCqoc{L+bKEqU0(phFhY;_!p<)3+6&J?xcG}- z9%twJ$)VlA{__uyDBmxMvAlx`>Euvjv8vs9>)=5v>4012aRQK80+3k(kXil$-^t`Q Tr=W8400000NkvXXu0mjfgl{v_ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 382e5cbba7c0648eaa36480951cb4a06ab79c1ae..17c3bf6f5a35427961f52f8612b2327bd4169347 100644 GIT binary patch literal 2077 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 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 6f0aa29e9ea3eb4435fc5f5a7d73fadbe4ded5e3..e08875948483fe69cb185729672172981cc2cdd4 100644 GIT binary patch literal 3794 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 T&wG3QUMi92tb5^p8o*$ literal 2809 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 JS?83{1OOm#?{feE delta 414 zcmV;P0b%~z1g!&*8Gix*0008(idp~w0dz@3K~#90jg-$y12GWBzsatMUFtz)E3M$k z;z4Lnif^G$;K?`g?7OftWnnQvzj z!+Qu|fYb}2cZ>=BmX2@iSdj*D_j83w+QB-F9n!fD22YNi0p&mN_M5mpw`=N}Y=*WFb98N!qHY!PvlZFfQ{6 zr>&COs1^&Um4Ewp+5k0I*nWcu`SmCa`@Lj+y zL{4*zK7M!rYfDsJbYL=qNxwZs_ccCliAJ^9VcVmvYw5BfuZN$j$9xWkQ(8c=IhI#1!QwDurM+(umD*=3P>;_*zK8cwjHAo zoDEV2R5%H!6GVe~Kw_hBSbOsFffaFx;TbJ zw7#8wK3m#ddZp80v*ej9JDqcqIj#g0c~$2rHa4tR{PHOJf7?Iqzv5LI zrj}19Jel@mLdygPzGZs6YMHJX*H>&@EPCkDx-HhWJFbL=&XHXoEj^jN`sPgY`~3O$ zs*RN!o^i~!%4zt1ll{P!X_oaHrs^o}uV8Gr_cmNW)+b9of@SHFg7w!5*I%1>>0qwU zuBj|n)^uMfTEO!2vomMV>4j;r6P#J5M9n*OiRs;=J11uBws23)WL)|5vd5Csj+!Ya zLSz<|85pJYK0g^Evt)wj)r_-@tVP$CU)Y%Tp~!fm_qQn7K&OJ|;*XOL${1+xm|ySf zD6)C$zOVm&DowFl+q{(HowCfcoPh8w*S}5Q+$3RG` zL}KAomG3`i&tz_s`v1-1bN@!K+7S-LUhbGX?zW#%Psns>;vEIQ)>k*g>Rzb+IqlmgsU>StHginVeUg*+V*0s2R z=7bys`XNJi{lfTs{77=J z@26dtu=!al@otXu)7AbPvh*H)PD^rFth>#>f1>TGXrts}pcKK&nl*`Q*JdrAubecLw`rdhJbI#fL_nu$$X)EmJid(5rQEm~&Z zM@_vsi@qFn-SyR|YJ!iC5m)-Yt`yOS(*O4}zgcH;Vy^u0nX4b2@sZ+u>9Kk5ttGoJ z8GJIXOFQW){rhonggSS}R2>uN5)on3?JxJ=a4c$`0ANIFDu6lHM1Ajy2+Y`+H Z7%MJ*|L}I3tS~4)dAjCm268r%*#vcdUqP9|-h-s^p zraL<$JKITilik^wG#^XY&AfTv%zHE6ydj_o1Rwwmz!skzz<(1jwrB#;gaA}DX@I1g zIFiW~FJ82j#rZ>N+#<0p+NPhWQ}2u5(lj7?PCq#O776Sqr^t6Jz0OC%X-0a8}l6^&X~>B zzOWx1i{78o?SD{>a7$uawe(|wHiCs_eW)O$VPgILx}}4h-%B4BYk%064th^#q5Lns zuP5Rv3C)%A?}jk(&;Wa1;yT2^{&xJ7Ye;6D0l`xWajHQ8VM6?ssVD21v9s>& zd{Qg+_c+%F*N4Zod|5#JIj0Hw2096Jki=2u2y{7Get*OgBaO$40*tF-!?^!wf&&3d zcO$viUhw%pxNmE`tLch+^|>yw9pqSZl;2mB&A`7dYx{!aoF#QBt+JPM;eXh~9RIm$ z=EcvlpRDCQ4~Mn0csqu}nwvWaPMCdmzH8etIj<-j&R} z_*o7e&MIunf$PDFU!$->iQdeUkrRMGNAqlIal@uNgE^9ZK9ZV%XiS=1Jise!-lI>! zCo7cP8QPYe+zV5?08nLENoGw=04e|g00;mC0RR950000100000 z0RaF60000100000hiL!=000010000!0TKWN00001000000000000EK+00aO400961 z00000006N~S1v3W_*pYW5*$CRI8IU_qZYUBv76>*KOa1{rfM38076n8r zP(T88!3s(il`1NlC@BdoP1H7Z+SrNh*mF6D#guZZlPELeR*v7*jONpK-gnOVF6Yb$ zpxH$*o;HDQqkk{B*U=Z;>#({%qF}u?0)TsY8Qks`iLMJaUnx+0cLIP)^9tUT2VEV% z>w=BJBxW}8_}c6C_+1|FsUzAM7D&9YpZMM^jY|ut-x^OeGsXrOI90}8TBALAE7IOC z(ixV5MfSu2awp3qj*fP;OB@{~cd|_O!~vub?HokAq<<|zVkVe^#LbRo68CM4mY=kUcy@muU3bNj(sG-`eg$?eR&nqQ;(?F}#lZ?+aD#gJ#Xsc3z< z@b`Ughkja`*2UjLq4Zzux)_dBux=?--A1)symh;yowxqTrf#EKDoRQbZx3r8)eAG2 zdotLUmpYo+KQ8h>Yuvkas6~4u5c!FRJ5|A@vVWSokbfPM`3KKg=t9eu%} zLcpBJV;vuX)Fwlv46NfLm=k%3_DCz*vLvM>h4&_~hSOB{&9m{@jsF{&Lh7wC2G1PC ztu$HrXbOG16&a^!>#(cPN@L}d44y9IJ>2kjLH|Jfbcw;!WvqM>XSo4xw4GM8t>qn? z>VM=M&bd^+;_v zRKJ_2@#zfM(NhU$Nhbs;TAeejt!Z4GrGGxPgmyjOPC&aJ^{FKq7iK$)75}`OL^$a? z_>ztZ&%OUL**6d2*f#3s`jhQi8hddStCS-)k^v3PYs=I=xdzKlXZNpVE|b zy~)ej4uY2@Ll8qAUvCBVYmJAuru@uN-^O%_?^6ufmjA8L{eL_F)}yVpo1x!JKXVAV zw+=4YQD{$r;D68#8Ai(QS9!oFXiu9>6>BGbRk7q$Xhy!>U zZ>>#pxzfdmVkj hy^g-%UdOHz{3q?phVkZ+i2DEl002ovPDHLkV1i*;Wbyz2 delta 1316 zcmV+<1>5?w3ako{8Gix*006a~P9*>U1oKHmK~#90)mqJq97Pm=Rb4$ZJCiku$Od*% z2ti09A*>`B799L1MAVabQSg!&Jg7+A4PHFu;=!9YLGUUF@ep=5h-^?WXjF`%Ye;mX ztGn~r-Br@Br?NdW)jd^RwY%ewX1A%Sk6*v{>b+MV4D7)Gh=2Sm064yM@uxDD5C9J# z!k;)m=wLvLre&lOAdf&*r!!JQ0jBUJ0_YVV*&J0ASP!6S-j)Nj@r3|X@Eh8w4HFoW zuA)#|W{uPkQV%6$VbZ5IX&>vFOJzKI_*A6!G@wViiYD8(WS5H$a;a*Gd>rXy-n4c> zz@rH9so@7id4CmiNi(#H0<+BqO*9oR&s4HWd-&|CNo1iT{b^VIdj@HSj5FHo1(E>d ztKGkfG{xHcBI7hd=~71QTSdC!z!Q_Bhkz0BX_-TL)eGF^(U{!(PHPCvFI=}K|i zU@x2mkALWs_MSUIW~v2AC(*huX&c+{h?%_-!}>$$PedO4e;MSz~0RouI73&`@(N%aN zvAzXU?xaJUN;-J=y1IOE#mMf*4Fk+o0u7LxZ+|(|XXMsA_6jVH;S-qn=OFgecAkD8 zPy*(1itG(Ul6(BW5W74_=|4Go&|hqXAAHSM*N1+h^;!1nlfmUx+ij5kNbX2OD@?XK zLQ44ZZx4#;!W_GJ4mx2{!2x*U@+jC(M{%zizO%xX8tnX;Q1tz%SD*DRo`WC?msZ8^ zTYnB1_iZ{HSe@H|-`)%^E&G?AVi(Q=7eI;kdL24pxU|X*^Qugg;+3J}gh~9g87!{& zOAS(s_g6$KaLHKifg$PCmV#LiZ!U;C+u?`b4Dk-%US^ApxZ}$T#@@3h=;@i@t!qU{ zqZuRVV0g1k6Xc0U;Gwdj=i8gT3s;E%aerT7cJ%neal-;)lEGV=hoim19c;ASCpD6! zB;D@Y_~asWG9gI|d^IKAjmUIKquwB@J_M1mZTLF|obnzmW6S0@|D=sa>HHD?)jiE7 zL{E})0#<6E>o$KF_oKKY)1w-Nj198f}!9rm6%9)Ek4 zkG?fvOwqZe!rvh2p4s-PSl+oT``VNhXFwytrKvuf$pXJ| zxA*dAbYYIn9vI_-M7;8iuCK$tCR0ZhtyK}x)VNy#|GemiVm`_kLq?2FMLN0v!W>+I zv4W4Ltm2T7d-U)tVgGphIR7%VoqwG$b+Kjk0rf7TED2;FH_Hpq13Ed5(Z>|(x%kzI zWWT+sbqB)%wC|Sa<^;^#R$=Vx)<^Lf?J&WJWVn&ca2_Tm0Q zrVXjT>mmaz-#)aR>+VwZYL2v5GJ0}+QrV1YANeYpdxe}%IaP7IqH&;Tr z_FT1GvouI6W08ofVRM0x$#tjmw_^ir^B3h6THCzH$FVaXB#Rba+q-bl zucG!B3#O_0?0Ro<%Wm1$xR;-{Uek8DeZ09jZpu=v^?RNw6wTC|UslgkdPCusrLUYi z@0D5l6XItcl_+O4I2QX!dvnC+R+Gd_j(^V@)jyorzuTa33-6s)&O^%b^_y!p23S5; z^fF_(nAiL2bM7KH?%W$HSzB(M-~INTsH(K(<^Sc&8P6x(=kqM`f2Zi4J1aV#F(CdG z%f9{-FJ#ufy>!dshi?47-p?C6{=T&{dAvOMz1t<$S;JJ!9d*u8Wu13*KKuA=;8D}tYP7@^}%}1 z(#+paQ*GxRs4fvs%-TDZDMTQs_ABH6qxb$hUUb+!+gV!P?ZwBl>!Ji7{^NZ3+qpyE k!|4UX8!p*x>y^H!E4u88es<>mEl{%bboFyt=akR{0B?av;s5{u delta 610 zcmV-o0-gPj2Ji%s8Gix*007zX@K^u<0yjxSK~#90m6p#-RY4TT&-}OvnJ!8aN~1-Q zJffgQiJ%Czb=e|<{sCzv)yn^%bQkm&v~N*Sx=3-M!o$@=lpe2iBba7Eyxz>66Zg&? z?|pB6+}q)CnRjN+XTE1h(EiHd+4S(~{EiOq#*+i#F!-&%f zyjprQOXpPo2?axB05lIbT#`P6P*NMh#$gGXWD8=r71or`-Vw8(N>oPx z;*`rvpmcInt?p&*sZq7E=N&3Cn&+H#8+$mW*7mdZqgse-jE7f%&0LG$f9>@WU^Ca_ zcf0PG^my=W$A3IVr?14z->lgveQ?#w&GcfR7jWkYg!Mit#O)uUFeuY2-)uR&3gzp1 zD2!Cww`7S`mRC4^GcoW$P0ELTZ079`&EVKmZuKft-DtIS08T+FM?1s~o(Bzz7VgWJ z>&bqM3`EbzJ9En*f@mx|4~a=77dH5_dt|ULdND2*H-Af@T1-GMxqOZc_pAB=woc@$ zjcD$!Slqb}VPNW{M=~}J$&;JxaSf0EcI7d6hTa+$ZyP?bg}Sb*eUAd6#QX}K z7$M!N@mr`Z=D$GO(pw-HE~Y;l{YR>$mOI}bqlVMP)t4J}p{rQvKGtZG-%(Yq{QE&C wBK+SwHqkT8Qy=tDX8w{i6}?cT;Pg-Y4?f)0$GC&R(*OVf07*qoM6N<$g0)OD761SM diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index 85b3cd51195750bc2a899067a92136ef90f7d21d..179ed04ca492732af3f5d4d8590e4e5791293544 100644 GIT binary patch delta 1256 zcmV0Mx!%gKe*EL$_6^JlRL)jR^qicS?9y@3w91 zwh9%NhA?E~*=%nxMY~1~fq23Id5DdqVPl9$sYEI?F*iy6!YszQk)}4rxe@XgW=YIV zMoA@Fq2@%6_-Kasc($ocd^}5hG=n*ji;_yjp(`36aeqiu3US{qN`xv@j9JJR@{kDN z)`}rt%p=CE@Q4wn&={oWPLcoM4EpS`mfh$t9V7q#8Pap7LY;>2TuFh}pFqo)$iCKR zLuO61jEUBt02y99hrdPxbBkuNJ~p*(SLj1S0lvEb-75BfyYDL`gjaBSips*Dcohd{ zb+ctR&VQ{|HnrJ*XD=0^q!O)A_wok!E^i!a z<1d!Ex40A~4WOGxI}cr2LoIin_Zho&62BJ$Zx*+}b zNvvo3*_q6-eeuWt+GHfDccw_q3{zYAi>(`1U^kj}ZFE9Cg}YTl=#tdTFkaE7e!F<^ zS^&wnCP~c z7k?;!ya-!PBvhdB$41)Pm(yAL-aCysmccFUA`?0QUe!U%#Ie^(Y+byDw@^I7Q!qrn z>v{`CO7kl?8x^#yiHJ)?T%u)7oQ(>l`IX2;LKvZ&mS|@Oz)+|rq38dygDZW^@hq{u z=8{*kDSm#F`d7^h{@Bw=b{Tkg*#{IpK| zL4o1Q)9I*4Yv5!GDK^ z_-89o1yA*Xh5A(oT&`1TJRgf;OS-CWBZe3lHQ_Y3K^=G<1b_8oEL|4Sxe#3ts&^ Sdx(z!0000({( ziVKQfG{%5YbP0+V(UT$wIf+Qri{wv8#6yfCB8cG0fHwtEL=-(_V}iR8F>{EZYrtgO zHQC+FSASJWznb+O*y($JJp?~3v08so_;LE|!2EKMTPxPDCyVJ8e`g8s)2qEl$@&Z8-qF;?n$R6a}#Gy<6-CMd(^MvkEMe z-~fI6VB&jn9x@&b%a>Zar_vLv&3Hv1LFB@PrvrTnLn5qc25D9HL80t$(rF!TYsG=A4(m8nJuhy(js( zHSdEZ#rwt|HKJ>k&0Wa0I^0?Zje-}KaC-w*3+7!T=wCC5h;AjOHaSEyhIA!zi*#5T z**+uw+LXMU>}ZH9edrahC~VBM+>oxJXbJ3vd+9TGI|sjnO^J+Er~11G_zxGn_kS)d zNuXj~<$vNQ#EecT0aKH@m{HI*sHsXsk6`MSFl9IaSr)`E<#3yfviT4qc&P!$u|dJD z1pnI=xaz=~T%788{)wh&uBpq@ft3ki;{0Xr{AKy;{m+`}!cf7b=uR5}-KuJvO$T~z ziZ!p>(5}pvE2Hau{0n^gTavDG=o@e=s`q_hWq-4WpD3U_qKY zg*HMZ(5^4-YS&jFBNfuU$%Kvcyo}76VqLn)n1g_BnL=s{v!?OU(|qx7@A#QmEF+-5 zzK1-z(|P4cn=Pc%K;2$0s-%zv1{&i}f#RtLD6{!6yOwa4$!P6Rig=nPZL^^YzZ zkm|{f!U#_aWau@-xJ9^!iwY~a;3>esJZ9WvO5_YqbcV(*x8;-r3*)QA9)Mhm6K>tt z(4?wuN*JgM>141-Mi$;QByE@5Hzv^$qzN7!3ek(k>1Z2HLEaXsQ}qOjYAwFIWoisn q#UisFKHEm|kpzAS%K=i>mi_}S5VDPzHfXy50000=9+{6 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index e34cd6435a1b5eed81a9065a901aa9cc1d546489..b4b3e9a6dfc6473f501f9d7c5d17f2b47a57a483 100644 GIT binary patch literal 1928 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index fd15ff97b287ebb58ea20ed1b8c8d56aad213c72..dbcabffb4f102fe5a76748928159c445b0fc2efa 100644 GIT binary patch delta 955 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 literal 2976 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 delta 1112 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 Mw2gyx1Hv!$Ut!#xyZ`_I literal 2421 zcmV-*35xcKP)6+ZXQomubN-iMP$Nz=q`>V#4WBOoe7O&U@tt;A1MDL-3LRsB&wAcVxrKmeg4 zAy9wzM}#UNK%lAt0g)=G8WJ@`rft$TEp?KDp?NoUyx#Thye^tEJNB-bH96+L0T9D&;SU43Bd5(02lzM-~r$t2LW^d6@UXM_(uv*2Utj_ z$xQ0qviO}%e6J!eE3nvfYyiypTxS4@Hjz7t#7QBmh!$iaJC=wG*6;;**&^B<0F(i2 z5f}GIl4ND@`CI-5(mi<7#k2TrbnK_eAQ`fTv4iONw57rA!WuvUzt#uh?;x72A$;1E z#CwDAw2Fb&ghP;0uJG8nW-f#;6p8;t;I!LvNQ~@Z>HzSW5D(O z1+=wTjz1PzD#p-VJfbw=cFAmOwh#`{$#OgYeB{rqAuQm-E=G1B5y{GTXf2|K z4rCzOcZot)4l{|wk_p++(evI0a4qa7LX+hVeCviO7dpBy4G%&f99cu_%ch#)*2_i! zZUmnjf-Du^*MXn%gBwX?f~_HlEH_G!Sv*JuH4G+#;)x)#2Bin+3Q-Q;Lo~(w%~@EK zn3OMKXN*r)F@<=F?%&QHx!XCandYz0O1<}ufPBcGtQ_`a>Dde^W*SFBs_c8a@qOOc zMEuE81y{~`12z^fRd0r=6+UDYkmFkuWi+m^WM$WBNEIH{_U7V4R)`2q3@9O+a{O{| z-Iv^!<8w7Iq&QKty|Z3LR!(A6ML>P}(`0AK{@uUKU%l1gMCJ42%HtDoxuSpduTYZ` zPgL3%*DA6|`|g?u@)3>r-qZDaum>e?l%uCpIbjx0`=C&(9?}cT!)jK6_+Euii%mdu)P!Vl&*o&3gR` zR0NE;?CbZEe1`2T8DD-;L<4=GdsX6PvI57W?y+IO(FiFNa;gxr$gt+T=QRj-bq z^T=8YYK}DvSE^{aWaZ+_INn02ViC0Bl12NL6$Dh^AX$B;0)ZI>lBEQmD+0517z1K+ zQJFAbG)Yc{s+3d~0|@5yMky6h^SL*bn#o_i0_AX1nt>Pm#T0#DyZxv0Lar77%SoJ> z6Szysa`=05=F=wyUeG>u(LQxi-~|K0dP!iFE;ukqqxjL2aN?|fO(rCGy4_H>z~)XF zUJq)-d}B-?hMn5OrNHDBkyoQ?TF;)QpSs<8{@u;cgb3_n*;|d4n6tntdg@KZ8 z=+Ilp0qc9Mu;Bf4CkmLP%HKLb-?AdWaqtpm41GZFEYi=7*)Pr5?*@zK=y-`fFlxOt z!>6Sw?N)-NI9s87Wj7rw&;z69p%eDm>mAOdyGGde_mZ4SKfYOi@~Ff%Ho-}x%aK=- z3B%PYj1|ZLW0UupKY7JIeYNdb^zIF8@;+y7!jY<{l=LfZZkS%qV>~mZ{pfxe%{zR3 zb5C3Qa%{?DieD@nKmA8KZB>^hS<~|I3%c>$W9sByK3-h1ujcq-+)v+(^kKfwE-)O9rBs+@iz^G?a3&!N%Q)9{#&LCL}oz`F^ z4PMsu?>P2Fc9uwnk=VUFt{NT9bzs3qH{=?rd zr_2k&| zWzSaS8=r)#p+EH^%=+30NBdHLydSpbn{28hQ<|bZ^B~!nwGN*#|9CEu*0bff$|K9h z(J`SQX4i;gEQ1=ot7uPM?r@^xzrKBpJ@HZCU>!aa*!#$~VX`sj_>f&Aq8sfYyR1uY z3bFJE>(CQKxo-aKRN9U3;H!hLc{@Axv4nif@}1V;o#Lx66-V%k=+W?L2%-0H}z zTrb@&Eo6xaCxx$9B80n+g(ndEXe$?~t|F`2?T%b+aJ^xxbVtcPDN=q$x8)l#nea@g zT}so=vUI`5WxxFa=bR8iKQjuyf5#Ee2mt~5@Q%hU-VwUrpwd32X-8HC7aod}tMChR zFf;F@$|D2nmv$RJKLsL$L+s!^bXUo_{(^4*^NNU;ddRk4Pw!L8s^Vrn;!+6n+1J%W z`#_;&-&S?Y0s9Zr_PJRe%ftwyWVE0>w1a+Zlk2mr!)Kr_t|R1p*Yf(1Wns1`E=INA zx^6!ACVO;(5JJXF^lSHW&SPiqWa*Qf+kd`b{p~+uTInL|yN9TMk+c$aqok0qj!eT` zUHRe!$!BmJ>$cFrYo_(vx2+cfgmBSN=(O8v$BOa|!At9`muKv$OUh@*oTAn3L%`zj zML6(lRsPx)>*#x2Y)O77K;<4(eyM_d6FW8RjHbl>9wb9SSJi3WvVm~3U~`x#`Rx7E+aQ>+gkUPllwloUBW3{W zsfI&v#1F?*#QD|h>&w0rbT3iTq==Kzb-Y2X_cxbR!}_UaM4!2z>;3UDi#5)OOvRNs zxEoapujI(VFqVR2EtQC?30cHf3FFlsa$zGlM#W9KyA#mNV$bKju;Y1Fz>afWtBr1= zvmC_7wKjJORFEb^Kv6)1G|RwO0oydc#wLec?S!Dc+{EeF+y=RuR<4DC(hUiPZh0qK z^yfOS627PMb07rAI}QD*Xt$7zLedQ>a_BGWvN9xw0U$|mv`JD<>Q+27Z^X2c*+3$T zJ&t1EPk497?Xu_e(ot$700000NkvXXu0mjfR2ZN?p;T2KqKT@!ls1hUH+F2_#|#Ve2qbA5-#eyq{cSASN8fy(@0mGg&J}>B z!y*4~0qsE7WPhiiYqC?&R+IL*etJF~!9LgD&Sl$cGJEzQxwj6JJ$tB~%eL2~NLU~R zBH7+uVs147vbYC1oPDx@;ViPa2ZS*t=1j_?Sqhgd$h?caKZ_6`@-9#{qQ&6mFn^eTyF`3X(v>(D4x#HI-cR$b zaae-n`9XwZ(fDB&YAP0Kv6-}v_3*;G$587&%j3%}afktj-nWa$w9WEtnQ-h;EZSl- zDI6Q=*q}7Rv0CFuTF8`zbZmqZUy^m(x=z7!VKTPF*YyC^2vGF^-3VJMAleI2^#G(m z*MqpE#eZiqoGkO`+X-|%L@zYp#XY;`(DQXxuS_9Q7U5($E@?5=$(o|^-MwfW3cPD` zvB=p7gM0GX83G#XUG+*0|9%y%HL6tGGDekZ`1dOaAyd~gbxNwE6mZPfTxLd+!9c63j(*QPqQ#N#i1gO&KM zR{w>$jt9_-9_7(HSUCrGxPZ(i0Z1P1MF^-(txz7lgI@GZ%7`IdEde=e+cB$}SY!3; zalC~!glq;0DexAW->ZpjT0cRP*NQ&Bjenf3>5q!^yng~;1*qw*4-Pe7#aBM5BY9*x zK~Snu`eq#e=f!P#6XyrXfBY)!PvPHs7)4^lbhQNOSNd>=3p8dP;s3bJP+5a_b&l1X zL+14Xs=qAYU7dU4l`~Z=;0_lcRIFQ55Vn3ljb>5+QvCDzB!rsU=mT435lI|v3pba%j?u`Y+p;Cns1Y+|C1$1z4J0X7lugmWe^D&Ep0VuU*SKfQoc4# z{mM+F8VPZj{F6l<&yhUYhrKtAuv;{$K?jQ9;X2-(2hICs3tv^(YqC?& gHQ6cXn(P$(0X)ZTjdDq)SO5S307*qoM6N<$f~yNaegFUf delta 1288 zcmV+j1^4>@36Khq8Gix*000Ae6w&|y1lLJKK~#90)tX;u97P<*=g-dGUGJJ|YeQ06 z6Yy#awyB_qM(jiLD2?EIAB2KWzPBPih~SIR_ge6w4}y8JV5uk)gcABvYAL2*`bTOt zri8XO$?e|m%*y`ew%NViy_=cctn|wgZs#U5pZ(2mW`4ifvwu)QG5BjC6htK)ArDF{ zxW#aF5uymWg}SoL5pBS7;MPl3@Zst!sRK6$^eR{zU_vL0Z>XH;LmeT4P$xDT3esV1 zf^9l_c-fNB3@&A2HC6`Rwq6J?Lmpf;U{&x61=a?%=nSuDC>IP-AKt}4R*cZLEih+V z9Iht#gUGFw&wnbPwF1zmT8A-ob8&N6DST5Us8k0=(#ginVXfGrac$&UH;c6z9~~fR z-PBr^Vr{^a6%c-LgnfS0`(hzjTs3b+S~t0tCs~=%Zj=-rZ{d*|9&Z(5m1|jYAxpB- ztz1WSCBO&prYFQ$n}ysIPwWVm*O1E-rHtUc+k-og4SyST!Eqy>jIsK8|DmAmHaj!J z=WguecvxLMx_|z~3}&#F}zsJuwo-P4(1F-DV+-5u@fUcQ@HgCW8kX;`)k8Wqe>KnS74_a9g_@7h*A2vf74A75(6Fu=G%ol^$=C>Afbb zGK%Wi)qi+a5iDC#>knh1UOOos;##2t6ieBvkQX{?vAMPGp$=E52iow0jRLzOK~_j> z+Cul#JnE=Akbmx1^zBWg_c8CoeL91dCYb1|)k{I9QP6yJU9pMSkpMhD?$YndI_{4cUfiLa}X6~@=k z1grf_#930|`f9pGSFdvHN~T81(S700kIn+%i_XPde^1zzC_;H=bf5228O&=-wp7=UAUX^ zl>Ye}{Kq@Naz?y@+t1^JyW&SFO0s)w(SMpn^gstI6VzN*?`=ZF+9~JYt$fAa;69Gm^^B*ep$?IeI@NV(TJq^%g zjvZqS;IdS=27hjmyfIEcdW{^L2<|@#@~suVksWK|sZsRE;pcCtf>P8M1194E4}T82 z6t}T8uX}o)oqQF)xd$J5VMt|xf~(8^*=vD%I?4K%rc~_GNgR0&(IeOU@=AC}I8qz3 zp{#lRVa;GQGS<+nILpyI;`D@G@;y`ym^LY=%3>AZ(FIRrja36z%-TwG;#ZOJYM~0r zcfUE#k{@e}#F=sxl0%;#oD9qh1b^7Io;xU|iNMf1L*j&CIZP1YT3dGXX~PPvFnVhF zmJ_|7_YHrlk}VpUQ|;_WIX4Vo(Z%3ZqOgtaXrOcs4mI=GAR4S%^ot;^a7X^YMLw$j y_?YJ0(ykc##|!=002ovPDHLkU;%;z?2AYM diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png index c4298a3b6192a22c566e98cef2f6d04d456c4d25..6f1043d7875093515689dbfd81f36a72fee51651 100644 GIT binary patch literal 2338 zcmZ`*dpHyP9v>!)qs(PWgf%a7PYt9n+@k|Y2C zkj7$Au0o9Xc0qfD@zz9of)HFTd0~Sb9Rb=x4gvs0005wn2|*Y{{=+jvzjG@Q+3);Y znXvG-@Z~rBmlB3LNAG?i03(rDcaZRqKo_rn%SXDNMgpGpE6)jg5wR#+_t-*C(HKGb z%)z##VEuSrb<54qrqPVhJ54X>8Zb3=+OO0qcXu>Wrb<%5gTd|k(Do>~LyqB&LD{{) zVjNv`P$#jll29={;}Z1ezY@oV)acv z;iW<{QzovuM9eVkZ>Oj~9iS8hpztZ+%Dq1jAAu@1_T;_Q2^;{+KJM{+b9Hqs=1lHx zy9lFQSxc+V z#)wS@(;?yL6p&f6Bl-eG;4f@0Gf_PV&Y4ETr|CxQp$9pmqL-bO(ro;6uBpDMHZFkb zVOr?-Uzd!=8U|TU1Fv>b$ zJ4%`6sqUC{%7NjO+p?d$rZWwIve_~G6O8+jpgtzY>55S2t~C2Ta>B0ipO^QSp=xovx%MZ9DC1s{Ne7g;L+(^xQ1q^4rP4*|6mp zrD@_d=5mE?W*xM&@T~Nc?!QK0Nr1S6omnVXYC%n@HjftW&BfKz5jjV65+mbtKBry# zJ*dVRIq}%V!sPiH%Z6KilwC9E4|(k4k)@iW?t4hRB=%=zgkPv_M~&ufD8(>-#prJ9 zM3_}a{k=F``*`J(*ZvPetU8XESd2QV7eGZw=?r&YfoW7a!}qMfR79>PlKKhvUq<{` zq=o1gND*)GZd;6}>UzQjTq!ndVKig32)Z+`uWQgXUeQ>YU<&Rre@&4ORqY+ve81YU zWsz4)jJFI*T<@u}S3nbP+-z(`ZLy-$j$js-rgqnqdD-vuvKUdQ2t0k;$kfMG4alTp zR2SRhSJh(IdkKCW_1sAd%ocG!lWqel)Ppam7H4!@{X37zd+AUt)ku)bMktTP{&aCpRIUqF<_==I5Y zOm4zwK%Es~F1YG!MQmc>3ZWLY^#m67vD;RnE@)O^kP9zVIXGTyoTgy|S_qTXlDg3Hhp)%iCLcH0dNg9aoJ$>A=p36);yO1ezA<8vs{|z`(YVPAcH2&G5bKsn!E(?sU~TKPE+wg#8XM&d7pN6qCL9+d0X%Bl z_^Vp9JA#bfQTtxBzU_Pmpy$F4i<+*iw`aE$M5-CwjnFLmkaTT$-8j#n&RkYLG2#k` zw%uvg>2`8hW$%Fh$V-^ppvFvgBmAW~uyac?)j~vyv9zcexo|M;0)_PQ;dZ(tPs;7G zYNQy5gpZ1e`WQ`XlfC}Hk#q*)`c=x_(Y@!9QdvZ(?2LV$L8Wg2ED0a7V+k05aLX6%r3EbVH!^-X4|%^5cEHxK z<)SJymZHa3WxZX^v${1qI)~p&!Xy577XS@h@y|XUOtDrcQmW1p#&vvcnjln9oOI&| zb>Y+vDXq$T9$G;3N9d)}td)mSPdv>s2FD-)DCe&Ea39YG8i}m?TH*jMwGI8A0Li7z zZdDYKDW+_>enJa4*OZ-F6q=Ps)8>(-L*(k6L>1wud)*20_xg`9>0G}_?81f<26))- z4iznAjM{(7EHc@(C0EOn`Opn3%l2COR@TY~P0Zp~CjK&MU}Ew3IYKLoZ@=T-Eyb2v z%~2`@$UJZB?;!JnuP^lm#=n`j@P6vL8!I}S`Zk)0BMtuij$3}DAt79X8TUmi4_`^t zmVrn$RnJ7WtZ2|$C)EV+oL!E!Ynr#KQUIG~>24>z_TFb_PxSR}#BDssfm{a7>K{$e zmKTXyFr?Q_mN-h^c54F=?$7C*vqSWw&tG|LGdRETM=(6W!gh1bgB=oUhRv{p0U%xy zqvY1#9T)QV74`!?nQ<5KW5=mTPZGaUa_}DqXRVvFmHRWFW?$SuS>4c6(^t9_XzNlz zc@4}9v()>f7#gdM{G~L=YyS_H_5WYve`N&X`$MP$`NO_--##f=v=iznG9cwo&NVmV literal 2865 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 literal 2976 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 WB5g3A3laYS0000gJXqnD`B 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 delta 1687 zcmV;I259-V44e&+8Gix*007#LBoF`q24hJ?K~#90-CNC%6h#z&)m8JkGt44;V3pu5 z7|;dxu;@YJM~q-lF9r{gXpCV`M&chJ9yIad&8r@a5kpA)AOR8+^@8HE2Vw}>T@Xe> zfJKOWEc-RnGhJ1tUr+DO%y!Robyx39`0b?A+uil?>w53itAAIo8EA(AzyMMJ!oA|0 zl<}AXAoz>~pm;R#0j{|hOj}JocoYCtfC~ORQTFLY#$~&xr#q2}Q5*ygP^uduRap3Q zkVSgcx+|d&f!GoyP4Bn{r?w@Pb-V6@OwB6<{HPE`@_LdWu6Am{=Ok^y&HQ!ROQVI=t5*b%QeTCRALql(ix|aDM03@eL4P3m#V*JuDhGc7Q{M{{*iY&) z242K6s9JiFqOh?5L_$6ZW=UtwCfb78=F2OHW#aSzW5O&zu6Zz6^MA6t4Tc{oxYRJ0 zFEkR%=yaM$e~t_k#F=T&yT-Hq^7ewfwnB!&p@L?-4t2z61LNpm2S>=ZKI`M(#l)Sa zTVzvj;(yR{;@VvG!(X+vR{=;Y`+ar9Qo6M(#Rw2cCp>QDl4vNY&gLvM{msOv))}T# zsG$3g;TD&e_TlOXs%Rzv*R80XkQnU} z4zV8eO(Mg1bgRC z4;MWjpl@!Wd$&|S82Ka1v3M3^q<@fxe3HIB1dQ;b7n<&}y<3fUx7&|Bl8!|c zY+upBWwhSC6##MWHvjrOJ21@lJp~EMzpsrO0igSbjD1gnl;ZHD7{95xVW@2tp(v*G z6SLpii6W6eM~B(oEhJNmL9qQp~IxjI7+CQ0y+16N+P_!$1hGRHXQJ!a8;-#wO9uBeBJx$rCNW-X9^4<*J{Y zuC2A|z)uzl0pq~1t$T+jy;%`Re6$lRQT=>8Qa@Y|QQL?aJ0B(aBw3e{w>`FmtrJCK z#*WyC(>J`!_auyG`t6vcK|n;Xlz&Dos_hc13+KlK@yGQTYj;p*-d$oArn|&RY&DQw z-i)pj#l=`AG&Mw-t&IjVQAdmi%2D#gd{z4ESX+=@bU{ojyn`u2#CorWo;yyWv!8zgV_PzuR^3_EKloAYy4nEOSKoQWWi({uc*bL@i=x_glB9+YJ(XxK1gZ&gc> z;>c9E(e~6JuX)UD&Y1`Z{@YbKQ)cgMCj$kNR+u5>^qh5MO8n_F#kEGlJ7(k6d=>XN zc^C^R&fcn=y#;+4vMv+U1%G`|k^hEGaiShV=)mW_E}*iBusfZ1N~+t?VL)1?efbMa zSY=y16af5V@pm4g7b%URTvyCW-(qFx0M zqF_dO(M?n8te2cb;~q@y1J5=K__hiF`ZkKykVlOO?1!o>G&!=$;eXI+O#p0z1pXAg zbM+PUy!&~O8UGr`aD7+xutliQK*h~^?7@>bLas~Su!NmRBg8zM&v=sV7iFFwUxq*m zmAUGXn5~4!!5qg<8g5E1$1AF9MS?GgVm&223alwgCF<0Gp_DqG$V%+Az`icAP2$XP!1FAY7P7v+mc%}dV002ovPDHLkV1oS(L=OM} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png index b656689ecfaeada71e9bb78216a865e150e3a38e..f96e994ea661aa1f005ddb6cbdfed6a64dd088f2 100644 GIT binary patch literal 2879 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index b18d03d111298ecdf20a78db7974452220ea650d..cb2efa409de975a69d28a92a2ccb3861caa2ef60 100644 GIT binary patch literal 1616 zcmZWpdpOg382`y)hEq0|9&D+pBf?ys$ZZkXF!v*u8nY6yW5o_mgj|NG2+*u@aLQBz|gY$PaBL0sW!3%EW~^;?6Dp z_a=r~oKKgSKoK@rPq6sJf={Jw`YZr!FT&bddq&geX~W@)zo{p1jf~7$ zjid9V+bMf>A1Ln83`8X-+rn^}8micyg0;zFp+7;WW?<`v&DLWF$8$%A_0!+z#g@rQKKVy4MJhxilL+<4f{05@yZ?%N^j#)n5Seafd4Z5joSp z>LTeV9i^i!)l(DEJb{Ii^cBM18KD1;WD4a_A_YtI&sm5PCFLq|=sYg|1b6c-XmoKw zdXE;J#^bVIm172=1*qxPdCed%**k16V%mHFgr+#U*H zTMfF*txIH}yWA}+(Cs8Xyq^-CHptP9gd=Rqd{@!X1EILl`N#YfS(rxk)YCV{UeXKs z0g#rCB;Hw38BB}PSe#`zE88xTbHUE+J=AI zdaM1Bov$BsKsdy$+%%w?+@fZ@V`u1CHS`e=X042!^b0%L^sz^oaGv8#{8nGYK2v2J zROS2OqT+Z-4Tju2xs-rhgfsj`rr++WD})&<490qs4DILpp%`qOVIyJv&;5?`UJ}VA z_32A&u#@*h#o>j6xkoO=lO~4xp6f4fPwqYBp%64CPuO!ozud(7?X9bQCD)(b4XYA#)SYuHug?2JGHl zKJBf_8n)iGZF3CIk%W11OtCSs60?rgo|&BM-2^y1`YxRkS+agIUT)U0V?s7p-&tzs zIT&mWbuB+a{%jOvuyKR=C9q)hP=CNSuIDrCs^pmSG)8?C8yTgqLH?CBY1N=UV}6?4 z3>-dw2VW_in{9J4^Zx4AS(a4DR7fZ!GpUUVsn&4}jEHqkm8%C*(%Z>I0r70i=t_F| z21=;VStf6ju(JTGMgTuKk=I(oaNqdj&Yx6^|C`DY@hM9;48;%d zT9!vQTv=(b?HF^%4xy3*Q_y=;I;(SUpvILZR^xi9(vg1440N}$#JE^*YOE^Rq>R4%Y;I_lgf`E#13~bW6f4kQ}f66uqz5P`vfQ ziv3T&evs)wR*%5XFPm(OKD}20Cjjb!b3Pf z(WVIsmv`b;t=TS)5%*s9TXqnabM;1gM4bx3vGnva#K8RU>$ZLyM4>jv3Ab344Z&U{{Z27+nxXb delta 1817 zcmV+!2j=+D44n>;8Gix*002n!#(V$(2INUZK~#90V5IP{3f2CmxJ%zGzH*Fd;EwBJqQdqJUcDBasjJ zriHY-ZMWT-x!25@UAkpwcIV^Xx~;!Vn(ge)-21!t+}}O-+$A1|*R*iCYSw znBXQ3ar2O+X3}cTBtT-4CiTELHGEk@g(?`MzesF#fI_01B_Nrh;*)e;Q-~^C&y2@o z-X<1FgOsC027fj)C{csvXpl!2r|2YeWR|0muS*Jt3N--ok*zlhNl*R8$cp|eSajq_ zpY5<0g(jJzW9B>17o(&IEMO|CLpYK-YQGj?=wX~pw>^~dg&=9h9PY|t(Y%#uuDZ=g znsl$w)<4s5NCnL%K0`y2o@BQyLk~6yVMECcCTUcqtbg_4kj170UF86hI%(;c{QBU6 zEa)h%$rdrYs{ekjWdO;H8h_1X#@-jf(5%qw?sY|@X4GAV5%k{LOR)LP)OE|_%0#S#B+A8g0Y zG#?m}S$|}KMqG_*PA93!UOjcDjUDU25#?9+!+70v#5Cih=P|>|8xDvo3Kym|B-<1i zs>wh{&^2gdJ|x?8%oy%!*fb=UJ2uVTt09>R>(tKo22mLIvQws_k1V{>YG6#%J;5v>a5My}?1 zXR=Bf%^ZfQdJ1U;i2s8#paH85_{>$PRex8lcb?X_*#7b~j0NlgnvA{j1V4724-6`+ z1TdLtRCUl_?$wqrz^A&ctv|u=y-p9dr;M#@@R3ej*2UjCsHzg~m~<-99u;uDI)jhT z)0T9kwy!O3`LXsZ%%_a^*Wl7lQFfd!PAe(igB#b^^?!$-j#(%ao z_TFE;^O#}my=Ps9Hg`Vm=O=F}DIO}@&()BmGVqf(tsRHird3)`hp}mu7s6w2uEhBn zw-0w8b&g$ESUgcvPSWLh;->wC{_OX}6{_?coBAJIBb}%!9;$SzB**XFomB*JLwy+@GE=Lo8joj=`G0q8YM-u# zWU+aYS}3%_vSxb9|8(CKX>i6Cp`xp0cz-s;PXkdI(m}p%NKDUinNBj<$mD+wX&`WU zH!jS^9DD64t#7gWxXhW`F)IN+`AT)Gka9I&DLUMh#Ki(G=`gmfwfFQ#ttj^7jg2cY zpBA@E{BYbT9`i{ftRrbrOMk>O76I0teq+~j*qt$Ut`mbLF&gxU&%Hp?aQz`fHyHcL zF_?@yW->lGp?>ShV%W6jdWe4t)}|lW>npIg3%fJeor!R*5aQB^z3;d<8+TVLAiX1C z(L52~(G!=8*4qbhX(#s1tE3Bt09ss}yOU@rrfJUb+Ykt-ghetNcYkoYR4)vR3u=S^ z)1VQR0I>^h=?OAgNp7uL=v39MvR~ffPWF{(hmAC!)T|*jqZxY+9t4wVXo$JT-8X?AdH*km|!o&^Aa

6IM3*HL{5=*+IU9GX|&9dY+h~U>KG)9V~MW+!V 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 literal 3943 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 PuZfM7y=9I0ou~f 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 aWb%Jqbod*sW)h+R00004AOQgA3Q($ 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 literal 72121 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 Date: Mon, 27 Apr 2026 20:43:12 +0200 Subject: [PATCH 06/12] refactor(settings): split download/options into focused pages - Extract files, metadata, lyrics into dedicated pages - Move search source + fallback into download page - Move app/update/debug settings into new app_settings_page - Replace options_settings_page with app_settings_page - Reorganize settings_tab into 3 logical groups --- lib/screens/settings/app_settings_page.dart | 372 +++ .../settings/download_settings_page.dart | 2046 ++++------------- lib/screens/settings/files_settings_page.dart | 1055 +++++++++ .../settings/lyrics_settings_page.dart | 373 +++ .../settings/metadata_settings_page.dart | 253 ++ .../settings/options_settings_page.dart | 1012 -------- lib/screens/settings/settings_tab.dart | 106 +- 7 files changed, 2547 insertions(+), 2670 deletions(-) create mode 100644 lib/screens/settings/app_settings_page.dart create mode 100644 lib/screens/settings/files_settings_page.dart create mode 100644 lib/screens/settings/lyrics_settings_page.dart create mode 100644 lib/screens/settings/metadata_settings_page.dart delete mode 100644 lib/screens/settings/options_settings_page.dart diff --git a/lib/screens/settings/app_settings_page.dart b/lib/screens/settings/app_settings_page.dart new file mode 100644 index 00000000..8b7d14bf --- /dev/null +++ b/lib/screens/settings/app_settings_page.dart @@ -0,0 +1,372 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:spotiflac_android/l10n/l10n.dart'; +import 'package:spotiflac_android/providers/download_queue_provider.dart'; +import 'package:spotiflac_android/providers/settings_provider.dart'; +import 'package:spotiflac_android/utils/app_bar_layout.dart'; +import 'package:spotiflac_android/widgets/settings_group.dart'; + +class AppSettingsPage extends ConsumerWidget { + const AppSettingsPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final settings = ref.watch(settingsProvider); + final colorScheme = Theme.of(context).colorScheme; + final topPadding = normalizedHeaderTopPadding(context); + + return PopScope( + canPop: true, + child: Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + 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( + context.l10n.settingsApp, + style: TextStyle( + fontSize: 20 + (8 * expandRatio), + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + ); + }, + ), + ), + + // ── Updates ──────────────────────────────────────────────── + SliverToBoxAdapter( + child: SettingsSectionHeader(title: context.l10n.sectionApp), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsSwitchItem( + icon: Icons.extension, + title: context.l10n.optionsExtensionStore, + subtitle: context.l10n.optionsExtensionStoreSubtitle, + value: settings.showExtensionStore, + onChanged: (v) => ref + .read(settingsProvider.notifier) + .setShowExtensionStore(v), + ), + SettingsSwitchItem( + icon: Icons.system_update, + title: context.l10n.optionsCheckUpdates, + subtitle: context.l10n.optionsCheckUpdatesSubtitle, + value: settings.checkForUpdates, + onChanged: (v) => ref + .read(settingsProvider.notifier) + .setCheckForUpdates(v), + showDivider: settings.checkForUpdates, + ), + if (settings.checkForUpdates) + _UpdateChannelSelector( + currentChannel: settings.updateChannel, + onChanged: (v) => ref + .read(settingsProvider.notifier) + .setUpdateChannel(v), + ), + ], + ), + ), + + // ── Data ─────────────────────────────────────────────────── + SliverToBoxAdapter( + child: SettingsSectionHeader(title: context.l10n.sectionData), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsItem( + icon: Icons.cleaning_services_outlined, + title: context.l10n.cleanupOrphanedDownloads, + subtitle: context.l10n.cleanupOrphanedDownloadsSubtitle, + onTap: () => _cleanupOrphanedDownloads(context, ref), + ), + SettingsItem( + icon: Icons.delete_forever, + title: context.l10n.optionsClearHistory, + subtitle: context.l10n.optionsClearHistorySubtitle, + onTap: () => + _showClearHistoryDialog(context, ref, colorScheme), + showDivider: false, + ), + ], + ), + ), + + // ── Debug ────────────────────────────────────────────────── + SliverToBoxAdapter( + child: SettingsSectionHeader(title: context.l10n.sectionDebug), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsSwitchItem( + icon: Icons.bug_report, + title: context.l10n.optionsDetailedLogging, + subtitle: settings.enableLogging + ? context.l10n.optionsDetailedLoggingOn + : context.l10n.optionsDetailedLoggingOff, + value: settings.enableLogging, + onChanged: (v) => ref + .read(settingsProvider.notifier) + .setEnableLogging(v), + showDivider: false, + ), + ], + ), + ), + + const SliverToBoxAdapter(child: SizedBox(height: 32)), + ], + ), + ), + ); + } + + void _showClearHistoryDialog( + BuildContext context, + WidgetRef ref, + ColorScheme colorScheme, + ) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(context.l10n.dialogClearHistoryTitle), + content: Text(context.l10n.dialogClearHistoryMessage), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(context.l10n.dialogCancel), + ), + TextButton( + onPressed: () { + ref.read(downloadHistoryProvider.notifier).clearHistory(); + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.snackbarHistoryCleared)), + ); + }, + child: Text( + context.l10n.dialogClear, + style: TextStyle(color: colorScheme.error), + ), + ), + ], + ), + ); + } + + Future _cleanupOrphanedDownloads( + BuildContext context, + WidgetRef ref, + ) async { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AlertDialog( + content: Row( + children: [ + const CircularProgressIndicator(), + const SizedBox(width: 16), + Text(context.l10n.cleanupOrphanedDownloads), + ], + ), + ), + ); + try { + final removed = await ref + .read(downloadHistoryProvider.notifier) + .cleanupOrphanedDownloads(); + if (context.mounted) { + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + removed > 0 + ? context.l10n.cleanupOrphanedDownloadsResult(removed) + : context.l10n.cleanupOrphanedDownloadsNone, + ), + ), + ); + } + } catch (e) { + if (context.mounted) { + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.snackbarError(e.toString()))), + ); + } + } + } +} + +class _UpdateChannelSelector extends StatelessWidget { + final String currentChannel; + final ValueChanged onChanged; + const _UpdateChannelSelector({ + required this.currentChannel, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + final unselectedColor = isDark + ? Color.alphaBlend( + Colors.white.withValues(alpha: 0.05), + colorScheme.surface, + ) + : colorScheme.surfaceContainerHigh; + + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.new_releases, + color: colorScheme.onSurfaceVariant, + size: 24, + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.optionsUpdateChannel, + style: Theme.of(context).textTheme.bodyLarge, + ), + const SizedBox(height: 2), + Text( + currentChannel == 'preview' + ? context.l10n.optionsUpdateChannelPreview + : context.l10n.optionsUpdateChannelStable, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + _ChannelChip( + label: context.l10n.channelStable, + isSelected: currentChannel == 'stable', + onTap: () => onChanged('stable'), + ), + const SizedBox(width: 8), + _ChannelChip( + label: context.l10n.channelPreview, + isSelected: currentChannel == 'preview', + onTap: () => onChanged('preview'), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Icon( + Icons.info_outline, + size: 16, + color: colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + context.l10n.optionsUpdateChannelWarning, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ), + ], + ), + ], + ), + ); + } +} + +class _ChannelChip extends StatelessWidget { + final String label; + final bool isSelected; + final VoidCallback onTap; + const _ChannelChip({ + required this.label, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + final unselectedColor = isDark + ? Color.alphaBlend( + Colors.white.withValues(alpha: 0.05), + colorScheme.surface, + ) + : colorScheme.surfaceContainerHigh; + return Expanded( + child: Material( + color: isSelected ? colorScheme.primaryContainer : unselectedColor, + borderRadius: BorderRadius.circular(12), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Center( + child: Text( + label, + style: TextStyle( + fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, + color: isSelected + ? colorScheme.onPrimaryContainer + : colorScheme.onSurfaceVariant, + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/settings/download_settings_page.dart b/lib/screens/settings/download_settings_page.dart index 543dbc1f..2a2de59f 100644 --- a/lib/screens/settings/download_settings_page.dart +++ b/lib/screens/settings/download_settings_page.dart @@ -1,18 +1,13 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:file_picker/file_picker.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:permission_handler/permission_handler.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/models/settings.dart'; import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/providers/extension_provider.dart'; -import 'package:spotiflac_android/services/platform_bridge.dart'; import 'package:spotiflac_android/utils/app_bar_layout.dart'; -import 'package:spotiflac_android/utils/file_access.dart'; -import 'package:spotiflac_android/screens/settings/lyrics_provider_priority_page.dart'; +import 'package:spotiflac_android/screens/settings/download_fallback_extensions_page.dart'; import 'package:spotiflac_android/widgets/settings_group.dart'; class DownloadSettingsPage extends ConsumerStatefulWidget { @@ -25,278 +20,12 @@ class DownloadSettingsPage extends ConsumerStatefulWidget { class _DownloadSettingsPageState extends ConsumerState { static const _builtInServices = ['tidal', 'qobuz']; - static const _songLinkRegions = [ - 'AD', - 'AE', - 'AG', - 'AL', - 'AM', - 'AO', - 'AR', - 'AT', - 'AU', - 'AZ', - 'BA', - 'BB', - 'BD', - 'BE', - 'BF', - 'BG', - 'BH', - 'BI', - 'BJ', - 'BN', - 'BO', - 'BR', - 'BS', - 'BT', - 'BW', - 'BZ', - 'CA', - 'CD', - 'CG', - 'CH', - 'CI', - 'CL', - 'CM', - 'CO', - 'CR', - 'CV', - 'CW', - 'CY', - 'CZ', - 'DE', - 'DJ', - 'DK', - 'DM', - 'DO', - 'DZ', - 'EC', - 'EE', - 'EG', - 'ES', - 'ET', - 'FI', - 'FJ', - 'FM', - 'FR', - 'GA', - 'GB', - 'GD', - 'GE', - 'GH', - 'GM', - 'GN', - 'GQ', - 'GR', - 'GT', - 'GW', - 'GY', - 'HK', - 'HN', - 'HR', - 'HT', - 'HU', - 'ID', - 'IE', - 'IL', - 'IN', - 'IQ', - 'IS', - 'IT', - 'JM', - 'JO', - 'JP', - 'KE', - 'KG', - 'KH', - 'KI', - 'KM', - 'KN', - 'KR', - 'KW', - 'KZ', - 'LA', - 'LB', - 'LC', - 'LI', - 'LK', - 'LR', - 'LS', - 'LT', - 'LU', - 'LV', - 'LY', - 'MA', - 'MC', - 'MD', - 'ME', - 'MG', - 'MH', - 'MK', - 'ML', - 'MN', - 'MO', - 'MR', - 'MT', - 'MU', - 'MV', - 'MW', - 'MX', - 'MY', - 'MZ', - 'NA', - 'NE', - 'NG', - 'NI', - 'NL', - 'NO', - 'NP', - 'NR', - 'NZ', - 'OM', - 'PA', - 'PE', - 'PG', - 'PH', - 'PK', - 'PL', - 'PS', - 'PT', - 'PW', - 'PY', - 'QA', - 'RO', - 'RS', - 'RW', - 'SA', - 'SB', - 'SC', - 'SE', - 'SG', - 'SI', - 'SK', - 'SL', - 'SM', - 'SN', - 'SR', - 'ST', - 'SV', - 'SZ', - 'TD', - 'TG', - 'TH', - 'TJ', - 'TL', - 'TN', - 'TO', - 'TR', - 'TT', - 'TV', - 'TW', - 'TZ', - 'UA', - 'UG', - 'US', - 'UY', - 'UZ', - 'VC', - 'VE', - 'VN', - 'VU', - 'WS', - 'XK', - 'ZA', - 'ZM', - 'ZW', - ]; - static const _songLinkRegionNames = { - 'US': 'United States', - 'GB': 'United Kingdom', - 'FR': 'France', - 'DE': 'Germany', - 'JP': 'Japan', - 'KR': 'South Korea', - 'IN': 'India', - 'ID': 'Indonesia', - 'BR': 'Brazil', - 'MX': 'Mexico', - 'AU': 'Australia', - 'CA': 'Canada', - 'XK': 'Kosovo', - }; - int _androidSdkVersion = 0; - bool _hasAllFilesAccess = false; - bool _artistFolderFiltersExpanded = false; - - @override - void initState() { - super.initState(); - _initDeviceInfo(); - } - - Future _initDeviceInfo() async { - if (Platform.isAndroid) { - final deviceInfo = DeviceInfoPlugin(); - final androidInfo = await deviceInfo.androidInfo; - final sdkVersion = androidInfo.version.sdkInt; - final hasAccess = await Permission.manageExternalStorage.isGranted; - if (mounted) { - setState(() { - _androidSdkVersion = sdkVersion; - _hasAllFilesAccess = hasAccess; - }); - } - } - } - - Future _requestAllFilesAccess() async { - final status = await Permission.manageExternalStorage.request(); - if (status.isGranted) { - ref.read(settingsProvider.notifier).setUseAllFilesAccess(true); - if (mounted) { - setState(() => _hasAllFilesAccess = true); - } - } else if (status.isPermanentlyDenied) { - if (mounted) { - final shouldOpen = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(context.l10n.setupStorageAccessRequired), - content: Text(context.l10n.allFilesAccessDeniedMessage), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: Text(context.l10n.dialogCancel), - ), - FilledButton( - onPressed: () => Navigator.pop(context, true), - child: Text(context.l10n.setupOpenSettings), - ), - ], - ), - ); - if (shouldOpen == true) { - await openAppSettings(); - } - } - } - } - - Future _disableAllFilesAccess() async { - ref.read(settingsProvider.notifier).setUseAllFilesAccess(false); - // Note: We can't revoke the permission programmatically, - // but we can stop using it in the app - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.allFilesAccessDisabledMessage)), - ); - } - } @override Widget build(BuildContext context) { final settings = ref.watch(settingsProvider); + final extensionState = ref.watch(extensionProvider); + final hasExtensions = extensionState.extensions.isNotEmpty; final colorScheme = Theme.of(context).colorScheme; final topPadding = normalizedHeaderTopPadding(context); @@ -336,7 +65,7 @@ class _DownloadSettingsPageState extends ConsumerState { bottom: 16, ), title: Text( - context.l10n.downloadTitle, + context.l10n.settingsDownload, style: TextStyle( fontSize: 20 + (8 * expandRatio), fontWeight: FontWeight.bold, @@ -348,6 +77,7 @@ class _DownloadSettingsPageState extends ConsumerState { ), ), + // ── Service ──────────────────────────────────────────────── SliverToBoxAdapter( child: SettingsSectionHeader(title: context.l10n.sectionService), ), @@ -364,6 +94,7 @@ class _DownloadSettingsPageState extends ConsumerState { ), ), + // ── Audio Quality ────────────────────────────────────────── SliverToBoxAdapter( child: SettingsSectionHeader( title: context.l10n.sectionAudioQuality, @@ -411,7 +142,6 @@ class _DownloadSettingsPageState extends ConsumerState { .setAudioQuality('HI_RES_LOSSLESS'), showDivider: isTidalService, ), - // Lossy 320kbps option (Tidal only) - downloads M4A AAC from server, converts to MP3/Opus if (isTidalService) _QualityOption( title: context.l10n.downloadLossy320, @@ -441,7 +171,7 @@ class _DownloadSettingsPageState extends ConsumerState { showDivider: false, ), ], - if (!isBuiltInService) ...[ + if (!isBuiltInService) Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), child: Row( @@ -464,278 +194,25 @@ class _DownloadSettingsPageState extends ConsumerState { ], ), ), - ], - ], - ), - ), - - SliverToBoxAdapter( - child: SettingsSectionHeader(title: context.l10n.sectionLyrics), - ), - SliverToBoxAdapter( - child: SettingsGroup( - children: [ - SettingsSwitchItem( - icon: Icons.subtitles_outlined, - title: context.l10n.optionsEmbedLyrics, - subtitle: settings.embedMetadata - ? context.l10n.optionsEmbedLyricsSubtitle - : context.l10n.downloadEmbedLyricsDisabled, - value: settings.embedLyrics, - enabled: settings.embedMetadata, - onChanged: (value) => ref - .read(settingsProvider.notifier) - .setEmbedLyrics(value), - showDivider: settings.embedMetadata && settings.embedLyrics, - ), - if (settings.embedMetadata && settings.embedLyrics) ...[ - SettingsItem( - icon: Icons.lyrics_outlined, - title: context.l10n.lyricsMode, - subtitle: _getLyricsModeLabel( - context, - settings.lyricsMode, - ), - onTap: () => _showLyricsModePicker( - context, - ref, - settings.lyricsMode, - ), - ), - SettingsItem( - icon: Icons.source_outlined, - title: context.l10n.lyricsProvidersTitle, - subtitle: _getLyricsProvidersSubtitle( - settings.lyricsProviders, - ), - onTap: () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const LyricsProviderPriorityPage(), - ), - ), - ), - SettingsSwitchItem( - icon: Icons.translate_outlined, - title: context.l10n.downloadNeteaseIncludeTranslation, - subtitle: settings.lyricsIncludeTranslationNetease - ? context - .l10n - .downloadNeteaseIncludeTranslationEnabled - : context - .l10n - .downloadNeteaseIncludeTranslationDisabled, - value: settings.lyricsIncludeTranslationNetease, - onChanged: (value) => ref - .read(settingsProvider.notifier) - .setLyricsIncludeTranslationNetease(value), - ), - SettingsSwitchItem( - icon: Icons.text_fields_outlined, - title: context.l10n.downloadNeteaseIncludeRomanization, - subtitle: settings.lyricsIncludeRomanizationNetease - ? context - .l10n - .downloadNeteaseIncludeRomanizationEnabled - : context - .l10n - .downloadNeteaseIncludeRomanizationDisabled, - value: settings.lyricsIncludeRomanizationNetease, - onChanged: (value) => ref - .read(settingsProvider.notifier) - .setLyricsIncludeRomanizationNetease(value), - ), - SettingsSwitchItem( - icon: Icons.record_voice_over_outlined, - title: context.l10n.downloadAppleQqMultiPerson, - subtitle: settings.lyricsMultiPersonWordByWord - ? context.l10n.downloadAppleQqMultiPersonEnabled - : context.l10n.downloadAppleQqMultiPersonDisabled, - value: settings.lyricsMultiPersonWordByWord, - onChanged: (value) => ref - .read(settingsProvider.notifier) - .setLyricsMultiPersonWordByWord(value), - ), - SettingsItem( - icon: Icons.language_outlined, - title: context.l10n.downloadMusixmatchLanguage, - subtitle: settings.musixmatchLanguage.isEmpty - ? context.l10n.downloadMusixmatchLanguageAuto - : settings.musixmatchLanguage.toUpperCase(), - onTap: () => _showMusixmatchLanguagePicker( - context, - ref, - settings.musixmatchLanguage, - ), - showDivider: false, - ), - ], ], ), ), + // ── Network & Performance ────────────────────────────────── SliverToBoxAdapter( child: SettingsSectionHeader( - title: context.l10n.sectionFileSettings, + title: context.l10n.sectionPerformance, ), ), SliverToBoxAdapter( child: SettingsGroup( children: [ - SettingsItem( - icon: Icons.text_fields, - title: context.l10n.downloadFilenameFormat, - subtitle: settings.filenameFormat, - onTap: () => _showFormatEditor( - context, - ref, - settings.filenameFormat, - ), - ), - SettingsItem( - icon: Icons.music_note_outlined, - title: context.l10n.downloadSingleFilenameFormat, - subtitle: settings.singleFilenameFormat, - onTap: () => _showFormatEditor( - context, - ref, - settings.singleFilenameFormat, - onSave: ref - .read(settingsProvider.notifier) - .setSingleFilenameFormat, - title: context.l10n.downloadSingleFilenameFormat, - description: - context.l10n.downloadSingleFilenameFormatDescription, - ), - ), - SettingsItem( - icon: Icons.folder_outlined, - title: context.l10n.downloadDirectory, - subtitle: settings.downloadDirectory.isEmpty - ? (Platform.isIOS - ? context.l10n.setupAppDocumentsFolder - : 'Music/SpotiFLAC') - : settings.downloadDirectory, - onTap: () => _pickDirectory(context, ref), - ), - SettingsSwitchItem( - icon: Icons.library_music_outlined, - title: context.l10n.downloadSeparateSinglesFolder, - subtitle: settings.separateSingles - ? context.l10n.downloadSeparateSinglesEnabled - : context.l10n.downloadSeparateSinglesDisabled, - value: settings.separateSingles, - onChanged: (value) => ref + _ConcurrentDownloadsItem( + currentValue: settings.concurrentDownloads, + onChanged: (v) => ref .read(settingsProvider.notifier) - .setSeparateSingles(value), + .setConcurrentDownloads(v), ), - if (settings.separateSingles) - SettingsItem( - icon: Icons.folder_outlined, - title: context.l10n.downloadAlbumFolderStructure, - subtitle: _getAlbumFolderStructureLabel( - settings.albumFolderStructure, - ), - onTap: () => _showAlbumFolderStructurePicker( - context, - ref, - settings.albumFolderStructure, - ), - ), - if (!settings.separateSingles) - SettingsItem( - icon: Icons.create_new_folder_outlined, - title: context.l10n.downloadFolderOrganization, - subtitle: _getFolderOrganizationLabel( - settings.folderOrganization, - ), - onTap: () => _showFolderOrganizationPicker( - context, - ref, - settings.folderOrganization, - ), - ), - SettingsSwitchItem( - icon: Icons.playlist_play_outlined, - title: context.l10n.downloadCreatePlaylistSourceFolder, - subtitle: _getPlaylistFolderSubtitle(settings), - value: settings.createPlaylistFolder, - onChanged: (value) => ref - .read(settingsProvider.notifier) - .setCreatePlaylistFolder(value), - ), - SettingsSwitchItem( - icon: Icons.person_search_outlined, - title: context.l10n.downloadUseAlbumArtistForFolders, - subtitle: settings.useAlbumArtistForFolders - ? context - .l10n - .downloadUseAlbumArtistForFoldersAlbumSubtitle - : context - .l10n - .downloadUseAlbumArtistForFoldersTrackSubtitle, - value: settings.useAlbumArtistForFolders, - onChanged: (value) => ref - .read(settingsProvider.notifier) - .setUseAlbumArtistForFolders(value), - ), - SettingsItem( - icon: Icons.filter_alt_outlined, - title: context.l10n.downloadArtistNameFilters, - subtitle: _getArtistFolderFilterSubtitle( - context, - usePrimaryArtistOnly: settings.usePrimaryArtistOnly, - filterAlbumArtistContributors: - settings.filterContributingArtistsInAlbumArtist, - ), - trailing: Icon( - _artistFolderFiltersExpanded - ? Icons.expand_less - : Icons.expand_more, - ), - onTap: () { - setState(() { - _artistFolderFiltersExpanded = - !_artistFolderFiltersExpanded; - }); - }, - showDivider: !_artistFolderFiltersExpanded, - ), - if (_artistFolderFiltersExpanded) - SettingsSwitchItem( - icon: Icons.person_outline, - title: context.l10n.downloadUsePrimaryArtistOnly, - subtitle: settings.usePrimaryArtistOnly - ? context.l10n.downloadUsePrimaryArtistOnlyEnabled - : context.l10n.downloadUsePrimaryArtistOnlyDisabled, - value: settings.usePrimaryArtistOnly, - onChanged: (value) => ref - .read(settingsProvider.notifier) - .setUsePrimaryArtistOnly(value), - ), - if (_artistFolderFiltersExpanded) - SettingsSwitchItem( - icon: Icons.group_remove_outlined, - title: context.l10n.downloadFilterContributing, - subtitle: settings.filterContributingArtistsInAlbumArtist - ? context.l10n.downloadFilterContributingEnabled - : context.l10n.downloadFilterContributingDisabled, - value: settings.filterContributingArtistsInAlbumArtist, - onChanged: (value) => ref - .read(settingsProvider.notifier) - .setFilterContributingArtistsInAlbumArtist(value), - showDivider: false, - ), - ], - ), - ), - - SliverToBoxAdapter( - child: SettingsSectionHeader(title: context.l10n.sectionDownload), - ), - SliverToBoxAdapter( - child: SettingsGroup( - children: [ SettingsItem( icon: Icons.wifi, title: context.l10n.settingsDownloadNetwork, @@ -748,6 +225,77 @@ class _DownloadSettingsPageState extends ConsumerState { settings.downloadNetworkMode, ), ), + SettingsSwitchItem( + icon: Icons.security_outlined, + title: context.l10n.downloadNetworkCompatibilityMode, + subtitle: settings.networkCompatibilityMode + ? context.l10n.downloadNetworkCompatibilityModeEnabled + : context.l10n.downloadNetworkCompatibilityModeDisabled, + value: settings.networkCompatibilityMode, + onChanged: (value) => ref + .read(settingsProvider.notifier) + .setNetworkCompatibilityMode(value), + showDivider: false, + ), + ], + ), + ), + + // ── Fallback & Search ────────────────────────────────────── + SliverToBoxAdapter( + child: SettingsSectionHeader( + title: context.l10n.sectionSearchSource, + ), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + const _MetadataSourceSelector(), + const _DefaultSearchTabSelector(), + SettingsSwitchItem( + icon: Icons.sync, + title: context.l10n.optionsAutoFallback, + subtitle: context.l10n.optionsAutoFallbackSubtitle, + value: settings.autoFallback, + onChanged: (v) => + ref.read(settingsProvider.notifier).setAutoFallback(v), + ), + if (hasExtensions) + SettingsSwitchItem( + icon: Icons.extension, + title: context.l10n.optionsUseExtensionProviders, + subtitle: settings.useExtensionProviders + ? context.l10n.optionsUseExtensionProvidersOn + : context.l10n.optionsUseExtensionProvidersOff, + value: settings.useExtensionProviders, + onChanged: (v) => ref + .read(settingsProvider.notifier) + .setUseExtensionProviders(v), + ), + SettingsItem( + icon: Icons.extension_outlined, + title: context.l10n.downloadFallbackExtensions, + subtitle: context.l10n.downloadFallbackExtensionsSubtitle, + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => + const DownloadFallbackExtensionsPage(), + ), + ), + showDivider: false, + ), + ], + ), + ), + + // ── Misc ─────────────────────────────────────────────────── + SliverToBoxAdapter( + child: SettingsSectionHeader(title: context.l10n.sectionDownload), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ SettingsItem( icon: Icons.public, title: context.l10n.downloadSongLinkRegion, @@ -758,88 +306,20 @@ class _DownloadSettingsPageState extends ConsumerState { settings.songLinkRegion, ), ), - SettingsSwitchItem( - icon: Icons.security_outlined, - title: context.l10n.downloadNetworkCompatibilityMode, - subtitle: settings.networkCompatibilityMode - ? context.l10n.downloadNetworkCompatibilityModeEnabled - : context.l10n.downloadNetworkCompatibilityModeDisabled, - value: settings.networkCompatibilityMode, - onChanged: (value) { - ref - .read(settingsProvider.notifier) - .setNetworkCompatibilityMode(value); - }, - ), SettingsSwitchItem( icon: Icons.file_download_outlined, title: context.l10n.settingsAutoExportFailed, subtitle: context.l10n.settingsAutoExportFailedSubtitle, value: settings.autoExportFailedDownloads, - onChanged: (value) { - ref - .read(settingsProvider.notifier) - .setAutoExportFailedDownloads(value); - }, + onChanged: (value) => ref + .read(settingsProvider.notifier) + .setAutoExportFailedDownloads(value), showDivider: false, ), ], ), ), - if (Platform.isAndroid && _androidSdkVersion >= 33) ...[ - SliverToBoxAdapter( - child: SettingsSectionHeader( - title: context.l10n.sectionStorageAccess, - ), - ), - SliverToBoxAdapter( - child: SettingsGroup( - children: [ - SettingsSwitchItem( - icon: Icons.folder_special_outlined, - title: context.l10n.allFilesAccess, - subtitle: _hasAllFilesAccess - ? context.l10n.allFilesAccessEnabledSubtitle - : context.l10n.allFilesAccessDisabledSubtitle, - value: _hasAllFilesAccess && settings.useAllFilesAccess, - onChanged: (value) { - if (value) { - _requestAllFilesAccess(); - } else { - _disableAllFilesAccess(); - } - }, - showDivider: false, - ), - ], - ), - ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon( - Icons.info_outline, - size: 16, - color: colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - context.l10n.allFilesAccessDescription, - style: Theme.of(context).textTheme.bodySmall - ?.copyWith(color: colorScheme.onSurfaceVariant), - ), - ), - ], - ), - ), - ), - ], - const SliverToBoxAdapter(child: SizedBox(height: 32)), ], ), @@ -847,765 +327,6 @@ class _DownloadSettingsPageState extends ConsumerState { ); } - String _getAlbumFolderStructureLabel(String structure) { - switch (structure) { - case 'album_only': - return 'Albums/Album Name/'; - case 'artist_year_album': - return 'Albums/Artist/[Year] Album/'; - case 'year_album': - return 'Albums/[Year] Album/'; - case 'artist_album_singles': - return 'Artist/Album/ + Artist/Singles/'; - case 'artist_album_flat': - return 'Artist/Album/ + Artist/song.flac'; - default: - return 'Albums/Artist/Album Name/'; - } - } - - void _showAlbumFolderStructurePicker( - BuildContext context, - WidgetRef ref, - String current, - ) { - showModalBottomSheet( - context: context, - useRootNavigator: true, - builder: (context) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - leading: const Icon(Icons.folder_outlined), - title: Text(context.l10n.albumFolderArtistAlbum), - subtitle: Text(context.l10n.albumFolderArtistAlbumSubtitle), - trailing: current == 'artist_album' - ? const Icon(Icons.check) - : null, - onTap: () { - ref - .read(settingsProvider.notifier) - .setAlbumFolderStructure('artist_album'); - Navigator.pop(context); - }, - ), - ListTile( - leading: const Icon(Icons.calendar_today_outlined), - title: Text(context.l10n.albumFolderArtistYearAlbum), - subtitle: Text(context.l10n.albumFolderArtistYearAlbumSubtitle), - trailing: current == 'artist_year_album' - ? const Icon(Icons.check) - : null, - onTap: () { - ref - .read(settingsProvider.notifier) - .setAlbumFolderStructure('artist_year_album'); - Navigator.pop(context); - }, - ), - ListTile( - leading: const Icon(Icons.album_outlined), - title: Text(context.l10n.albumFolderAlbumOnly), - subtitle: Text(context.l10n.albumFolderAlbumOnlySubtitle), - trailing: current == 'album_only' - ? const Icon(Icons.check) - : null, - onTap: () { - ref - .read(settingsProvider.notifier) - .setAlbumFolderStructure('album_only'); - Navigator.pop(context); - }, - ), - ListTile( - leading: const Icon(Icons.event_outlined), - title: Text(context.l10n.albumFolderYearAlbum), - subtitle: Text(context.l10n.albumFolderYearAlbumSubtitle), - trailing: current == 'year_album' - ? const Icon(Icons.check) - : null, - onTap: () { - ref - .read(settingsProvider.notifier) - .setAlbumFolderStructure('year_album'); - Navigator.pop(context); - }, - ), - ListTile( - leading: const Icon(Icons.person_outlined), - title: Text(context.l10n.albumFolderArtistAlbumSingles), - subtitle: Text( - context.l10n.albumFolderArtistAlbumSinglesSubtitle, - ), - trailing: current == 'artist_album_singles' - ? const Icon(Icons.check) - : null, - onTap: () { - ref - .read(settingsProvider.notifier) - .setAlbumFolderStructure('artist_album_singles'); - Navigator.pop(context); - }, - ), - ListTile( - leading: const Icon(Icons.person_outline_outlined), - title: Text(context.l10n.albumFolderArtistAlbumFlat), - subtitle: Text(context.l10n.albumFolderArtistAlbumFlatSubtitle), - trailing: current == 'artist_album_flat' - ? const Icon(Icons.check) - : null, - onTap: () { - ref - .read(settingsProvider.notifier) - .setAlbumFolderStructure('artist_album_flat'); - Navigator.pop(context); - }, - ), - ], - ), - ), - ); - } - - void _showFormatEditor( - BuildContext context, - WidgetRef ref, - String current, { - void Function(String)? onSave, - String? title, - String? description, - }) { - final controller = TextEditingController(text: current); - final colorScheme = Theme.of(context).colorScheme; - - final basicTags = [ - '{artist}', - '{title}', - '{album}', - '{track}', - '{year}', - '{date}', - '{disc}', - ]; - final advancedTags = [ - '{track_raw}', - '{track:02}', - '{track:1}', - '{date:%Y}', - '{date:%Y-%m-%d}', - '{disc_raw}', - '{disc:02}', - ]; - var showAdvancedTags = RegExp( - r'\{(?:track_raw|disc_raw|track:\d+|disc:\d+|date:[^}]+)\}', - caseSensitive: false, - ).hasMatch(current); - - void insertTag(String tag) { - final text = controller.text; - final selection = controller.selection; - final start = selection.start >= 0 ? selection.start : text.length; - final end = selection.end >= 0 ? selection.end : text.length; - - String insertion = tag; - if (start > 0) { - final before = text.substring(0, start); - if (!before.trim().endsWith('-')) { - insertion = ' - $tag'; - } else if (before.trim().endsWith('-') && !before.endsWith(' ')) { - insertion = ' $tag'; - } - } - - final newText = text.replaceRange(start, end, insertion); - controller.value = TextEditingValue( - text: newText, - selection: TextSelection.collapsed(offset: start + insertion.length), - ); - } - - showModalBottomSheet( - context: context, - useRootNavigator: true, - isScrollControlled: true, - backgroundColor: colorScheme.surface, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(28)), - ), - builder: (context) => StatefulBuilder( - builder: (context, setModalState) => 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( - title ?? context.l10n.filenameFormat, - style: Theme.of(context).textTheme.headlineSmall - ?.copyWith(fontWeight: FontWeight.bold), - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - description ?? context.l10n.downloadFilenameDescription, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 24), - - TextField( - controller: controller, - decoration: InputDecoration( - hintText: '{artist} - {title}', - filled: true, - fillColor: colorScheme.surfaceContainerHighest - .withValues(alpha: 0.3), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: BorderSide.none, - ), - ), - autofocus: true, - ), - const SizedBox(height: 24), - - Text( - context.l10n.downloadFilenameInsertTag, - style: Theme.of(context).textTheme.titleSmall?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 12), - Wrap( - spacing: 8, - runSpacing: 8, - children: basicTags.map((tag) { - return ActionChip( - label: Text(tag), - onPressed: () => insertTag(tag), - backgroundColor: colorScheme.surfaceContainerHighest - .withValues(alpha: 0.5), - side: BorderSide.none, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - labelStyle: TextStyle( - color: colorScheme.onSurface, - fontWeight: FontWeight.w500, - ), - ); - }).toList(), - ), - const SizedBox(height: 12), - SwitchListTile( - value: showAdvancedTags, - onChanged: (value) => - setModalState(() => showAdvancedTags = value), - contentPadding: EdgeInsets.zero, - title: Text(context.l10n.filenameShowAdvancedTags), - subtitle: Text( - context.l10n.filenameShowAdvancedTagsDescription, - ), - ), - if (showAdvancedTags) ...[ - const SizedBox(height: 8), - Wrap( - spacing: 8, - runSpacing: 8, - children: advancedTags.map((tag) { - return ActionChip( - label: Text(tag), - onPressed: () => insertTag(tag), - backgroundColor: colorScheme.surfaceContainerHighest - .withValues(alpha: 0.5), - side: BorderSide.none, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - labelStyle: TextStyle( - color: colorScheme.onSurface, - fontWeight: FontWeight.w500, - ), - ); - }).toList(), - ), - ], - - const SizedBox(height: 32), - - Row( - children: [ - Expanded( - child: TextButton( - onPressed: () => Navigator.pop(context), - style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - ), - child: Text(context.l10n.dialogCancel), - ), - ), - const SizedBox(width: 12), - Expanded( - flex: 2, - child: FilledButton( - onPressed: () { - final save = - onSave ?? - ref - .read(settingsProvider.notifier) - .setFilenameFormat; - save(controller.text); - Navigator.pop(context); - }, - style: FilledButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - ), - child: Text(context.l10n.dialogSave), - ), - ), - ], - ), - const SizedBox(height: 8), - ], - ), - ), - ), - ), - ), - ), - ); - } - - Future _pickDirectory(BuildContext context, WidgetRef ref) async { - if (Platform.isIOS) { - _showIOSDirectoryOptions(context, ref); - } else if (Platform.isAndroid) { - _showAndroidDirectoryOptions(context, ref); - } - } - - Future _getDefaultAndroidDirectory() async { - final directMusicPath = '/storage/emulated/0/Music/SpotiFLAC'; - try { - final musicDir = Directory(directMusicPath); - if (!await musicDir.exists()) { - await musicDir.create(recursive: true); - } - return musicDir.path; - } catch (_) {} - - try { - final externalDir = await getExternalStorageDirectory(); - if (externalDir != null) { - final musicDir = Directory( - '${externalDir.parent.parent.parent.parent.path}/Music/SpotiFLAC', - ); - if (!await musicDir.exists()) { - await musicDir.create(recursive: true); - } - return musicDir.path; - } - } catch (_) {} - - final appDir = await getApplicationDocumentsDirectory(); - final fallbackDir = Directory('${appDir.path}/SpotiFLAC'); - if (!await fallbackDir.exists()) { - await fallbackDir.create(recursive: true); - } - return fallbackDir.path; - } - - void _showAndroidDirectoryOptions(BuildContext context, WidgetRef ref) { - final colorScheme = Theme.of(context).colorScheme; - final settings = ref.read(settingsProvider); - final isSafMode = - settings.storageMode == 'saf' && settings.downloadTreeUri.isNotEmpty; - showModalBottomSheet( - context: context, - useRootNavigator: true, - backgroundColor: colorScheme.surfaceContainerHigh, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(28)), - ), - builder: (ctx) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), - child: Text( - context.l10n.setupDownloadLocationTitle, - style: Theme.of( - ctx, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), - child: Text( - context.l10n.downloadLocationSubtitle, - style: Theme.of(ctx).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ), - ListTile( - leading: Icon(Icons.folder_special, color: colorScheme.primary), - title: Text(context.l10n.storageModeAppFolder), - subtitle: Text(context.l10n.storageModeAppFolderSubtitle), - trailing: !isSafMode ? const Icon(Icons.check) : null, - onTap: () async { - Navigator.pop(ctx); - final defaultDir = await _getDefaultAndroidDirectory(); - final notifier = ref.read(settingsProvider.notifier); - notifier.setStorageMode('app'); - notifier.setDownloadDirectory(defaultDir); - notifier.setDownloadTreeUri(''); - }, - ), - ListTile( - leading: Icon(Icons.folder_open, color: colorScheme.primary), - title: Text(context.l10n.storageModeSaf), - subtitle: Text(context.l10n.storageModeSafSubtitle), - trailing: isSafMode ? const Icon(Icons.check) : null, - onTap: () async { - Navigator.pop(ctx); - final result = await PlatformBridge.pickSafTree(); - if (result != null) { - final treeUri = result['tree_uri'] as String? ?? ''; - final displayName = result['display_name'] as String? ?? ''; - if (treeUri.isNotEmpty) { - ref.read(settingsProvider.notifier).setStorageMode('saf'); - ref - .read(settingsProvider.notifier) - .setDownloadTreeUri( - treeUri, - displayName: displayName.isNotEmpty - ? displayName - : treeUri, - ); - } - } - }, - ), - const SizedBox(height: 8), - ], - ), - ), - ); - } - - void _showIOSDirectoryOptions(BuildContext context, WidgetRef ref) { - final colorScheme = Theme.of(context).colorScheme; - showModalBottomSheet( - context: context, - useRootNavigator: true, - backgroundColor: colorScheme.surfaceContainerHigh, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(28)), - ), - builder: (ctx) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), - child: Text( - context.l10n.setupDownloadLocationTitle, - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), - child: Text( - context.l10n.setupDownloadLocationIosMessage, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ), - ListTile( - leading: Icon(Icons.folder_special, color: colorScheme.primary), - title: Text(context.l10n.setupAppDocumentsFolder), - subtitle: Text(context.l10n.setupAppDocumentsFolderSubtitle), - trailing: Icon(Icons.check_circle, color: colorScheme.primary), - onTap: () async { - final dir = await getApplicationDocumentsDirectory(); - ref - .read(settingsProvider.notifier) - .setDownloadDirectory(dir.path); - if (ctx.mounted) Navigator.pop(ctx); - }, - ), - ListTile( - leading: Icon(Icons.cloud, color: colorScheme.onSurfaceVariant), - title: Text(context.l10n.setupChooseFromFiles), - subtitle: Text(context.l10n.setupChooseFromFilesSubtitle), - onTap: () async { - Navigator.pop(ctx); - if (Platform.isIOS) { - await Future.delayed(const Duration(milliseconds: 250)); - } - - // Note: iOS requires folder to have at least one file to be selectable - String? result; - try { - result = await FilePicker.platform.getDirectoryPath(); - } catch (e) { - if (ctx.mounted) { - ScaffoldMessenger.of(ctx).showSnackBar( - SnackBar( - content: Text( - ctx.l10n.snackbarFolderPickerFailed(e.toString()), - ), - backgroundColor: Theme.of(ctx).colorScheme.error, - duration: const Duration(seconds: 4), - ), - ); - } - return; - } - - if (result != null) { - // iOS: Validate the selected path is writable (not iCloud or container root) - if (Platform.isIOS) { - final validation = validateIosPath(result); - if (!validation.isValid) { - if (ctx.mounted) { - ScaffoldMessenger.of(ctx).showSnackBar( - SnackBar( - content: Text( - validation.errorReason ?? - context.l10n.setupIcloudNotSupported, - ), - backgroundColor: Theme.of(ctx).colorScheme.error, - duration: const Duration(seconds: 4), - ), - ); - } - return; - } - } - ref - .read(settingsProvider.notifier) - .setDownloadDirectory(result); - } - }, - ), - Padding( - padding: const EdgeInsets.fromLTRB(24, 8, 24, 16), - child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: colorScheme.tertiaryContainer.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(12), - ), - child: Row( - children: [ - Icon( - Icons.info_outline, - size: 20, - color: colorScheme.tertiary, - ), - const SizedBox(width: 12), - Expanded( - child: Text( - context.l10n.setupIosEmptyFolderWarning, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: colorScheme.onTertiaryContainer, - ), - ), - ), - ], - ), - ), - ), - const SizedBox(height: 8), - ], - ), - ), - ); - } - - String _getFolderOrganizationLabel(String value) { - switch (value) { - case 'playlist': - return 'By Playlist'; - case 'artist': - return 'By Artist'; - case 'album': - return 'By Album'; - case 'artist_album': - return 'Artist/Album'; - default: - return 'None'; - } - } - - String _getPlaylistFolderSubtitle(AppSettings settings) { - if (settings.folderOrganization == 'playlist') { - return context.l10n.downloadCreatePlaylistSourceFolderRedundant; - } - if (settings.createPlaylistFolder) { - return context.l10n.downloadCreatePlaylistSourceFolderEnabled; - } - return context.l10n.downloadCreatePlaylistSourceFolderDisabled; - } - - String _getArtistFolderFilterSubtitle( - BuildContext context, { - required bool usePrimaryArtistOnly, - required bool filterAlbumArtistContributors, - }) { - final statuses = [ - usePrimaryArtistOnly ? 'Primary only: On' : 'Primary only: Off', - filterAlbumArtistContributors - ? 'Album Artist metadata: Primary only' - : 'Album Artist metadata: Full', - ]; - return statuses.join(' | '); - } - - String _getLyricsModeLabel(BuildContext context, String mode) { - switch (mode) { - case 'external': - return context.l10n.lyricsModeExternal; - case 'both': - return context.l10n.lyricsModeBoth; - default: - return context.l10n.lyricsModeEmbed; - } - } - - String _getSongLinkRegionLabel(String code) { - final normalized = code.trim().toUpperCase(); - final effective = normalized.isEmpty ? 'US' : normalized; - final name = _songLinkRegionNames[effective]; - if (name == null) return effective; - return '$effective - $name'; - } - - void _showLyricsModePicker( - BuildContext context, - WidgetRef ref, - String current, - ) { - final colorScheme = Theme.of(context).colorScheme; - showModalBottomSheet( - context: context, - useRootNavigator: true, - backgroundColor: colorScheme.surfaceContainerHigh, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(28)), - ), - builder: (context) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), - child: Text( - context.l10n.lyricsMode, - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), - child: Text( - context.l10n.lyricsModeDescription, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ), - ListTile( - leading: const Icon(Icons.audiotrack), - title: Text(context.l10n.lyricsModeEmbed), - subtitle: Text(context.l10n.lyricsModeEmbedSubtitle), - trailing: current == 'embed' ? const Icon(Icons.check) : null, - onTap: () { - ref.read(settingsProvider.notifier).setLyricsMode('embed'); - Navigator.pop(context); - }, - ), - ListTile( - leading: const Icon(Icons.insert_drive_file_outlined), - title: Text(context.l10n.lyricsModeExternal), - subtitle: Text(context.l10n.lyricsModeExternalSubtitle), - trailing: current == 'external' ? const Icon(Icons.check) : null, - onTap: () { - ref.read(settingsProvider.notifier).setLyricsMode('external'); - Navigator.pop(context); - }, - ), - ListTile( - leading: const Icon(Icons.library_music_outlined), - title: Text(context.l10n.lyricsModeBoth), - subtitle: Text(context.l10n.lyricsModeBothSubtitle), - trailing: current == 'both' ? const Icon(Icons.check) : null, - onTap: () { - ref.read(settingsProvider.notifier).setLyricsMode('both'); - Navigator.pop(context); - }, - ), - const SizedBox(height: 16), - ], - ), - ), - ); - } - - static const _providerDisplayNames = { - 'lrclib': 'LRCLIB', - 'netease': 'Netease', - 'musixmatch': 'Musixmatch', - 'apple_music': 'Apple Music', - 'qqmusic': 'QQ Music', - }; - - String _getLyricsProvidersSubtitle(List providers) { - if (providers.isEmpty) return context.l10n.downloadProvidersNoneEnabled; - return providers.map((p) => _providerDisplayNames[p] ?? p).join(' > '); - } - - String _normalizeMusixmatchLanguage(String value) { - final normalized = value.trim().toLowerCase(); - return normalized.replaceAll(RegExp(r'[^a-z0-9\-_]'), ''); - } - String _getTidalHighFormatLabel(BuildContext context, String format) { switch (format) { case 'mp3_320': @@ -1619,6 +340,19 @@ class _DownloadSettingsPageState extends ConsumerState { } } + String _getSongLinkRegionLabel(String code) { + const names = { + 'US': 'United States', 'GB': 'United Kingdom', 'FR': 'France', + 'DE': 'Germany', 'JP': 'Japan', 'KR': 'South Korea', + 'IN': 'India', 'ID': 'Indonesia', 'BR': 'Brazil', + 'MX': 'Mexico', 'AU': 'Australia', 'CA': 'Canada', 'XK': 'Kosovo', + }; + final normalized = code.trim().toUpperCase(); + final effective = normalized.isEmpty ? 'US' : normalized; + final name = names[effective]; + return name == null ? effective : '$effective - $name'; + } + void _showTidalHighFormatPicker( BuildContext context, WidgetRef ref, @@ -1641,18 +375,16 @@ class _DownloadSettingsPageState extends ConsumerState { padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), child: Text( context.l10n.downloadLossy320Format, - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), + style: Theme.of(context).textTheme.titleLarge + ?.copyWith(fontWeight: FontWeight.bold), ), ), Padding( padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), child: Text( context.l10n.downloadLossy320FormatDesc, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith(color: colorScheme.onSurfaceVariant), ), ), ListTile( @@ -1663,9 +395,7 @@ class _DownloadSettingsPageState extends ConsumerState { ? Icon(Icons.check, color: colorScheme.primary) : null, onTap: () { - ref - .read(settingsProvider.notifier) - .setTidalHighFormat('mp3_320'); + ref.read(settingsProvider.notifier).setTidalHighFormat('mp3_320'); Navigator.pop(context); }, ), @@ -1677,9 +407,7 @@ class _DownloadSettingsPageState extends ConsumerState { ? Icon(Icons.check, color: colorScheme.primary) : null, onTap: () { - ref - .read(settingsProvider.notifier) - .setTidalHighFormat('opus_256'); + ref.read(settingsProvider.notifier).setTidalHighFormat('opus_256'); Navigator.pop(context); }, ), @@ -1691,9 +419,7 @@ class _DownloadSettingsPageState extends ConsumerState { ? Icon(Icons.check, color: colorScheme.primary) : null, onTap: () { - ref - .read(settingsProvider.notifier) - .setTidalHighFormat('opus_128'); + ref.read(settingsProvider.notifier).setTidalHighFormat('opus_128'); Navigator.pop(context); }, ), @@ -1704,94 +430,6 @@ class _DownloadSettingsPageState extends ConsumerState { ); } - void _showMusixmatchLanguagePicker( - BuildContext context, - WidgetRef ref, - String currentLanguage, - ) { - final colorScheme = Theme.of(context).colorScheme; - final controller = TextEditingController(text: currentLanguage); - - showModalBottomSheet( - context: context, - useRootNavigator: true, - backgroundColor: colorScheme.surfaceContainerHigh, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(28)), - ), - isScrollControlled: true, - builder: (context) => Padding( - padding: EdgeInsets.only( - left: 24, - right: 24, - top: 24, - bottom: 24 + MediaQuery.of(context).viewInsets.bottom, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.downloadMusixmatchLanguage, - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - Text( - context.l10n.downloadMusixmatchLanguageDesc, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - const SizedBox(height: 16), - TextField( - controller: controller, - textInputAction: TextInputAction.done, - decoration: InputDecoration( - labelText: context.l10n.downloadMusixmatchLanguageCode, - hintText: context.l10n.downloadMusixmatchLanguageHint, - ), - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(context.l10n.dialogCancel), - ), - const SizedBox(width: 8), - TextButton( - onPressed: () { - ref - .read(settingsProvider.notifier) - .setMusixmatchLanguage(''); - Navigator.pop(context); - }, - child: Text(context.l10n.downloadMusixmatchAuto), - ), - const SizedBox(width: 8), - FilledButton( - onPressed: () { - final normalized = _normalizeMusixmatchLanguage( - controller.text, - ); - ref - .read(settingsProvider.notifier) - .setMusixmatchLanguage(normalized); - Navigator.pop(context); - }, - child: Text(context.l10n.dialogSave), - ), - ], - ), - ], - ), - ), - ); - } - void _showNetworkModePicker( BuildContext context, WidgetRef ref, @@ -1814,18 +452,16 @@ class _DownloadSettingsPageState extends ConsumerState { padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), child: Text( context.l10n.settingsDownloadNetwork, - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), + style: Theme.of(context).textTheme.titleLarge + ?.copyWith(fontWeight: FontWeight.bold), ), ), Padding( padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), child: Text( context.l10n.settingsDownloadNetworkSubtitle, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith(color: colorScheme.onSurfaceVariant), ), ), ListTile( @@ -1836,9 +472,7 @@ class _DownloadSettingsPageState extends ConsumerState { ? Icon(Icons.check, color: colorScheme.primary) : null, onTap: () { - ref - .read(settingsProvider.notifier) - .setDownloadNetworkMode('any'); + ref.read(settingsProvider.notifier).setDownloadNetworkMode('any'); Navigator.pop(context); }, ), @@ -1868,6 +502,27 @@ class _DownloadSettingsPageState extends ConsumerState { WidgetRef ref, String current, ) { + const regions = [ + 'AD','AE','AG','AL','AM','AO','AR','AT','AU','AZ','BA','BB','BD','BE', + 'BF','BG','BH','BI','BJ','BN','BO','BR','BS','BT','BW','BZ','CA','CD', + 'CG','CH','CI','CL','CM','CO','CR','CV','CW','CY','CZ','DE','DJ','DK', + 'DM','DO','DZ','EC','EE','EG','ES','ET','FI','FJ','FM','FR','GA','GB', + 'GD','GE','GH','GM','GN','GQ','GR','GT','GW','GY','HK','HN','HR','HT', + 'HU','ID','IE','IL','IN','IQ','IS','IT','JM','JO','JP','KE','KG','KH', + 'KI','KM','KN','KR','KW','KZ','LA','LB','LC','LI','LK','LR','LS','LT', + 'LU','LV','LY','MA','MC','MD','ME','MG','MH','MK','ML','MN','MO','MR', + 'MT','MU','MV','MW','MX','MY','MZ','NA','NE','NG','NI','NL','NO','NP', + 'NR','NZ','OM','PA','PE','PG','PH','PK','PL','PS','PT','PW','PY','QA', + 'RO','RS','RW','SA','SB','SC','SE','SG','SI','SK','SL','SM','SN','SR', + 'ST','SV','SZ','TD','TG','TH','TJ','TL','TN','TO','TR','TT','TV','TW', + 'TZ','UA','UG','US','UY','UZ','VC','VE','VN','VU','WS','XK','ZA','ZM','ZW', + ]; + const names = { + 'US': 'United States', 'GB': 'United Kingdom', 'FR': 'France', + 'DE': 'Germany', 'JP': 'Japan', 'KR': 'South Korea', + 'IN': 'India', 'ID': 'Indonesia', 'BR': 'Brazil', + 'MX': 'Mexico', 'AU': 'Australia', 'CA': 'Canada', 'XK': 'Kosovo', + }; final colorScheme = Theme.of(context).colorScheme; final normalizedCurrent = current.trim().toUpperCase(); showModalBottomSheet( @@ -1888,30 +543,27 @@ class _DownloadSettingsPageState extends ConsumerState { padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), child: Text( context.l10n.downloadSongLinkRegion, - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), + style: Theme.of(context).textTheme.titleLarge + ?.copyWith(fontWeight: FontWeight.bold), ), ), Padding( padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), child: Text( context.l10n.downloadSongLinkRegionDesc, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith(color: colorScheme.onSurfaceVariant), ), ), Expanded( child: ListView.builder( - itemCount: _songLinkRegions.length, + itemCount: regions.length, itemBuilder: (context, index) { - final code = _songLinkRegions[index]; + final code = regions[index]; final isSelected = code == normalizedCurrent; - final displayName = _songLinkRegionNames[code]; return ListTile( title: Text(code), - subtitle: displayName != null ? Text(displayName) : null, + subtitle: names[code] != null ? Text(names[code]!) : null, trailing: isSelected ? Icon(Icons.check, color: colorScheme.primary) : null, @@ -1931,117 +583,10 @@ class _DownloadSettingsPageState extends ConsumerState { ), ); } - - void _showFolderOrganizationPicker( - BuildContext context, - WidgetRef ref, - String current, - ) { - final colorScheme = Theme.of(context).colorScheme; - showModalBottomSheet( - context: context, - useRootNavigator: true, - backgroundColor: colorScheme.surfaceContainerHigh, - isScrollControlled: true, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(28)), - ), - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.7, - ), - builder: (context) => SafeArea( - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), - child: Text( - context.l10n.downloadFolderOrganization, - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), - child: Text( - context.l10n.folderOrganizationDescription, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ), - _FolderOption( - title: context.l10n.folderOrganizationNone, - subtitle: context.l10n.folderOrganizationNoneSubtitle, - example: 'SpotiFLAC/Track.flac', - isSelected: current == 'none', - onTap: () { - ref - .read(settingsProvider.notifier) - .setFolderOrganization('none'); - 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, - example: 'SpotiFLAC/Artist Name/Track.flac', - isSelected: current == 'artist', - onTap: () { - ref - .read(settingsProvider.notifier) - .setFolderOrganization('artist'); - Navigator.pop(context); - }, - ), - _FolderOption( - title: context.l10n.folderOrganizationByAlbum, - subtitle: context.l10n.folderOrganizationByAlbumSubtitle, - example: 'SpotiFLAC/Album Name/Track.flac', - isSelected: current == 'album', - onTap: () { - ref - .read(settingsProvider.notifier) - .setFolderOrganization('album'); - Navigator.pop(context); - }, - ), - _FolderOption( - title: context.l10n.folderOrganizationByArtistAlbum, - subtitle: context.l10n.folderOrganizationByArtistAlbumSubtitle, - example: 'SpotiFLAC/Artist/Album/Track.flac', - isSelected: current == 'artist_album', - onTap: () { - ref - .read(settingsProvider.notifier) - .setFolderOrganization('artist_album'); - Navigator.pop(context); - }, - ), - const SizedBox(height: 16), - ], - ), - ), - ), - ); - } } +// ── Private widgets (reused from original) ───────────────────────────────── + class _ServiceSelector extends ConsumerWidget { final String currentService; final ValueChanged onChanged; @@ -2129,14 +674,12 @@ class _ServiceChip extends StatelessWidget { Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; - final unselectedColor = isDark ? Color.alphaBlend( Colors.white.withValues(alpha: 0.05), colorScheme.surface, ) : colorScheme.surfaceContainerHigh; - return Material( color: isSelected ? colorScheme.primaryContainer : unselectedColor, borderRadius: BorderRadius.circular(12), @@ -2234,16 +777,91 @@ class _QualityOption extends StatelessWidget { } } -class _FolderOption extends StatelessWidget { - final String title; - final String subtitle; - final String example; +class _ConcurrentDownloadsItem extends StatelessWidget { + final int currentValue; + final ValueChanged onChanged; + const _ConcurrentDownloadsItem({ + required this.currentValue, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.download_for_offline, + color: colorScheme.onSurfaceVariant, + size: 24, + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.optionsConcurrentDownloads, + style: Theme.of(context).textTheme.bodyLarge, + ), + const SizedBox(height: 2), + Text( + currentValue == 1 + ? context.l10n.optionsConcurrentSequential + : context.l10n.optionsConcurrentParallel(currentValue), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + for (final n in [1, 2, 3, 4, 5]) ...[ + if (n > 1) const SizedBox(width: 8), + _ConcurrentChip( + label: '$n', + isSelected: currentValue == n, + onTap: () => onChanged(n), + ), + ], + ], + ), + 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.optionsConcurrentWarning, + style: Theme.of(context).textTheme.bodySmall + ?.copyWith(color: colorScheme.error), + ), + ), + ], + ), + ], + ), + ); + } +} + +class _ConcurrentChip extends StatelessWidget { + final String label; final bool isSelected; final VoidCallback onTap; - const _FolderOption({ - required this.title, - required this.subtitle, - required this.example, + const _ConcurrentChip({ + required this.label, required this.isSelected, required this.onTap, }); @@ -2251,28 +869,204 @@ class _FolderOption extends StatelessWidget { @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - return ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 4), - title: Text(title), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(subtitle), - const SizedBox(height: 4), - Text( - example, - style: TextStyle( - fontFamily: 'monospace', - fontSize: 11, - color: colorScheme.primary, + final isDark = Theme.of(context).brightness == Brightness.dark; + final unselectedColor = isDark + ? Color.alphaBlend( + Colors.white.withValues(alpha: 0.05), + colorScheme.surface, + ) + : colorScheme.surfaceContainerHigh; + return Expanded( + child: Material( + color: isSelected ? colorScheme.primaryContainer : unselectedColor, + borderRadius: BorderRadius.circular(12), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Center( + child: Text( + label, + style: TextStyle( + fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, + color: isSelected + ? colorScheme.onPrimaryContainer + : colorScheme.onSurfaceVariant, + ), + ), ), ), - ], + ), ), - trailing: isSelected - ? Icon(Icons.check_circle, color: colorScheme.primary) - : Icon(Icons.circle_outlined, color: colorScheme.outline), - onTap: onTap, + ); + } +} + +// Imported from options_settings_page — search source selectors +class _MetadataSourceSelector extends ConsumerWidget { + const _MetadataSourceSelector(); + + static const _builtInProviders = {'tidal': 'Tidal', 'qobuz': 'Qobuz'}; + + Extension? _defaultSearchExtension(List extensions) { + return extensions + .where( + (ext) => + ext.enabled && + ext.hasCustomSearch && + ext.searchBehavior?.primary == true, + ) + .firstOrNull ?? + extensions + .where((ext) => ext.enabled && ext.hasCustomSearch) + .firstOrNull; + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final colorScheme = Theme.of(context).colorScheme; + final settings = ref.watch(settingsProvider); + final extState = ref.watch(extensionProvider); + + final rawSearchProvider = settings.searchProvider?.trim() ?? ''; + final isValidBuiltIn = _builtInProviders.containsKey(rawSearchProvider); + final primarySearchExtension = _defaultSearchExtension(extState.extensions); + final defaultProviderTarget = + primarySearchExtension?.displayName ?? 'Tidal'; + final defaultProviderLabel = + '${context.l10n.extensionsHomeFeedAuto} ($defaultProviderTarget)'; + final searchProvider = + isValidBuiltIn || + extState.extensions.any( + (e) => + e.enabled && e.hasCustomSearch && e.id == rawSearchProvider, + ) + ? rawSearchProvider + : ''; + final isBuiltIn = _builtInProviders.containsKey(searchProvider); + + Extension? activeExtension; + if (searchProvider.isNotEmpty && !isBuiltIn) { + activeExtension = extState.extensions + .where((e) => e.id == searchProvider && e.enabled) + .firstOrNull; + } + final hasNonDefaultProvider = isBuiltIn || activeExtension != null; + + String subtitle; + if (isBuiltIn) { + subtitle = 'Using ${_builtInProviders[searchProvider]}'; + } else if (activeExtension != null) { + subtitle = context.l10n.optionsUsingExtension(activeExtension.displayName); + } else { + subtitle = context.l10n.optionsPrimaryProviderSubtitle; + } + + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.optionsPrimaryProvider, + style: Theme.of(context).textTheme.titleMedium + ?.copyWith(fontWeight: FontWeight.w500), + ), + const SizedBox(height: 4), + Text( + subtitle, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: hasNonDefaultProvider + ? colorScheme.primary + : colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: 12), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + _SearchProviderChip( + label: defaultProviderLabel, + isSelected: searchProvider.isEmpty, + onTap: () => ref + .read(settingsProvider.notifier) + .setSearchProvider(''), + ), + for (final entry in _builtInProviders.entries) + _SearchProviderChip( + label: entry.value, + isSelected: searchProvider == entry.key, + onTap: () => ref + .read(settingsProvider.notifier) + .setSearchProvider(entry.key), + ), + for (final ext in extState.extensions.where( + (e) => e.enabled && e.hasCustomSearch, + )) + _SearchProviderChip( + label: ext.displayName, + isSelected: searchProvider == ext.id, + onTap: () => ref + .read(settingsProvider.notifier) + .setSearchProvider(ext.id), + ), + ], + ), + ], + ), + ); + } +} + +class _SearchProviderChip extends StatelessWidget { + final String label; + final bool isSelected; + final VoidCallback onTap; + const _SearchProviderChip({ + required this.label, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return FilterChip( + label: Text(label), + selected: isSelected, + onSelected: (_) => onTap(), + selectedColor: colorScheme.primaryContainer, + checkmarkColor: colorScheme.onPrimaryContainer, + labelStyle: TextStyle( + color: isSelected + ? colorScheme.onPrimaryContainer + : colorScheme.onSurface, + ), + ); + } +} + +class _DefaultSearchTabSelector extends ConsumerWidget { + const _DefaultSearchTabSelector(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final settings = ref.watch(settingsProvider); + final colorScheme = Theme.of(context).colorScheme; + return SettingsItem( + icon: Icons.tab_outlined, + title: context.l10n.optionsDefaultSearchTab, + subtitle: settings.defaultSearchTab == 'albums' + ? context.l10n.optionsDefaultSearchTabAlbums + : context.l10n.optionsDefaultSearchTabTracks, + onTap: () { + final current = settings.defaultSearchTab; + ref.read(settingsProvider.notifier).setDefaultSearchTab( + current == 'albums' ? 'tracks' : 'albums', + ); + }, ); } } diff --git a/lib/screens/settings/files_settings_page.dart b/lib/screens/settings/files_settings_page.dart new file mode 100644 index 00000000..0382abc4 --- /dev/null +++ b/lib/screens/settings/files_settings_page.dart @@ -0,0 +1,1055 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:spotiflac_android/l10n/l10n.dart'; +import 'package:spotiflac_android/models/settings.dart'; +import 'package:spotiflac_android/providers/settings_provider.dart'; +import 'package:spotiflac_android/services/platform_bridge.dart'; +import 'package:spotiflac_android/utils/app_bar_layout.dart'; +import 'package:spotiflac_android/utils/file_access.dart'; +import 'package:spotiflac_android/widgets/settings_group.dart'; + +class FilesSettingsPage extends ConsumerStatefulWidget { + const FilesSettingsPage({super.key}); + + @override + ConsumerState createState() => _FilesSettingsPageState(); +} + +class _FilesSettingsPageState extends ConsumerState { + int _androidSdkVersion = 0; + bool _hasAllFilesAccess = false; + bool _artistFolderFiltersExpanded = false; + + @override + void initState() { + super.initState(); + _initDeviceInfo(); + } + + Future _initDeviceInfo() async { + if (Platform.isAndroid) { + final deviceInfo = DeviceInfoPlugin(); + final androidInfo = await deviceInfo.androidInfo; + final sdkVersion = androidInfo.version.sdkInt; + final hasAccess = await Permission.manageExternalStorage.isGranted; + if (mounted) { + setState(() { + _androidSdkVersion = sdkVersion; + _hasAllFilesAccess = hasAccess; + }); + } + } + } + + Future _requestAllFilesAccess() async { + final status = await Permission.manageExternalStorage.request(); + if (status.isGranted) { + ref.read(settingsProvider.notifier).setUseAllFilesAccess(true); + if (mounted) setState(() => _hasAllFilesAccess = true); + } else if (status.isPermanentlyDenied) { + if (mounted) { + final shouldOpen = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(context.l10n.setupStorageAccessRequired), + content: Text(context.l10n.allFilesAccessDeniedMessage), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text(context.l10n.dialogCancel), + ), + FilledButton( + onPressed: () => Navigator.pop(context, true), + child: Text(context.l10n.setupOpenSettings), + ), + ], + ), + ); + if (shouldOpen == true) await openAppSettings(); + } + } + } + + Future _disableAllFilesAccess() async { + ref.read(settingsProvider.notifier).setUseAllFilesAccess(false); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.allFilesAccessDisabledMessage)), + ); + } + } + + @override + Widget build(BuildContext context) { + final settings = ref.watch(settingsProvider); + final colorScheme = Theme.of(context).colorScheme; + final topPadding = normalizedHeaderTopPadding(context); + + return PopScope( + canPop: true, + child: Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + 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( + context.l10n.settingsFiles, + style: TextStyle( + fontSize: 20 + (8 * expandRatio), + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + ); + }, + ), + ), + + // ── Download Location ────────────────────────────────────── + SliverToBoxAdapter( + child: SettingsSectionHeader( + title: context.l10n.setupDownloadLocationTitle, + ), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsItem( + icon: Icons.folder_outlined, + title: context.l10n.downloadDirectory, + subtitle: settings.downloadDirectory.isEmpty + ? (Platform.isIOS + ? context.l10n.setupAppDocumentsFolder + : 'Music/SpotiFLAC') + : settings.downloadDirectory, + onTap: () => _pickDirectory(context, ref), + showDivider: false, + ), + ], + ), + ), + + // ── Filename Formats ─────────────────────────────────────── + SliverToBoxAdapter( + child: SettingsSectionHeader( + title: context.l10n.sectionFileSettings, + ), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsItem( + icon: Icons.text_fields, + title: context.l10n.downloadFilenameFormat, + subtitle: settings.filenameFormat, + onTap: () => _showFormatEditor( + context, + ref, + settings.filenameFormat, + ), + ), + SettingsItem( + icon: Icons.music_note_outlined, + title: context.l10n.downloadSingleFilenameFormat, + subtitle: settings.singleFilenameFormat, + onTap: () => _showFormatEditor( + context, + ref, + settings.singleFilenameFormat, + onSave: ref + .read(settingsProvider.notifier) + .setSingleFilenameFormat, + title: context.l10n.downloadSingleFilenameFormat, + description: + context.l10n.downloadSingleFilenameFormatDescription, + ), + showDivider: false, + ), + ], + ), + ), + + // ── Folder Structure ─────────────────────────────────────── + SliverToBoxAdapter( + child: SettingsSectionHeader( + title: context.l10n.downloadFolderOrganization, + ), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsSwitchItem( + icon: Icons.library_music_outlined, + title: context.l10n.downloadSeparateSinglesFolder, + subtitle: settings.separateSingles + ? context.l10n.downloadSeparateSinglesEnabled + : context.l10n.downloadSeparateSinglesDisabled, + value: settings.separateSingles, + onChanged: (value) => ref + .read(settingsProvider.notifier) + .setSeparateSingles(value), + ), + if (settings.separateSingles) + SettingsItem( + icon: Icons.folder_outlined, + title: context.l10n.downloadAlbumFolderStructure, + subtitle: _getAlbumFolderStructureLabel( + settings.albumFolderStructure, + ), + onTap: () => _showAlbumFolderStructurePicker( + context, + ref, + settings.albumFolderStructure, + ), + ), + if (!settings.separateSingles) + SettingsItem( + icon: Icons.create_new_folder_outlined, + title: context.l10n.downloadFolderOrganization, + subtitle: _getFolderOrganizationLabel( + settings.folderOrganization, + ), + onTap: () => _showFolderOrganizationPicker( + context, + ref, + settings.folderOrganization, + ), + ), + SettingsSwitchItem( + icon: Icons.playlist_play_outlined, + title: context.l10n.downloadCreatePlaylistSourceFolder, + subtitle: _getPlaylistFolderSubtitle(settings), + value: settings.createPlaylistFolder, + onChanged: (value) => ref + .read(settingsProvider.notifier) + .setCreatePlaylistFolder(value), + ), + SettingsSwitchItem( + icon: Icons.person_search_outlined, + title: context.l10n.downloadUseAlbumArtistForFolders, + subtitle: settings.useAlbumArtistForFolders + ? context + .l10n + .downloadUseAlbumArtistForFoldersAlbumSubtitle + : context + .l10n + .downloadUseAlbumArtistForFoldersTrackSubtitle, + value: settings.useAlbumArtistForFolders, + onChanged: (value) => ref + .read(settingsProvider.notifier) + .setUseAlbumArtistForFolders(value), + ), + SettingsItem( + icon: Icons.filter_alt_outlined, + title: context.l10n.downloadArtistNameFilters, + subtitle: _getArtistFolderFilterSubtitle( + context, + usePrimaryArtistOnly: settings.usePrimaryArtistOnly, + filterAlbumArtistContributors: + settings.filterContributingArtistsInAlbumArtist, + ), + trailing: Icon( + _artistFolderFiltersExpanded + ? Icons.expand_less + : Icons.expand_more, + ), + onTap: () => setState(() { + _artistFolderFiltersExpanded = + !_artistFolderFiltersExpanded; + }), + showDivider: !_artistFolderFiltersExpanded, + ), + if (_artistFolderFiltersExpanded) ...[ + SettingsSwitchItem( + icon: Icons.person_outline, + title: context.l10n.downloadUsePrimaryArtistOnly, + subtitle: settings.usePrimaryArtistOnly + ? context.l10n.downloadUsePrimaryArtistOnlyEnabled + : context.l10n.downloadUsePrimaryArtistOnlyDisabled, + value: settings.usePrimaryArtistOnly, + onChanged: (value) => ref + .read(settingsProvider.notifier) + .setUsePrimaryArtistOnly(value), + ), + SettingsSwitchItem( + icon: Icons.group_remove_outlined, + title: context.l10n.downloadFilterContributing, + subtitle: settings.filterContributingArtistsInAlbumArtist + ? context.l10n.downloadFilterContributingEnabled + : context.l10n.downloadFilterContributingDisabled, + value: settings.filterContributingArtistsInAlbumArtist, + onChanged: (value) => ref + .read(settingsProvider.notifier) + .setFilterContributingArtistsInAlbumArtist(value), + showDivider: false, + ), + ], + ], + ), + ), + + // ── Storage Access (Android 13+) ─────────────────────────── + if (Platform.isAndroid && _androidSdkVersion >= 33) ...[ + SliverToBoxAdapter( + child: SettingsSectionHeader( + title: context.l10n.sectionStorageAccess, + ), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsSwitchItem( + icon: Icons.folder_special_outlined, + title: context.l10n.allFilesAccess, + subtitle: _hasAllFilesAccess + ? context.l10n.allFilesAccessEnabledSubtitle + : context.l10n.allFilesAccessDisabledSubtitle, + value: _hasAllFilesAccess && + settings.useAllFilesAccess, + onChanged: (value) { + if (value) { + _requestAllFilesAccess(); + } else { + _disableAllFilesAccess(); + } + }, + showDivider: false, + ), + ], + ), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.info_outline, + size: 16, + color: colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + context.l10n.allFilesAccessDescription, + style: Theme.of(context).textTheme.bodySmall + ?.copyWith(color: colorScheme.onSurfaceVariant), + ), + ), + ], + ), + ), + ), + ], + + const SliverToBoxAdapter(child: SizedBox(height: 32)), + ], + ), + ), + ); + } + + // ── Helpers ────────────────────────────────────────────────────────────── + + String _getAlbumFolderStructureLabel(String structure) { + switch (structure) { + case 'album_only': + return 'Albums/Album Name/'; + case 'artist_year_album': + return 'Albums/Artist/[Year] Album/'; + case 'year_album': + return 'Albums/[Year] Album/'; + case 'artist_album_singles': + return 'Artist/Album/ + Artist/Singles/'; + case 'artist_album_flat': + return 'Artist/Album/ + Artist/song.flac'; + default: + return 'Albums/Artist/Album Name/'; + } + } + + String _getFolderOrganizationLabel(String value) { + switch (value) { + case 'playlist': + return 'By Playlist'; + case 'artist': + return 'By Artist'; + case 'album': + return 'By Album'; + case 'artist_album': + return 'Artist/Album'; + default: + return 'None'; + } + } + + String _getPlaylistFolderSubtitle(AppSettings settings) { + if (settings.folderOrganization == 'playlist') { + return context.l10n.downloadCreatePlaylistSourceFolderRedundant; + } + if (settings.createPlaylistFolder) { + return context.l10n.downloadCreatePlaylistSourceFolderEnabled; + } + return context.l10n.downloadCreatePlaylistSourceFolderDisabled; + } + + String _getArtistFolderFilterSubtitle( + BuildContext context, { + required bool usePrimaryArtistOnly, + required bool filterAlbumArtistContributors, + }) { + final statuses = [ + usePrimaryArtistOnly ? 'Primary only: On' : 'Primary only: Off', + filterAlbumArtistContributors + ? 'Album Artist metadata: Primary only' + : 'Album Artist metadata: Full', + ]; + return statuses.join(' | '); + } + + Future _pickDirectory(BuildContext context, WidgetRef ref) async { + if (Platform.isIOS) { + _showIOSDirectoryOptions(context, ref); + } else if (Platform.isAndroid) { + _showAndroidDirectoryOptions(context, ref); + } + } + + Future _getDefaultAndroidDirectory() async { + const directMusicPath = '/storage/emulated/0/Music/SpotiFLAC'; + try { + final musicDir = Directory(directMusicPath); + if (!await musicDir.exists()) await musicDir.create(recursive: true); + return musicDir.path; + } catch (_) {} + try { + final externalDir = await getExternalStorageDirectory(); + if (externalDir != null) { + final musicDir = Directory( + '${externalDir.parent.parent.parent.parent.path}/Music/SpotiFLAC', + ); + if (!await musicDir.exists()) await musicDir.create(recursive: true); + return musicDir.path; + } + } catch (_) {} + final appDir = await getApplicationDocumentsDirectory(); + final fallbackDir = Directory('${appDir.path}/SpotiFLAC'); + if (!await fallbackDir.exists()) await fallbackDir.create(recursive: true); + return fallbackDir.path; + } + + void _showAndroidDirectoryOptions(BuildContext context, WidgetRef ref) { + final colorScheme = Theme.of(context).colorScheme; + final settings = ref.read(settingsProvider); + final isSafMode = + settings.storageMode == 'saf' && settings.downloadTreeUri.isNotEmpty; + showModalBottomSheet( + context: context, + useRootNavigator: true, + backgroundColor: colorScheme.surfaceContainerHigh, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(28)), + ), + builder: (ctx) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), + child: Text( + context.l10n.setupDownloadLocationTitle, + style: Theme.of(ctx).textTheme.titleLarge + ?.copyWith(fontWeight: FontWeight.bold), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), + child: Text( + context.l10n.downloadLocationSubtitle, + style: Theme.of(ctx).textTheme.bodyMedium + ?.copyWith(color: colorScheme.onSurfaceVariant), + ), + ), + ListTile( + leading: Icon(Icons.folder_special, color: colorScheme.primary), + title: Text(context.l10n.storageModeAppFolder), + subtitle: Text(context.l10n.storageModeAppFolderSubtitle), + trailing: !isSafMode ? const Icon(Icons.check) : null, + onTap: () async { + Navigator.pop(ctx); + final defaultDir = await _getDefaultAndroidDirectory(); + final notifier = ref.read(settingsProvider.notifier); + notifier.setStorageMode('app'); + notifier.setDownloadDirectory(defaultDir); + notifier.setDownloadTreeUri(''); + }, + ), + ListTile( + leading: Icon(Icons.folder_open, color: colorScheme.primary), + title: Text(context.l10n.storageModeSaf), + subtitle: Text(context.l10n.storageModeSafSubtitle), + trailing: isSafMode ? const Icon(Icons.check) : null, + onTap: () async { + Navigator.pop(ctx); + final result = await PlatformBridge.pickSafTree(); + if (result != null) { + final treeUri = result['tree_uri'] as String? ?? ''; + final displayName = result['display_name'] as String? ?? ''; + if (treeUri.isNotEmpty) { + ref.read(settingsProvider.notifier).setStorageMode('saf'); + ref.read(settingsProvider.notifier).setDownloadTreeUri( + treeUri, + displayName: displayName.isNotEmpty ? displayName : treeUri, + ); + } + } + }, + ), + const SizedBox(height: 8), + ], + ), + ), + ); + } + + void _showIOSDirectoryOptions(BuildContext context, WidgetRef ref) { + final colorScheme = Theme.of(context).colorScheme; + showModalBottomSheet( + context: context, + useRootNavigator: true, + backgroundColor: colorScheme.surfaceContainerHigh, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(28)), + ), + builder: (ctx) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), + child: Text( + context.l10n.setupDownloadLocationTitle, + style: Theme.of(context).textTheme.titleLarge + ?.copyWith(fontWeight: FontWeight.bold), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), + child: Text( + context.l10n.setupDownloadLocationIosMessage, + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith(color: colorScheme.onSurfaceVariant), + ), + ), + ListTile( + leading: Icon(Icons.folder_special, color: colorScheme.primary), + title: Text(context.l10n.setupAppDocumentsFolder), + subtitle: Text(context.l10n.setupAppDocumentsFolderSubtitle), + trailing: Icon(Icons.check_circle, color: colorScheme.primary), + onTap: () async { + final dir = await getApplicationDocumentsDirectory(); + ref + .read(settingsProvider.notifier) + .setDownloadDirectory(dir.path); + if (ctx.mounted) Navigator.pop(ctx); + }, + ), + ListTile( + leading: Icon(Icons.cloud, color: colorScheme.onSurfaceVariant), + title: Text(context.l10n.setupChooseFromFiles), + subtitle: Text(context.l10n.setupChooseFromFilesSubtitle), + onTap: () async { + Navigator.pop(ctx); + if (Platform.isIOS) { + await Future.delayed(const Duration(milliseconds: 250)); + } + String? result; + try { + result = await FilePicker.platform.getDirectoryPath(); + } catch (e) { + if (ctx.mounted) { + ScaffoldMessenger.of(ctx).showSnackBar( + SnackBar( + content: Text( + ctx.l10n.snackbarFolderPickerFailed(e.toString()), + ), + backgroundColor: Theme.of(ctx).colorScheme.error, + duration: const Duration(seconds: 4), + ), + ); + } + return; + } + if (result != null) { + if (Platform.isIOS) { + final validation = validateIosPath(result); + if (!validation.isValid) { + if (ctx.mounted) { + ScaffoldMessenger.of(ctx).showSnackBar( + SnackBar( + content: Text( + validation.errorReason ?? + context.l10n.setupIcloudNotSupported, + ), + backgroundColor: Theme.of(ctx).colorScheme.error, + duration: const Duration(seconds: 4), + ), + ); + } + return; + } + } + ref + .read(settingsProvider.notifier) + .setDownloadDirectory(result); + } + }, + ), + Padding( + padding: const EdgeInsets.fromLTRB(24, 8, 24, 16), + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colorScheme.tertiaryContainer.withValues(alpha: 0.3), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Icon(Icons.info_outline, size: 20, color: colorScheme.tertiary), + const SizedBox(width: 12), + Expanded( + child: Text( + context.l10n.setupIosEmptyFolderWarning, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: colorScheme.onTertiaryContainer, + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 8), + ], + ), + ), + ); + } + + void _showFormatEditor( + BuildContext context, + WidgetRef ref, + String current, { + void Function(String)? onSave, + String? title, + String? description, + }) { + final controller = TextEditingController(text: current); + final colorScheme = Theme.of(context).colorScheme; + + final basicTags = [ + '{artist}', '{title}', '{album}', '{track}', '{year}', '{date}', '{disc}', + ]; + final advancedTags = [ + '{track_raw}', '{track:02}', '{track:1}', + '{date:%Y}', '{date:%Y-%m-%d}', '{disc_raw}', '{disc:02}', + ]; + var showAdvancedTags = RegExp( + r'\{(?:track_raw|disc_raw|track:\d+|disc:\d+|date:[^}]+)\}', + caseSensitive: false, + ).hasMatch(current); + + void insertTag(String tag) { + final text = controller.text; + final selection = controller.selection; + final start = selection.start >= 0 ? selection.start : text.length; + final end = selection.end >= 0 ? selection.end : text.length; + String insertion = tag; + if (start > 0) { + final before = text.substring(0, start); + if (!before.trim().endsWith('-')) { + insertion = ' - $tag'; + } else if (before.trim().endsWith('-') && !before.endsWith(' ')) { + insertion = ' $tag'; + } + } + final newText = text.replaceRange(start, end, insertion); + controller.value = TextEditingValue( + text: newText, + selection: TextSelection.collapsed(offset: start + insertion.length), + ); + } + + showModalBottomSheet( + context: context, + useRootNavigator: true, + isScrollControlled: true, + backgroundColor: colorScheme.surface, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(28)), + ), + builder: (context) => StatefulBuilder( + builder: (context, setModalState) => 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( + title ?? context.l10n.filenameFormat, + style: Theme.of(context).textTheme.headlineSmall + ?.copyWith(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + description ?? context.l10n.downloadFilenameDescription, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + TextField( + controller: controller, + decoration: InputDecoration( + hintText: '{artist} - {title}', + filled: true, + fillColor: colorScheme.surfaceContainerHighest + .withValues(alpha: 0.3), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide.none, + ), + ), + autofocus: true, + ), + const SizedBox(height: 24), + Text( + context.l10n.downloadFilenameInsertTag, + style: Theme.of(context).textTheme.titleSmall + ?.copyWith(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + Wrap( + spacing: 8, + runSpacing: 8, + children: basicTags.map((tag) { + return ActionChip( + label: Text(tag), + onPressed: () => insertTag(tag), + backgroundColor: colorScheme.surfaceContainerHighest + .withValues(alpha: 0.5), + side: BorderSide.none, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + labelStyle: TextStyle( + color: colorScheme.onSurface, + fontWeight: FontWeight.w500, + ), + ); + }).toList(), + ), + const SizedBox(height: 12), + SwitchListTile( + value: showAdvancedTags, + onChanged: (value) => + setModalState(() => showAdvancedTags = value), + contentPadding: EdgeInsets.zero, + title: Text(context.l10n.filenameShowAdvancedTags), + subtitle: Text( + context.l10n.filenameShowAdvancedTagsDescription, + ), + ), + if (showAdvancedTags) ...[ + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 8, + children: advancedTags.map((tag) { + return ActionChip( + label: Text(tag), + onPressed: () => insertTag(tag), + backgroundColor: colorScheme.surfaceContainerHighest + .withValues(alpha: 0.5), + side: BorderSide.none, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + labelStyle: TextStyle( + color: colorScheme.onSurface, + fontWeight: FontWeight.w500, + ), + ); + }).toList(), + ), + ], + const SizedBox(height: 32), + Row( + children: [ + Expanded( + child: TextButton( + onPressed: () => Navigator.pop(context), + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: Text(context.l10n.dialogCancel), + ), + ), + const SizedBox(width: 12), + Expanded( + flex: 2, + child: FilledButton( + onPressed: () { + final save = onSave ?? + ref + .read(settingsProvider.notifier) + .setFilenameFormat; + save(controller.text); + Navigator.pop(context); + }, + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: Text(context.l10n.dialogSave), + ), + ), + ], + ), + const SizedBox(height: 8), + ], + ), + ), + ), + ), + ), + ), + ); + } + + void _showAlbumFolderStructurePicker( + BuildContext context, + WidgetRef ref, + String current, + ) { + showModalBottomSheet( + context: context, + useRootNavigator: true, + builder: (context) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final option in [ + ('artist_album', context.l10n.albumFolderArtistAlbum, + context.l10n.albumFolderArtistAlbumSubtitle, + Icons.folder_outlined), + ('artist_year_album', context.l10n.albumFolderArtistYearAlbum, + context.l10n.albumFolderArtistYearAlbumSubtitle, + Icons.calendar_today_outlined), + ('album_only', context.l10n.albumFolderAlbumOnly, + context.l10n.albumFolderAlbumOnlySubtitle, Icons.album_outlined), + ('year_album', context.l10n.albumFolderYearAlbum, + context.l10n.albumFolderYearAlbumSubtitle, + Icons.event_outlined), + ('artist_album_singles', context.l10n.albumFolderArtistAlbumSingles, + context.l10n.albumFolderArtistAlbumSinglesSubtitle, + Icons.person_outlined), + ('artist_album_flat', context.l10n.albumFolderArtistAlbumFlat, + context.l10n.albumFolderArtistAlbumFlatSubtitle, + Icons.person_outline_outlined), + ]) + ListTile( + leading: Icon(option.$4), + title: Text(option.$2), + subtitle: Text(option.$3), + trailing: current == option.$1 + ? const Icon(Icons.check) + : null, + onTap: () { + ref + .read(settingsProvider.notifier) + .setAlbumFolderStructure(option.$1); + Navigator.pop(context); + }, + ), + ], + ), + ), + ); + } + + void _showFolderOrganizationPicker( + BuildContext context, + WidgetRef ref, + String current, + ) { + final colorScheme = Theme.of(context).colorScheme; + showModalBottomSheet( + context: context, + useRootNavigator: true, + backgroundColor: colorScheme.surfaceContainerHigh, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(28)), + ), + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.7, + ), + builder: (context) => SafeArea( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), + child: Text( + context.l10n.downloadFolderOrganization, + style: Theme.of(context).textTheme.titleLarge + ?.copyWith(fontWeight: FontWeight.bold), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), + child: Text( + context.l10n.folderOrganizationDescription, + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith(color: colorScheme.onSurfaceVariant), + ), + ), + for (final option in [ + ('none', context.l10n.folderOrganizationNone, + context.l10n.folderOrganizationNoneSubtitle, + 'SpotiFLAC/Track.flac'), + ('playlist', context.l10n.folderOrganizationByPlaylist, + context.l10n.folderOrganizationByPlaylistSubtitle, + 'SpotiFLAC/Playlist Name/Track.flac'), + ('artist', context.l10n.folderOrganizationByArtist, + context.l10n.folderOrganizationByArtistSubtitle, + 'SpotiFLAC/Artist Name/Track.flac'), + ('album', context.l10n.folderOrganizationByAlbum, + context.l10n.folderOrganizationByAlbumSubtitle, + 'SpotiFLAC/Album Name/Track.flac'), + ('artist_album', context.l10n.folderOrganizationByArtistAlbum, + context.l10n.folderOrganizationByArtistAlbumSubtitle, + 'SpotiFLAC/Artist/Album/Track.flac'), + ]) + _FolderOption( + title: option.$2, + subtitle: option.$3, + example: option.$4, + isSelected: current == option.$1, + onTap: () { + ref + .read(settingsProvider.notifier) + .setFolderOrganization(option.$1); + Navigator.pop(context); + }, + ), + ], + ), + ), + ), + ); + } +} + +class _FolderOption extends StatelessWidget { + final String title; + final String subtitle; + final String example; + final bool isSelected; + final VoidCallback onTap; + const _FolderOption({ + required this.title, + required this.subtitle, + required this.example, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 4), + title: Text(title), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(subtitle), + const SizedBox(height: 4), + Text( + example, + style: TextStyle( + fontFamily: 'monospace', + fontSize: 11, + color: colorScheme.primary, + ), + ), + ], + ), + trailing: isSelected + ? Icon(Icons.check_circle, color: colorScheme.primary) + : Icon(Icons.circle_outlined, color: colorScheme.outline), + onTap: onTap, + ); + } +} diff --git a/lib/screens/settings/lyrics_settings_page.dart b/lib/screens/settings/lyrics_settings_page.dart new file mode 100644 index 00000000..862da242 --- /dev/null +++ b/lib/screens/settings/lyrics_settings_page.dart @@ -0,0 +1,373 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:spotiflac_android/l10n/l10n.dart'; +import 'package:spotiflac_android/providers/settings_provider.dart'; +import 'package:spotiflac_android/utils/app_bar_layout.dart'; +import 'package:spotiflac_android/screens/settings/lyrics_provider_priority_page.dart'; +import 'package:spotiflac_android/widgets/settings_group.dart'; + +class LyricsSettingsPage extends ConsumerWidget { + const LyricsSettingsPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final settings = ref.watch(settingsProvider); + final colorScheme = Theme.of(context).colorScheme; + final topPadding = normalizedHeaderTopPadding(context); + + return PopScope( + canPop: true, + child: Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + 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( + context.l10n.settingsLyrics, + style: TextStyle( + fontSize: 20 + (8 * expandRatio), + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + ); + }, + ), + ), + + // ── Lyrics Embedding ─────────────────────────────────────── + SliverToBoxAdapter( + child: SettingsSectionHeader(title: context.l10n.sectionLyrics), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsSwitchItem( + icon: Icons.subtitles_outlined, + title: context.l10n.optionsEmbedLyrics, + subtitle: settings.embedMetadata + ? context.l10n.optionsEmbedLyricsSubtitle + : context.l10n.downloadEmbedLyricsDisabled, + value: settings.embedLyrics, + enabled: settings.embedMetadata, + onChanged: (value) => ref + .read(settingsProvider.notifier) + .setEmbedLyrics(value), + showDivider: + settings.embedMetadata && settings.embedLyrics, + ), + if (settings.embedMetadata && settings.embedLyrics) ...[ + SettingsItem( + icon: Icons.lyrics_outlined, + title: context.l10n.lyricsMode, + subtitle: _getLyricsModeLabel( + context, + settings.lyricsMode, + ), + onTap: () => + _showLyricsModePicker(context, ref, settings.lyricsMode), + ), + SettingsItem( + icon: Icons.source_outlined, + title: context.l10n.lyricsProvidersTitle, + subtitle: _getLyricsProvidersSubtitle( + context, + settings.lyricsProviders, + ), + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const LyricsProviderPriorityPage(), + ), + ), + showDivider: false, + ), + ], + ], + ), + ), + + // ── Provider Options ─────────────────────────────────────── + if (settings.embedMetadata && settings.embedLyrics) ...[ + SliverToBoxAdapter( + child: SettingsSectionHeader( + title: context.l10n.sectionLyricsProviderOptions, + ), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsSwitchItem( + icon: Icons.translate_outlined, + title: context.l10n.downloadNeteaseIncludeTranslation, + subtitle: settings.lyricsIncludeTranslationNetease + ? context.l10n.downloadNeteaseIncludeTranslationEnabled + : context.l10n.downloadNeteaseIncludeTranslationDisabled, + value: settings.lyricsIncludeTranslationNetease, + onChanged: (value) => ref + .read(settingsProvider.notifier) + .setLyricsIncludeTranslationNetease(value), + ), + SettingsSwitchItem( + icon: Icons.text_fields_outlined, + title: context.l10n.downloadNeteaseIncludeRomanization, + subtitle: settings.lyricsIncludeRomanizationNetease + ? context + .l10n + .downloadNeteaseIncludeRomanizationEnabled + : context + .l10n + .downloadNeteaseIncludeRomanizationDisabled, + value: settings.lyricsIncludeRomanizationNetease, + onChanged: (value) => ref + .read(settingsProvider.notifier) + .setLyricsIncludeRomanizationNetease(value), + ), + SettingsSwitchItem( + icon: Icons.record_voice_over_outlined, + title: context.l10n.downloadAppleQqMultiPerson, + subtitle: settings.lyricsMultiPersonWordByWord + ? context.l10n.downloadAppleQqMultiPersonEnabled + : context.l10n.downloadAppleQqMultiPersonDisabled, + value: settings.lyricsMultiPersonWordByWord, + onChanged: (value) => ref + .read(settingsProvider.notifier) + .setLyricsMultiPersonWordByWord(value), + ), + SettingsItem( + icon: Icons.language_outlined, + title: context.l10n.downloadMusixmatchLanguage, + subtitle: settings.musixmatchLanguage.isEmpty + ? context.l10n.downloadMusixmatchLanguageAuto + : settings.musixmatchLanguage.toUpperCase(), + onTap: () => _showMusixmatchLanguagePicker( + context, + ref, + settings.musixmatchLanguage, + ), + showDivider: false, + ), + ], + ), + ), + ], + + const SliverToBoxAdapter(child: SizedBox(height: 32)), + ], + ), + ), + ); + } + + String _getLyricsModeLabel(BuildContext context, String mode) { + switch (mode) { + case 'external': + return context.l10n.lyricsModeExternal; + case 'both': + return context.l10n.lyricsModeBoth; + default: + return context.l10n.lyricsModeEmbed; + } + } + + static const _providerDisplayNames = { + 'lrclib': 'LRCLIB', + 'netease': 'Netease', + 'musixmatch': 'Musixmatch', + 'apple_music': 'Apple Music', + 'qqmusic': 'QQ Music', + }; + + String _getLyricsProvidersSubtitle( + BuildContext context, + List providers, + ) { + if (providers.isEmpty) return context.l10n.downloadProvidersNoneEnabled; + return providers + .map((p) => _providerDisplayNames[p] ?? p) + .join(' > '); + } + + void _showLyricsModePicker( + BuildContext context, + WidgetRef ref, + String current, + ) { + final colorScheme = Theme.of(context).colorScheme; + showModalBottomSheet( + context: context, + useRootNavigator: true, + backgroundColor: colorScheme.surfaceContainerHigh, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(28)), + ), + builder: (context) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), + child: Text( + context.l10n.lyricsMode, + style: Theme.of(context).textTheme.titleLarge + ?.copyWith(fontWeight: FontWeight.bold), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), + child: Text( + context.l10n.lyricsModeDescription, + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith(color: colorScheme.onSurfaceVariant), + ), + ), + ListTile( + leading: const Icon(Icons.audiotrack), + title: Text(context.l10n.lyricsModeEmbed), + subtitle: Text(context.l10n.lyricsModeEmbedSubtitle), + trailing: current == 'embed' ? const Icon(Icons.check) : null, + onTap: () { + ref.read(settingsProvider.notifier).setLyricsMode('embed'); + Navigator.pop(context); + }, + ), + ListTile( + leading: const Icon(Icons.insert_drive_file_outlined), + title: Text(context.l10n.lyricsModeExternal), + subtitle: Text(context.l10n.lyricsModeExternalSubtitle), + trailing: current == 'external' ? const Icon(Icons.check) : null, + onTap: () { + ref.read(settingsProvider.notifier).setLyricsMode('external'); + Navigator.pop(context); + }, + ), + ListTile( + leading: const Icon(Icons.library_music_outlined), + title: Text(context.l10n.lyricsModeBoth), + subtitle: Text(context.l10n.lyricsModeBothSubtitle), + trailing: current == 'both' ? const Icon(Icons.check) : null, + onTap: () { + ref.read(settingsProvider.notifier).setLyricsMode('both'); + Navigator.pop(context); + }, + ), + const SizedBox(height: 16), + ], + ), + ), + ); + } + + void _showMusixmatchLanguagePicker( + BuildContext context, + WidgetRef ref, + String currentLanguage, + ) { + final colorScheme = Theme.of(context).colorScheme; + final controller = TextEditingController(text: currentLanguage); + + showModalBottomSheet( + context: context, + useRootNavigator: true, + backgroundColor: colorScheme.surfaceContainerHigh, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(28)), + ), + isScrollControlled: true, + builder: (context) => Padding( + padding: EdgeInsets.only( + left: 24, + right: 24, + top: 24, + bottom: 24 + MediaQuery.of(context).viewInsets.bottom, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.downloadMusixmatchLanguage, + style: Theme.of(context).textTheme.titleLarge + ?.copyWith(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Text( + context.l10n.downloadMusixmatchLanguageDesc, + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith(color: colorScheme.onSurfaceVariant), + ), + const SizedBox(height: 16), + TextField( + controller: controller, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + labelText: context.l10n.downloadMusixmatchLanguageCode, + hintText: context.l10n.downloadMusixmatchLanguageHint, + ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(context.l10n.dialogCancel), + ), + const SizedBox(width: 8), + TextButton( + onPressed: () { + ref + .read(settingsProvider.notifier) + .setMusixmatchLanguage(''); + Navigator.pop(context); + }, + child: Text(context.l10n.downloadMusixmatchAuto), + ), + const SizedBox(width: 8), + FilledButton( + onPressed: () { + final normalized = controller.text + .trim() + .toLowerCase() + .replaceAll(RegExp(r'[^a-z0-9\-_]'), ''); + ref + .read(settingsProvider.notifier) + .setMusixmatchLanguage(normalized); + Navigator.pop(context); + }, + child: Text(context.l10n.dialogSave), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/settings/metadata_settings_page.dart b/lib/screens/settings/metadata_settings_page.dart new file mode 100644 index 00000000..83f1afb1 --- /dev/null +++ b/lib/screens/settings/metadata_settings_page.dart @@ -0,0 +1,253 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:spotiflac_android/l10n/l10n.dart'; +import 'package:spotiflac_android/providers/settings_provider.dart'; +import 'package:spotiflac_android/utils/app_bar_layout.dart'; +import 'package:spotiflac_android/utils/artist_utils.dart'; +import 'package:spotiflac_android/screens/settings/metadata_provider_priority_page.dart'; +import 'package:spotiflac_android/widgets/settings_group.dart'; + +class MetadataSettingsPage extends ConsumerWidget { + const MetadataSettingsPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final settings = ref.watch(settingsProvider); + final colorScheme = Theme.of(context).colorScheme; + final topPadding = normalizedHeaderTopPadding(context); + + return PopScope( + canPop: true, + child: Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + 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( + context.l10n.settingsMetadata, + style: TextStyle( + fontSize: 20 + (8 * expandRatio), + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + ); + }, + ), + ), + + // ── Embedding ────────────────────────────────────────────── + SliverToBoxAdapter( + child: SettingsSectionHeader(title: context.l10n.sectionDownload), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsSwitchItem( + icon: Icons.sell_outlined, + title: 'Embed Metadata', + subtitle: settings.embedMetadata + ? 'Write metadata, cover art, and lyrics to files' + : 'Disabled (advanced): skip all metadata embedding', + value: settings.embedMetadata, + onChanged: (v) => + ref.read(settingsProvider.notifier).setEmbedMetadata(v), + showDivider: settings.embedMetadata, + ), + if (settings.embedMetadata) ...[ + SettingsItem( + icon: Icons.people_alt_outlined, + title: context.l10n.optionsArtistTagMode, + subtitle: _getArtistTagModeLabel( + context, + settings.artistTagMode, + ), + onTap: () => + _showArtistTagModePicker(context, ref, settings.artistTagMode), + ), + SettingsSwitchItem( + icon: Icons.image, + title: context.l10n.optionsMaxQualityCover, + subtitle: context.l10n.optionsMaxQualityCoverSubtitle, + value: settings.maxQualityCover, + onChanged: (v) => ref + .read(settingsProvider.notifier) + .setMaxQualityCover(v), + ), + SettingsSwitchItem( + icon: Icons.graphic_eq, + title: context.l10n.optionsReplayGain, + subtitle: settings.embedReplayGain + ? context.l10n.optionsReplayGainSubtitleOn + : context.l10n.optionsReplayGainSubtitleOff, + value: settings.embedReplayGain, + onChanged: (v) => ref + .read(settingsProvider.notifier) + .setEmbedReplayGain(v), + showDivider: false, + ), + ], + ], + ), + ), + + // ── Providers ───────────────────────────────────────────── + SliverToBoxAdapter( + child: SettingsSectionHeader( + title: context.l10n.sectionMetadataProviders, + ), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsItem( + icon: Icons.source_outlined, + title: context.l10n.metadataProvidersTitle, + subtitle: context.l10n.metadataProvidersSubtitle, + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const MetadataProviderPriorityPage(), + ), + ), + showDivider: false, + ), + ], + ), + ), + + // ── Deduplication ────────────────────────────────────────── + SliverToBoxAdapter( + child: SettingsSectionHeader( + title: context.l10n.sectionDuplicates, + ), + ), + SliverToBoxAdapter( + child: SettingsGroup( + children: [ + SettingsSwitchItem( + icon: Icons.filter_list_outlined, + title: context.l10n.downloadDeduplication, + subtitle: settings.deduplicateDownloads + ? context.l10n.downloadDeduplicationEnabled + : context.l10n.downloadDeduplicationDisabled, + value: settings.deduplicateDownloads, + onChanged: (value) => ref + .read(settingsProvider.notifier) + .setDeduplicateDownloads(value), + showDivider: false, + ), + ], + ), + ), + + const SliverToBoxAdapter(child: SizedBox(height: 32)), + ], + ), + ), + ); + } + + String _getArtistTagModeLabel(BuildContext context, String mode) { + switch (mode) { + case artistTagModeSplitVorbis: + return context.l10n.optionsArtistTagModeSplitVorbis; + default: + return context.l10n.optionsArtistTagModeJoined; + } + } + + void _showArtistTagModePicker( + BuildContext context, + WidgetRef ref, + String currentMode, + ) { + final colorScheme = Theme.of(context).colorScheme; + showModalBottomSheet( + context: context, + useRootNavigator: true, + backgroundColor: colorScheme.surfaceContainerHigh, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(28)), + ), + builder: (context) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), + child: Text( + context.l10n.optionsArtistTagMode, + style: Theme.of(context).textTheme.titleLarge + ?.copyWith(fontWeight: FontWeight.bold), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), + child: Text( + context.l10n.optionsArtistTagModeDescription, + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith(color: colorScheme.onSurfaceVariant), + ), + ), + ListTile( + leading: const Icon(Icons.segment_outlined), + title: Text(context.l10n.optionsArtistTagModeJoined), + subtitle: Text(context.l10n.optionsArtistTagModeJoinedSubtitle), + trailing: currentMode == artistTagModeJoined + ? const Icon(Icons.check) + : null, + onTap: () { + ref + .read(settingsProvider.notifier) + .setArtistTagMode(artistTagModeJoined); + Navigator.pop(context); + }, + ), + ListTile( + leading: const Icon(Icons.library_music_outlined), + title: Text(context.l10n.optionsArtistTagModeSplitVorbis), + subtitle: Text(context.l10n.optionsArtistTagModeSplitVorbisSubtitle), + trailing: currentMode == artistTagModeSplitVorbis + ? const Icon(Icons.check) + : null, + onTap: () { + ref + .read(settingsProvider.notifier) + .setArtistTagMode(artistTagModeSplitVorbis); + Navigator.pop(context); + }, + ), + const SizedBox(height: 16), + ], + ), + ), + ); + } +} diff --git a/lib/screens/settings/options_settings_page.dart b/lib/screens/settings/options_settings_page.dart deleted file mode 100644 index 147394aa..00000000 --- a/lib/screens/settings/options_settings_page.dart +++ /dev/null @@ -1,1012 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotiflac_android/l10n/l10n.dart'; -import 'package:spotiflac_android/providers/download_queue_provider.dart'; -import 'package:spotiflac_android/providers/extension_provider.dart'; -import 'package:spotiflac_android/providers/settings_provider.dart'; -import 'package:spotiflac_android/utils/app_bar_layout.dart'; -import 'package:spotiflac_android/utils/artist_utils.dart'; -import 'package:spotiflac_android/widgets/settings_group.dart'; - -class OptionsSettingsPage extends ConsumerWidget { - const OptionsSettingsPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final settings = ref.watch(settingsProvider); - final extensionState = ref.watch(extensionProvider); - final hasExtensions = extensionState.extensions.isNotEmpty; - final colorScheme = Theme.of(context).colorScheme; - final topPadding = normalizedHeaderTopPadding(context); - - return PopScope( - canPop: true, - child: Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - 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( - context.l10n.optionsTitle, - style: TextStyle( - fontSize: 20 + (8 * expandRatio), - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), - ), - ); - }, - ), - ), - - SliverToBoxAdapter( - child: SettingsSectionHeader( - title: context.l10n.sectionSearchSource, - ), - ), - SliverToBoxAdapter( - child: SettingsGroup( - children: const [ - _MetadataSourceSelector(), - _DefaultSearchTabSelector(), - ], - ), - ), - - SliverToBoxAdapter( - child: SettingsSectionHeader(title: context.l10n.sectionDownload), - ), - SliverToBoxAdapter( - child: SettingsGroup( - children: [ - SettingsSwitchItem( - icon: Icons.sync, - title: context.l10n.optionsAutoFallback, - subtitle: context.l10n.optionsAutoFallbackSubtitle, - value: settings.autoFallback, - onChanged: (v) => - ref.read(settingsProvider.notifier).setAutoFallback(v), - ), - if (hasExtensions) - SettingsSwitchItem( - icon: Icons.extension, - title: context.l10n.optionsUseExtensionProviders, - subtitle: settings.useExtensionProviders - ? context.l10n.optionsUseExtensionProvidersOn - : context.l10n.optionsUseExtensionProvidersOff, - value: settings.useExtensionProviders, - onChanged: (v) => ref - .read(settingsProvider.notifier) - .setUseExtensionProviders(v), - ), - SettingsSwitchItem( - icon: Icons.sell_outlined, - title: 'Embed Metadata', - subtitle: settings.embedMetadata - ? 'Write metadata, cover art, and embedded lyrics to files' - : 'Disabled (advanced): skip all metadata embedding', - value: settings.embedMetadata, - onChanged: (v) => - ref.read(settingsProvider.notifier).setEmbedMetadata(v), - showDivider: settings.embedMetadata, - ), - if (settings.embedMetadata) - SettingsItem( - icon: Icons.people_alt_outlined, - title: context.l10n.optionsArtistTagMode, - subtitle: _getArtistTagModeLabel( - context, - settings.artistTagMode, - ), - onTap: () => _showArtistTagModePicker( - context, - ref, - settings.artistTagMode, - ), - ), - SettingsSwitchItem( - icon: Icons.image, - title: context.l10n.optionsMaxQualityCover, - subtitle: settings.embedMetadata - ? context.l10n.optionsMaxQualityCoverSubtitle - : 'Disabled when metadata embedding is off', - value: settings.maxQualityCover, - enabled: settings.embedMetadata, - onChanged: (v) => ref - .read(settingsProvider.notifier) - .setMaxQualityCover(v), - ), - SettingsSwitchItem( - icon: Icons.graphic_eq, - title: context.l10n.optionsReplayGain, - subtitle: settings.embedReplayGain - ? context.l10n.optionsReplayGainSubtitleOn - : context.l10n.optionsReplayGainSubtitleOff, - value: settings.embedReplayGain, - enabled: settings.embedMetadata, - onChanged: (v) => ref - .read(settingsProvider.notifier) - .setEmbedReplayGain(v), - showDivider: false, - ), - ], - ), - ), - - SliverToBoxAdapter( - child: SettingsSectionHeader( - title: context.l10n.sectionPerformance, - ), - ), - SliverToBoxAdapter( - child: SettingsGroup( - children: [ - _ConcurrentDownloadsItem( - currentValue: settings.concurrentDownloads, - onChanged: (v) => ref - .read(settingsProvider.notifier) - .setConcurrentDownloads(v), - ), - ], - ), - ), - - SliverToBoxAdapter( - child: SettingsSectionHeader(title: context.l10n.sectionApp), - ), - SliverToBoxAdapter( - child: SettingsGroup( - children: [ - SettingsSwitchItem( - icon: Icons.extension, - title: context.l10n.optionsExtensionStore, - subtitle: context.l10n.optionsExtensionStoreSubtitle, - value: settings.showExtensionStore, - onChanged: (v) => ref - .read(settingsProvider.notifier) - .setShowExtensionStore(v), - ), - SettingsSwitchItem( - icon: Icons.system_update, - title: context.l10n.optionsCheckUpdates, - subtitle: context.l10n.optionsCheckUpdatesSubtitle, - value: settings.checkForUpdates, - onChanged: (v) => ref - .read(settingsProvider.notifier) - .setCheckForUpdates(v), - ), - _UpdateChannelSelector( - currentChannel: settings.updateChannel, - onChanged: (v) => - ref.read(settingsProvider.notifier).setUpdateChannel(v), - ), - ], - ), - ), - - SliverToBoxAdapter( - child: SettingsSectionHeader(title: context.l10n.sectionData), - ), - SliverToBoxAdapter( - child: SettingsGroup( - children: [ - SettingsItem( - icon: Icons.cleaning_services_outlined, - title: context.l10n.cleanupOrphanedDownloads, - subtitle: context.l10n.cleanupOrphanedDownloadsSubtitle, - onTap: () => _cleanupOrphanedDownloads(context, ref), - ), - SettingsItem( - icon: Icons.delete_forever, - title: context.l10n.optionsClearHistory, - subtitle: context.l10n.optionsClearHistorySubtitle, - onTap: () => - _showClearHistoryDialog(context, ref, colorScheme), - showDivider: false, - ), - ], - ), - ), - - SliverToBoxAdapter( - child: SettingsSectionHeader(title: context.l10n.sectionDebug), - ), - SliverToBoxAdapter( - child: SettingsGroup( - children: [ - SettingsSwitchItem( - icon: Icons.bug_report, - title: context.l10n.optionsDetailedLogging, - subtitle: settings.enableLogging - ? context.l10n.optionsDetailedLoggingOn - : context.l10n.optionsDetailedLoggingOff, - value: settings.enableLogging, - onChanged: (v) => - ref.read(settingsProvider.notifier).setEnableLogging(v), - showDivider: false, - ), - ], - ), - ), - - const SliverToBoxAdapter(child: SizedBox(height: 32)), - ], - ), - ), - ); - } - - String _getArtistTagModeLabel(BuildContext context, String mode) { - switch (mode) { - case artistTagModeSplitVorbis: - return context.l10n.optionsArtistTagModeSplitVorbis; - default: - return context.l10n.optionsArtistTagModeJoined; - } - } - - void _showArtistTagModePicker( - BuildContext context, - WidgetRef ref, - String currentMode, - ) { - final colorScheme = Theme.of(context).colorScheme; - showModalBottomSheet( - context: context, - useRootNavigator: true, - backgroundColor: colorScheme.surfaceContainerHigh, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(28)), - ), - builder: (context) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), - child: Text( - context.l10n.optionsArtistTagMode, - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), - child: Text( - context.l10n.optionsArtistTagModeDescription, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ), - ListTile( - leading: const Icon(Icons.segment_outlined), - title: Text(context.l10n.optionsArtistTagModeJoined), - subtitle: Text(context.l10n.optionsArtistTagModeJoinedSubtitle), - trailing: currentMode == artistTagModeJoined - ? const Icon(Icons.check) - : null, - onTap: () { - ref - .read(settingsProvider.notifier) - .setArtistTagMode(artistTagModeJoined); - Navigator.pop(context); - }, - ), - ListTile( - leading: const Icon(Icons.library_music_outlined), - title: Text(context.l10n.optionsArtistTagModeSplitVorbis), - subtitle: Text( - context.l10n.optionsArtistTagModeSplitVorbisSubtitle, - ), - trailing: currentMode == artistTagModeSplitVorbis - ? const Icon(Icons.check) - : null, - onTap: () { - ref - .read(settingsProvider.notifier) - .setArtistTagMode(artistTagModeSplitVorbis); - Navigator.pop(context); - }, - ), - const SizedBox(height: 16), - ], - ), - ), - ); - } - - void _showClearHistoryDialog( - BuildContext context, - WidgetRef ref, - ColorScheme colorScheme, - ) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(context.l10n.dialogClearHistoryTitle), - content: Text(context.l10n.dialogClearHistoryMessage), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(context.l10n.dialogCancel), - ), - TextButton( - onPressed: () { - ref.read(downloadHistoryProvider.notifier).clearHistory(); - Navigator.pop(context); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.snackbarHistoryCleared)), - ); - }, - child: Text( - context.l10n.dialogClear, - style: TextStyle(color: colorScheme.error), - ), - ), - ], - ), - ); - } - - Future _cleanupOrphanedDownloads( - BuildContext context, - WidgetRef ref, - ) async { - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => AlertDialog( - content: Row( - children: [ - const CircularProgressIndicator(), - const SizedBox(width: 16), - Text(context.l10n.cleanupOrphanedDownloads), - ], - ), - ), - ); - - try { - final removed = await ref - .read(downloadHistoryProvider.notifier) - .cleanupOrphanedDownloads(); - - if (context.mounted) { - Navigator.pop(context); // Close loading dialog - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - removed > 0 - ? context.l10n.cleanupOrphanedDownloadsResult(removed) - : context.l10n.cleanupOrphanedDownloadsNone, - ), - ), - ); - } - } catch (e) { - if (context.mounted) { - Navigator.pop(context); // Close loading dialog - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.snackbarError(e.toString()))), - ); - } - } - } -} - -class _ConcurrentDownloadsItem extends StatelessWidget { - final int currentValue; - final ValueChanged onChanged; - const _ConcurrentDownloadsItem({ - required this.currentValue, - required this.onChanged, - }); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - Icons.download_for_offline, - color: colorScheme.onSurfaceVariant, - size: 24, - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.optionsConcurrentDownloads, - style: Theme.of(context).textTheme.bodyLarge, - ), - const SizedBox(height: 2), - Text( - currentValue == 1 - ? context.l10n.optionsConcurrentSequential - : context.l10n.optionsConcurrentParallel( - currentValue, - ), - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 16), - Row( - children: [ - _ConcurrentChip( - label: '1', - isSelected: currentValue == 1, - onTap: () => onChanged(1), - ), - const SizedBox(width: 8), - _ConcurrentChip( - label: '2', - isSelected: currentValue == 2, - onTap: () => onChanged(2), - ), - const SizedBox(width: 8), - _ConcurrentChip( - label: '3', - isSelected: currentValue == 3, - onTap: () => onChanged(3), - ), - const SizedBox(width: 8), - _ConcurrentChip( - label: '4', - isSelected: currentValue == 4, - onTap: () => onChanged(4), - ), - const SizedBox(width: 8), - _ConcurrentChip( - label: '5', - isSelected: currentValue == 5, - onTap: () => onChanged(5), - ), - ], - ), - 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.optionsConcurrentWarning, - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith(color: colorScheme.error), - ), - ), - ], - ), - ], - ), - ); - } -} - -class _ConcurrentChip extends StatelessWidget { - final String label; - final bool isSelected; - final VoidCallback onTap; - const _ConcurrentChip({ - required this.label, - required this.isSelected, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - final isDark = Theme.of(context).brightness == Brightness.dark; - - final unselectedColor = isDark - ? Color.alphaBlend( - Colors.white.withValues(alpha: 0.05), - colorScheme.surface, - ) - : colorScheme.surfaceContainerHigh; - - return Expanded( - child: Material( - color: isSelected ? colorScheme.primaryContainer : unselectedColor, - borderRadius: BorderRadius.circular(12), - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(12), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: Center( - child: Text( - label, - style: TextStyle( - fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, - color: isSelected - ? colorScheme.onPrimaryContainer - : colorScheme.onSurfaceVariant, - ), - ), - ), - ), - ), - ), - ); - } -} - -class _UpdateChannelSelector extends StatelessWidget { - final String currentChannel; - final ValueChanged onChanged; - const _UpdateChannelSelector({ - required this.currentChannel, - required this.onChanged, - }); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - Icons.new_releases, - color: colorScheme.onSurfaceVariant, - size: 24, - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.optionsUpdateChannel, - style: Theme.of(context).textTheme.bodyLarge, - ), - const SizedBox(height: 2), - Text( - currentChannel == 'preview' - ? context.l10n.optionsUpdateChannelPreview - : context.l10n.optionsUpdateChannelStable, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 16), - Row( - children: [ - _ChannelChip( - label: context.l10n.channelStable, - isSelected: currentChannel == 'stable', - onTap: () => onChanged('stable'), - ), - const SizedBox(width: 8), - _ChannelChip( - label: context.l10n.channelPreview, - isSelected: currentChannel == 'preview', - onTap: () => onChanged('preview'), - ), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - Icon( - Icons.info_outline, - size: 16, - color: colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - context.l10n.optionsUpdateChannelWarning, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ), - ], - ), - ], - ), - ); - } -} - -class _ChannelChip extends StatelessWidget { - final String label; - final bool isSelected; - final VoidCallback onTap; - const _ChannelChip({ - required this.label, - required this.isSelected, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - final isDark = Theme.of(context).brightness == Brightness.dark; - - final unselectedColor = isDark - ? Color.alphaBlend( - Colors.white.withValues(alpha: 0.05), - colorScheme.surface, - ) - : colorScheme.surfaceContainerHigh; - - return Expanded( - child: Material( - color: isSelected ? colorScheme.primaryContainer : unselectedColor, - borderRadius: BorderRadius.circular(12), - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(12), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: Center( - child: Text( - label, - style: TextStyle( - fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, - color: isSelected - ? colorScheme.onPrimaryContainer - : colorScheme.onSurfaceVariant, - ), - ), - ), - ), - ), - ), - ); - } -} - -class _MetadataSourceSelector extends ConsumerWidget { - const _MetadataSourceSelector(); - - static const _builtInProviders = {'tidal': 'Tidal', 'qobuz': 'Qobuz'}; - - Extension? _defaultSearchExtension(List extensions) { - return extensions - .where( - (ext) => - ext.enabled && - ext.hasCustomSearch && - ext.searchBehavior?.primary == true, - ) - .firstOrNull ?? - extensions - .where((ext) => ext.enabled && ext.hasCustomSearch) - .firstOrNull; - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - final colorScheme = Theme.of(context).colorScheme; - final settings = ref.watch(settingsProvider); - final extState = ref.watch(extensionProvider); - - final rawSearchProvider = settings.searchProvider?.trim() ?? ''; - final isValidBuiltIn = _builtInProviders.containsKey(rawSearchProvider); - final primarySearchExtension = _defaultSearchExtension(extState.extensions); - final defaultProviderTarget = - primarySearchExtension?.displayName ?? 'Tidal'; - final defaultProviderLabel = - '${context.l10n.extensionsHomeFeedAuto} ($defaultProviderTarget)'; - final searchProvider = - isValidBuiltIn || - extState.extensions.any( - (e) => - e.enabled && e.hasCustomSearch && e.id == rawSearchProvider, - ) - ? rawSearchProvider - : ''; - final isBuiltIn = _builtInProviders.containsKey(searchProvider); - - Extension? activeExtension; - if (searchProvider.isNotEmpty && !isBuiltIn) { - activeExtension = extState.extensions - .where((e) => e.id == searchProvider && e.enabled) - .firstOrNull; - } - final hasNonDefaultProvider = isBuiltIn || activeExtension != null; - - String subtitle; - if (isBuiltIn) { - subtitle = 'Using ${_builtInProviders[searchProvider]}'; - } else if (activeExtension != null) { - subtitle = context.l10n.optionsUsingExtension( - activeExtension.displayName, - ); - } else { - subtitle = context.l10n.optionsPrimaryProviderSubtitle; - } - - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.optionsPrimaryProvider, - style: Theme.of( - context, - ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500), - ), - const SizedBox(height: 4), - Text( - subtitle, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: hasNonDefaultProvider - ? colorScheme.primary - : colorScheme.onSurfaceVariant, - ), - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: _SourceChip( - icon: Icons.graphic_eq, - label: defaultProviderLabel, - isSelected: searchProvider.isEmpty, - onTap: () { - if (hasNonDefaultProvider) { - ref - .read(settingsProvider.notifier) - .setSearchProvider(null); - } - }, - ), - ), - const SizedBox(width: 8), - Expanded( - child: _SourceChip( - icon: Icons.waves, - label: 'Tidal', - isSelected: searchProvider == 'tidal', - onTap: () { - ref - .read(settingsProvider.notifier) - .setSearchProvider('tidal'); - }, - ), - ), - const SizedBox(width: 8), - Expanded( - child: _SourceChip( - icon: Icons.album, - label: 'Qobuz', - isSelected: searchProvider == 'qobuz', - onTap: () { - ref - .read(settingsProvider.notifier) - .setSearchProvider('qobuz'); - }, - ), - ), - ], - ), - if (activeExtension != null) ...[ - const SizedBox(height: 12), - Row( - children: [ - Icon( - Icons.info_outline, - size: 16, - color: colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - 'Tap $defaultProviderLabel to switch back from extension', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ), - ], - ), - ], - ], - ), - ); - } -} - -class _DefaultSearchTabSelector extends ConsumerWidget { - const _DefaultSearchTabSelector(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final colorScheme = Theme.of(context).colorScheme; - final selectedTab = ref.watch( - settingsProvider.select((s) => s.defaultSearchTab), - ); - - return Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.optionsDefaultSearchTab, - style: Theme.of( - context, - ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500), - ), - const SizedBox(height: 4), - Text( - context.l10n.optionsDefaultSearchTabSubtitle, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: _SourceChip( - icon: Icons.dashboard_outlined, - label: context.l10n.historyFilterAll, - isSelected: selectedTab == 'all', - onTap: () => ref - .read(settingsProvider.notifier) - .setDefaultSearchTab('all'), - ), - ), - const SizedBox(width: 8), - Expanded( - child: _SourceChip( - icon: Icons.music_note, - label: context.l10n.searchSongs, - isSelected: selectedTab == 'track', - onTap: () => ref - .read(settingsProvider.notifier) - .setDefaultSearchTab('track'), - ), - ), - const SizedBox(width: 8), - Expanded( - child: _SourceChip( - icon: Icons.person, - label: context.l10n.searchArtists, - isSelected: selectedTab == 'artist', - onTap: () => ref - .read(settingsProvider.notifier) - .setDefaultSearchTab('artist'), - ), - ), - const SizedBox(width: 8), - Expanded( - child: _SourceChip( - icon: Icons.album, - label: context.l10n.searchAlbums, - isSelected: selectedTab == 'album', - onTap: () => ref - .read(settingsProvider.notifier) - .setDefaultSearchTab('album'), - ), - ), - ], - ), - ], - ), - ); - } -} - -class _SourceChip extends StatelessWidget { - final IconData icon; - final String label; - final bool isSelected; - final VoidCallback? onTap; - - const _SourceChip({ - required this.icon, - required this.label, - required this.isSelected, - this.onTap, - }); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - final isDark = Theme.of(context).brightness == Brightness.dark; - - final unselectedColor = isDark - ? Color.alphaBlend( - Colors.white.withValues(alpha: 0.05), - colorScheme.surface, - ) - : colorScheme.surfaceContainerHigh; - - return Material( - color: isSelected ? colorScheme.primaryContainer : unselectedColor, - borderRadius: BorderRadius.circular(12), - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(12), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 18), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - icon, - size: 28, - color: isSelected - ? colorScheme.onPrimaryContainer - : colorScheme.onSurfaceVariant, - ), - const SizedBox(height: 6), - Text( - label, - style: TextStyle( - fontSize: 12, - fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, - color: isSelected - ? colorScheme.onPrimaryContainer - : colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/screens/settings/settings_tab.dart b/lib/screens/settings/settings_tab.dart index f08d967a..97b51288 100644 --- a/lib/screens/settings/settings_tab.dart +++ b/lib/screens/settings/settings_tab.dart @@ -4,9 +4,12 @@ import 'package:spotiflac_android/constants/app_info.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/screens/settings/appearance_settings_page.dart'; import 'package:spotiflac_android/screens/settings/download_settings_page.dart'; +import 'package:spotiflac_android/screens/settings/files_settings_page.dart'; +import 'package:spotiflac_android/screens/settings/lyrics_settings_page.dart'; +import 'package:spotiflac_android/screens/settings/metadata_settings_page.dart'; import 'package:spotiflac_android/screens/settings/extensions_page.dart'; import 'package:spotiflac_android/screens/settings/library_settings_page.dart'; -import 'package:spotiflac_android/screens/settings/options_settings_page.dart'; +import 'package:spotiflac_android/screens/settings/app_settings_page.dart'; import 'package:spotiflac_android/screens/settings/about_page.dart'; import 'package:spotiflac_android/screens/settings/cache_management_page.dart'; import 'package:spotiflac_android/screens/settings/donate_page.dart'; @@ -48,7 +51,7 @@ class SettingsTab extends ConsumerWidget { title: Text( context.l10n.settingsTitle, style: TextStyle( - fontSize: 20 + (14 * expandRatio), // 20 -> 34 + fontSize: 20 + (14 * expandRatio), fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), @@ -58,6 +61,7 @@ class SettingsTab extends ConsumerWidget { ), ), + // ── Group 1: Appearance & Content ────────────────────────────── SliverToBoxAdapter( child: Builder( builder: (context) { @@ -72,6 +76,34 @@ class SettingsTab extends ConsumerWidget { onTap: () => _navigateTo(context, const AppearanceSettingsPage()), ), + SettingsItem( + icon: Icons.library_music_outlined, + title: l10n.settingsLocalLibrary, + subtitle: l10n.settingsLocalLibrarySubtitle, + onTap: () => + _navigateTo(context, const LibrarySettingsPage()), + ), + SettingsItem( + icon: Icons.extension_outlined, + title: l10n.settingsExtensions, + subtitle: l10n.settingsExtensionsSubtitle, + onTap: () => _navigateTo(context, const ExtensionsPage()), + showDivider: false, + ), + ], + ); + }, + ), + ), + + // ── Group 2: Download ────────────────────────────────────────── + SliverToBoxAdapter( + child: Builder( + builder: (context) { + final l10n = context.l10n; + return SettingsGroup( + margin: const EdgeInsets.fromLTRB(16, 4, 16, 4), + children: [ SettingsItem( icon: Icons.download_outlined, title: l10n.settingsDownload, @@ -80,12 +112,41 @@ class SettingsTab extends ConsumerWidget { _navigateTo(context, const DownloadSettingsPage()), ), SettingsItem( - icon: Icons.library_music_outlined, - title: l10n.settingsLocalLibrary, - subtitle: l10n.settingsLocalLibrarySubtitle, + icon: Icons.folder_outlined, + title: l10n.settingsFiles, + subtitle: l10n.settingsFilesSubtitle, onTap: () => - _navigateTo(context, const LibrarySettingsPage()), + _navigateTo(context, const FilesSettingsPage()), ), + SettingsItem( + icon: Icons.sell_outlined, + title: l10n.settingsMetadata, + subtitle: l10n.settingsMetadataSubtitle, + onTap: () => + _navigateTo(context, const MetadataSettingsPage()), + ), + SettingsItem( + icon: Icons.lyrics_outlined, + title: l10n.settingsLyrics, + subtitle: l10n.settingsLyricsSubtitle, + onTap: () => + _navigateTo(context, const LyricsSettingsPage()), + showDivider: false, + ), + ], + ); + }, + ), + ), + + // ── Group 3: App ─────────────────────────────────────────────── + SliverToBoxAdapter( + child: Builder( + builder: (context) { + final l10n = context.l10n; + return SettingsGroup( + margin: const EdgeInsets.fromLTRB(16, 4, 16, 4), + children: [ SettingsItem( icon: Icons.storage_outlined, title: l10n.settingsCache, @@ -95,41 +156,22 @@ class SettingsTab extends ConsumerWidget { ), SettingsItem( icon: Icons.tune_outlined, - title: l10n.settingsOptions, - subtitle: l10n.settingsOptionsSubtitle, + title: l10n.settingsApp, + subtitle: l10n.settingsAppSubtitle, onTap: () => - _navigateTo(context, const OptionsSettingsPage()), + _navigateTo(context, const AppSettingsPage()), ), SettingsItem( - icon: Icons.extension_outlined, - title: l10n.settingsExtensions, - subtitle: l10n.settingsExtensionsSubtitle, - onTap: () => _navigateTo(context, const ExtensionsPage()), + icon: Icons.article_outlined, + title: l10n.logTitle, + subtitle: l10n.settingsLogsSubtitle, + onTap: () => _navigateTo(context, const LogScreen()), ), SettingsItem( icon: Icons.favorite_outline, title: l10n.settingsDonate, subtitle: l10n.settingsDonateSubtitle, onTap: () => _navigateTo(context, const DonatePage()), - showDivider: false, - ), - ], - ); - }, - ), - ), - - SliverToBoxAdapter( - child: Builder( - builder: (context) { - final l10n = context.l10n; - return SettingsGroup( - children: [ - SettingsItem( - icon: Icons.article_outlined, - title: l10n.logTitle, - subtitle: l10n.settingsLogsSubtitle, - onTap: () => _navigateTo(context, const LogScreen()), ), SettingsItem( icon: Icons.info_outline, From 6e9fa459151f99b1c4e31e43ccb75ee1b5b058af Mon Sep 17 00:00:00 2001 From: Amonoman Date: Tue, 28 Apr 2026 13:46:44 +0200 Subject: [PATCH 07/12] feat(settings): reorganize settings into focused pages - split download/options into download, files, metadata, lyrics, app - add dedicated pages for files & folders, metadata, and lyrics - move search source and fallback into download page - replace options_settings_page with app_settings_page - add missing l10n keys for all new pages - improve subtitle copy for download, embed lyrics, primary provider --- lib/l10n/arb/app_en.arb | 289 +++++++++++++++++++++++----------------- 1 file changed, 165 insertions(+), 124 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index e3f4a6d0..a5d4d5ac 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1,6 +1,6 @@ { "@@locale": "en", - "@@last_modified": "2026-01-16", + "@@last_modified": "2026-04-28", "appName": "SpotiFLAC", "@appName": { "description": "App name - DO NOT TRANSLATE" @@ -99,7 +99,7 @@ }, "downloadFolderOrganization": "Folder Organization", "@downloadFolderOrganization": { - "description": "Setting for folder structure" + "description": "Title of the folder organization picker bottom sheet" }, "appearanceTitle": "Appearance", "@appearanceTitle": { @@ -145,7 +145,7 @@ "@optionsPrimaryProvider": { "description": "Main search provider setting" }, - "optionsPrimaryProviderSubtitle": "Service used when searching by track name.", + "optionsPrimaryProviderSubtitle": "Service used for searching by track or album name", "@optionsPrimaryProviderSubtitle": { "description": "Subtitle for primary provider" }, @@ -194,7 +194,7 @@ "@optionsEmbedLyrics": { "description": "Embed lyrics in audio files" }, - "optionsEmbedLyricsSubtitle": "Embed synced lyrics into FLAC files", + "optionsEmbedLyricsSubtitle": "Save synced lyrics alongside your downloaded tracks", "@optionsEmbedLyricsSubtitle": { "description": "Subtitle for embed lyrics" }, @@ -729,7 +729,7 @@ }, "dialogDownload": "Download", "@dialogDownload": { - "description": "Dialog button - download action" + "description": "Confirm button in Download All dialog" }, "dialogDiscard": "Discard", "@dialogDiscard": { @@ -1449,11 +1449,11 @@ "@settingsAppearanceSubtitle": { "description": "Appearance settings description" }, - "settingsDownloadSubtitle": "Service, quality, filename format", + "settingsDownloadSubtitle": "Service, quality, fallback", "@settingsDownloadSubtitle": { "description": "Download settings description" }, - "settingsOptionsSubtitle": "Fallback, lyrics, cover art, updates", + "settingsOptionsSubtitle": "Fallback, metadata, lyrics, cover art", "@settingsOptionsSubtitle": { "description": "Options settings description" }, @@ -3592,18 +3592,17 @@ } } }, - "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Artist folders use Album Artist when available", + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Folder named after Album Artist tag", "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { - "description": "Subtitle when Album Artist is used for folder naming" + "description": "Subtitle when album artist is used for folder names" }, - "downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only", + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Folder named after Track Artist tag", "@downloadUseAlbumArtistForFoldersTrackSubtitle": { - "description": "Subtitle when Track Artist is used for folder naming" + "description": "Subtitle when track artist is used for folder names" }, - - "lyricsProvidersTitle": "Lyrics Providers", + "lyricsProvidersTitle": "Lyrics Provider Priority", "@lyricsProvidersTitle": { - "description": "Title for the lyrics provider priority page" + "description": "Settings item title for lyrics provider order" }, "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", "@lyricsProvidersDescription": { @@ -3667,7 +3666,6 @@ "@lyricsProviderExtensionDesc": { "description": "Generic description for extension-based lyrics providers" }, - "safMigrationTitle": "Storage Update Required", "@safMigrationTitle": { "description": "Title of SAF migration dialog" @@ -3684,16 +3682,14 @@ "@safMigrationSuccess": { "description": "Snackbar after successfully migrating to SAF" }, - - "settingsDonate": "Donate", + "settingsDonate": "Support Development", "@settingsDonate": { - "description": "Settings menu item - donate" + "description": "Settings menu item - donate page" }, - "settingsDonateSubtitle": "Support SpotiFLAC-Mobile development", + "settingsDonateSubtitle": "Buy the developer a coffee", "@settingsDonateSubtitle": { "description": "Subtitle for donate menu item" }, - "tooltipLoveAll": "Love All", "@tooltipLoveAll": { "description": "Tooltip for the Love All button on album/playlist screens" @@ -3720,10 +3716,9 @@ } } }, - "dialogDownloadAllTitle": "Download All", "@dialogDownloadAllTitle": { - "description": "Title of the Download All confirmation dialog" + "description": "Dialog title for bulk download confirmation" }, "dialogDownloadAllMessage": "Download {count} tracks?", "@dialogDownloadAllMessage": { @@ -3734,11 +3729,6 @@ } } }, - "dialogDownload": "Download", - "@dialogDownload": { - "description": "Confirm button in Download All dialog" - }, - "homeSkipAlreadyDownloaded": "Skip already downloaded songs", "@homeSkipAlreadyDownloaded": { "description": "Checkbox label in import dialog to skip already-downloaded songs" @@ -3751,7 +3741,6 @@ "@homeAlbumInfoUnavailable": { "description": "Snackbar when album info cannot be loaded" }, - "snackbarLoadingCueSheet": "Loading CUE sheet...", "@snackbarLoadingCueSheet": { "description": "Snackbar while loading a CUE sheet file" @@ -3781,190 +3770,181 @@ "@snackbarNoActionDefined": { "description": "Snackbar when an extension button has no action configured" }, - "noTracksFoundForAlbum": "No tracks found for this album", "@noTracksFoundForAlbum": { "description": "Empty state message when an album has no tracks" }, - - "downloadLocationSubtitle": "Choose storage mode for downloaded files.", + "downloadLocationSubtitle": "Choose where to save your downloaded tracks", "@downloadLocationSubtitle": { - "description": "Subtitle text in Android download location bottom sheet" + "description": "Subtitle shown in the download location picker sheet" }, - "storageModeAppFolder": "App folder (non-SAF)", + "storageModeAppFolder": "App Folder (Recommended)", "@storageModeAppFolder": { - "description": "Storage mode option - use legacy app folder" + "description": "Storage mode option - app-managed folder" }, - "storageModeAppFolderSubtitle": "Use default Music/SpotiFLAC path", + "storageModeAppFolderSubtitle": "Saves to Music/SpotiFLAC by default", "@storageModeAppFolderSubtitle": { "description": "Subtitle for app folder storage mode" }, - "storageModeSaf": "SAF folder", + "storageModeSaf": "Custom Folder (SAF)", "@storageModeSaf": { - "description": "Storage mode option - use Android SAF picker" + "description": "Storage mode option - Storage Access Framework" }, - "storageModeSafSubtitle": "Pick folder via Android Storage Access Framework", + "storageModeSafSubtitle": "Pick any folder, including SD card", "@storageModeSafSubtitle": { "description": "Subtitle for SAF storage mode" }, - "downloadFilenameDescription": "Customize how your files are named.", + "downloadFilenameDescription": "Use {artist}, {title}, {album}, {track}, {year}, {date}, {disc} as placeholders.", "@downloadFilenameDescription": { - "description": "Description text in filename format bottom sheet" + "description": "Description shown in filename format editor" }, "downloadFilenameInsertTag": "Tap to insert tag:", "@downloadFilenameInsertTag": { "description": "Label above filename tag chips" }, - "downloadSeparateSinglesEnabled": "Albums/ and Singles/ folders", + "downloadSeparateSinglesEnabled": "Singles and EPs saved in a separate folder", "@downloadSeparateSinglesEnabled": { - "description": "Subtitle when separate singles folder is enabled" + "description": "Subtitle when separate singles folder is on" }, - "downloadSeparateSinglesDisabled": "All files in same structure", + "downloadSeparateSinglesDisabled": "Singles and albums saved in the same folder", "@downloadSeparateSinglesDisabled": { - "description": "Subtitle when separate singles folder is disabled" + "description": "Subtitle when separate singles folder is off" }, "downloadArtistNameFilters": "Artist Name Filters", "@downloadArtistNameFilters": { "description": "Setting title for artist folder filter options" }, - "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "downloadCreatePlaylistSourceFolder": "Playlist Source Folder", "@downloadCreatePlaylistSourceFolder": { - "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + "description": "Setting to create a subfolder per playlist source" }, - "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "downloadCreatePlaylistSourceFolderEnabled": "A subfolder is created for each playlist", "@downloadCreatePlaylistSourceFolderEnabled": { - "description": "Subtitle when playlist source folder prefix is enabled" + "description": "Subtitle when playlist folder is enabled" }, - "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "downloadCreatePlaylistSourceFolderDisabled": "All tracks saved directly to download folder", "@downloadCreatePlaylistSourceFolderDisabled": { - "description": "Subtitle when playlist source folder prefix is disabled" + "description": "Subtitle when playlist folder is disabled" }, - "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "downloadCreatePlaylistSourceFolderRedundant": "Handled by folder organization setting", "@downloadCreatePlaylistSourceFolderRedundant": { - "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + "description": "Subtitle when folder organization is already set to playlist" }, "downloadSongLinkRegion": "SongLink Region", "@downloadSongLinkRegion": { - "description": "Setting title for SongLink country region" + "description": "Setting for SongLink region used during fallback resolution" }, - "downloadNetworkCompatibilityMode": "Network compatibility mode", + "downloadNetworkCompatibilityMode": "Network Compatibility Mode", "@downloadNetworkCompatibilityMode": { - "description": "Setting title for network compatibility toggle" + "description": "Setting for legacy TLS/network handling" }, - "downloadNetworkCompatibilityModeEnabled": "Enabled: try HTTP + accept invalid TLS certificates (unsafe)", + "downloadNetworkCompatibilityModeEnabled": "Using legacy TLS settings for older networks", "@downloadNetworkCompatibilityModeEnabled": { - "description": "Subtitle when network compatibility mode is enabled" + "description": "Subtitle when network compatibility mode is on" }, - "downloadNetworkCompatibilityModeDisabled": "Off: strict HTTPS certificate validation (recommended)", + "downloadNetworkCompatibilityModeDisabled": "Using standard network settings", "@downloadNetworkCompatibilityModeDisabled": { - "description": "Subtitle when network compatibility mode is disabled" + "description": "Subtitle when network compatibility mode is off" }, - "downloadSelectServiceToEnable": "Select a built-in service to enable", + "downloadSelectServiceToEnable": "Select Tidal or Qobuz to enable this option", "@downloadSelectServiceToEnable": { - "description": "Hint shown instead of Ask-quality subtitle when no built-in service selected" + "description": "Subtitle when quality picker is disabled due to extension service" }, - - "downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality", + "downloadSelectTidalQobuz": "Select Tidal or Qobuz to choose audio quality", "@downloadSelectTidalQobuz": { - "description": "Info hint when non-Tidal/Qobuz service is selected" + "description": "Info shown when a non-built-in service is selected" }, - "downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off", + "downloadEmbedLyricsDisabled": "Enable metadata embedding first", "@downloadEmbedLyricsDisabled": { - "description": "Subtitle for Embed Lyrics when Embed Metadata is disabled" + "description": "Subtitle when lyrics embedding is blocked by metadata toggle" }, "downloadNeteaseIncludeTranslation": "Netease: Include Translation", "@downloadNeteaseIncludeTranslation": { - "description": "Toggle title for including Netease translated lyrics" + "description": "Setting to include translated lyrics from Netease" }, - "downloadNeteaseIncludeTranslationEnabled": "Append translated lyrics when available", + "downloadNeteaseIncludeTranslationEnabled": "Chinese translation lines included", "@downloadNeteaseIncludeTranslationEnabled": { - "description": "Subtitle when Netease translation is enabled" + "description": "Subtitle when Netease translation is on" }, - "downloadNeteaseIncludeTranslationDisabled": "Use original lyrics only", + "downloadNeteaseIncludeTranslationDisabled": "Original lyrics only", "@downloadNeteaseIncludeTranslationDisabled": { - "description": "Subtitle when Netease translation is disabled" + "description": "Subtitle when Netease translation is off" }, "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", "@downloadNeteaseIncludeRomanization": { - "description": "Toggle title for including Netease romanized lyrics" + "description": "Setting to include romanized lyrics from Netease" }, - "downloadNeteaseIncludeRomanizationEnabled": "Append romanized lyrics when available", + "downloadNeteaseIncludeRomanizationEnabled": "Romanization lines included", "@downloadNeteaseIncludeRomanizationEnabled": { - "description": "Subtitle when Netease romanization is enabled" + "description": "Subtitle when Netease romanization is on" }, - "downloadNeteaseIncludeRomanizationDisabled": "Disabled", + "downloadNeteaseIncludeRomanizationDisabled": "No romanization", "@downloadNeteaseIncludeRomanizationDisabled": { - "description": "Subtitle when Netease romanization is disabled" + "description": "Subtitle when Netease romanization is off" }, - "downloadAppleQqMultiPerson": "Apple/QQ Multi-Person Word-by-Word", + "downloadAppleQqMultiPerson": "Apple / QQ: Multi-Person Lyrics", "@downloadAppleQqMultiPerson": { - "description": "Toggle title for Apple/QQ multi-person word-by-word lyrics" + "description": "Setting for word-by-word multi-person lyrics from Apple Music and QQ Music" }, - "downloadAppleQqMultiPersonEnabled": "Enable v1/v2 speaker and [bg:] tags", + "downloadAppleQqMultiPersonEnabled": "Speaker labels included for duets and group tracks", "@downloadAppleQqMultiPersonEnabled": { - "description": "Subtitle when multi-person word-by-word is enabled" + "description": "Subtitle when multi-person lyrics is on" }, - "downloadAppleQqMultiPersonDisabled": "Simplified word-by-word formatting", + "downloadAppleQqMultiPersonDisabled": "Standard lyrics without speaker labels", "@downloadAppleQqMultiPersonDisabled": { - "description": "Subtitle when multi-person word-by-word is disabled" + "description": "Subtitle when multi-person lyrics is off" }, "downloadMusixmatchLanguage": "Musixmatch Language", "@downloadMusixmatchLanguage": { - "description": "Setting title for Musixmatch language preference" + "description": "Setting for Musixmatch lyrics translation language" }, - "downloadMusixmatchLanguageAuto": "Auto (original)", + "downloadMusixmatchLanguageAuto": "Auto (original language)", "@downloadMusixmatchLanguageAuto": { - "description": "Option label when Musixmatch uses original language" + "description": "Subtitle when no language is set" }, - "downloadFilterContributing": "Filter contributing artists in Album Artist", + "downloadFilterContributing": "Filter Contributing Artists", "@downloadFilterContributing": { - "description": "Toggle title for filtering contributing artists in Album Artist metadata" + "description": "Setting to strip contributing artists from Album Artist folder name" }, - "downloadFilterContributingEnabled": "Album Artist metadata uses primary artist only", + "downloadFilterContributingEnabled": "Contributing artists removed from Album Artist folder name", "@downloadFilterContributingEnabled": { - "description": "Subtitle when contributing artist filter is enabled" + "description": "Subtitle when contributing artist filter is on" }, - "downloadFilterContributingDisabled": "Keep full Album Artist metadata value", + "downloadFilterContributingDisabled": "Full Album Artist string used", "@downloadFilterContributingDisabled": { - "description": "Subtitle when contributing artist filter is disabled" + "description": "Subtitle when contributing artist filter is off" }, - - "downloadProvidersNoneEnabled": "None enabled", + "downloadProvidersNoneEnabled": "No providers enabled", "@downloadProvidersNoneEnabled": { - "description": "Subtitle for lyrics providers setting when no providers are enabled" + "description": "Shown when no lyrics providers are active" }, "downloadMusixmatchLanguageCode": "Language code", "@downloadMusixmatchLanguageCode": { - "description": "Label for the Musixmatch language code text field" + "description": "Label for Musixmatch language input field" }, - "downloadMusixmatchLanguageHint": "auto / en / es / ja", + "downloadMusixmatchLanguageHint": "e.g. en, de, ja", "@downloadMusixmatchLanguageHint": { - "description": "Hint text for the Musixmatch language code field" + "description": "Placeholder for Musixmatch language input" }, - "downloadMusixmatchLanguageDesc": "Set preferred language code (example: en, es, ja). Leave empty for auto.", + "downloadMusixmatchLanguageDesc": "Enter a BCP-47 language code (e.g. en, de, ja) to request translated lyrics from Musixmatch.", "@downloadMusixmatchLanguageDesc": { - "description": "Description in the Musixmatch language picker" + "description": "Description in Musixmatch language picker" }, "downloadMusixmatchAuto": "Auto", "@downloadMusixmatchAuto": { - "description": "Button to reset Musixmatch language to automatic" + "description": "Button to clear Musixmatch language (use auto)" }, - - "downloadNetworkAnySubtitle": "WiFi + Mobile Data", + "downloadNetworkAnySubtitle": "Use WiFi or mobile data", "@downloadNetworkAnySubtitle": { - "description": "Subtitle for 'Any' network mode option" + "description": "Subtitle for any-network option in picker" }, - "downloadNetworkWifiOnlySubtitle": "Pause downloads on mobile data", + "downloadNetworkWifiOnlySubtitle": "Downloads pause when on mobile data", "@downloadNetworkWifiOnlySubtitle": { - "description": "Subtitle for 'WiFi only' network mode option" + "description": "Subtitle for WiFi-only option in picker" }, - "downloadSongLinkRegionDesc": "Used as userCountry for SongLink API lookup.", + "downloadSongLinkRegionDesc": "Region used when resolving track links via SongLink. Choose the country where your streaming services are available.", "@downloadSongLinkRegionDesc": { - "description": "Description in the SongLink region picker" - }, - "downloadFolderOrganization": "Folder Organization", - "@downloadFolderOrganization": { - "description": "Title of the folder organization picker bottom sheet" + "description": "Description in SongLink region picker" }, "snackbarUnsupportedAudioFormat": "Unsupported audio format", "@snackbarUnsupportedAudioFormat": { @@ -3974,10 +3954,6 @@ "@cacheRefresh": { "description": "Tooltip for refresh button on cache management page" }, - "dialogDownloadAllTitle": "Download All", - "@dialogDownloadAllTitle": { - "description": "Dialog title for bulk download confirmation" - }, "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", "@dialogDownloadPlaylistsMessage": { "description": "Dialog message for bulk playlist download confirmation", @@ -4105,7 +4081,6 @@ "@editMetadataSelectEmpty": { "description": "Button to select only fields that are currently empty" }, - "queueDownloadingCount": "Downloading ({count})", "@queueDownloadingCount": { "description": "Header for active downloads section with count", @@ -4229,7 +4204,6 @@ "@audioAnalysisSamples": { "description": "Total samples metric label" }, - "extensionsSearchWith": "Search with {providerName}", "@extensionsSearchWith": { "description": "Extensions page - subtitle for built-in search provider option", @@ -4249,7 +4223,7 @@ }, "extensionsHomeFeedAuto": "Auto", "@extensionsHomeFeedAuto": { - "description": "Extensions page - home feed provider option: auto" + "description": "Label for auto-selected search provider" }, "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", "@extensionsHomeFeedAutoSubtitle": { @@ -4268,7 +4242,6 @@ "@extensionsNoHomeFeedExtensions": { "description": "Extensions page - shown when no installed extension has home feed" }, - "sortAlphaAsc": "A-Z", "@sortAlphaAsc": { "description": "Sort option - alphabetical ascending" @@ -4294,7 +4267,6 @@ "@cancelDownloadKeep": { "description": "Dialog button - keep the active download (do not cancel)" }, - "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", "@metadataSaveFailedFfmpeg": { "description": "Snackbar error when FFmpeg fails to write metadata" @@ -4303,7 +4275,6 @@ "@metadataSaveFailedStorage": { "description": "Snackbar error when writing metadata file back to storage fails" }, - "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", "@snackbarFolderPickerFailed": { "description": "Snackbar shown when folder picker fails to open", @@ -4313,7 +4284,6 @@ } } }, - "errorLoadAlbum": "Failed to load album", "@errorLoadAlbum": { "description": "Error state shown when album fails to load" @@ -4326,7 +4296,6 @@ "@errorLoadArtist": { "description": "Error state shown when artist fails to load" }, - "notifChannelDownloadName": "Download Progress", "@notifChannelDownloadName": { "description": "Android notification channel name for download progress" @@ -4540,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } -} +} \ No newline at end of file From bb0cc23461a5d45ac6087f94f62cae264cbd295b Mon Sep 17 00:00:00 2001 From: Amonoman Date: Sat, 2 May 2026 18:13:04 +0200 Subject: [PATCH 08/12] i18n: sync missing EN strings to all locales & fix DE consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add 18 missing keys to DE, ES_ES, FR, HI, ID, JA, KO, NL, PT_PT, RU, TR, UK, ZH_CN, ZH_TW - Add 580 missing keys to ES, PT, ZH (outdated partial files) - Fix DE: Sie→du throughout, typos (Standart, auwählen), grammar errors in dialogs --- lib/l10n/arb/app_de.arb | 110 +- lib/l10n/arb/app_es.arb | 2853 ++++++++++++++++++++++++++++++++++++ lib/l10n/arb/app_es_ES.arb | 72 + lib/l10n/arb/app_fr.arb | 72 + lib/l10n/arb/app_hi.arb | 72 + lib/l10n/arb/app_id.arb | 72 + lib/l10n/arb/app_ja.arb | 72 + lib/l10n/arb/app_ko.arb | 72 + lib/l10n/arb/app_nl.arb | 72 + lib/l10n/arb/app_pt.arb | 2853 ++++++++++++++++++++++++++++++++++++ lib/l10n/arb/app_pt_PT.arb | 72 + lib/l10n/arb/app_ru.arb | 72 + lib/l10n/arb/app_tr.arb | 72 + lib/l10n/arb/app_uk.arb | 72 + lib/l10n/arb/app_zh.arb | 2853 ++++++++++++++++++++++++++++++++++++ lib/l10n/arb/app_zh_CN.arb | 72 + lib/l10n/arb/app_zh_TW.arb | 72 + 17 files changed, 9586 insertions(+), 19 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 188f5d34..33150a80 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -121,7 +121,7 @@ "@appearanceDynamicColor": { "description": "Material You dynamic colors" }, - "appearanceDynamicColorSubtitle": "Farben von Ihrem Hintergrundbild verwenden", + "appearanceDynamicColorSubtitle": "Farben deines Hintergrundbilds verwenden", "@appearanceDynamicColorSubtitle": { "description": "Subtitle for dynamic color" }, @@ -178,7 +178,7 @@ "@optionsAutoFallbackSubtitle": { "description": "Subtitle for auto fallback" }, - "optionsUseExtensionProviders": "Erweiterungs-Anbieter verwenden", + "optionsUseExtensionProviders": "Erweiterungsanbieter verwenden", "@optionsUseExtensionProviders": { "description": "Enable extension download providers" }, @@ -194,7 +194,7 @@ "@optionsEmbedLyrics": { "description": "Embed lyrics in audio files" }, - "optionsEmbedLyricsSubtitle": "Synchronisierte Lyrics in FLAC-Dateien einbetten", + "optionsEmbedLyricsSubtitle": "Synchronisierte Songtexte neben heruntergeladenen Titeln speichern", "@optionsEmbedLyricsSubtitle": { "description": "Subtitle for embed lyrics" }, @@ -230,7 +230,7 @@ "@optionsArtistTagModeJoined": { "description": "Artist tag mode option that joins multiple artists into one value" }, - "optionsArtistTagModeJoinedSubtitle": "Einen Künstler wert wie \"Artist A, Artist B\" für maximale Spieler-Kompatibilität schreiben.", + "optionsArtistTagModeJoinedSubtitle": "Einen Künstler-Wert wie \"Künstler A, Künstler B\" für maximale Player-Kompatibilität schreiben.", "@optionsArtistTagModeJoinedSubtitle": { "description": "Subtitle for joined artist tag mode" }, @@ -438,7 +438,7 @@ "@aboutReportIssue": { "description": "Link to report bugs" }, - "aboutReportIssueSubtitle": "Melde jedes Problem, die dir auftreten", + "aboutReportIssueSubtitle": "Melde Probleme, die dir auffallen", "@aboutReportIssueSubtitle": { "description": "Subtitle for report issue" }, @@ -486,7 +486,7 @@ "@aboutSachinsenalDesc": { "description": "Credit description for sachinsenal0x64" }, - "aboutSjdonadoDesc": "Ersteller von I Don't Have Spotify (IDHS). Der Fallback-Link-Resolver, der den Tag rettete!", + "aboutSjdonadoDesc": "Ersteller von I Don't Have Spotify (IDHS). Der Fallback-Link-Resolver, der den Tag rettet!", "@aboutSjdonadoDesc": { "description": "Credit description for sjdonado" }, @@ -576,7 +576,7 @@ "@setupPermissionDeniedMessage": { "description": "Error when permission denied" }, - "setupPermissionRequired": "{permissionType} Zugriff verweigert", + "setupPermissionRequired": "{permissionType}-Berechtigung erforderlich", "@setupPermissionRequired": { "description": "Generic permission required title", "placeholders": { @@ -586,7 +586,7 @@ } } }, - "setupPermissionRequiredMessage": "{permissionType} Berechtigung ist erforderlich für\ndie beste Benutzererfahrung. Für kannst dies später in den Einstellungen ändern.", + "setupPermissionRequiredMessage": "{permissionType}-Berechtigung ist erforderlich für\ndie beste Benutzererfahrung. Du kannst dies später in den Einstellungen ändern.", "@setupPermissionRequiredMessage": { "description": "Generic permission required message", "placeholders": { @@ -603,7 +603,7 @@ "@setupNoFolderSelected": { "description": "Prompt when no folder selected" }, - "setupUseDefault": "Standart benutzen", + "setupUseDefault": "Standard verwenden", "@setupUseDefault": { "description": "Button to use default folder" }, @@ -663,7 +663,7 @@ "@setupNotificationEnable": { "description": "Button to enable notifications" }, - "setupFolderChoose": "Speicherort auwählen", + "setupFolderChoose": "Speicherort auswählen", "@setupFolderChoose": { "description": "Button to choose folder" }, @@ -747,7 +747,7 @@ "@dialogDiscardChanges": { "description": "Dialog title - unsaved changes warning" }, - "dialogUnsavedChanges": "Hast du noch nicht alle Änderungen gespeichert. Möchtest du die Änderungen verwerfen?", + "dialogUnsavedChanges": "Du hast ungespeicherte Änderungen. Möchtest du sie verwerfen?", "@dialogUnsavedChanges": { "description": "Dialog message - unsaved changes" }, @@ -759,7 +759,7 @@ "@dialogRemoveExtension": { "description": "Dialog title - uninstall extension" }, - "dialogRemoveExtensionMessage": "Bist Du sicher, dass Du diese Erweiterung entfernen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.", + "dialogRemoveExtensionMessage": "Bist du sicher, dass du diese Erweiterung entfernen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.", "@dialogRemoveExtensionMessage": { "description": "Dialog message - uninstall confirmation" }, @@ -767,7 +767,7 @@ "@dialogUninstallExtension": { "description": "Dialog title - uninstall extension" }, - "dialogUninstallExtensionMessage": "Bist du dir sicher, dass du {extensionName} entfernen möchtest?", + "dialogUninstallExtensionMessage": "Bist du sicher, dass du {extensionName} entfernen möchtest?", "@dialogUninstallExtensionMessage": { "description": "Dialog message - uninstall specific extension", "placeholders": { @@ -780,7 +780,7 @@ "@dialogClearHistoryTitle": { "description": "Dialog title - clear download history" }, - "dialogClearHistoryMessage": "Bist du dir sicher, dass du den gesamten Download verlauf löschen möchten? Dies kann nicht rückgängig gemacht werden.", + "dialogClearHistoryMessage": "Bist du sicher, dass du den gesamten Downloadverlauf löschen möchtest? Dies kann nicht rückgängig gemacht werden.", "@dialogClearHistoryMessage": { "description": "Dialog message - clear history confirmation" }, @@ -846,7 +846,7 @@ } } }, - "snackbarAlreadyInLibrary": "\"{trackName}\" existiert bereits in Ihrer Bibliothek", + "snackbarAlreadyInLibrary": "\"{trackName}\" existiert bereits in deiner Bibliothek", "@snackbarAlreadyInLibrary": { "description": "Snackbar - track already exists in local library", "placeholders": { @@ -867,7 +867,7 @@ "@snackbarCredentialsCleared": { "description": "Snackbar - Spotify credentials removed" }, - "snackbarDeletedTracks": "{count} {count, plural, one {}=1{Titel} other{Titel}}", + "snackbarDeletedTracks": "{count} {count, plural, =1{Titel gelöscht} other{Titel gelöscht}}", "@snackbarDeletedTracks": { "description": "Snackbar - tracks deleted", "placeholders": { @@ -969,7 +969,7 @@ "@errorUrlFetchFailed": { "description": "Error message - generic URL fetch failure" }, - "errorMissingExtensionSource": "Kann {item} nicht lade wegen fehlender Erweiterungsquelle", + "errorMissingExtensionSource": "Kann {item} nicht laden wegen fehlender Erweiterungsquelle", "@errorMissingExtensionSource": { "description": "Error - extension source not available", "placeholders": { @@ -1287,7 +1287,7 @@ "@logClearLogsTitle": { "description": "Clear logs dialog title" }, - "logClearLogsMessage": "Bist du dir sicher, dass Sie alle Protokolle löschen möchtest?", + "logClearLogsMessage": "Bist du sicher, dass du alle Protokolle löschen möchtest?", "@logClearLogsMessage": { "description": "Clear logs confirmation message" }, @@ -2462,7 +2462,7 @@ "@libraryClearConfirmTitle": { "description": "Dialog title for clear confirmation" }, - "libraryClearConfirmMessage": "Dadurch werden alle gescannten Titel aus Ihrer Bibliothek entfernt. Ihre eigentlichen Musikdateien werden nicht gelöscht.", + "libraryClearConfirmMessage": "Dadurch werden alle gescannten Titel aus deiner Bibliothek entfernt. Deine eigentlichen Musikdateien werden nicht gelöscht.", "@libraryClearConfirmMessage": { "description": "Dialog message for clear confirmation" }, @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_es.arb b/lib/l10n/arb/app_es.arb index 248b1478..a17f7299 100644 --- a/lib/l10n/arb/app_es.arb +++ b/lib/l10n/arb/app_es.arb @@ -1728,5 +1728,2858 @@ "type": "int" } } + }, + "navLibrary": "Library", + "@navLibrary": { + "description": "Bottom navigation - Library tab" + }, + "historySearchHint": "Search history...", + "@historySearchHint": { + "description": "Search bar placeholder in history" + }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Artist Tag Mode", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Choose how multiple artists are written into embedded tags.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Single joined value", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Split tags for FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, + "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" + }, + "aboutTranslators": "Translators", + "@aboutTranslators": { + "description": "Section for translators" + }, + "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" + }, + "aboutSjdonadoDesc": "Creator of I Don't Have Spotify (IDHS). The fallback link resolver that saves the day!", + "@aboutSjdonadoDesc": { + "description": "Credit description for sjdonado" + }, + "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" + }, + "artistPopular": "Popular", + "@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" + } + } + }, + "setupIcloudNotSupported": "iCloud Drive is not supported. Please use the app Documents folder.", + "@setupIcloudNotSupported": { + "description": "Error when user selects iCloud Drive on iOS" + }, + "dialogDownload": "Download", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, + "csvImportTracks": "{count} tracks from CSV", + "@csvImportTracks": { + "description": "Label shown in quality picker for CSV import", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAlreadyInLibrary": "\"{trackName}\" already exists in your library", + "@snackbarAlreadyInLibrary": { + "description": "Snackbar - track already exists in local library", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "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" + }, + "searchSortTitle": "Sort Results", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Default", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Title (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Title (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artist (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artist (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duration (Shortest)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duration (Longest)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Release Date (Oldest)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Release Date (Newest)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, + "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" + }, + "folderOrganizationByPlaylist": "By Playlist", + "@folderOrganizationByPlaylist": { + "description": "Folder option - playlist folders" + }, + "folderOrganizationByPlaylistSubtitle": "Separate folder for each playlist", + "@folderOrganizationByPlaylistSubtitle": { + "description": "Subtitle for playlist folder option" + }, + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, + "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" + }, + "trackGenre": "Genre", + "@trackGenre": { + "description": "Metadata label - music genre" + }, + "trackLabel": "Label", + "@trackLabel": { + "description": "Metadata label - record label" + }, + "trackCopyright": "Copyright", + "@trackCopyright": { + "description": "Metadata label - copyright information" + }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, + "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)" + }, + "storeAddRepoTitle": "Add Extension Repository", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "e.g. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Add Repository", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Change repository", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Extension Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Current repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "New Repository URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionsFallbackTitle": "Fallback Extensions", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, + "downloadLossy320": "Lossy 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Lossy Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Best compatibility, ~10MB per track", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Best quality Opus, ~8MB per track", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, + "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" + }, + "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" + }, + "albumFolderArtistAlbumSingles": "Artist / Album + Singles", + "@albumFolderArtistAlbumSingles": { + "description": "Album folder option with singles inside artist" + }, + "albumFolderArtistAlbumSinglesSubtitle": "Artist/Album/ and Artist/Singles/", + "@albumFolderArtistAlbumSinglesSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, + "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" + }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, + "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" + } + } + }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "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" + }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, + "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" + }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, + "libraryFilterSort": "Sort", + "@libraryFilterSort": { + "description": "Filter section - sort order" + }, + "libraryFilterSortLatest": "Latest", + "@libraryFilterSortLatest": { + "description": "Sort option - newest first" + }, + "libraryFilterSortOldest": "Oldest", + "@libraryFilterSortOldest": { + "description": "Sort option - oldest first" + }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, + "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 Deezer", + "@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 Repo 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" + }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, + "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" + }, + "queueFlacAction": "Queue FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, + "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, Opus, ALAC, or FLAC", + "@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" + } + } + }, + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, + "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" + }, + "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" + }, + "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" + } + } + }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "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" + } + } + }, + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Folder named after Album Artist tag", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when album artist is used for folder names" + }, + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Folder named after Track Artist tag", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when track artist is used for folder names" + }, + "lyricsProvidersTitle": "Lyrics Provider Priority", + "@lyricsProvidersTitle": { + "description": "Settings item title for lyrics provider order" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Support Development", + "@settingsDonate": { + "description": "Settings menu item - donate page" + }, + "settingsDonateSubtitle": "Buy the developer a coffee", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose where to save your downloaded tracks", + "@downloadLocationSubtitle": { + "description": "Subtitle shown in the download location picker sheet" + }, + "storageModeAppFolder": "App Folder (Recommended)", + "@storageModeAppFolder": { + "description": "Storage mode option - app-managed folder" + }, + "storageModeAppFolderSubtitle": "Saves to Music/SpotiFLAC by default", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "Custom Folder (SAF)", + "@storageModeSaf": { + "description": "Storage mode option - Storage Access Framework" + }, + "storageModeSafSubtitle": "Pick any folder, including SD card", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Use {artist}, {title}, {album}, {track}, {year}, {date}, {disc} as placeholders.", + "@downloadFilenameDescription": { + "description": "Description shown in filename format editor" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Singles and EPs saved in a separate folder", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is on" + }, + "downloadSeparateSinglesDisabled": "Singles and albums saved in the same folder", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is off" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Playlist Source Folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting to create a subfolder per playlist source" + }, + "downloadCreatePlaylistSourceFolderEnabled": "A subfolder is created for each playlist", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist folder is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "All tracks saved directly to download folder", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist folder is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "Handled by folder organization setting", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when folder organization is already set to playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting for SongLink region used during fallback resolution" + }, + "downloadNetworkCompatibilityMode": "Network Compatibility Mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting for legacy TLS/network handling" + }, + "downloadNetworkCompatibilityModeEnabled": "Using legacy TLS settings for older networks", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is on" + }, + "downloadNetworkCompatibilityModeDisabled": "Using standard network settings", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is off" + }, + "downloadSelectServiceToEnable": "Select Tidal or Qobuz to enable this option", + "@downloadSelectServiceToEnable": { + "description": "Subtitle when quality picker is disabled due to extension service" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz to choose audio quality", + "@downloadSelectTidalQobuz": { + "description": "Info shown when a non-built-in service is selected" + }, + "downloadEmbedLyricsDisabled": "Enable metadata embedding first", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle when lyrics embedding is blocked by metadata toggle" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Setting to include translated lyrics from Netease" + }, + "downloadNeteaseIncludeTranslationEnabled": "Chinese translation lines included", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is on" + }, + "downloadNeteaseIncludeTranslationDisabled": "Original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is off" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Setting to include romanized lyrics from Netease" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Romanization lines included", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is on" + }, + "downloadNeteaseIncludeRomanizationDisabled": "No romanization", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is off" + }, + "downloadAppleQqMultiPerson": "Apple / QQ: Multi-Person Lyrics", + "@downloadAppleQqMultiPerson": { + "description": "Setting for word-by-word multi-person lyrics from Apple Music and QQ Music" + }, + "downloadAppleQqMultiPersonEnabled": "Speaker labels included for duets and group tracks", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person lyrics is on" + }, + "downloadAppleQqMultiPersonDisabled": "Standard lyrics without speaker labels", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person lyrics is off" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting for Musixmatch lyrics translation language" + }, + "downloadMusixmatchLanguageAuto": "Auto (original language)", + "@downloadMusixmatchLanguageAuto": { + "description": "Subtitle when no language is set" + }, + "downloadFilterContributing": "Filter Contributing Artists", + "@downloadFilterContributing": { + "description": "Setting to strip contributing artists from Album Artist folder name" + }, + "downloadFilterContributingEnabled": "Contributing artists removed from Album Artist folder name", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is on" + }, + "downloadFilterContributingDisabled": "Full Album Artist string used", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is off" + }, + "downloadProvidersNoneEnabled": "No providers enabled", + "@downloadProvidersNoneEnabled": { + "description": "Shown when no lyrics providers are active" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for Musixmatch language input field" + }, + "downloadMusixmatchLanguageHint": "e.g. en, de, ja", + "@downloadMusixmatchLanguageHint": { + "description": "Placeholder for Musixmatch language input" + }, + "downloadMusixmatchLanguageDesc": "Enter a BCP-47 language code (e.g. en, de, ja) to request translated lyrics from Musixmatch.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to clear Musixmatch language (use auto)" + }, + "downloadNetworkAnySubtitle": "Use WiFi or mobile data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for any-network option in picker" + }, + "downloadNetworkWifiOnlySubtitle": "Downloads pause when on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for WiFi-only option in picker" + }, + "downloadSongLinkRegionDesc": "Region used when resolving track links via SongLink. Choose the country where your streaming services are available.", + "@downloadSongLinkRegionDesc": { + "description": "Description in SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Label for auto-selected search provider" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } diff --git a/lib/l10n/arb/app_es_ES.arb b/lib/l10n/arb/app_es_ES.arb index 286f0a0c..7fccb158 100644 --- a/lib/l10n/arb/app_es_ES.arb +++ b/lib/l10n/arb/app_es_ES.arb @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index dccad293..ab865785 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_hi.arb b/lib/l10n/arb/app_hi.arb index 355c5c7e..7935892f 100644 --- a/lib/l10n/arb/app_hi.arb +++ b/lib/l10n/arb/app_hi.arb @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index 667c7d8f..6b0733dd 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_ja.arb b/lib/l10n/arb/app_ja.arb index c265d491..75f9e1b0 100644 --- a/lib/l10n/arb/app_ja.arb +++ b/lib/l10n/arb/app_ja.arb @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_ko.arb b/lib/l10n/arb/app_ko.arb index 862b39cd..398c09b5 100644 --- a/lib/l10n/arb/app_ko.arb +++ b/lib/l10n/arb/app_ko.arb @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_nl.arb b/lib/l10n/arb/app_nl.arb index 5e4be112..b9cbaa09 100644 --- a/lib/l10n/arb/app_nl.arb +++ b/lib/l10n/arb/app_nl.arb @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_pt.arb b/lib/l10n/arb/app_pt.arb index 1f41f8f7..de1ff199 100644 --- a/lib/l10n/arb/app_pt.arb +++ b/lib/l10n/arb/app_pt.arb @@ -1728,5 +1728,2858 @@ "type": "int" } } + }, + "navLibrary": "Library", + "@navLibrary": { + "description": "Bottom navigation - Library tab" + }, + "historySearchHint": "Search history...", + "@historySearchHint": { + "description": "Search bar placeholder in history" + }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Artist Tag Mode", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Choose how multiple artists are written into embedded tags.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Single joined value", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Split tags for FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, + "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" + }, + "aboutTranslators": "Translators", + "@aboutTranslators": { + "description": "Section for translators" + }, + "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" + }, + "aboutSjdonadoDesc": "Creator of I Don't Have Spotify (IDHS). The fallback link resolver that saves the day!", + "@aboutSjdonadoDesc": { + "description": "Credit description for sjdonado" + }, + "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" + }, + "artistPopular": "Popular", + "@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" + } + } + }, + "setupIcloudNotSupported": "iCloud Drive is not supported. Please use the app Documents folder.", + "@setupIcloudNotSupported": { + "description": "Error when user selects iCloud Drive on iOS" + }, + "dialogDownload": "Download", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, + "csvImportTracks": "{count} tracks from CSV", + "@csvImportTracks": { + "description": "Label shown in quality picker for CSV import", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAlreadyInLibrary": "\"{trackName}\" already exists in your library", + "@snackbarAlreadyInLibrary": { + "description": "Snackbar - track already exists in local library", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "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" + }, + "searchSortTitle": "Sort Results", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Default", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Title (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Title (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artist (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artist (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duration (Shortest)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duration (Longest)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Release Date (Oldest)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Release Date (Newest)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, + "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" + }, + "folderOrganizationByPlaylist": "By Playlist", + "@folderOrganizationByPlaylist": { + "description": "Folder option - playlist folders" + }, + "folderOrganizationByPlaylistSubtitle": "Separate folder for each playlist", + "@folderOrganizationByPlaylistSubtitle": { + "description": "Subtitle for playlist folder option" + }, + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, + "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" + }, + "trackGenre": "Genre", + "@trackGenre": { + "description": "Metadata label - music genre" + }, + "trackLabel": "Label", + "@trackLabel": { + "description": "Metadata label - record label" + }, + "trackCopyright": "Copyright", + "@trackCopyright": { + "description": "Metadata label - copyright information" + }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, + "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)" + }, + "storeAddRepoTitle": "Add Extension Repository", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "e.g. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Add Repository", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Change repository", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Extension Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Current repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "New Repository URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionsFallbackTitle": "Fallback Extensions", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, + "downloadLossy320": "Lossy 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Lossy Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Best compatibility, ~10MB per track", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Best quality Opus, ~8MB per track", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, + "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" + }, + "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" + }, + "albumFolderArtistAlbumSingles": "Artist / Album + Singles", + "@albumFolderArtistAlbumSingles": { + "description": "Album folder option with singles inside artist" + }, + "albumFolderArtistAlbumSinglesSubtitle": "Artist/Album/ and Artist/Singles/", + "@albumFolderArtistAlbumSinglesSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, + "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" + }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, + "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" + } + } + }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "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" + }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, + "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" + }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, + "libraryFilterSort": "Sort", + "@libraryFilterSort": { + "description": "Filter section - sort order" + }, + "libraryFilterSortLatest": "Latest", + "@libraryFilterSortLatest": { + "description": "Sort option - newest first" + }, + "libraryFilterSortOldest": "Oldest", + "@libraryFilterSortOldest": { + "description": "Sort option - oldest first" + }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, + "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 Deezer", + "@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 Repo 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" + }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, + "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" + }, + "queueFlacAction": "Queue FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, + "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, Opus, ALAC, or FLAC", + "@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" + } + } + }, + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, + "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" + }, + "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" + }, + "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" + } + } + }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "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" + } + } + }, + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Folder named after Album Artist tag", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when album artist is used for folder names" + }, + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Folder named after Track Artist tag", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when track artist is used for folder names" + }, + "lyricsProvidersTitle": "Lyrics Provider Priority", + "@lyricsProvidersTitle": { + "description": "Settings item title for lyrics provider order" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Support Development", + "@settingsDonate": { + "description": "Settings menu item - donate page" + }, + "settingsDonateSubtitle": "Buy the developer a coffee", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose where to save your downloaded tracks", + "@downloadLocationSubtitle": { + "description": "Subtitle shown in the download location picker sheet" + }, + "storageModeAppFolder": "App Folder (Recommended)", + "@storageModeAppFolder": { + "description": "Storage mode option - app-managed folder" + }, + "storageModeAppFolderSubtitle": "Saves to Music/SpotiFLAC by default", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "Custom Folder (SAF)", + "@storageModeSaf": { + "description": "Storage mode option - Storage Access Framework" + }, + "storageModeSafSubtitle": "Pick any folder, including SD card", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Use {artist}, {title}, {album}, {track}, {year}, {date}, {disc} as placeholders.", + "@downloadFilenameDescription": { + "description": "Description shown in filename format editor" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Singles and EPs saved in a separate folder", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is on" + }, + "downloadSeparateSinglesDisabled": "Singles and albums saved in the same folder", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is off" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Playlist Source Folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting to create a subfolder per playlist source" + }, + "downloadCreatePlaylistSourceFolderEnabled": "A subfolder is created for each playlist", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist folder is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "All tracks saved directly to download folder", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist folder is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "Handled by folder organization setting", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when folder organization is already set to playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting for SongLink region used during fallback resolution" + }, + "downloadNetworkCompatibilityMode": "Network Compatibility Mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting for legacy TLS/network handling" + }, + "downloadNetworkCompatibilityModeEnabled": "Using legacy TLS settings for older networks", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is on" + }, + "downloadNetworkCompatibilityModeDisabled": "Using standard network settings", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is off" + }, + "downloadSelectServiceToEnable": "Select Tidal or Qobuz to enable this option", + "@downloadSelectServiceToEnable": { + "description": "Subtitle when quality picker is disabled due to extension service" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz to choose audio quality", + "@downloadSelectTidalQobuz": { + "description": "Info shown when a non-built-in service is selected" + }, + "downloadEmbedLyricsDisabled": "Enable metadata embedding first", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle when lyrics embedding is blocked by metadata toggle" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Setting to include translated lyrics from Netease" + }, + "downloadNeteaseIncludeTranslationEnabled": "Chinese translation lines included", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is on" + }, + "downloadNeteaseIncludeTranslationDisabled": "Original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is off" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Setting to include romanized lyrics from Netease" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Romanization lines included", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is on" + }, + "downloadNeteaseIncludeRomanizationDisabled": "No romanization", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is off" + }, + "downloadAppleQqMultiPerson": "Apple / QQ: Multi-Person Lyrics", + "@downloadAppleQqMultiPerson": { + "description": "Setting for word-by-word multi-person lyrics from Apple Music and QQ Music" + }, + "downloadAppleQqMultiPersonEnabled": "Speaker labels included for duets and group tracks", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person lyrics is on" + }, + "downloadAppleQqMultiPersonDisabled": "Standard lyrics without speaker labels", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person lyrics is off" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting for Musixmatch lyrics translation language" + }, + "downloadMusixmatchLanguageAuto": "Auto (original language)", + "@downloadMusixmatchLanguageAuto": { + "description": "Subtitle when no language is set" + }, + "downloadFilterContributing": "Filter Contributing Artists", + "@downloadFilterContributing": { + "description": "Setting to strip contributing artists from Album Artist folder name" + }, + "downloadFilterContributingEnabled": "Contributing artists removed from Album Artist folder name", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is on" + }, + "downloadFilterContributingDisabled": "Full Album Artist string used", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is off" + }, + "downloadProvidersNoneEnabled": "No providers enabled", + "@downloadProvidersNoneEnabled": { + "description": "Shown when no lyrics providers are active" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for Musixmatch language input field" + }, + "downloadMusixmatchLanguageHint": "e.g. en, de, ja", + "@downloadMusixmatchLanguageHint": { + "description": "Placeholder for Musixmatch language input" + }, + "downloadMusixmatchLanguageDesc": "Enter a BCP-47 language code (e.g. en, de, ja) to request translated lyrics from Musixmatch.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to clear Musixmatch language (use auto)" + }, + "downloadNetworkAnySubtitle": "Use WiFi or mobile data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for any-network option in picker" + }, + "downloadNetworkWifiOnlySubtitle": "Downloads pause when on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for WiFi-only option in picker" + }, + "downloadSongLinkRegionDesc": "Region used when resolving track links via SongLink. Choose the country where your streaming services are available.", + "@downloadSongLinkRegionDesc": { + "description": "Description in SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Label for auto-selected search provider" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } diff --git a/lib/l10n/arb/app_pt_PT.arb b/lib/l10n/arb/app_pt_PT.arb index 3cf494eb..5b7a11fc 100644 --- a/lib/l10n/arb/app_pt_PT.arb +++ b/lib/l10n/arb/app_pt_PT.arb @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index c3d650fa..54b47b1a 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_tr.arb b/lib/l10n/arb/app_tr.arb index e0aa8df7..9ffbe3b0 100644 --- a/lib/l10n/arb/app_tr.arb +++ b/lib/l10n/arb/app_tr.arb @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_uk.arb b/lib/l10n/arb/app_uk.arb index 62c47453..d9f2ed50 100644 --- a/lib/l10n/arb/app_uk.arb +++ b/lib/l10n/arb/app_uk.arb @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Не вдалося завантажити оновлення. Спробуйте пізніше.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } \ No newline at end of file diff --git a/lib/l10n/arb/app_zh.arb b/lib/l10n/arb/app_zh.arb index 307f156b..3127c84e 100644 --- a/lib/l10n/arb/app_zh.arb +++ b/lib/l10n/arb/app_zh.arb @@ -1728,5 +1728,2858 @@ "type": "int" } } + }, + "navLibrary": "Library", + "@navLibrary": { + "description": "Bottom navigation - Library tab" + }, + "historySearchHint": "Search history...", + "@historySearchHint": { + "description": "Search bar placeholder in history" + }, + "downloadSingleFilenameFormat": "Single Filename Format", + "@downloadSingleFilenameFormat": { + "description": "Setting for output filename pattern for singles/EPs" + }, + "downloadSingleFilenameFormatDescription": "Filename pattern for singles and EPs. Uses the same tags as the album format.", + "@downloadSingleFilenameFormatDescription": { + "description": "Subtitle description for single filename format setting" + }, + "optionsDefaultSearchTab": "Default Search Tab", + "@optionsDefaultSearchTab": { + "description": "Title for the preferred default search tab setting" + }, + "optionsDefaultSearchTabSubtitle": "Choose which tab opens first for new search results.", + "@optionsDefaultSearchTabSubtitle": { + "description": "Subtitle for the preferred default search tab setting" + }, + "optionsReplayGain": "ReplayGain", + "@optionsReplayGain": { + "description": "Title for ReplayGain setting toggle" + }, + "optionsReplayGainSubtitleOn": "Scan loudness and embed ReplayGain tags (EBU R128)", + "@optionsReplayGainSubtitleOn": { + "description": "Subtitle when ReplayGain is enabled" + }, + "optionsReplayGainSubtitleOff": "Disabled: no loudness normalization tags", + "@optionsReplayGainSubtitleOff": { + "description": "Subtitle when ReplayGain is disabled" + }, + "optionsArtistTagMode": "Artist Tag Mode", + "@optionsArtistTagMode": { + "description": "Setting title for how artist metadata is written into files" + }, + "optionsArtistTagModeDescription": "Choose how multiple artists are written into embedded tags.", + "@optionsArtistTagModeDescription": { + "description": "Bottom-sheet description for artist tag mode setting" + }, + "optionsArtistTagModeJoined": "Single joined value", + "@optionsArtistTagModeJoined": { + "description": "Artist tag mode option that joins multiple artists into one value" + }, + "optionsArtistTagModeJoinedSubtitle": "Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.", + "@optionsArtistTagModeJoinedSubtitle": { + "description": "Subtitle for joined artist tag mode" + }, + "optionsArtistTagModeSplitVorbis": "Split tags for FLAC/Opus", + "@optionsArtistTagModeSplitVorbis": { + "description": "Artist tag mode option that writes repeated ARTIST tags for Vorbis formats" + }, + "optionsArtistTagModeSplitVorbisSubtitle": "Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.", + "@optionsArtistTagModeSplitVorbisSubtitle": { + "description": "Subtitle for split Vorbis artist tag mode" + }, + "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" + }, + "aboutTranslators": "Translators", + "@aboutTranslators": { + "description": "Section for translators" + }, + "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" + }, + "aboutSjdonadoDesc": "Creator of I Don't Have Spotify (IDHS). The fallback link resolver that saves the day!", + "@aboutSjdonadoDesc": { + "description": "Credit description for sjdonado" + }, + "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" + }, + "artistPopular": "Popular", + "@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" + } + } + }, + "setupIcloudNotSupported": "iCloud Drive is not supported. Please use the app Documents folder.", + "@setupIcloudNotSupported": { + "description": "Error when user selects iCloud Drive on iOS" + }, + "dialogDownload": "Download", + "@dialogDownload": { + "description": "Confirm button in Download All dialog" + }, + "csvImportTracks": "{count} tracks from CSV", + "@csvImportTracks": { + "description": "Label shown in quality picker for CSV import", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAlreadyInLibrary": "\"{trackName}\" already exists in your library", + "@snackbarAlreadyInLibrary": { + "description": "Snackbar - track already exists in local library", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "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" + }, + "searchSortTitle": "Sort Results", + "@searchSortTitle": { + "description": "Bottom sheet title for search sort options" + }, + "searchSortDefault": "Default", + "@searchSortDefault": { + "description": "Sort option - default API order" + }, + "searchSortTitleAZ": "Title (A-Z)", + "@searchSortTitleAZ": { + "description": "Sort option - title ascending" + }, + "searchSortTitleZA": "Title (Z-A)", + "@searchSortTitleZA": { + "description": "Sort option - title descending" + }, + "searchSortArtistAZ": "Artist (A-Z)", + "@searchSortArtistAZ": { + "description": "Sort option - artist ascending" + }, + "searchSortArtistZA": "Artist (Z-A)", + "@searchSortArtistZA": { + "description": "Sort option - artist descending" + }, + "searchSortDurationShort": "Duration (Shortest)", + "@searchSortDurationShort": { + "description": "Sort option - shortest duration first" + }, + "searchSortDurationLong": "Duration (Longest)", + "@searchSortDurationLong": { + "description": "Sort option - longest duration first" + }, + "searchSortDateOldest": "Release Date (Oldest)", + "@searchSortDateOldest": { + "description": "Sort option - oldest release first" + }, + "searchSortDateNewest": "Release Date (Newest)", + "@searchSortDateNewest": { + "description": "Sort option - newest release first" + }, + "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" + }, + "folderOrganizationByPlaylist": "By Playlist", + "@folderOrganizationByPlaylist": { + "description": "Folder option - playlist folders" + }, + "folderOrganizationByPlaylistSubtitle": "Separate folder for each playlist", + "@folderOrganizationByPlaylistSubtitle": { + "description": "Subtitle for playlist folder option" + }, + "providerPriorityFallbackExtensionsTitle": "Extension Fallback", + "@providerPriorityFallbackExtensionsTitle": { + "description": "Section title for choosing which download extensions can be used as fallback providers" + }, + "providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.", + "@providerPriorityFallbackExtensionsDescription": { + "description": "Section description for extension fallback selection" + }, + "providerPriorityFallbackExtensionsHint": "Only enabled extensions with download-provider capability are listed here.", + "@providerPriorityFallbackExtensionsHint": { + "description": "Hint below the extension fallback selection list" + }, + "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" + }, + "trackGenre": "Genre", + "@trackGenre": { + "description": "Metadata label - music genre" + }, + "trackLabel": "Label", + "@trackLabel": { + "description": "Metadata label - record label" + }, + "trackCopyright": "Copyright", + "@trackCopyright": { + "description": "Metadata label - copyright information" + }, + "trackLyricsNotInFile": "No lyrics found in this file", + "@trackLyricsNotInFile": { + "description": "Message when no embedded lyrics in audio file" + }, + "trackFetchOnlineLyrics": "Fetch from Online", + "@trackFetchOnlineLyrics": { + "description": "Action - fetch lyrics from online providers" + }, + "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)" + }, + "storeAddRepoTitle": "Add Extension Repository", + "@storeAddRepoTitle": { + "description": "Store setup screen - heading when no repo is configured" + }, + "storeAddRepoDescription": "Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.", + "@storeAddRepoDescription": { + "description": "Store setup screen - explanatory text" + }, + "storeRepoUrlLabel": "Repository URL", + "@storeRepoUrlLabel": { + "description": "Label for the repository URL input field" + }, + "storeRepoUrlHint": "https://github.com/user/repo", + "@storeRepoUrlHint": { + "description": "Hint/placeholder for the repository URL input field" + }, + "storeRepoUrlHelper": "e.g. https://github.com/user/extensions-repo", + "@storeRepoUrlHelper": { + "description": "Helper text below the repository URL input field" + }, + "storeAddRepoButton": "Add Repository", + "@storeAddRepoButton": { + "description": "Button to submit a new repository URL" + }, + "storeChangeRepoTooltip": "Change repository", + "@storeChangeRepoTooltip": { + "description": "Tooltip for the change-repository icon button in the app bar" + }, + "storeRepoDialogTitle": "Extension Repository", + "@storeRepoDialogTitle": { + "description": "Title of the change/remove repository dialog" + }, + "storeRepoDialogCurrent": "Current repository:", + "@storeRepoDialogCurrent": { + "description": "Label shown above the current repository URL in the dialog" + }, + "storeNewRepoUrlLabel": "New Repository URL", + "@storeNewRepoUrlLabel": { + "description": "Label for the new repository URL field inside the dialog" + }, + "storeLoadError": "Failed to load repository", + "@storeLoadError": { + "description": "Error heading when the store cannot be loaded" + }, + "storeEmptyNoExtensions": "No extensions available", + "@storeEmptyNoExtensions": { + "description": "Message when store has no extensions" + }, + "storeEmptyNoResults": "No extensions found", + "@storeEmptyNoResults": { + "description": "Message when search/filter returns no results" + }, + "extensionsFallbackTitle": "Fallback Extensions", + "@extensionsFallbackTitle": { + "description": "Setting and page title for choosing which download extensions can be used during fallback" + }, + "extensionsFallbackSubtitle": "Choose which installed download extensions can be used as fallback", + "@extensionsFallbackSubtitle": { + "description": "Subtitle for download fallback extensions menu" + }, + "downloadLossy320": "Lossy 320kbps", + "@downloadLossy320": { + "description": "Quality option label for Tidal lossy 320kbps" + }, + "downloadLossyFormat": "Lossy Format", + "@downloadLossyFormat": { + "description": "Setting title to pick output format for Tidal lossy downloads" + }, + "downloadLossy320Format": "Lossy 320kbps Format", + "@downloadLossy320Format": { + "description": "Title of the Tidal lossy format picker bottom sheet" + }, + "downloadLossy320FormatDesc": "Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.", + "@downloadLossy320FormatDesc": { + "description": "Description in the Tidal lossy format picker" + }, + "downloadLossyMp3": "MP3 320kbps", + "@downloadLossyMp3": { + "description": "Tidal lossy format option - MP3 320kbps" + }, + "downloadLossyMp3Subtitle": "Best compatibility, ~10MB per track", + "@downloadLossyMp3Subtitle": { + "description": "Subtitle for MP3 320kbps Tidal lossy option" + }, + "downloadLossyOpus256": "Opus 256kbps", + "@downloadLossyOpus256": { + "description": "Tidal lossy format option - Opus 256kbps" + }, + "downloadLossyOpus256Subtitle": "Best quality Opus, ~8MB per track", + "@downloadLossyOpus256Subtitle": { + "description": "Subtitle for Opus 256kbps Tidal lossy option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps Tidal lossy option" + }, + "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" + }, + "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" + }, + "albumFolderArtistAlbumSingles": "Artist / Album + Singles", + "@albumFolderArtistAlbumSingles": { + "description": "Album folder option with singles inside artist" + }, + "albumFolderArtistAlbumSinglesSubtitle": "Artist/Album/ and Artist/Singles/", + "@albumFolderArtistAlbumSinglesSubtitle": { + "description": "Folder structure example" + }, + "albumFolderArtistAlbumFlat": "Artist / Album (Singles flat)", + "@albumFolderArtistAlbumFlat": { + "description": "Album folder option with singles directly in artist folder" + }, + "albumFolderArtistAlbumFlatSubtitle": "Artist/Album/ and Artist/song.flac", + "@albumFolderArtistAlbumFlatSubtitle": { + "description": "Folder structure example for flat singles" + }, + "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" + }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, + "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" + } + } + }, + "libraryFilesUnit": "{count, plural, =1{file} other{files}}", + "@libraryFilesUnit": { + "description": "Unit label for files count during library scanning", + "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" + }, + "libraryScanFinalizing": "Finalizing library...", + "@libraryScanFinalizing": { + "description": "Status shown after file scanning finishes but library persistence is still running" + }, + "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" + }, + "libraryFilterMetadata": "Metadata", + "@libraryFilterMetadata": { + "description": "Filter section - metadata completeness" + }, + "libraryFilterMetadataComplete": "Complete metadata", + "@libraryFilterMetadataComplete": { + "description": "Filter option - items with complete metadata" + }, + "libraryFilterMetadataMissingAny": "Missing any metadata", + "@libraryFilterMetadataMissingAny": { + "description": "Filter option - items missing any tracked metadata field" + }, + "libraryFilterMetadataMissingYear": "Missing year", + "@libraryFilterMetadataMissingYear": { + "description": "Filter option - items missing release year/date" + }, + "libraryFilterMetadataMissingGenre": "Missing genre", + "@libraryFilterMetadataMissingGenre": { + "description": "Filter option - items missing genre" + }, + "libraryFilterMetadataMissingAlbumArtist": "Missing album artist", + "@libraryFilterMetadataMissingAlbumArtist": { + "description": "Filter option - items missing album artist" + }, + "libraryFilterSort": "Sort", + "@libraryFilterSort": { + "description": "Filter section - sort order" + }, + "libraryFilterSortLatest": "Latest", + "@libraryFilterSortLatest": { + "description": "Sort option - newest first" + }, + "libraryFilterSortOldest": "Oldest", + "@libraryFilterSortOldest": { + "description": "Sort option - oldest first" + }, + "libraryFilterSortAlbumAsc": "Album (A-Z)", + "@libraryFilterSortAlbumAsc": { + "description": "Sort option - album ascending" + }, + "libraryFilterSortAlbumDesc": "Album (Z-A)", + "@libraryFilterSortAlbumDesc": { + "description": "Sort option - album descending" + }, + "libraryFilterSortGenreAsc": "Genre (A-Z)", + "@libraryFilterSortGenreAsc": { + "description": "Sort option - genre ascending" + }, + "libraryFilterSortGenreDesc": "Genre (Z-A)", + "@libraryFilterSortGenreDesc": { + "description": "Sort option - genre descending" + }, + "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 Deezer", + "@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 Repo 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" + }, + "trackReEnrichFieldsTitle": "Fields to update", + "@trackReEnrichFieldsTitle": { + "description": "Section title for field selection in re-enrich dialog" + }, + "trackReEnrichFieldCover": "Cover Art", + "@trackReEnrichFieldCover": { + "description": "Checkbox label for cover art field in re-enrich" + }, + "trackReEnrichFieldLyrics": "Lyrics", + "@trackReEnrichFieldLyrics": { + "description": "Checkbox label for lyrics field in re-enrich" + }, + "trackReEnrichFieldBasicTags": "Album, Album Artist", + "@trackReEnrichFieldBasicTags": { + "description": "Checkbox label for basic tags in re-enrich (title/artist are never overwritten)" + }, + "trackReEnrichFieldTrackInfo": "Track & Disc Number", + "@trackReEnrichFieldTrackInfo": { + "description": "Checkbox label for track info in re-enrich" + }, + "trackReEnrichFieldReleaseInfo": "Date & ISRC", + "@trackReEnrichFieldReleaseInfo": { + "description": "Checkbox label for release info in re-enrich" + }, + "trackReEnrichFieldExtra": "Genre, Label, Copyright", + "@trackReEnrichFieldExtra": { + "description": "Checkbox label for extra metadata in re-enrich" + }, + "trackReEnrichSelectAll": "Select All", + "@trackReEnrichSelectAll": { + "description": "Select all fields checkbox in re-enrich" + }, + "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" + }, + "queueFlacAction": "Queue FLAC", + "@queueFlacAction": { + "description": "Action/button label for queueing FLAC redownloads for local tracks" + }, + "queueFlacConfirmMessage": "Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n{count} selected", + "@queueFlacConfirmMessage": { + "description": "Confirmation dialog body before queueing FLAC redownloads for local tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueFlacFindingProgress": "Finding FLAC matches... ({current}/{total})", + "@queueFlacFindingProgress": { + "description": "Snackbar while resolving remote matches for local FLAC redownloads", + "placeholders": { + "current": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "queueFlacNoReliableMatches": "No reliable online matches found for the selection", + "@queueFlacNoReliableMatches": { + "description": "Snackbar when no safe FLAC redownload matches were found" + }, + "queueFlacQueuedWithSkipped": "Added {addedCount} tracks to queue, skipped {skippedCount}", + "@queueFlacQueuedWithSkipped": { + "description": "Snackbar when some selected local tracks were queued for FLAC redownload and some were skipped", + "placeholders": { + "addedCount": { + "type": "int" + }, + "skippedCount": { + "type": "int" + } + } + }, + "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, Opus, ALAC, or FLAC", + "@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" + } + } + }, + "trackConvertConfirmMessageLossless": "Convert from {sourceFormat} to {targetFormat}? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.", + "@trackConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless-to-lossless conversion", + "placeholders": { + "sourceFormat": { + "type": "String" + }, + "targetFormat": { + "type": "String" + } + } + }, + "trackConvertLosslessHint": "Lossless conversion — no quality loss", + "@trackConvertLosslessHint": { + "description": "Hint shown when converting between lossless formats" + }, + "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" + }, + "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" + }, + "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" + } + } + }, + "selectionBatchConvertConfirmMessageLossless": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.", + "@selectionBatchConvertConfirmMessageLossless": { + "description": "Confirmation dialog message for lossless batch conversion", + "placeholders": { + "count": { + "type": "int" + }, + "format": { + "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" + } + } + }, + "downloadUseAlbumArtistForFoldersAlbumSubtitle": "Folder named after Album Artist tag", + "@downloadUseAlbumArtistForFoldersAlbumSubtitle": { + "description": "Subtitle when album artist is used for folder names" + }, + "downloadUseAlbumArtistForFoldersTrackSubtitle": "Folder named after Track Artist tag", + "@downloadUseAlbumArtistForFoldersTrackSubtitle": { + "description": "Subtitle when track artist is used for folder names" + }, + "lyricsProvidersTitle": "Lyrics Provider Priority", + "@lyricsProvidersTitle": { + "description": "Settings item title for lyrics provider order" + }, + "lyricsProvidersDescription": "Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.", + "@lyricsProvidersDescription": { + "description": "Description on the lyrics provider priority page" + }, + "lyricsProvidersInfoText": "Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.", + "@lyricsProvidersInfoText": { + "description": "Info tip on lyrics provider priority page" + }, + "lyricsProvidersEnabledSection": "Enabled ({count})", + "@lyricsProvidersEnabledSection": { + "description": "Section header for enabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersDisabledSection": "Disabled ({count})", + "@lyricsProvidersDisabledSection": { + "description": "Section header for disabled providers", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "lyricsProvidersAtLeastOne": "At least one provider must remain enabled", + "@lyricsProvidersAtLeastOne": { + "description": "Snackbar when user tries to disable the last enabled provider" + }, + "lyricsProvidersSaved": "Lyrics provider priority saved", + "@lyricsProvidersSaved": { + "description": "Snackbar after saving lyrics provider priority" + }, + "lyricsProvidersDiscardContent": "You have unsaved changes that will be lost.", + "@lyricsProvidersDiscardContent": { + "description": "Body text of the discard-changes dialog on lyrics provider page" + }, + "lyricsProviderLrclibDesc": "Open-source synced lyrics database", + "@lyricsProviderLrclibDesc": { + "description": "Description for LRCLIB provider" + }, + "lyricsProviderNeteaseDesc": "NetEase Cloud Music (good for Asian songs)", + "@lyricsProviderNeteaseDesc": { + "description": "Description for Netease provider" + }, + "lyricsProviderMusixmatchDesc": "Largest lyrics database (multi-language)", + "@lyricsProviderMusixmatchDesc": { + "description": "Description for Musixmatch provider" + }, + "lyricsProviderAppleMusicDesc": "Word-by-word synced lyrics (via proxy)", + "@lyricsProviderAppleMusicDesc": { + "description": "Description for Apple Music provider" + }, + "lyricsProviderQqMusicDesc": "QQ Music (good for Chinese songs, via proxy)", + "@lyricsProviderQqMusicDesc": { + "description": "Description for QQ Music provider" + }, + "lyricsProviderExtensionDesc": "Extension provider", + "@lyricsProviderExtensionDesc": { + "description": "Generic description for extension-based lyrics providers" + }, + "safMigrationTitle": "Storage Update Required", + "@safMigrationTitle": { + "description": "Title of SAF migration dialog" + }, + "safMigrationMessage1": "SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.", + "@safMigrationMessage1": { + "description": "First paragraph of SAF migration dialog" + }, + "safMigrationMessage2": "Please select your download folder again to switch to the new storage system.", + "@safMigrationMessage2": { + "description": "Second paragraph of SAF migration dialog" + }, + "safMigrationSuccess": "Download folder updated to SAF mode", + "@safMigrationSuccess": { + "description": "Snackbar after successfully migrating to SAF" + }, + "settingsDonate": "Support Development", + "@settingsDonate": { + "description": "Settings menu item - donate page" + }, + "settingsDonateSubtitle": "Buy the developer a coffee", + "@settingsDonateSubtitle": { + "description": "Subtitle for donate menu item" + }, + "tooltipLoveAll": "Love All", + "@tooltipLoveAll": { + "description": "Tooltip for the Love All button on album/playlist screens" + }, + "tooltipAddToPlaylist": "Add to Playlist", + "@tooltipAddToPlaylist": { + "description": "Tooltip for the Add to Playlist button" + }, + "snackbarRemovedTracksFromLoved": "Removed {count} tracks from Loved", + "@snackbarRemovedTracksFromLoved": { + "description": "Snackbar after removing multiple tracks from Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "snackbarAddedTracksToLoved": "Added {count} tracks to Loved", + "@snackbarAddedTracksToLoved": { + "description": "Snackbar after adding multiple tracks to Loved folder", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dialogDownloadAllTitle": "Download All", + "@dialogDownloadAllTitle": { + "description": "Dialog title for bulk download confirmation" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "homeSkipAlreadyDownloaded": "Skip already downloaded songs", + "@homeSkipAlreadyDownloaded": { + "description": "Checkbox label in import dialog to skip already-downloaded songs" + }, + "homeGoToAlbum": "Go to Album", + "@homeGoToAlbum": { + "description": "Context menu item to navigate to the album page" + }, + "homeAlbumInfoUnavailable": "Album info not available", + "@homeAlbumInfoUnavailable": { + "description": "Snackbar when album info cannot be loaded" + }, + "snackbarLoadingCueSheet": "Loading CUE sheet...", + "@snackbarLoadingCueSheet": { + "description": "Snackbar while loading a CUE sheet file" + }, + "snackbarMetadataSaved": "Metadata saved successfully", + "@snackbarMetadataSaved": { + "description": "Snackbar after successfully saving track metadata" + }, + "snackbarFailedToEmbedLyrics": "Failed to embed lyrics", + "@snackbarFailedToEmbedLyrics": { + "description": "Snackbar when lyrics embedding fails" + }, + "snackbarFailedToWriteStorage": "Failed to write back to storage", + "@snackbarFailedToWriteStorage": { + "description": "Snackbar when writing metadata back to file fails" + }, + "snackbarError": "Error: {error}", + "@snackbarError": { + "description": "Generic error snackbar with error detail", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "snackbarNoActionDefined": "No action defined for this button", + "@snackbarNoActionDefined": { + "description": "Snackbar when an extension button has no action configured" + }, + "noTracksFoundForAlbum": "No tracks found for this album", + "@noTracksFoundForAlbum": { + "description": "Empty state message when an album has no tracks" + }, + "downloadLocationSubtitle": "Choose where to save your downloaded tracks", + "@downloadLocationSubtitle": { + "description": "Subtitle shown in the download location picker sheet" + }, + "storageModeAppFolder": "App Folder (Recommended)", + "@storageModeAppFolder": { + "description": "Storage mode option - app-managed folder" + }, + "storageModeAppFolderSubtitle": "Saves to Music/SpotiFLAC by default", + "@storageModeAppFolderSubtitle": { + "description": "Subtitle for app folder storage mode" + }, + "storageModeSaf": "Custom Folder (SAF)", + "@storageModeSaf": { + "description": "Storage mode option - Storage Access Framework" + }, + "storageModeSafSubtitle": "Pick any folder, including SD card", + "@storageModeSafSubtitle": { + "description": "Subtitle for SAF storage mode" + }, + "downloadFilenameDescription": "Use {artist}, {title}, {album}, {track}, {year}, {date}, {disc} as placeholders.", + "@downloadFilenameDescription": { + "description": "Description shown in filename format editor" + }, + "downloadFilenameInsertTag": "Tap to insert tag:", + "@downloadFilenameInsertTag": { + "description": "Label above filename tag chips" + }, + "downloadSeparateSinglesEnabled": "Singles and EPs saved in a separate folder", + "@downloadSeparateSinglesEnabled": { + "description": "Subtitle when separate singles folder is on" + }, + "downloadSeparateSinglesDisabled": "Singles and albums saved in the same folder", + "@downloadSeparateSinglesDisabled": { + "description": "Subtitle when separate singles folder is off" + }, + "downloadArtistNameFilters": "Artist Name Filters", + "@downloadArtistNameFilters": { + "description": "Setting title for artist folder filter options" + }, + "downloadCreatePlaylistSourceFolder": "Playlist Source Folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting to create a subfolder per playlist source" + }, + "downloadCreatePlaylistSourceFolderEnabled": "A subfolder is created for each playlist", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist folder is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "All tracks saved directly to download folder", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist folder is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "Handled by folder organization setting", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when folder organization is already set to playlist" + }, + "downloadSongLinkRegion": "SongLink Region", + "@downloadSongLinkRegion": { + "description": "Setting for SongLink region used during fallback resolution" + }, + "downloadNetworkCompatibilityMode": "Network Compatibility Mode", + "@downloadNetworkCompatibilityMode": { + "description": "Setting for legacy TLS/network handling" + }, + "downloadNetworkCompatibilityModeEnabled": "Using legacy TLS settings for older networks", + "@downloadNetworkCompatibilityModeEnabled": { + "description": "Subtitle when network compatibility mode is on" + }, + "downloadNetworkCompatibilityModeDisabled": "Using standard network settings", + "@downloadNetworkCompatibilityModeDisabled": { + "description": "Subtitle when network compatibility mode is off" + }, + "downloadSelectServiceToEnable": "Select Tidal or Qobuz to enable this option", + "@downloadSelectServiceToEnable": { + "description": "Subtitle when quality picker is disabled due to extension service" + }, + "downloadSelectTidalQobuz": "Select Tidal or Qobuz to choose audio quality", + "@downloadSelectTidalQobuz": { + "description": "Info shown when a non-built-in service is selected" + }, + "downloadEmbedLyricsDisabled": "Enable metadata embedding first", + "@downloadEmbedLyricsDisabled": { + "description": "Subtitle when lyrics embedding is blocked by metadata toggle" + }, + "downloadNeteaseIncludeTranslation": "Netease: Include Translation", + "@downloadNeteaseIncludeTranslation": { + "description": "Setting to include translated lyrics from Netease" + }, + "downloadNeteaseIncludeTranslationEnabled": "Chinese translation lines included", + "@downloadNeteaseIncludeTranslationEnabled": { + "description": "Subtitle when Netease translation is on" + }, + "downloadNeteaseIncludeTranslationDisabled": "Original lyrics only", + "@downloadNeteaseIncludeTranslationDisabled": { + "description": "Subtitle when Netease translation is off" + }, + "downloadNeteaseIncludeRomanization": "Netease: Include Romanization", + "@downloadNeteaseIncludeRomanization": { + "description": "Setting to include romanized lyrics from Netease" + }, + "downloadNeteaseIncludeRomanizationEnabled": "Romanization lines included", + "@downloadNeteaseIncludeRomanizationEnabled": { + "description": "Subtitle when Netease romanization is on" + }, + "downloadNeteaseIncludeRomanizationDisabled": "No romanization", + "@downloadNeteaseIncludeRomanizationDisabled": { + "description": "Subtitle when Netease romanization is off" + }, + "downloadAppleQqMultiPerson": "Apple / QQ: Multi-Person Lyrics", + "@downloadAppleQqMultiPerson": { + "description": "Setting for word-by-word multi-person lyrics from Apple Music and QQ Music" + }, + "downloadAppleQqMultiPersonEnabled": "Speaker labels included for duets and group tracks", + "@downloadAppleQqMultiPersonEnabled": { + "description": "Subtitle when multi-person lyrics is on" + }, + "downloadAppleQqMultiPersonDisabled": "Standard lyrics without speaker labels", + "@downloadAppleQqMultiPersonDisabled": { + "description": "Subtitle when multi-person lyrics is off" + }, + "downloadMusixmatchLanguage": "Musixmatch Language", + "@downloadMusixmatchLanguage": { + "description": "Setting for Musixmatch lyrics translation language" + }, + "downloadMusixmatchLanguageAuto": "Auto (original language)", + "@downloadMusixmatchLanguageAuto": { + "description": "Subtitle when no language is set" + }, + "downloadFilterContributing": "Filter Contributing Artists", + "@downloadFilterContributing": { + "description": "Setting to strip contributing artists from Album Artist folder name" + }, + "downloadFilterContributingEnabled": "Contributing artists removed from Album Artist folder name", + "@downloadFilterContributingEnabled": { + "description": "Subtitle when contributing artist filter is on" + }, + "downloadFilterContributingDisabled": "Full Album Artist string used", + "@downloadFilterContributingDisabled": { + "description": "Subtitle when contributing artist filter is off" + }, + "downloadProvidersNoneEnabled": "No providers enabled", + "@downloadProvidersNoneEnabled": { + "description": "Shown when no lyrics providers are active" + }, + "downloadMusixmatchLanguageCode": "Language code", + "@downloadMusixmatchLanguageCode": { + "description": "Label for Musixmatch language input field" + }, + "downloadMusixmatchLanguageHint": "e.g. en, de, ja", + "@downloadMusixmatchLanguageHint": { + "description": "Placeholder for Musixmatch language input" + }, + "downloadMusixmatchLanguageDesc": "Enter a BCP-47 language code (e.g. en, de, ja) to request translated lyrics from Musixmatch.", + "@downloadMusixmatchLanguageDesc": { + "description": "Description in Musixmatch language picker" + }, + "downloadMusixmatchAuto": "Auto", + "@downloadMusixmatchAuto": { + "description": "Button to clear Musixmatch language (use auto)" + }, + "downloadNetworkAnySubtitle": "Use WiFi or mobile data", + "@downloadNetworkAnySubtitle": { + "description": "Subtitle for any-network option in picker" + }, + "downloadNetworkWifiOnlySubtitle": "Downloads pause when on mobile data", + "@downloadNetworkWifiOnlySubtitle": { + "description": "Subtitle for WiFi-only option in picker" + }, + "downloadSongLinkRegionDesc": "Region used when resolving track links via SongLink. Choose the country where your streaming services are available.", + "@downloadSongLinkRegionDesc": { + "description": "Description in SongLink region picker" + }, + "snackbarUnsupportedAudioFormat": "Unsupported audio format", + "@snackbarUnsupportedAudioFormat": { + "description": "Snackbar when the audio format is not supported for the requested operation" + }, + "cacheRefresh": "Refresh", + "@cacheRefresh": { + "description": "Tooltip for refresh button on cache management page" + }, + "dialogDownloadPlaylistsMessage": "Download {trackCount} {trackCount, plural, =1{track} other{tracks}} from {playlistCount} {playlistCount, plural, =1{playlist} other{playlists}}?", + "@dialogDownloadPlaylistsMessage": { + "description": "Dialog message for bulk playlist download confirmation", + "placeholders": { + "trackCount": { + "type": "int" + }, + "playlistCount": { + "type": "int" + } + } + }, + "bulkDownloadPlaylistsButton": "Download {count} {count, plural, =1{playlist} other{playlists}}", + "@bulkDownloadPlaylistsButton": { + "description": "Button label for bulk downloading selected playlists", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bulkDownloadSelectPlaylists": "Select playlists to download", + "@bulkDownloadSelectPlaylists": { + "description": "Button label when no playlists are selected for download" + }, + "snackbarSelectedPlaylistsEmpty": "Selected playlists have no tracks", + "@snackbarSelectedPlaylistsEmpty": { + "description": "Snackbar when selected playlists contain no tracks" + }, + "playlistsCount": "{count, plural, =1{1 playlist} other{{count} playlists}}", + "@playlistsCount": { + "description": "Playlist count display", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFill": "Auto-fill from online", + "@editMetadataAutoFill": { + "description": "Section title for selective online metadata auto-fill in the edit metadata sheet" + }, + "editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata", + "@editMetadataAutoFillDesc": { + "description": "Description for the auto-fill section" + }, + "editMetadataAutoFillFetch": "Fetch & Fill", + "@editMetadataAutoFillFetch": { + "description": "Button label to fetch online metadata and fill selected fields" + }, + "editMetadataAutoFillSearching": "Searching online...", + "@editMetadataAutoFillSearching": { + "description": "Snackbar shown while searching for online metadata" + }, + "editMetadataAutoFillNoResults": "No matching metadata found online", + "@editMetadataAutoFillNoResults": { + "description": "Snackbar when online metadata search returns no results" + }, + "editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata", + "@editMetadataAutoFillDone": { + "description": "Snackbar confirming how many fields were auto-filled", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill", + "@editMetadataAutoFillNoneSelected": { + "description": "Snackbar when user taps Fetch without selecting any fields" + }, + "editMetadataFieldTitle": "Title", + "@editMetadataFieldTitle": { + "description": "Chip label for title field in auto-fill selector" + }, + "editMetadataFieldArtist": "Artist", + "@editMetadataFieldArtist": { + "description": "Chip label for artist field in auto-fill selector" + }, + "editMetadataFieldAlbum": "Album", + "@editMetadataFieldAlbum": { + "description": "Chip label for album field in auto-fill selector" + }, + "editMetadataFieldAlbumArtist": "Album Artist", + "@editMetadataFieldAlbumArtist": { + "description": "Chip label for album artist field in auto-fill selector" + }, + "editMetadataFieldDate": "Date", + "@editMetadataFieldDate": { + "description": "Chip label for date field in auto-fill selector" + }, + "editMetadataFieldTrackNum": "Track #", + "@editMetadataFieldTrackNum": { + "description": "Chip label for track number field in auto-fill selector" + }, + "editMetadataFieldDiscNum": "Disc #", + "@editMetadataFieldDiscNum": { + "description": "Chip label for disc number field in auto-fill selector" + }, + "editMetadataFieldGenre": "Genre", + "@editMetadataFieldGenre": { + "description": "Chip label for genre field in auto-fill selector" + }, + "editMetadataFieldIsrc": "ISRC", + "@editMetadataFieldIsrc": { + "description": "Chip label for ISRC field in auto-fill selector" + }, + "editMetadataFieldLabel": "Label", + "@editMetadataFieldLabel": { + "description": "Chip label for label field in auto-fill selector" + }, + "editMetadataFieldCopyright": "Copyright", + "@editMetadataFieldCopyright": { + "description": "Chip label for copyright field in auto-fill selector" + }, + "editMetadataFieldCover": "Cover Art", + "@editMetadataFieldCover": { + "description": "Chip label for cover art field in auto-fill selector" + }, + "editMetadataSelectAll": "All", + "@editMetadataSelectAll": { + "description": "Button to select all fields for auto-fill" + }, + "editMetadataSelectEmpty": "Empty only", + "@editMetadataSelectEmpty": { + "description": "Button to select only fields that are currently empty" + }, + "queueDownloadingCount": "Downloading ({count})", + "@queueDownloadingCount": { + "description": "Header for active downloads section with count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueDownloadedHeader": "Downloaded", + "@queueDownloadedHeader": { + "description": "Header label for downloaded items section in library" + }, + "queueFilteringIndicator": "Filtering...", + "@queueFilteringIndicator": { + "description": "Shown while filter results are being computed" + }, + "queueTrackCount": "{count, plural, =1{1 track} other{{count} tracks}}", + "@queueTrackCount": { + "description": "Track count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueAlbumCount": "{count, plural, =1{1 album} other{{count} albums}}", + "@queueAlbumCount": { + "description": "Album count label with plural support", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "queueEmptyAlbums": "No album downloads", + "@queueEmptyAlbums": { + "description": "Empty state title when no album downloads exist" + }, + "queueEmptyAlbumsSubtitle": "Download multiple tracks from an album to see them here", + "@queueEmptyAlbumsSubtitle": { + "description": "Empty state subtitle for album downloads" + }, + "queueEmptySingles": "No single downloads", + "@queueEmptySingles": { + "description": "Empty state title when no single track downloads exist" + }, + "queueEmptySinglesSubtitle": "Single track downloads will appear here", + "@queueEmptySinglesSubtitle": { + "description": "Empty state subtitle for single track downloads" + }, + "queueEmptyHistory": "No download history", + "@queueEmptyHistory": { + "description": "Empty state title when download history is empty" + }, + "queueEmptyHistorySubtitle": "Downloaded tracks will appear here", + "@queueEmptyHistorySubtitle": { + "description": "Empty state subtitle for download history" + }, + "selectionAllPlaylistsSelected": "All playlists selected", + "@selectionAllPlaylistsSelected": { + "description": "Shown when all playlists are selected in selection mode" + }, + "selectionTapPlaylistsToSelect": "Tap playlists to select", + "@selectionTapPlaylistsToSelect": { + "description": "Hint shown in playlist selection mode" + }, + "selectionSelectPlaylistsToDelete": "Select playlists to delete", + "@selectionSelectPlaylistsToDelete": { + "description": "Hint shown when no playlists are selected for deletion" + }, + "audioAnalysisTitle": "Audio Quality Analysis", + "@audioAnalysisTitle": { + "description": "Title for audio analysis section" + }, + "audioAnalysisDescription": "Verify lossless quality with spectrum analysis", + "@audioAnalysisDescription": { + "description": "Description for audio analysis tap-to-analyze prompt" + }, + "audioAnalysisAnalyzing": "Analyzing audio...", + "@audioAnalysisAnalyzing": { + "description": "Loading text while analyzing audio" + }, + "audioAnalysisSampleRate": "Sample Rate", + "@audioAnalysisSampleRate": { + "description": "Sample rate metric label" + }, + "audioAnalysisBitDepth": "Bit Depth", + "@audioAnalysisBitDepth": { + "description": "Bit depth metric label" + }, + "audioAnalysisChannels": "Channels", + "@audioAnalysisChannels": { + "description": "Channels metric label" + }, + "audioAnalysisDuration": "Duration", + "@audioAnalysisDuration": { + "description": "Duration metric label" + }, + "audioAnalysisNyquist": "Nyquist", + "@audioAnalysisNyquist": { + "description": "Nyquist frequency metric label" + }, + "audioAnalysisFileSize": "Size", + "@audioAnalysisFileSize": { + "description": "File size metric label" + }, + "audioAnalysisDynamicRange": "Dynamic Range", + "@audioAnalysisDynamicRange": { + "description": "Dynamic range metric label" + }, + "audioAnalysisPeak": "Peak", + "@audioAnalysisPeak": { + "description": "Peak amplitude metric label" + }, + "audioAnalysisRms": "RMS", + "@audioAnalysisRms": { + "description": "RMS level metric label" + }, + "audioAnalysisSamples": "Samples", + "@audioAnalysisSamples": { + "description": "Total samples metric label" + }, + "extensionsSearchWith": "Search with {providerName}", + "@extensionsSearchWith": { + "description": "Extensions page - subtitle for built-in search provider option", + "placeholders": { + "providerName": { + "type": "String" + } + } + }, + "extensionsHomeFeedProvider": "Home Feed Provider", + "@extensionsHomeFeedProvider": { + "description": "Extensions page - label for home feed provider selector" + }, + "extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen", + "@extensionsHomeFeedDescription": { + "description": "Extensions page - description for home feed provider picker" + }, + "extensionsHomeFeedAuto": "Auto", + "@extensionsHomeFeedAuto": { + "description": "Label for auto-selected search provider" + }, + "extensionsHomeFeedAutoSubtitle": "Automatically select the best available", + "@extensionsHomeFeedAutoSubtitle": { + "description": "Extensions page - subtitle for auto home feed option" + }, + "extensionsHomeFeedUse": "Use {extensionName} home feed", + "@extensionsHomeFeedUse": { + "description": "Extensions page - subtitle for a specific extension home feed option", + "placeholders": { + "extensionName": { + "type": "String" + } + } + }, + "extensionsNoHomeFeedExtensions": "No extensions with home feed", + "@extensionsNoHomeFeedExtensions": { + "description": "Extensions page - shown when no installed extension has home feed" + }, + "sortAlphaAsc": "A-Z", + "@sortAlphaAsc": { + "description": "Sort option - alphabetical ascending" + }, + "sortAlphaDesc": "Z-A", + "@sortAlphaDesc": { + "description": "Sort option - alphabetical descending" + }, + "cancelDownloadTitle": "Cancel download?", + "@cancelDownloadTitle": { + "description": "Dialog title when confirming cancellation of an active download" + }, + "cancelDownloadContent": "This will cancel the active download for \"{trackName}\".", + "@cancelDownloadContent": { + "description": "Dialog body when confirming cancellation of an active download", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "cancelDownloadKeep": "Keep", + "@cancelDownloadKeep": { + "description": "Dialog button - keep the active download (do not cancel)" + }, + "metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg", + "@metadataSaveFailedFfmpeg": { + "description": "Snackbar error when FFmpeg fails to write metadata" + }, + "metadataSaveFailedStorage": "Failed to write metadata back to storage", + "@metadataSaveFailedStorage": { + "description": "Snackbar error when writing metadata file back to storage fails" + }, + "snackbarFolderPickerFailed": "Failed to open folder picker: {error}", + "@snackbarFolderPickerFailed": { + "description": "Snackbar shown when folder picker fails to open", + "placeholders": { + "error": { + "type": "String" + } + } + }, + "errorLoadAlbum": "Failed to load album", + "@errorLoadAlbum": { + "description": "Error state shown when album fails to load" + }, + "errorLoadPlaylist": "Failed to load playlist", + "@errorLoadPlaylist": { + "description": "Error state shown when playlist fails to load" + }, + "errorLoadArtist": "Failed to load artist", + "@errorLoadArtist": { + "description": "Error state shown when artist fails to load" + }, + "notifChannelDownloadName": "Download Progress", + "@notifChannelDownloadName": { + "description": "Android notification channel name for download progress" + }, + "notifChannelDownloadDesc": "Shows download progress for tracks", + "@notifChannelDownloadDesc": { + "description": "Android notification channel description for download progress" + }, + "notifChannelLibraryScanName": "Library Scan", + "@notifChannelLibraryScanName": { + "description": "Android notification channel name for library scan" + }, + "notifChannelLibraryScanDesc": "Shows local library scan progress", + "@notifChannelLibraryScanDesc": { + "description": "Android notification channel description for library scan" + }, + "notifDownloadingTrack": "Downloading {trackName}", + "@notifDownloadingTrack": { + "description": "Notification title while downloading a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifFinalizingTrack": "Finalizing {trackName}", + "@notifFinalizingTrack": { + "description": "Notification title while finalizing (embedding metadata) a track", + "placeholders": { + "trackName": { + "type": "String" + } + } + }, + "notifEmbeddingMetadata": "Embedding metadata...", + "@notifEmbeddingMetadata": { + "description": "Notification body while embedding metadata into a downloaded track" + }, + "notifAlreadyInLibraryCount": "Already in Library ({completed}/{total})", + "@notifAlreadyInLibraryCount": { + "description": "Notification title when track is already in library, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifAlreadyInLibrary": "Already in Library", + "@notifAlreadyInLibrary": { + "description": "Notification title when track is already in library" + }, + "notifDownloadCompleteCount": "Download Complete ({completed}/{total})", + "@notifDownloadCompleteCount": { + "description": "Notification title when download is complete, with count", + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "notifDownloadComplete": "Download Complete", + "@notifDownloadComplete": { + "description": "Notification title when a single download is complete" + }, + "notifDownloadsFinished": "Downloads Finished ({completed} done, {failed} failed)", + "@notifDownloadsFinished": { + "description": "Notification title when queue finishes with some failures", + "placeholders": { + "completed": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "notifAllDownloadsComplete": "All Downloads Complete", + "@notifAllDownloadsComplete": { + "description": "Notification title when all downloads finish successfully" + }, + "notifTracksDownloadedSuccess": "{count} tracks downloaded successfully", + "@notifTracksDownloadedSuccess": { + "description": "Notification body for queue complete - how many tracks were downloaded", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifScanningLibrary": "Scanning local library", + "@notifScanningLibrary": { + "description": "Notification title while scanning local library" + }, + "notifLibraryScanProgressWithTotal": "{scanned}/{total} files • {percentage}%", + "@notifLibraryScanProgressWithTotal": { + "description": "Notification body for library scan progress when total is known", + "placeholders": { + "scanned": { + "type": "int" + }, + "total": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanProgressNoTotal": "{scanned} files scanned • {percentage}%", + "@notifLibraryScanProgressNoTotal": { + "description": "Notification body for library scan progress when total is unknown", + "placeholders": { + "scanned": { + "type": "int" + }, + "percentage": { + "type": "int" + } + } + }, + "notifLibraryScanComplete": "Library scan complete", + "@notifLibraryScanComplete": { + "description": "Notification title when library scan finishes" + }, + "notifLibraryScanCompleteBody": "{count} tracks indexed", + "@notifLibraryScanCompleteBody": { + "description": "Notification body for library scan complete - number of indexed tracks", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanExcluded": "{count} excluded", + "@notifLibraryScanExcluded": { + "description": "Library scan complete suffix - excluded track count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanErrors": "{count} errors", + "@notifLibraryScanErrors": { + "description": "Library scan complete suffix - error count", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notifLibraryScanFailed": "Library scan failed", + "@notifLibraryScanFailed": { + "description": "Notification title when library scan fails" + }, + "notifLibraryScanCancelled": "Library scan cancelled", + "@notifLibraryScanCancelled": { + "description": "Notification title when library scan is cancelled by the user" + }, + "notifLibraryScanStopped": "Scan stopped before completion.", + "@notifLibraryScanStopped": { + "description": "Notification body when library scan is cancelled" + }, + "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "@notifDownloadingUpdate": { + "description": "Notification title while downloading an app update", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateProgress": "{received} / {total} MB • {percentage}%", + "@notifUpdateProgress": { + "description": "Notification body showing update download progress", + "placeholders": { + "received": { + "type": "String" + }, + "total": { + "type": "String" + }, + "percentage": { + "type": "int" + } + } + }, + "notifUpdateReady": "Update Ready", + "@notifUpdateReady": { + "description": "Notification title when app update download is complete" + }, + "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "@notifUpdateReadyBody": { + "description": "Notification body when app update is ready to install", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "notifUpdateFailed": "Update Failed", + "@notifUpdateFailed": { + "description": "Notification title when app update download fails" + }, + "notifUpdateFailedBody": "Could not download update. Try again later.", + "@notifUpdateFailedBody": { + "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } diff --git a/lib/l10n/arb/app_zh_CN.arb b/lib/l10n/arb/app_zh_CN.arb index 5b271364..cd341cef 100644 --- a/lib/l10n/arb/app_zh_CN.arb +++ b/lib/l10n/arb/app_zh_CN.arb @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } diff --git a/lib/l10n/arb/app_zh_TW.arb b/lib/l10n/arb/app_zh_TW.arb index 4d9a042a..139262bf 100644 --- a/lib/l10n/arb/app_zh_TW.arb +++ b/lib/l10n/arb/app_zh_TW.arb @@ -4509,5 +4509,77 @@ "notifUpdateFailedBody": "Could not download update. Try again later.", "@notifUpdateFailedBody": { "description": "Notification body when app update download fails" + }, + "settingsFiles": "Files & Folders", + "@settingsFiles": { + "description": "Settings menu item - file and folder settings" + }, + "settingsFilesSubtitle": "Download location, filename, folder structure", + "@settingsFilesSubtitle": { + "description": "Subtitle for files & folders settings" + }, + "settingsMetadata": "Metadata", + "@settingsMetadata": { + "description": "Settings menu item - metadata settings" + }, + "settingsMetadataSubtitle": "Cover art, tags, ReplayGain, providers", + "@settingsMetadataSubtitle": { + "description": "Subtitle for metadata settings" + }, + "settingsLyrics": "Lyrics", + "@settingsLyrics": { + "description": "Settings menu item - lyrics settings" + }, + "settingsLyricsSubtitle": "Embed, mode, providers, language options", + "@settingsLyricsSubtitle": { + "description": "Subtitle for lyrics settings" + }, + "settingsApp": "App", + "@settingsApp": { + "description": "Settings menu item - app settings" + }, + "settingsAppSubtitle": "Updates, data, extension repo, debug", + "@settingsAppSubtitle": { + "description": "Subtitle for app settings" + }, + "sectionMetadataProviders": "Providers", + "@sectionMetadataProviders": { + "description": "Settings section header for metadata providers" + }, + "sectionDuplicates": "Duplicates", + "@sectionDuplicates": { + "description": "Settings section header for deduplication" + }, + "sectionLyricsProviderOptions": "Provider Options", + "@sectionLyricsProviderOptions": { + "description": "Settings section header for per-provider lyrics options" + }, + "metadataProvidersTitle": "Metadata Provider Priority", + "@metadataProvidersTitle": { + "description": "Settings item title for metadata provider order" + }, + "metadataProvidersSubtitle": "Drag to set search and metadata source order", + "@metadataProvidersSubtitle": { + "description": "Subtitle for metadata provider priority item" + }, + "downloadDeduplication": "Skip Duplicate Downloads", + "@downloadDeduplication": { + "description": "Setting - skip tracks already in download history" + }, + "downloadDeduplicationEnabled": "Already-downloaded tracks will be skipped", + "@downloadDeduplicationEnabled": { + "description": "Subtitle when deduplication is on" + }, + "downloadDeduplicationDisabled": "All tracks will be downloaded regardless of history", + "@downloadDeduplicationDisabled": { + "description": "Subtitle when deduplication is off" + }, + "downloadFallbackExtensions": "Fallback Extensions", + "@downloadFallbackExtensions": { + "description": "Settings item for configuring fallback extension providers" + }, + "downloadFallbackExtensionsSubtitle": "Choose which extensions can be used as fallback", + "@downloadFallbackExtensionsSubtitle": { + "description": "Subtitle for fallback extensions item" } } From 8f14ff169a429260734ef437799a13c1e1db3aec Mon Sep 17 00:00:00 2001 From: Amonoman Date: Sat, 2 May 2026 18:24:03 +0200 Subject: [PATCH 09/12] Aktualisieren von release.yml --- .github/workflows/release.yml | 505 +--------------------------------- 1 file changed, 5 insertions(+), 500 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4769f2ec..e2d0a080 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Release +name: Build Android on: push: @@ -12,52 +12,18 @@ on: default: "v1.0.0" jobs: - # Get version first (quick job) - get-version: - runs-on: ubuntu-latest - outputs: - version: ${{ steps.version.outputs.version }} - is_prerelease: ${{ steps.version.outputs.is_prerelease }} - steps: - - name: Get version - id: version - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - VERSION="${{ github.event.inputs.version }}" - else - VERSION="${GITHUB_REF#refs/tags/}" - fi - echo "version=$VERSION" >> $GITHUB_OUTPUT - - # Check if version contains -preview, -beta, -rc, or -alpha (NOT -hotfix) - VERSION_LOWER=$(echo "$VERSION" | tr '[:upper:]' '[:lower:]') - if [[ "$VERSION_LOWER" == *"-preview"* ]] || [[ "$VERSION_LOWER" == *"-beta"* ]] || [[ "$VERSION_LOWER" == *"-rc"* ]] || [[ "$VERSION_LOWER" == *"-alpha"* ]]; then - echo "is_prerelease=true" >> $GITHUB_OUTPUT - echo "Detected pre-release version: $VERSION" - else - echo "is_prerelease=false" >> $GITHUB_OUTPUT - echo "Detected stable version: $VERSION" - fi - - # Android and iOS build in PARALLEL build-android: runs-on: ubuntu-latest - needs: get-version - steps: - name: Free disk space run: | - # Remove large unused tools (~15GB total) sudo rm -rf /usr/share/dotnet sudo rm -rf /opt/ghc sudo rm -rf /opt/hostedtoolcache/CodeQL sudo rm -rf /usr/local/share/boost sudo rm -rf /usr/share/swift sudo rm -rf /usr/local/.ghcup - # Clean docker images sudo docker image prune --all --force - # Show available space - df -h - name: Checkout repository uses: actions/checkout@v6 @@ -74,7 +40,6 @@ jobs: go-version: "1.25.8" cache-dependency-path: go_backend/go.sum - # Cache Gradle for faster builds - name: Cache Gradle uses: actions/cache@v5 with: @@ -82,22 +47,11 @@ jobs: ~/.gradle/caches ~/.gradle/wrapper key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: gradle-${{ runner.os }}- - name: Install Android SDK & NDK run: | - # Use pre-installed Android SDK on GitHub runners - echo "ANDROID_HOME=$ANDROID_HOME" - echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" - - # Accept licenses yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses || true - - # Install NDK r29 (supports 16KB page size for Android 15+) - # Platform android-36 and build-tools 36.0.0 for targetSdk 36 (Android 16) $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "ndk;29.0.14206865" "platforms;android-36" "build-tools;36.0.0" - - # Set NDK path echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/29.0.14206865" >> $GITHUB_ENV - name: Install gomobile @@ -122,460 +76,11 @@ jobs: - name: Get Flutter dependencies run: flutter pub get - - name: Generate app icons - run: dart run flutter_launcher_icons - - - name: Build APK (Release - unsigned) - run: | - flutter build apk --release --split-per-abi || true - # Verify APKs were created - ls -la build/app/outputs/flutter-apk/ - if [ ! -f "build/app/outputs/flutter-apk/app-arm64-v8a-release.apk" ]; then - echo "ERROR: APK not found!" - exit 1 - fi - - - name: Sign APKs - uses: r0adkll/sign-android-release@v1 - id: sign_arm64 - with: - releaseDirectory: build/app/outputs/flutter-apk - signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }} - alias: ${{ secrets.KEY_ALIAS }} - keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }} - keyPassword: ${{ secrets.KEY_PASSWORD }} - env: - BUILD_TOOLS_VERSION: "36.0.0" - - - name: Rename APKs - run: | - VERSION=${{ needs.get-version.outputs.version }} - cd build/app/outputs/flutter-apk - # Signed files have -signed suffix - mv app-arm64-v8a-release-signed.apk SpotiFLAC-${VERSION}-arm64.apk || mv app-arm64-v8a-release.apk SpotiFLAC-${VERSION}-arm64.apk || true - mv app-armeabi-v7a-release-signed.apk SpotiFLAC-${VERSION}-arm32.apk || mv app-armeabi-v7a-release.apk SpotiFLAC-${VERSION}-arm32.apk || true - mv app-release-signed.apk SpotiFLAC-${VERSION}-universal.apk || mv app-release.apk SpotiFLAC-${VERSION}-universal.apk || true - ls -la + - name: Build APK (Only arm64) + run: flutter build apk --release --target-platform android-arm64 - name: Upload APK artifact uses: actions/upload-artifact@v6 with: - name: android-apk - path: build/app/outputs/flutter-apk/SpotiFLAC-*.apk - - build-ios: - runs-on: macos-15 - needs: get-version # Only depends on version, NOT android build! - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Select Xcode 26.1.1 - run: | - sudo xcode-select -s /Applications/Xcode_26.1.1.app - xcodebuild -version - - - name: Setup Go - uses: actions/setup-go@v6 - with: - go-version: "1.25.8" - cache-dependency-path: go_backend/go.sum - - # Cache CocoaPods - - name: Cache CocoaPods - uses: actions/cache@v5 - with: - path: ios/Pods - key: pods-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }} - restore-keys: pods-${{ runner.os }}- - - - name: Install gomobile - run: | - go install golang.org/x/mobile/cmd/gomobile@latest - gomobile init - - - name: Build Go backend for iOS - working-directory: go_backend - run: | - mkdir -p ../ios/Frameworks - gomobile bind -target=ios -tags ios -o ../ios/Frameworks/Gobackend.xcframework . - env: - CGO_ENABLED: 1 - - - name: Verify XCFramework created - run: | - ls -la ios/Frameworks/ - ls -la ios/Frameworks/Gobackend.xcframework/ || (echo "ERROR: XCFramework not found!" && exit 1) - - - name: Add XCFramework to Xcode project - run: | - # Install xcodeproj gem for modifying Xcode project - sudo gem install xcodeproj - - # Create Ruby script to add framework - cat > add_framework.rb << 'EOF' - require 'xcodeproj' - - project_path = 'ios/Runner.xcodeproj' - project = Xcodeproj::Project.open(project_path) - - # Get the main target - target = project.targets.find { |t| t.name == 'Runner' } - - # Get or create Frameworks group - frameworks_group = project.main_group.find_subpath('Frameworks', true) - frameworks_group ||= project.main_group.new_group('Frameworks') - - # Add XCFramework reference - framework_path = 'Frameworks/Gobackend.xcframework' - framework_ref = frameworks_group.new_file(framework_path, :project) - - # Add to frameworks build phase - frameworks_build_phase = target.frameworks_build_phase - frameworks_build_phase.add_file_reference(framework_ref) - - # Add to embed frameworks build phase - embed_phase = target.build_phases.find { |p| p.is_a?(Xcodeproj::Project::Object::PBXCopyFilesBuildPhase) && p.name == 'Embed Frameworks' } - if embed_phase - build_file = embed_phase.add_file_reference(framework_ref) - build_file.settings = { 'ATTRIBUTES' => ['CodeSignOnCopy', 'RemoveHeadersOnCopy'] } - end - - project.save - puts "Successfully added Gobackend.xcframework to Xcode project" - EOF - - ruby add_framework.rb - - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - cache: true - - - name: Get Flutter dependencies - run: flutter pub get - - - name: Generate app icons - run: dart run flutter_launcher_icons - - - name: Build iOS (unsigned) - run: | - # Build Flutter iOS without codesigning - flutter build ios --release --no-codesign --config-only - - # Use xcodebuild with code signing disabled - cd ios - xcodebuild -workspace Runner.xcworkspace \ - -scheme Runner \ - -configuration Release \ - -sdk iphoneos \ - -destination 'generic/platform=iOS' \ - -archivePath build/Runner.xcarchive \ - archive \ - CODE_SIGNING_ALLOWED=NO \ - CODE_SIGNING_REQUIRED=NO \ - CODE_SIGN_IDENTITY="" \ - DEVELOPMENT_TEAM="" - - - name: Create IPA - run: | - VERSION=${{ needs.get-version.outputs.version }} - mkdir -p build/ios/ipa - cd ios/build/Runner.xcarchive/Products/Applications - mkdir Payload - cp -r Runner.app Payload/ - # Use absolute path to avoid relative path issues - zip -r $GITHUB_WORKSPACE/build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa Payload - rm -rf Payload - - - name: Verify IPA created - run: | - ls -la build/ios/ipa/ - VERSION=${{ needs.get-version.outputs.version }} - if [ ! -f "build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa" ]; then - echo "ERROR: IPA not created!" - exit 1 - fi - - - name: Upload IPA artifact - uses: actions/upload-artifact@v6 - with: - name: ios-ipa - path: build/ios/ipa/SpotiFLAC-*.ipa - - create-release: - runs-on: ubuntu-latest - needs: [get-version, build-android, build-ios] - permissions: - contents: write - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 # Full history needed for git-cliff - - - 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: | - echo "Generated changelog:" - cat /tmp/changelog.txt - - - name: Download Android APK - uses: actions/download-artifact@v7 - with: - name: android-apk - path: ./release - - - name: Download iOS IPA - uses: actions/download-artifact@v7 - with: - name: ios-ipa - path: ./release - - - name: Prepare release body - run: | - VERSION=${{ needs.get-version.outputs.version }} - REPO_OWNER="${{ github.repository_owner }}" - REPO_NAME="${{ github.event.repository.name }}" - CURRENT_REF=$(git rev-list -n 1 "$VERSION" 2>/dev/null || git rev-parse HEAD) - PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${CURRENT_REF}^" 2>/dev/null || true) - - # Start with git-cliff changelog, but replace its compare footer with a - # deterministic previous-tag lookup from git. - sed '/^## [0-9][0-9.[:alpha:]-]*$/d; /^\*\*Full Changelog\*\*/d' /tmp/changelog.txt > /tmp/release_body.txt - - if [ -n "$PREVIOUS_TAG" ]; then - printf '\n**Full Changelog**: [%s...%s](https://github.com/%s/%s/compare/%s...%s)\n' \ - "$PREVIOUS_TAG" "$VERSION" "$REPO_OWNER" "$REPO_NAME" "$PREVIOUS_TAG" "$VERSION" \ - >> /tmp/release_body.txt - fi - - # Append download section - cat >> /tmp/release_body.txt << FOOTER - - --- - - ### Downloads - - #### Android - - **arm64**: \`SpotiFLAC-${VERSION}-arm64.apk\` (recommended for modern devices) - - **arm32**: \`SpotiFLAC-${VERSION}-arm32.apk\` (older devices) - - #### iOS - - **iOS**: \`SpotiFLAC-${VERSION}-ios-unsigned.ipa\` (sideload required) - - ### Installation - **Android**: Enable "Install from unknown sources" and install the APK - **iOS**: Use AltStore, Sideloadly, or similar tools to sideload the IPA - - ![arm64](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-arm64.apk?style=flat-square&logo=android&label=arm64&color=3DDC84) ![arm32](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-arm32.apk?style=flat-square&logo=android&label=arm32&color=3DDC84) ![iOS](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-ios-unsigned.ipa?style=flat-square&logo=apple&label=iOS&color=0078D6) - FOOTER - - echo "Release body:" - cat /tmp/release_body.txt - - - name: Create Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ needs.get-version.outputs.version }} - name: SpotiFLAC ${{ needs.get-version.outputs.version }} - body_path: /tmp/release_body.txt - files: ./release/* - draft: false - prerelease: ${{ needs.get-version.outputs.is_prerelease == 'true' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - update-altstore: - runs-on: ubuntu-latest - needs: [get-version, build-ios, create-release] - if: ${{ needs.get-version.outputs.is_prerelease != 'true' }} - permissions: - contents: write - - steps: - - name: Checkout main branch - uses: actions/checkout@v6 - with: - ref: main - - - name: Download iOS IPA - uses: actions/download-artifact@v7 - with: - name: ios-ipa - path: ./release - - - name: Update apps.json - run: | - VERSION="${{ needs.get-version.outputs.version }}" - VERSION_NUM="${VERSION#v}" - DATE=$(date -u +%Y-%m-%d) - IPA_FILE=$(find ./release -name "*ios*.ipa" | head -1) - - if [ -z "$IPA_FILE" ]; then - echo "WARNING: IPA file not found, skipping apps.json update" - exit 0 - fi - - IPA_SIZE=$(stat -c%s "$IPA_FILE" 2>/dev/null || stat -f%z "$IPA_FILE") - - if [ ! -f apps.json ]; then - echo "WARNING: apps.json not found on main, skipping" - exit 0 - fi - - jq --arg ver "$VERSION_NUM" \ - --arg date "$DATE" \ - --arg url "https://github.com/zarzet/SpotiFLAC-Mobile/releases/download/${VERSION}/SpotiFLAC-${VERSION}-ios-unsigned.ipa" \ - --argjson size "$IPA_SIZE" \ - '.apps[0].version = $ver | .apps[0].versionDate = $date | .apps[0].downloadURL = $url | .apps[0].size = $size' \ - apps.json > apps.json.tmp && mv apps.json.tmp apps.json - - echo "Updated apps.json:" - cat apps.json - - - name: Commit and push - run: | - VERSION="${{ needs.get-version.outputs.version }}" - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add apps.json - git diff --cached --quiet && echo "No changes to commit" || \ - (git commit -m "chore: update AltStore source to ${VERSION}" && git push) - - notify-telegram: - runs-on: ubuntu-latest - needs: [get-version, create-release] - if: ${{ needs.get-version.outputs.is_prerelease != 'true' }} - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Download Android APK - uses: actions/download-artifact@v7 - with: - name: android-apk - path: ./release - - - name: Download iOS IPA - uses: actions/download-artifact@v7 - with: - name: ios-ipa - path: ./release - - - 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: | - if [ ! -s /tmp/cliff_tg.txt ]; then - echo "See release notes on GitHub for details." > /tmp/changelog.txt - else - # Convert Markdown to Telegram HTML - CHANGELOG=$(cat /tmp/cliff_tg.txt | \ - sed '/^## [0-9][0-9.[:alpha:]-]*$/d' | \ - sed '/^\*\*Full Changelog\*\*/d' | \ - sed 's/ by \[@[^]]*\](https:\/\/github\.com\/[^)]*)//g' | \ - sed 's/ by @[A-Za-z0-9_-]\+//g' | \ - sed 's/\[#\([0-9]*\)\]([^)]*)/#\1/g' | \ - sed 's/\[@\([^]]*\)\]([^)]*)/@\1/g' | \ - sed 's/&/\&/g' | \ - sed 's//\>/g' | \ - sed 's/\*\*\([^*]*\)\*\*/\1<\/b>/g' | \ - sed 's/^### \(.*\)$/\1<\/b>/g' | \ - sed 's/^## \(.*\)$/\1<\/b>/g' | \ - sed 's/^- /• /g') - - # Truncate for Telegram 4096 char limit - CHANGELOG=$(echo "$CHANGELOG" | head -c 2500 | sed '$d') - echo "$CHANGELOG" > /tmp/changelog.txt - fi - - echo "Telegram changelog:" - cat /tmp/changelog.txt - - - name: Send to Telegram Channel - env: - TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} - TELEGRAM_CHANNEL_ID: ${{ secrets.TELEGRAM_CHANNEL_ID }} - run: | - VERSION=${{ needs.get-version.outputs.version }} - CHANGELOG=$(cat /tmp/changelog.txt) - - # Find APK files - ARM64_APK=$(find ./release -name "*arm64*.apk" | head -1) - ARM32_APK=$(find ./release -name "*arm32*.apk" | head -1) - - # Prepare message with changelog (HTML format) - printf '%s\n' \ - "SpotiFLAC Mobile ${VERSION} Released!" \ - "" \ - "What's New:" \ - "${CHANGELOG}" \ - "" \ - "View Release Notes" \ - > /tmp/telegram_message.txt - - MESSAGE=$(cat /tmp/telegram_message.txt) - - # Send message first (using HTML parse mode) - # Use --data-urlencode for proper encoding of special chars (+, &, etc.) - # Use || true to ensure file uploads continue even if message fails - curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ - --data-urlencode "chat_id=${TELEGRAM_CHANNEL_ID}" \ - --data-urlencode "text=${MESSAGE}" \ - --data-urlencode "parse_mode=HTML" \ - --data-urlencode "disable_web_page_preview=true" || true - - # Upload arm64 APK to channel - if [ -f "$ARM64_APK" ]; then - echo "Uploading arm64 APK to Telegram..." - curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ - -F chat_id="${TELEGRAM_CHANNEL_ID}" \ - -F document=@"${ARM64_APK}" \ - -F caption="SpotiFLAC ${VERSION} - arm64 (recommended)" - fi - - # Upload arm32 APK to channel - if [ -f "$ARM32_APK" ]; then - echo "Uploading arm32 APK to Telegram..." - curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ - -F chat_id="${TELEGRAM_CHANNEL_ID}" \ - -F document=@"${ARM32_APK}" \ - -F caption="SpotiFLAC ${VERSION} - arm32" - fi - - # Upload iOS IPA to channel - IOS_IPA=$(find ./release -name "*ios*.ipa" | head -1) - if [ -f "$IOS_IPA" ]; then - echo "Uploading iOS IPA to Telegram..." - curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ - -F chat_id="${TELEGRAM_CHANNEL_ID}" \ - -F document=@"${IOS_IPA}" \ - -F caption="SpotiFLAC ${VERSION} - iOS (unsigned, sideload required)" - fi - - echo "Telegram notification sent!" + name: SpotiFLAC-arm64-apk + path: build/app/outputs/flutter-apk/app-release.apk From f8b7812943f08d5e17d21785d6bd0fa8cc9ab996 Mon Sep 17 00:00:00 2001 From: Amonoman Date: Sat, 2 May 2026 18:47:09 +0200 Subject: [PATCH 10/12] feat: add deduplicateDownloads setting & fix build errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add deduplicateDownloads field to AppSettings (default: true) - Add setDeduplicateDownloads() to SettingsNotifier - Fix type mismatch in files_settings_page (Object → String cast) - Run build_runner to regenerate settings.g.dart --- .github/workflows/release.yml | 3 +++ lib/models/settings.dart | 6 ++++++ lib/providers/settings_provider.dart | 5 +++++ lib/screens/settings/files_settings_page.dart | 2 +- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2d0a080..51f74317 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -76,6 +76,9 @@ jobs: - name: Get Flutter dependencies run: flutter pub get + - name: Generate code + run: dart run build_runner build --delete-conflicting-outputs + - name: Build APK (Only arm64) run: flutter build apk --release --target-platform android-arm64 diff --git a/lib/models/settings.dart b/lib/models/settings.dart index 147c2c20..17f125d5 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -82,6 +82,9 @@ class AppSettings { final String lastSeenVersion; // Last app version the user has acknowledged (e.g. '3.7.0') + final bool + deduplicateDownloads; // Skip downloading tracks already present in history + const AppSettings({ this.defaultService = 'tidal', this.audioQuality = 'LOSSLESS', @@ -144,6 +147,7 @@ class AppSettings { this.lyricsMultiPersonWordByWord = false, this.musixmatchLanguage = '', this.lastSeenVersion = '', + this.deduplicateDownloads = true, }); AppSettings copyWith({ @@ -205,6 +209,7 @@ class AppSettings { bool? lyricsMultiPersonWordByWord, String? musixmatchLanguage, String? lastSeenVersion, + bool? deduplicateDownloads, }) { return AppSettings( defaultService: defaultService ?? this.defaultService, @@ -281,6 +286,7 @@ class AppSettings { lyricsMultiPersonWordByWord ?? this.lyricsMultiPersonWordByWord, musixmatchLanguage: musixmatchLanguage ?? this.musixmatchLanguage, lastSeenVersion: lastSeenVersion ?? this.lastSeenVersion, + deduplicateDownloads: deduplicateDownloads ?? this.deduplicateDownloads, ); } diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 4e2ea6ca..906715c3 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -544,6 +544,11 @@ class SettingsNotifier extends Notifier { state = state.copyWith(hasCompletedTutorial: true); _saveSettings(); } + + void setDeduplicateDownloads(bool enabled) { + state = state.copyWith(deduplicateDownloads: enabled); + _saveSettings(); + } } final settingsProvider = NotifierProvider( diff --git a/lib/screens/settings/files_settings_page.dart b/lib/screens/settings/files_settings_page.dart index 0382abc4..85a7bee6 100644 --- a/lib/screens/settings/files_settings_page.dart +++ b/lib/screens/settings/files_settings_page.dart @@ -753,7 +753,7 @@ class _FilesSettingsPageState extends ConsumerState { ), const SizedBox(height: 8), Text( - description ?? context.l10n.downloadFilenameDescription, + description ?? context.l10n.downloadFilenameDescription as String, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), From b55be00fab81023e257afbacee2468fabd6595e5 Mon Sep 17 00:00:00 2001 From: Amonoman Date: Sat, 2 May 2026 19:04:39 +0200 Subject: [PATCH 11/12] i18n: add missing optionsDefaultSearchTabAlbums/Tracks keys to all locales --- lib/l10n/arb/app_de.arb | 8 ++++++++ lib/l10n/arb/app_en.arb | 8 ++++++++ lib/l10n/arb/app_es.arb | 8 ++++++++ lib/l10n/arb/app_es_ES.arb | 8 ++++++++ lib/l10n/arb/app_fr.arb | 8 ++++++++ lib/l10n/arb/app_hi.arb | 8 ++++++++ lib/l10n/arb/app_id.arb | 8 ++++++++ lib/l10n/arb/app_ja.arb | 8 ++++++++ lib/l10n/arb/app_ko.arb | 8 ++++++++ lib/l10n/arb/app_nl.arb | 8 ++++++++ lib/l10n/arb/app_pt.arb | 8 ++++++++ lib/l10n/arb/app_pt_PT.arb | 8 ++++++++ lib/l10n/arb/app_ru.arb | 8 ++++++++ lib/l10n/arb/app_tr.arb | 8 ++++++++ lib/l10n/arb/app_uk.arb | 8 ++++++++ lib/l10n/arb/app_zh.arb | 8 ++++++++ lib/l10n/arb/app_zh_CN.arb | 8 ++++++++ lib/l10n/arb/app_zh_TW.arb | 8 ++++++++ 18 files changed, 144 insertions(+) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 33150a80..c16f90a3 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Tippe auf Deezer oder Spotify, um von der Erweiterung zurückzuwechseln", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index a5d4d5ac..aa5ce62c 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_es.arb b/lib/l10n/arb/app_es.arb index a17f7299..a5294db3 100644 --- a/lib/l10n/arb/app_es.arb +++ b/lib/l10n/arb/app_es.arb @@ -1753,6 +1753,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsReplayGain": "ReplayGain", "@optionsReplayGain": { "description": "Title for ReplayGain setting toggle" diff --git a/lib/l10n/arb/app_es_ES.arb b/lib/l10n/arb/app_es_ES.arb index 7fccb158..3d1432d2 100644 --- a/lib/l10n/arb/app_es_ES.arb +++ b/lib/l10n/arb/app_es_ES.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Toque Deezer o Spotify para volver desde la extensión", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index ab865785..f68dedc8 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Appuyez sur Deezer ou Spotify pour revenir à l'extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_hi.arb b/lib/l10n/arb/app_hi.arb index 7935892f..2c18e2c2 100644 --- a/lib/l10n/arb/app_hi.arb +++ b/lib/l10n/arb/app_hi.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index 6b0733dd..a53fd98d 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Ketuk Deezer atau Spotify untuk beralih dari ekstensi", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_ja.arb b/lib/l10n/arb/app_ja.arb index 75f9e1b0..c755edf2 100644 --- a/lib/l10n/arb/app_ja.arb +++ b/lib/l10n/arb/app_ja.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_ko.arb b/lib/l10n/arb/app_ko.arb index 398c09b5..cf77be41 100644 --- a/lib/l10n/arb/app_ko.arb +++ b/lib/l10n/arb/app_ko.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Deezer 또는 Spotify를 탭하여 확장 기능에서 다시 전환하세요.", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_nl.arb b/lib/l10n/arb/app_nl.arb index b9cbaa09..91acf351 100644 --- a/lib/l10n/arb/app_nl.arb +++ b/lib/l10n/arb/app_nl.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_pt.arb b/lib/l10n/arb/app_pt.arb index de1ff199..bde8d0f1 100644 --- a/lib/l10n/arb/app_pt.arb +++ b/lib/l10n/arb/app_pt.arb @@ -1753,6 +1753,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsReplayGain": "ReplayGain", "@optionsReplayGain": { "description": "Title for ReplayGain setting toggle" diff --git a/lib/l10n/arb/app_pt_PT.arb b/lib/l10n/arb/app_pt_PT.arb index 5b7a11fc..a03a78e4 100644 --- a/lib/l10n/arb/app_pt_PT.arb +++ b/lib/l10n/arb/app_pt_PT.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Toque no Deezer ou Spotify para alternar de volta da extensão", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 54b47b1a..a3e2658c 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Нажмите Deezer или Spotify для возврата с расширения", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_tr.arb b/lib/l10n/arb/app_tr.arb index 9ffbe3b0..fb92a71b 100644 --- a/lib/l10n/arb/app_tr.arb +++ b/lib/l10n/arb/app_tr.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Dahili kaynaklara dönmek için Deezer veya Spotify'a tıkla", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_uk.arb b/lib/l10n/arb/app_uk.arb index d9f2ed50..d301f9e9 100644 --- a/lib/l10n/arb/app_uk.arb +++ b/lib/l10n/arb/app_uk.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Натисніть Deezer або Spotify, щоб повернутися до розширення", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_zh.arb b/lib/l10n/arb/app_zh.arb index 3127c84e..3c25cbc7 100644 --- a/lib/l10n/arb/app_zh.arb +++ b/lib/l10n/arb/app_zh.arb @@ -1753,6 +1753,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsReplayGain": "ReplayGain", "@optionsReplayGain": { "description": "Title for ReplayGain setting toggle" diff --git a/lib/l10n/arb/app_zh_CN.arb b/lib/l10n/arb/app_zh_CN.arb index cd341cef..0d72d929 100644 --- a/lib/l10n/arb/app_zh_CN.arb +++ b/lib/l10n/arb/app_zh_CN.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "点击 Deezer 或 Spotify 即可从扩展程序切换回来", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_zh_TW.arb b/lib/l10n/arb/app_zh_TW.arb index 139262bf..cb6bead3 100644 --- a/lib/l10n/arb/app_zh_TW.arb +++ b/lib/l10n/arb/app_zh_TW.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" From 2c7621c1a541f1d71b01db878b4d3b0ece7c1922 Mon Sep 17 00:00:00 2001 From: Amonoman Date: Sat, 2 May 2026 19:28:26 +0200 Subject: [PATCH 12/12] revert: release.yml to normal --- .github/workflows/release.yml | 506 +++++++++++++++++++++++++++++++++- 1 file changed, 499 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 51f74317..4769f2ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Build Android +name: Release on: push: @@ -12,18 +12,52 @@ on: default: "v1.0.0" jobs: + # Get version first (quick job) + get-version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + is_prerelease: ${{ steps.version.outputs.is_prerelease }} + steps: + - name: Get version + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + VERSION="${{ github.event.inputs.version }}" + else + VERSION="${GITHUB_REF#refs/tags/}" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + + # Check if version contains -preview, -beta, -rc, or -alpha (NOT -hotfix) + VERSION_LOWER=$(echo "$VERSION" | tr '[:upper:]' '[:lower:]') + if [[ "$VERSION_LOWER" == *"-preview"* ]] || [[ "$VERSION_LOWER" == *"-beta"* ]] || [[ "$VERSION_LOWER" == *"-rc"* ]] || [[ "$VERSION_LOWER" == *"-alpha"* ]]; then + echo "is_prerelease=true" >> $GITHUB_OUTPUT + echo "Detected pre-release version: $VERSION" + else + echo "is_prerelease=false" >> $GITHUB_OUTPUT + echo "Detected stable version: $VERSION" + fi + + # Android and iOS build in PARALLEL build-android: runs-on: ubuntu-latest + needs: get-version + steps: - name: Free disk space run: | + # Remove large unused tools (~15GB total) sudo rm -rf /usr/share/dotnet sudo rm -rf /opt/ghc sudo rm -rf /opt/hostedtoolcache/CodeQL sudo rm -rf /usr/local/share/boost sudo rm -rf /usr/share/swift sudo rm -rf /usr/local/.ghcup + # Clean docker images sudo docker image prune --all --force + # Show available space + df -h - name: Checkout repository uses: actions/checkout@v6 @@ -40,6 +74,7 @@ jobs: go-version: "1.25.8" cache-dependency-path: go_backend/go.sum + # Cache Gradle for faster builds - name: Cache Gradle uses: actions/cache@v5 with: @@ -47,11 +82,22 @@ jobs: ~/.gradle/caches ~/.gradle/wrapper key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: gradle-${{ runner.os }}- - name: Install Android SDK & NDK run: | + # Use pre-installed Android SDK on GitHub runners + echo "ANDROID_HOME=$ANDROID_HOME" + echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" + + # Accept licenses yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses || true + + # Install NDK r29 (supports 16KB page size for Android 15+) + # Platform android-36 and build-tools 36.0.0 for targetSdk 36 (Android 16) $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "ndk;29.0.14206865" "platforms;android-36" "build-tools;36.0.0" + + # Set NDK path echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/29.0.14206865" >> $GITHUB_ENV - name: Install gomobile @@ -76,14 +122,460 @@ jobs: - name: Get Flutter dependencies run: flutter pub get - - name: Generate code - run: dart run build_runner build --delete-conflicting-outputs + - name: Generate app icons + run: dart run flutter_launcher_icons - - name: Build APK (Only arm64) - run: flutter build apk --release --target-platform android-arm64 + - name: Build APK (Release - unsigned) + run: | + flutter build apk --release --split-per-abi || true + # Verify APKs were created + ls -la build/app/outputs/flutter-apk/ + if [ ! -f "build/app/outputs/flutter-apk/app-arm64-v8a-release.apk" ]; then + echo "ERROR: APK not found!" + exit 1 + fi + + - name: Sign APKs + uses: r0adkll/sign-android-release@v1 + id: sign_arm64 + with: + releaseDirectory: build/app/outputs/flutter-apk + signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }} + alias: ${{ secrets.KEY_ALIAS }} + keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }} + keyPassword: ${{ secrets.KEY_PASSWORD }} + env: + BUILD_TOOLS_VERSION: "36.0.0" + + - name: Rename APKs + run: | + VERSION=${{ needs.get-version.outputs.version }} + cd build/app/outputs/flutter-apk + # Signed files have -signed suffix + mv app-arm64-v8a-release-signed.apk SpotiFLAC-${VERSION}-arm64.apk || mv app-arm64-v8a-release.apk SpotiFLAC-${VERSION}-arm64.apk || true + mv app-armeabi-v7a-release-signed.apk SpotiFLAC-${VERSION}-arm32.apk || mv app-armeabi-v7a-release.apk SpotiFLAC-${VERSION}-arm32.apk || true + mv app-release-signed.apk SpotiFLAC-${VERSION}-universal.apk || mv app-release.apk SpotiFLAC-${VERSION}-universal.apk || true + ls -la - name: Upload APK artifact uses: actions/upload-artifact@v6 with: - name: SpotiFLAC-arm64-apk - path: build/app/outputs/flutter-apk/app-release.apk + name: android-apk + path: build/app/outputs/flutter-apk/SpotiFLAC-*.apk + + build-ios: + runs-on: macos-15 + needs: get-version # Only depends on version, NOT android build! + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Select Xcode 26.1.1 + run: | + sudo xcode-select -s /Applications/Xcode_26.1.1.app + xcodebuild -version + + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version: "1.25.8" + cache-dependency-path: go_backend/go.sum + + # Cache CocoaPods + - name: Cache CocoaPods + uses: actions/cache@v5 + with: + path: ios/Pods + key: pods-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }} + restore-keys: pods-${{ runner.os }}- + + - name: Install gomobile + run: | + go install golang.org/x/mobile/cmd/gomobile@latest + gomobile init + + - name: Build Go backend for iOS + working-directory: go_backend + run: | + mkdir -p ../ios/Frameworks + gomobile bind -target=ios -tags ios -o ../ios/Frameworks/Gobackend.xcframework . + env: + CGO_ENABLED: 1 + + - name: Verify XCFramework created + run: | + ls -la ios/Frameworks/ + ls -la ios/Frameworks/Gobackend.xcframework/ || (echo "ERROR: XCFramework not found!" && exit 1) + + - name: Add XCFramework to Xcode project + run: | + # Install xcodeproj gem for modifying Xcode project + sudo gem install xcodeproj + + # Create Ruby script to add framework + cat > add_framework.rb << 'EOF' + require 'xcodeproj' + + project_path = 'ios/Runner.xcodeproj' + project = Xcodeproj::Project.open(project_path) + + # Get the main target + target = project.targets.find { |t| t.name == 'Runner' } + + # Get or create Frameworks group + frameworks_group = project.main_group.find_subpath('Frameworks', true) + frameworks_group ||= project.main_group.new_group('Frameworks') + + # Add XCFramework reference + framework_path = 'Frameworks/Gobackend.xcframework' + framework_ref = frameworks_group.new_file(framework_path, :project) + + # Add to frameworks build phase + frameworks_build_phase = target.frameworks_build_phase + frameworks_build_phase.add_file_reference(framework_ref) + + # Add to embed frameworks build phase + embed_phase = target.build_phases.find { |p| p.is_a?(Xcodeproj::Project::Object::PBXCopyFilesBuildPhase) && p.name == 'Embed Frameworks' } + if embed_phase + build_file = embed_phase.add_file_reference(framework_ref) + build_file.settings = { 'ATTRIBUTES' => ['CodeSignOnCopy', 'RemoveHeadersOnCopy'] } + end + + project.save + puts "Successfully added Gobackend.xcframework to Xcode project" + EOF + + ruby add_framework.rb + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true + + - name: Get Flutter dependencies + run: flutter pub get + + - name: Generate app icons + run: dart run flutter_launcher_icons + + - name: Build iOS (unsigned) + run: | + # Build Flutter iOS without codesigning + flutter build ios --release --no-codesign --config-only + + # Use xcodebuild with code signing disabled + cd ios + xcodebuild -workspace Runner.xcworkspace \ + -scheme Runner \ + -configuration Release \ + -sdk iphoneos \ + -destination 'generic/platform=iOS' \ + -archivePath build/Runner.xcarchive \ + archive \ + CODE_SIGNING_ALLOWED=NO \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGN_IDENTITY="" \ + DEVELOPMENT_TEAM="" + + - name: Create IPA + run: | + VERSION=${{ needs.get-version.outputs.version }} + mkdir -p build/ios/ipa + cd ios/build/Runner.xcarchive/Products/Applications + mkdir Payload + cp -r Runner.app Payload/ + # Use absolute path to avoid relative path issues + zip -r $GITHUB_WORKSPACE/build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa Payload + rm -rf Payload + + - name: Verify IPA created + run: | + ls -la build/ios/ipa/ + VERSION=${{ needs.get-version.outputs.version }} + if [ ! -f "build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa" ]; then + echo "ERROR: IPA not created!" + exit 1 + fi + + - name: Upload IPA artifact + uses: actions/upload-artifact@v6 + with: + name: ios-ipa + path: build/ios/ipa/SpotiFLAC-*.ipa + + create-release: + runs-on: ubuntu-latest + needs: [get-version, build-android, build-ios] + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 # Full history needed for git-cliff + + - 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: | + echo "Generated changelog:" + cat /tmp/changelog.txt + + - name: Download Android APK + uses: actions/download-artifact@v7 + with: + name: android-apk + path: ./release + + - name: Download iOS IPA + uses: actions/download-artifact@v7 + with: + name: ios-ipa + path: ./release + + - name: Prepare release body + run: | + VERSION=${{ needs.get-version.outputs.version }} + REPO_OWNER="${{ github.repository_owner }}" + REPO_NAME="${{ github.event.repository.name }}" + CURRENT_REF=$(git rev-list -n 1 "$VERSION" 2>/dev/null || git rev-parse HEAD) + PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${CURRENT_REF}^" 2>/dev/null || true) + + # Start with git-cliff changelog, but replace its compare footer with a + # deterministic previous-tag lookup from git. + sed '/^## [0-9][0-9.[:alpha:]-]*$/d; /^\*\*Full Changelog\*\*/d' /tmp/changelog.txt > /tmp/release_body.txt + + if [ -n "$PREVIOUS_TAG" ]; then + printf '\n**Full Changelog**: [%s...%s](https://github.com/%s/%s/compare/%s...%s)\n' \ + "$PREVIOUS_TAG" "$VERSION" "$REPO_OWNER" "$REPO_NAME" "$PREVIOUS_TAG" "$VERSION" \ + >> /tmp/release_body.txt + fi + + # Append download section + cat >> /tmp/release_body.txt << FOOTER + + --- + + ### Downloads + + #### Android + - **arm64**: \`SpotiFLAC-${VERSION}-arm64.apk\` (recommended for modern devices) + - **arm32**: \`SpotiFLAC-${VERSION}-arm32.apk\` (older devices) + + #### iOS + - **iOS**: \`SpotiFLAC-${VERSION}-ios-unsigned.ipa\` (sideload required) + + ### Installation + **Android**: Enable "Install from unknown sources" and install the APK + **iOS**: Use AltStore, Sideloadly, or similar tools to sideload the IPA + + ![arm64](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-arm64.apk?style=flat-square&logo=android&label=arm64&color=3DDC84) ![arm32](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-arm32.apk?style=flat-square&logo=android&label=arm32&color=3DDC84) ![iOS](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-ios-unsigned.ipa?style=flat-square&logo=apple&label=iOS&color=0078D6) + FOOTER + + echo "Release body:" + cat /tmp/release_body.txt + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.get-version.outputs.version }} + name: SpotiFLAC ${{ needs.get-version.outputs.version }} + body_path: /tmp/release_body.txt + files: ./release/* + draft: false + prerelease: ${{ needs.get-version.outputs.is_prerelease == 'true' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + update-altstore: + runs-on: ubuntu-latest + needs: [get-version, build-ios, create-release] + if: ${{ needs.get-version.outputs.is_prerelease != 'true' }} + permissions: + contents: write + + steps: + - name: Checkout main branch + uses: actions/checkout@v6 + with: + ref: main + + - name: Download iOS IPA + uses: actions/download-artifact@v7 + with: + name: ios-ipa + path: ./release + + - name: Update apps.json + run: | + VERSION="${{ needs.get-version.outputs.version }}" + VERSION_NUM="${VERSION#v}" + DATE=$(date -u +%Y-%m-%d) + IPA_FILE=$(find ./release -name "*ios*.ipa" | head -1) + + if [ -z "$IPA_FILE" ]; then + echo "WARNING: IPA file not found, skipping apps.json update" + exit 0 + fi + + IPA_SIZE=$(stat -c%s "$IPA_FILE" 2>/dev/null || stat -f%z "$IPA_FILE") + + if [ ! -f apps.json ]; then + echo "WARNING: apps.json not found on main, skipping" + exit 0 + fi + + jq --arg ver "$VERSION_NUM" \ + --arg date "$DATE" \ + --arg url "https://github.com/zarzet/SpotiFLAC-Mobile/releases/download/${VERSION}/SpotiFLAC-${VERSION}-ios-unsigned.ipa" \ + --argjson size "$IPA_SIZE" \ + '.apps[0].version = $ver | .apps[0].versionDate = $date | .apps[0].downloadURL = $url | .apps[0].size = $size' \ + apps.json > apps.json.tmp && mv apps.json.tmp apps.json + + echo "Updated apps.json:" + cat apps.json + + - name: Commit and push + run: | + VERSION="${{ needs.get-version.outputs.version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add apps.json + git diff --cached --quiet && echo "No changes to commit" || \ + (git commit -m "chore: update AltStore source to ${VERSION}" && git push) + + notify-telegram: + runs-on: ubuntu-latest + needs: [get-version, create-release] + if: ${{ needs.get-version.outputs.is_prerelease != 'true' }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Download Android APK + uses: actions/download-artifact@v7 + with: + name: android-apk + path: ./release + + - name: Download iOS IPA + uses: actions/download-artifact@v7 + with: + name: ios-ipa + path: ./release + + - 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: | + if [ ! -s /tmp/cliff_tg.txt ]; then + echo "See release notes on GitHub for details." > /tmp/changelog.txt + else + # Convert Markdown to Telegram HTML + CHANGELOG=$(cat /tmp/cliff_tg.txt | \ + sed '/^## [0-9][0-9.[:alpha:]-]*$/d' | \ + sed '/^\*\*Full Changelog\*\*/d' | \ + sed 's/ by \[@[^]]*\](https:\/\/github\.com\/[^)]*)//g' | \ + sed 's/ by @[A-Za-z0-9_-]\+//g' | \ + sed 's/\[#\([0-9]*\)\]([^)]*)/#\1/g' | \ + sed 's/\[@\([^]]*\)\]([^)]*)/@\1/g' | \ + sed 's/&/\&/g' | \ + sed 's//\>/g' | \ + sed 's/\*\*\([^*]*\)\*\*/\1<\/b>/g' | \ + sed 's/^### \(.*\)$/\1<\/b>/g' | \ + sed 's/^## \(.*\)$/\1<\/b>/g' | \ + sed 's/^- /• /g') + + # Truncate for Telegram 4096 char limit + CHANGELOG=$(echo "$CHANGELOG" | head -c 2500 | sed '$d') + echo "$CHANGELOG" > /tmp/changelog.txt + fi + + echo "Telegram changelog:" + cat /tmp/changelog.txt + + - name: Send to Telegram Channel + env: + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHANNEL_ID: ${{ secrets.TELEGRAM_CHANNEL_ID }} + run: | + VERSION=${{ needs.get-version.outputs.version }} + CHANGELOG=$(cat /tmp/changelog.txt) + + # Find APK files + ARM64_APK=$(find ./release -name "*arm64*.apk" | head -1) + ARM32_APK=$(find ./release -name "*arm32*.apk" | head -1) + + # Prepare message with changelog (HTML format) + printf '%s\n' \ + "SpotiFLAC Mobile ${VERSION} Released!" \ + "" \ + "What's New:" \ + "${CHANGELOG}" \ + "" \ + "View Release Notes" \ + > /tmp/telegram_message.txt + + MESSAGE=$(cat /tmp/telegram_message.txt) + + # Send message first (using HTML parse mode) + # Use --data-urlencode for proper encoding of special chars (+, &, etc.) + # Use || true to ensure file uploads continue even if message fails + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + --data-urlencode "chat_id=${TELEGRAM_CHANNEL_ID}" \ + --data-urlencode "text=${MESSAGE}" \ + --data-urlencode "parse_mode=HTML" \ + --data-urlencode "disable_web_page_preview=true" || true + + # Upload arm64 APK to channel + if [ -f "$ARM64_APK" ]; then + echo "Uploading arm64 APK to Telegram..." + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ + -F chat_id="${TELEGRAM_CHANNEL_ID}" \ + -F document=@"${ARM64_APK}" \ + -F caption="SpotiFLAC ${VERSION} - arm64 (recommended)" + fi + + # Upload arm32 APK to channel + if [ -f "$ARM32_APK" ]; then + echo "Uploading arm32 APK to Telegram..." + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ + -F chat_id="${TELEGRAM_CHANNEL_ID}" \ + -F document=@"${ARM32_APK}" \ + -F caption="SpotiFLAC ${VERSION} - arm32" + fi + + # Upload iOS IPA to channel + IOS_IPA=$(find ./release -name "*ios*.ipa" | head -1) + if [ -f "$IOS_IPA" ]; then + echo "Uploading iOS IPA to Telegram..." + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ + -F chat_id="${TELEGRAM_CHANNEL_ID}" \ + -F document=@"${IOS_IPA}" \ + -F caption="SpotiFLAC ${VERSION} - iOS (unsigned, sideload required)" + fi + + echo "Telegram notification sent!"