From 48f614359e12daefe56aaa9f454ed50bf9df8fa8 Mon Sep 17 00:00:00 2001 From: zarzet Date: Fri, 13 Mar 2026 15:12:12 +0700 Subject: [PATCH] feat(i18n): replace all hardcoded strings with l10n keys across 13 screens - Added 80+ new keys to app_en.arb covering lyrics, SAF, download settings, snackbars, dialogs, home, cache, and store screens - Replaced hardcoded strings in main_shell, album_screen, playlist_screen, library_tracks_folder_screen, home_tab, settings_tab, download_settings_page, lyrics_provider_priority_page, track_metadata_screen, extension_detail_page, cache_management_page, local_album_screen, downloaded_album_screen, search_screen - Fixed structural bug in track_metadata_screen (duplicate closing brace) - Added missing l10n.dart import to search_screen.dart - Regenerated all app_localizations*.dart files via flutter gen-l10n --- lib/l10n/app_localizations.dart | 614 +++++++++++++++++- lib/l10n/app_localizations_de.dart | 354 ++++++++++ lib/l10n/app_localizations_en.dart | 354 ++++++++++ lib/l10n/app_localizations_es.dart | 354 ++++++++++ lib/l10n/app_localizations_fr.dart | 354 ++++++++++ lib/l10n/app_localizations_hi.dart | 354 ++++++++++ lib/l10n/app_localizations_id.dart | 354 ++++++++++ lib/l10n/app_localizations_ja.dart | 354 ++++++++++ lib/l10n/app_localizations_ko.dart | 354 ++++++++++ lib/l10n/app_localizations_nl.dart | 354 ++++++++++ lib/l10n/app_localizations_pt.dart | 354 ++++++++++ lib/l10n/app_localizations_ru.dart | 354 ++++++++++ lib/l10n/app_localizations_tr.dart | 354 ++++++++++ lib/l10n/app_localizations_zh.dart | 354 ++++++++++ lib/l10n/arb/app_en.arb | 452 +++++++++++++ lib/screens/album_screen.dart | 16 +- lib/screens/downloaded_album_screen.dart | 2 +- lib/screens/home_tab.dart | 6 +- lib/screens/library_tracks_folder_screen.dart | 6 +- lib/screens/local_album_screen.dart | 2 +- lib/screens/main_shell.dart | 23 +- lib/screens/playlist_screen.dart | 22 +- lib/screens/search_screen.dart | 3 +- .../settings/cache_management_page.dart | 6 +- .../settings/download_settings_page.dart | 152 ++--- .../settings/extension_detail_page.dart | 4 +- .../lyrics_provider_priority_page.dart | 54 +- lib/screens/settings/settings_tab.dart | 4 +- lib/screens/track_metadata_screen.dart | 24 +- 29 files changed, 5831 insertions(+), 161 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 488563dc..53bcba7f 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -256,7 +256,7 @@ abstract class AppLocalizations { /// **'Filename Format'** String get downloadFilenameFormat; - /// Setting for folder structure + /// Title of the folder organization picker bottom sheet /// /// In en, this message translates to: /// **'Folder Organization'** @@ -2236,6 +2236,84 @@ abstract class AppLocalizations { /// **'Clear filters'** String get storeClearFilters; + /// Store setup screen - heading when no repo is configured + /// + /// In en, this message translates to: + /// **'Add Extension Repository'** + String get storeAddRepoTitle; + + /// Store setup screen - explanatory text + /// + /// In en, this message translates to: + /// **'Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.'** + String get storeAddRepoDescription; + + /// Label for the repository URL input field + /// + /// In en, this message translates to: + /// **'Repository URL'** + String get storeRepoUrlLabel; + + /// Hint/placeholder for the repository URL input field + /// + /// In en, this message translates to: + /// **'https://github.com/user/repo'** + String get storeRepoUrlHint; + + /// Helper text below the repository URL input field + /// + /// In en, this message translates to: + /// **'e.g. https://github.com/user/extensions-repo'** + String get storeRepoUrlHelper; + + /// Button to submit a new repository URL + /// + /// In en, this message translates to: + /// **'Add Repository'** + String get storeAddRepoButton; + + /// Tooltip for the change-repository icon button in the app bar + /// + /// In en, this message translates to: + /// **'Change repository'** + String get storeChangeRepoTooltip; + + /// Title of the change/remove repository dialog + /// + /// In en, this message translates to: + /// **'Extension Repository'** + String get storeRepoDialogTitle; + + /// Label shown above the current repository URL in the dialog + /// + /// In en, this message translates to: + /// **'Current repository:'** + String get storeRepoDialogCurrent; + + /// Label for the new repository URL field inside the dialog + /// + /// In en, this message translates to: + /// **'New Repository URL'** + String get storeNewRepoUrlLabel; + + /// Error heading when the store cannot be loaded + /// + /// In en, this message translates to: + /// **'Failed to load store'** + String get storeLoadError; + + /// Message when store has no extensions + /// + /// In en, this message translates to: + /// **'No extensions available'** + String get storeEmptyNoExtensions; + + /// Message when search/filter returns no results + /// + /// In en, this message translates to: + /// **'No extensions found'** + String get storeEmptyNoResults; + /// Default search provider option /// /// In en, this message translates to: @@ -4205,6 +4283,540 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Artist folders use Track Artist only'** String get downloadUseAlbumArtistForFoldersTrackSubtitle; + + /// Title for the lyrics provider priority page + /// + /// In en, this message translates to: + /// **'Lyrics Providers'** + String get lyricsProvidersTitle; + + /// Description on the lyrics provider priority page + /// + /// In en, this message translates to: + /// **'Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.'** + String get lyricsProvidersDescription; + + /// Info tip on lyrics provider priority page + /// + /// In en, this message translates to: + /// **'Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.'** + String get lyricsProvidersInfoText; + + /// Section header for enabled providers + /// + /// In en, this message translates to: + /// **'Enabled ({count})'** + String lyricsProvidersEnabledSection(int count); + + /// Section header for disabled providers + /// + /// In en, this message translates to: + /// **'Disabled ({count})'** + String lyricsProvidersDisabledSection(int count); + + /// Snackbar when user tries to disable the last enabled provider + /// + /// In en, this message translates to: + /// **'At least one provider must remain enabled'** + String get lyricsProvidersAtLeastOne; + + /// Snackbar after saving lyrics provider priority + /// + /// In en, this message translates to: + /// **'Lyrics provider priority saved'** + String get lyricsProvidersSaved; + + /// Body text of the discard-changes dialog on lyrics provider page + /// + /// In en, this message translates to: + /// **'You have unsaved changes that will be lost.'** + String get lyricsProvidersDiscardContent; + + /// Description for Spotify Lyrics API provider + /// + /// In en, this message translates to: + /// **'Spotify-sourced synced lyrics via community API'** + String get lyricsProviderSpotifyApiDesc; + + /// Description for LRCLIB provider + /// + /// In en, this message translates to: + /// **'Open-source synced lyrics database'** + String get lyricsProviderLrclibDesc; + + /// Description for Netease provider + /// + /// In en, this message translates to: + /// **'NetEase Cloud Music (good for Asian songs)'** + String get lyricsProviderNeteaseDesc; + + /// Description for Musixmatch provider + /// + /// In en, this message translates to: + /// **'Largest lyrics database (multi-language)'** + String get lyricsProviderMusixmatchDesc; + + /// Description for Apple Music provider + /// + /// In en, this message translates to: + /// **'Word-by-word synced lyrics (via proxy)'** + String get lyricsProviderAppleMusicDesc; + + /// Description for QQ Music provider + /// + /// In en, this message translates to: + /// **'QQ Music (good for Chinese songs, via proxy)'** + String get lyricsProviderQqMusicDesc; + + /// Generic description for extension-based lyrics providers + /// + /// In en, this message translates to: + /// **'Extension provider'** + String get lyricsProviderExtensionDesc; + + /// Title of SAF migration dialog + /// + /// In en, this message translates to: + /// **'Storage Update Required'** + String get safMigrationTitle; + + /// First paragraph of SAF migration dialog + /// + /// In en, this message translates to: + /// **'SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.'** + String get safMigrationMessage1; + + /// Second paragraph of SAF migration dialog + /// + /// In en, this message translates to: + /// **'Please select your download folder again to switch to the new storage system.'** + String get safMigrationMessage2; + + /// Snackbar after successfully migrating to SAF + /// + /// In en, this message translates to: + /// **'Download folder updated to SAF mode'** + String get safMigrationSuccess; + + /// Settings menu item - donate + /// + /// In en, this message translates to: + /// **'Donate'** + String get settingsDonate; + + /// Subtitle for donate menu item + /// + /// In en, this message translates to: + /// **'Support SpotiFLAC-Mobile development'** + String get settingsDonateSubtitle; + + /// Tooltip for the Love All button on album/playlist screens + /// + /// In en, this message translates to: + /// **'Love All'** + String get tooltipLoveAll; + + /// Tooltip for the Add to Playlist button + /// + /// In en, this message translates to: + /// **'Add to Playlist'** + String get tooltipAddToPlaylist; + + /// Snackbar after removing multiple tracks from Loved folder + /// + /// In en, this message translates to: + /// **'Removed {count} tracks from Loved'** + String snackbarRemovedTracksFromLoved(int count); + + /// Snackbar after adding multiple tracks to Loved folder + /// + /// In en, this message translates to: + /// **'Added {count} tracks to Loved'** + String snackbarAddedTracksToLoved(int count); + + /// Title of the Download All confirmation dialog + /// + /// In en, this message translates to: + /// **'Download All'** + String get dialogDownloadAllTitle; + + /// Body of the Download All confirmation dialog + /// + /// In en, this message translates to: + /// **'Download {count} tracks?'** + String dialogDownloadAllMessage(int count); + + /// Confirm button in Download All dialog + /// + /// In en, this message translates to: + /// **'Download'** + String get dialogDownload; + + /// Checkbox label in import dialog to skip already-downloaded songs + /// + /// In en, this message translates to: + /// **'Skip already downloaded songs'** + String get homeSkipAlreadyDownloaded; + + /// Context menu item to navigate to the album page + /// + /// In en, this message translates to: + /// **'Go to Album'** + String get homeGoToAlbum; + + /// Snackbar when album info cannot be loaded + /// + /// In en, this message translates to: + /// **'Album info not available'** + String get homeAlbumInfoUnavailable; + + /// Snackbar while loading a CUE sheet file + /// + /// In en, this message translates to: + /// **'Loading CUE sheet...'** + String get snackbarLoadingCueSheet; + + /// Snackbar after successfully saving track metadata + /// + /// In en, this message translates to: + /// **'Metadata saved successfully'** + String get snackbarMetadataSaved; + + /// Snackbar when lyrics embedding fails + /// + /// In en, this message translates to: + /// **'Failed to embed lyrics'** + String get snackbarFailedToEmbedLyrics; + + /// Snackbar when writing metadata back to file fails + /// + /// In en, this message translates to: + /// **'Failed to write back to storage'** + String get snackbarFailedToWriteStorage; + + /// Generic error snackbar with error detail + /// + /// In en, this message translates to: + /// **'Error: {error}'** + String snackbarError(String error); + + /// Snackbar when an extension button has no action configured + /// + /// In en, this message translates to: + /// **'No action defined for this button'** + String get snackbarNoActionDefined; + + /// Empty state message when an album has no tracks + /// + /// In en, this message translates to: + /// **'No tracks found for this album'** + String get noTracksFoundForAlbum; + + /// Subtitle text in Android download location bottom sheet + /// + /// In en, this message translates to: + /// **'Choose storage mode for downloaded files.'** + String get downloadLocationSubtitle; + + /// Storage mode option - use legacy app folder + /// + /// In en, this message translates to: + /// **'App folder (non-SAF)'** + String get storageModeAppFolder; + + /// Subtitle for app folder storage mode + /// + /// In en, this message translates to: + /// **'Use default Music/SpotiFLAC path'** + String get storageModeAppFolderSubtitle; + + /// Storage mode option - use Android SAF picker + /// + /// In en, this message translates to: + /// **'SAF folder'** + String get storageModeSaf; + + /// Subtitle for SAF storage mode + /// + /// In en, this message translates to: + /// **'Pick folder via Android Storage Access Framework'** + String get storageModeSafSubtitle; + + /// Description text in filename format bottom sheet + /// + /// In en, this message translates to: + /// **'Customize how your files are named.'** + String get downloadFilenameDescription; + + /// Label above filename tag chips + /// + /// In en, this message translates to: + /// **'Tap to insert tag:'** + String get downloadFilenameInsertTag; + + /// Subtitle when separate singles folder is enabled + /// + /// In en, this message translates to: + /// **'Albums/ and Singles/ folders'** + String get downloadSeparateSinglesEnabled; + + /// Subtitle when separate singles folder is disabled + /// + /// In en, this message translates to: + /// **'All files in same structure'** + String get downloadSeparateSinglesDisabled; + + /// Setting title for artist folder filter options + /// + /// In en, this message translates to: + /// **'Artist Name Filters'** + String get downloadArtistNameFilters; + + /// Setting title for SongLink country region + /// + /// In en, this message translates to: + /// **'SongLink Region'** + String get downloadSongLinkRegion; + + /// Setting title for network compatibility toggle + /// + /// In en, this message translates to: + /// **'Network compatibility mode'** + String get downloadNetworkCompatibilityMode; + + /// Subtitle when network compatibility mode is enabled + /// + /// In en, this message translates to: + /// **'Enabled: try HTTP + accept invalid TLS certificates (unsafe)'** + String get downloadNetworkCompatibilityModeEnabled; + + /// Subtitle when network compatibility mode is disabled + /// + /// In en, this message translates to: + /// **'Off: strict HTTPS certificate validation (recommended)'** + String get downloadNetworkCompatibilityModeDisabled; + + /// Hint shown instead of Ask-quality subtitle when no built-in service selected + /// + /// In en, this message translates to: + /// **'Select a built-in service to enable'** + String get downloadSelectServiceToEnable; + + /// Quality option label for Tidal lossy 320kbps + /// + /// In en, this message translates to: + /// **'Lossy 320kbps'** + String get downloadLossy320; + + /// Setting title to pick output format for Tidal lossy downloads + /// + /// In en, this message translates to: + /// **'Lossy Format'** + String get downloadLossyFormat; + + /// Info hint when non-Tidal/Qobuz service is selected + /// + /// In en, this message translates to: + /// **'Select Tidal or Qobuz above to configure quality'** + String get downloadSelectTidalQobuz; + + /// Subtitle for Embed Lyrics when Embed Metadata is disabled + /// + /// In en, this message translates to: + /// **'Disabled while Embed Metadata is turned off'** + String get downloadEmbedLyricsDisabled; + + /// Toggle title for including Netease translated lyrics + /// + /// In en, this message translates to: + /// **'Netease: Include Translation'** + String get downloadNeteaseIncludeTranslation; + + /// Subtitle when Netease translation is enabled + /// + /// In en, this message translates to: + /// **'Append translated lyrics when available'** + String get downloadNeteaseIncludeTranslationEnabled; + + /// Subtitle when Netease translation is disabled + /// + /// In en, this message translates to: + /// **'Use original lyrics only'** + String get downloadNeteaseIncludeTranslationDisabled; + + /// Toggle title for including Netease romanized lyrics + /// + /// In en, this message translates to: + /// **'Netease: Include Romanization'** + String get downloadNeteaseIncludeRomanization; + + /// Subtitle when Netease romanization is enabled + /// + /// In en, this message translates to: + /// **'Append romanized lyrics when available'** + String get downloadNeteaseIncludeRomanizationEnabled; + + /// Subtitle when Netease romanization is disabled + /// + /// In en, this message translates to: + /// **'Disabled'** + String get downloadNeteaseIncludeRomanizationDisabled; + + /// Toggle title for Apple/QQ multi-person word-by-word lyrics + /// + /// In en, this message translates to: + /// **'Apple/QQ Multi-Person Word-by-Word'** + String get downloadAppleQqMultiPerson; + + /// Subtitle when multi-person word-by-word is enabled + /// + /// In en, this message translates to: + /// **'Enable v1/v2 speaker and [bg:] tags'** + String get downloadAppleQqMultiPersonEnabled; + + /// Subtitle when multi-person word-by-word is disabled + /// + /// In en, this message translates to: + /// **'Simplified word-by-word formatting'** + String get downloadAppleQqMultiPersonDisabled; + + /// Setting title for Musixmatch language preference + /// + /// In en, this message translates to: + /// **'Musixmatch Language'** + String get downloadMusixmatchLanguage; + + /// Option label when Musixmatch uses original language + /// + /// In en, this message translates to: + /// **'Auto (original)'** + String get downloadMusixmatchLanguageAuto; + + /// Toggle title for filtering contributing artists in Album Artist metadata + /// + /// In en, this message translates to: + /// **'Filter contributing artists in Album Artist'** + String get downloadFilterContributing; + + /// Subtitle when contributing artist filter is enabled + /// + /// In en, this message translates to: + /// **'Album Artist metadata uses primary artist only'** + String get downloadFilterContributingEnabled; + + /// Subtitle when contributing artist filter is disabled + /// + /// In en, this message translates to: + /// **'Keep full Album Artist metadata value'** + String get downloadFilterContributingDisabled; + + /// Subtitle for lyrics providers setting when no providers are enabled + /// + /// In en, this message translates to: + /// **'None enabled'** + String get downloadProvidersNoneEnabled; + + /// Label for the Musixmatch language code text field + /// + /// In en, this message translates to: + /// **'Language code'** + String get downloadMusixmatchLanguageCode; + + /// Hint text for the Musixmatch language code field + /// + /// In en, this message translates to: + /// **'auto / en / es / ja'** + String get downloadMusixmatchLanguageHint; + + /// Description in the Musixmatch language picker + /// + /// In en, this message translates to: + /// **'Set preferred language code (example: en, es, ja). Leave empty for auto.'** + String get downloadMusixmatchLanguageDesc; + + /// Button to reset Musixmatch language to automatic + /// + /// In en, this message translates to: + /// **'Auto'** + String get downloadMusixmatchAuto; + + /// Title of the Tidal lossy format picker bottom sheet + /// + /// In en, this message translates to: + /// **'Lossy 320kbps Format'** + String get downloadLossy320Format; + + /// Description in the Tidal lossy format picker + /// + /// In en, this message translates to: + /// **'Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.'** + String get downloadLossy320FormatDesc; + + /// Tidal lossy format option - MP3 320kbps + /// + /// In en, this message translates to: + /// **'MP3 320kbps'** + String get downloadLossyMp3; + + /// Subtitle for MP3 320kbps option + /// + /// In en, this message translates to: + /// **'Best compatibility, ~10MB per track'** + String get downloadLossyMp3Subtitle; + + /// Tidal lossy format option - Opus 256kbps + /// + /// In en, this message translates to: + /// **'Opus 256kbps'** + String get downloadLossyOpus256; + + /// Subtitle for Opus 256kbps option + /// + /// In en, this message translates to: + /// **'Best quality Opus, ~8MB per track'** + String get downloadLossyOpus256Subtitle; + + /// Tidal lossy format option - Opus 128kbps + /// + /// In en, this message translates to: + /// **'Opus 128kbps'** + String get downloadLossyOpus128; + + /// Subtitle for Opus 128kbps option + /// + /// In en, this message translates to: + /// **'Smallest size, ~4MB per track'** + String get downloadLossyOpus128Subtitle; + + /// Subtitle for 'Any' network mode option + /// + /// In en, this message translates to: + /// **'WiFi + Mobile Data'** + String get downloadNetworkAnySubtitle; + + /// Subtitle for 'WiFi only' network mode option + /// + /// In en, this message translates to: + /// **'Pause downloads on mobile data'** + String get downloadNetworkWifiOnlySubtitle; + + /// Description in the SongLink region picker + /// + /// In en, this message translates to: + /// **'Used as userCountry for SongLink API lookup.'** + String get downloadSongLinkRegionDesc; + + /// Snackbar when the audio format is not supported for the requested operation + /// + /// In en, this message translates to: + /// **'Unsupported audio format'** + String get snackbarUnsupportedAudioFormat; + + /// Tooltip for refresh button on cache management page + /// + /// In en, this message translates to: + /// **'Refresh'** + String get cacheRefresh; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 4cb5b75d..a3d43076 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1215,6 +1215,47 @@ class AppLocalizationsDe extends AppLocalizations { @override String get storeClearFilters => 'Filter entfernen'; + @override + 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 store'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + @override String get extensionDefaultProvider => 'Standard (Deezer/Spotify)'; @@ -2436,4 +2477,317 @@ class AppLocalizationsDe extends AppLocalizations { @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 lyricsProviderSpotifyApiDesc => + 'Spotify-sourced synced lyrics via community API'; + + @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 dialogDownload => 'Download'; + + @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 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 downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @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 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 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'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 646f9a58..03adf67d 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1195,6 +1195,47 @@ class AppLocalizationsEn extends AppLocalizations { @override String get storeClearFilters => 'Clear filters'; + @override + 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 store'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + @override String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; @@ -2408,4 +2449,317 @@ class AppLocalizationsEn extends AppLocalizations { @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 lyricsProviderSpotifyApiDesc => + 'Spotify-sourced synced lyrics via community API'; + + @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 dialogDownload => 'Download'; + + @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 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 downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @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 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 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'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index e44fa9d8..c953dbc8 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1195,6 +1195,47 @@ class AppLocalizationsEs extends AppLocalizations { @override String get storeClearFilters => 'Clear filters'; + @override + 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 store'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + @override String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; @@ -2408,6 +2449,319 @@ class AppLocalizationsEs extends AppLocalizations { @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 lyricsProviderSpotifyApiDesc => + 'Spotify-sourced synced lyrics via community API'; + + @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 dialogDownload => 'Download'; + + @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 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 downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @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 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 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'; } /// The translations for Spanish Castilian, as used in Spain (`es_ES`). diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index ae24b7e9..4baf9cba 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1197,6 +1197,47 @@ class AppLocalizationsFr extends AppLocalizations { @override String get storeClearFilters => 'Clear filters'; + @override + 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 store'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + @override String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; @@ -2410,4 +2451,317 @@ class AppLocalizationsFr extends AppLocalizations { @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 lyricsProviderSpotifyApiDesc => + 'Spotify-sourced synced lyrics via community API'; + + @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 dialogDownload => 'Download'; + + @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 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 downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @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 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 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'; } diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index 08101eac..46d6572a 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -1195,6 +1195,47 @@ class AppLocalizationsHi extends AppLocalizations { @override String get storeClearFilters => 'Clear filters'; + @override + 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 store'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + @override String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; @@ -2408,4 +2449,317 @@ class AppLocalizationsHi extends AppLocalizations { @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 lyricsProviderSpotifyApiDesc => + 'Spotify-sourced synced lyrics via community API'; + + @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 dialogDownload => 'Download'; + + @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 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 downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @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 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 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'; } diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index cab860dd..6aa97510 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -1200,6 +1200,47 @@ class AppLocalizationsId extends AppLocalizations { @override String get storeClearFilters => 'Hapus filter'; + @override + 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 store'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + @override String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; @@ -2415,4 +2456,317 @@ class AppLocalizationsId extends AppLocalizations { @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 lyricsProviderSpotifyApiDesc => + 'Spotify-sourced synced lyrics via community API'; + + @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 dialogDownload => 'Download'; + + @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 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 downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @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 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 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'; } diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 6ce38247..92184e0b 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1189,6 +1189,47 @@ class AppLocalizationsJa extends AppLocalizations { @override String get storeClearFilters => 'フィルターを消去'; + @override + 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 store'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + @override String get extensionDefaultProvider => 'デフォルト (Deezer/Spotify)'; @@ -2395,4 +2436,317 @@ class AppLocalizationsJa extends AppLocalizations { @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 lyricsProviderSpotifyApiDesc => + 'Spotify-sourced synced lyrics via community API'; + + @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 dialogDownload => 'Download'; + + @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 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 downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @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 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 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'; } diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 56287787..aed486d5 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -1175,6 +1175,47 @@ class AppLocalizationsKo extends AppLocalizations { @override String get storeClearFilters => 'Clear filters'; + @override + 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 store'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + @override String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; @@ -2388,4 +2429,317 @@ class AppLocalizationsKo extends AppLocalizations { @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 lyricsProviderSpotifyApiDesc => + 'Spotify-sourced synced lyrics via community API'; + + @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 dialogDownload => 'Download'; + + @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 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 downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @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 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 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'; } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 5f4975d9..87c5385a 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1195,6 +1195,47 @@ class AppLocalizationsNl extends AppLocalizations { @override String get storeClearFilters => 'Clear filters'; + @override + 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 store'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + @override String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; @@ -2408,4 +2449,317 @@ class AppLocalizationsNl extends AppLocalizations { @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 lyricsProviderSpotifyApiDesc => + 'Spotify-sourced synced lyrics via community API'; + + @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 dialogDownload => 'Download'; + + @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 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 downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @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 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 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'; } diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 62b648ae..d45df4f9 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1195,6 +1195,47 @@ class AppLocalizationsPt extends AppLocalizations { @override String get storeClearFilters => 'Clear filters'; + @override + 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 store'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + @override String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; @@ -2408,6 +2449,319 @@ class AppLocalizationsPt extends AppLocalizations { @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 lyricsProviderSpotifyApiDesc => + 'Spotify-sourced synced lyrics via community API'; + + @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 dialogDownload => 'Download'; + + @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 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 downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @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 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 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'; } /// The translations for Portuguese, as used in Portugal (`pt_PT`). diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 66c75d45..3e9d7e73 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1216,6 +1216,47 @@ class AppLocalizationsRu extends AppLocalizations { @override String get storeClearFilters => 'Очистить фильтры'; + @override + 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 store'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + @override String get extensionDefaultProvider => 'По умолчанию (Deezer/Spotify)'; @@ -2467,4 +2508,317 @@ class AppLocalizationsRu extends AppLocalizations { @override String get downloadUseAlbumArtistForFoldersTrackSubtitle => 'Папки исполнителя используют только трек исполнителя'; + + @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 lyricsProviderSpotifyApiDesc => + 'Spotify-sourced synced lyrics via community API'; + + @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 dialogDownload => 'Download'; + + @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 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 downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @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 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 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'; } diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index 3a88e179..48a9a7d2 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -1206,6 +1206,47 @@ class AppLocalizationsTr extends AppLocalizations { @override String get storeClearFilters => 'Filtreleri temizle'; + @override + 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 store'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + @override String get extensionDefaultProvider => 'Varsayılan (Deezer/Spotify)'; @@ -2420,4 +2461,317 @@ class AppLocalizationsTr extends AppLocalizations { @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 lyricsProviderSpotifyApiDesc => + 'Spotify-sourced synced lyrics via community API'; + + @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 dialogDownload => 'Download'; + + @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 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 downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @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 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 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'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 3306bb1c..2287f76a 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1195,6 +1195,47 @@ class AppLocalizationsZh extends AppLocalizations { @override String get storeClearFilters => 'Clear filters'; + @override + 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 store'; + + @override + String get storeEmptyNoExtensions => 'No extensions available'; + + @override + String get storeEmptyNoResults => 'No extensions found'; + @override String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; @@ -2408,6 +2449,319 @@ class AppLocalizationsZh extends AppLocalizations { @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 lyricsProviderSpotifyApiDesc => + 'Spotify-sourced synced lyrics via community API'; + + @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 dialogDownload => 'Download'; + + @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 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 downloadLossy320 => 'Lossy 320kbps'; + + @override + String get downloadLossyFormat => 'Lossy Format'; + + @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 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 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'; } /// The translations for Chinese, as used in China (`zh_CN`). diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 03313d7b..10bf4938 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1570,6 +1570,58 @@ "@storeClearFilters": { "description": "Button to clear all filters" }, + "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 store", + "@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/Spotify)", "@extensionDefaultProvider": { "description": "Default search provider option" @@ -3205,5 +3257,405 @@ "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" + }, + "lyricsProviderSpotifyApiDesc": "Spotify-sourced synced lyrics via community API", + "@lyricsProviderSpotifyApiDesc": { + "description": "Description for Spotify Lyrics API provider" + }, + "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": "Title of the Download All confirmation dialog" + }, + "dialogDownloadAllMessage": "Download {count} tracks?", + "@dialogDownloadAllMessage": { + "description": "Body of the Download All confirmation dialog", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "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" + }, + "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" + }, + "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" + }, + "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" + }, + "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" + }, + "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 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 option" + }, + "downloadLossyOpus128": "Opus 128kbps", + "@downloadLossyOpus128": { + "description": "Tidal lossy format option - Opus 128kbps" + }, + "downloadLossyOpus128Subtitle": "Smallest size, ~4MB per track", + "@downloadLossyOpus128Subtitle": { + "description": "Subtitle for Opus 128kbps option" + }, + "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" + }, + "downloadFolderOrganization": "Folder Organization", + "@downloadFolderOrganization": { + "description": "Title of the folder organization picker bottom sheet" + }, + "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" } } diff --git a/lib/screens/album_screen.dart b/lib/screens/album_screen.dart index f3f22790..2352cfac 100644 --- a/lib/screens/album_screen.dart +++ b/lib/screens/album_screen.dart @@ -619,7 +619,7 @@ class _AlbumScreenState extends ConsumerState { size: 22, color: allLoved ? Colors.redAccent : Colors.white, ), - tooltip: allLoved ? 'Remove from Loved' : 'Love All', + tooltip: allLoved ? context.l10n.trackOptionRemoveFromLoved : context.l10n.tooltipLoveAll, padding: EdgeInsets.zero, ), ); @@ -642,7 +642,7 @@ class _AlbumScreenState extends ConsumerState { ? null : () => showAddTracksToPlaylistSheet(context, ref, _tracks!), icon: const Icon(Icons.add, size: 22, color: Colors.white), - tooltip: 'Add to Playlist', + tooltip: context.l10n.tooltipAddToPlaylist, padding: EdgeInsets.zero, ), ); @@ -660,7 +660,11 @@ class _AlbumScreenState extends ConsumerState { } if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Removed ${tracks.length} tracks from Loved')), + SnackBar( + content: Text( + context.l10n.snackbarRemovedTracksFromLoved(tracks.length), + ), + ), ); } } else { @@ -673,7 +677,11 @@ class _AlbumScreenState extends ConsumerState { } if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Added $addedCount tracks to Loved')), + SnackBar( + content: Text( + context.l10n.snackbarAddedTracksToLoved(addedCount), + ), + ), ); } } diff --git a/lib/screens/downloaded_album_screen.dart b/lib/screens/downloaded_album_screen.dart index 468d7aff..04e5dcc0 100644 --- a/lib/screens/downloaded_album_screen.dart +++ b/lib/screens/downloaded_album_screen.dart @@ -363,7 +363,7 @@ class _DownloadedAlbumScreenState extends ConsumerState { if (tracks.isEmpty) { return Scaffold( appBar: AppBar(title: Text(widget.albumName)), - body: Center(child: Text('No tracks found for this album')), + body: Center(child: Text(context.l10n.noTracksFoundForAlbum)), ); } diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index 5663caa3..d7c2a8eb 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -816,7 +816,7 @@ class _HomeTabState extends ConsumerState const SizedBox(height: 12), CheckboxListTile( contentPadding: EdgeInsets.zero, - title: const Text('Skip already downloaded songs'), + title: Text(l10n.homeSkipAlreadyDownloaded), value: skipDownloaded, onChanged: (value) { setDialogState(() { @@ -1750,7 +1750,7 @@ class _HomeTabState extends ConsumerState ), ListTile( leading: Icon(Icons.album, color: colorScheme.onSurfaceVariant), - title: const Text('Go to Album'), + title: Text(context.l10n.homeGoToAlbum), onTap: () { Navigator.pop(context); _navigateToTrackAlbum(item); @@ -1824,7 +1824,7 @@ class _HomeTabState extends ConsumerState } else { ScaffoldMessenger.of( context, - ).showSnackBar(const SnackBar(content: Text('Album info not available'))); + ).showSnackBar(SnackBar(content: Text(context.l10n.homeAlbumInfoUnavailable))); } } diff --git a/lib/screens/library_tracks_folder_screen.dart b/lib/screens/library_tracks_folder_screen.dart index d6d1fc5e..c7ba7768 100644 --- a/lib/screens/library_tracks_folder_screen.dart +++ b/lib/screens/library_tracks_folder_screen.dart @@ -850,8 +850,8 @@ class _LibraryTracksFolderScreenState final colorScheme = Theme.of(dialogContext).colorScheme; return AlertDialog( backgroundColor: colorScheme.surfaceContainerHigh, - title: const Text('Download All'), - content: Text('Download ${tracks.length} tracks?'), + title: Text(context.l10n.dialogDownloadAllTitle), + content: Text(context.l10n.dialogDownloadAllMessage(tracks.length)), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), @@ -862,7 +862,7 @@ class _LibraryTracksFolderScreenState Navigator.pop(dialogContext); _downloadAll(tracks); }, - child: const Text('Download'), + child: Text(context.l10n.dialogDownload), ), ], ); diff --git a/lib/screens/local_album_screen.dart b/lib/screens/local_album_screen.dart index 9c560546..e5584b34 100644 --- a/lib/screens/local_album_screen.dart +++ b/lib/screens/local_album_screen.dart @@ -247,7 +247,7 @@ class _LocalAlbumScreenState extends ConsumerState { if (tracks.isEmpty) { return Scaffold( appBar: AppBar(title: Text(widget.albumName)), - body: const Center(child: Text('No tracks found for this album')), + body: Center(child: Text(context.l10n.noTracksFoundForAlbum)), ); } diff --git a/lib/screens/main_shell.dart b/lib/screens/main_shell.dart index b77fee99..79cb17a8 100644 --- a/lib/screens/main_shell.dart +++ b/lib/screens/main_shell.dart @@ -181,25 +181,20 @@ class _MainShellState extends ConsumerState { size: 32, color: colorScheme.primary, ), - title: const Text('Storage Update Required'), - content: const Column( + title: Text(context.l10n.safMigrationTitle), + content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. ' - 'This fixes "permission denied" errors on Android 10+.', - ), - SizedBox(height: 12), - Text( - 'Please select your download folder again to switch to the new storage system.', - ), + Text(context.l10n.safMigrationMessage1), + const SizedBox(height: 12), + Text(context.l10n.safMigrationMessage2), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), - child: const Text('Later'), + child: Text(context.l10n.updateLater), ), FilledButton( onPressed: () async { @@ -219,15 +214,15 @@ class _MainShellState extends ConsumerState { ); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Download folder updated to SAF mode'), + SnackBar( + content: Text(context.l10n.safMigrationSuccess), ), ); } } } }, - child: const Text('Select Folder'), + child: Text(context.l10n.setupSelectFolder), ), ], ), diff --git a/lib/screens/playlist_screen.dart b/lib/screens/playlist_screen.dart index 37bf14cf..a87676a1 100644 --- a/lib/screens/playlist_screen.dart +++ b/lib/screens/playlist_screen.dart @@ -482,7 +482,7 @@ class _PlaylistScreenState extends ConsumerState { size: 22, color: allLoved ? Colors.redAccent : Colors.white, ), - tooltip: allLoved ? 'Remove from Loved' : 'Love All', + tooltip: allLoved ? context.l10n.trackOptionRemoveFromLoved : context.l10n.tooltipLoveAll, padding: EdgeInsets.zero, ), ); @@ -505,7 +505,7 @@ class _PlaylistScreenState extends ConsumerState { Widget _buildAddToPlaylistButton(BuildContext context) { return _buildCircleButton( icon: Icons.playlist_add, - tooltip: 'Add to Playlist', + tooltip: context.l10n.tooltipAddToPlaylist, onPressed: _tracks.isEmpty ? null : () => showAddTracksToPlaylistSheet(context, ref, _tracks), @@ -520,8 +520,8 @@ class _PlaylistScreenState extends ConsumerState { final colorScheme = Theme.of(dialogContext).colorScheme; return AlertDialog( backgroundColor: colorScheme.surfaceContainerHigh, - title: const Text('Download All'), - content: Text('Download ${_tracks.length} tracks?'), + title: Text(context.l10n.dialogDownloadAllTitle), + content: Text(context.l10n.dialogDownloadAllMessage(_tracks.length)), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), @@ -532,7 +532,7 @@ class _PlaylistScreenState extends ConsumerState { Navigator.pop(dialogContext); _downloadAll(context); }, - child: const Text('Download'), + child: Text(context.l10n.dialogDownload), ), ], ); @@ -552,7 +552,11 @@ class _PlaylistScreenState extends ConsumerState { } if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Removed ${tracks.length} tracks from Loved')), + SnackBar( + content: Text( + context.l10n.snackbarRemovedTracksFromLoved(tracks.length), + ), + ), ); } } else { @@ -565,7 +569,11 @@ class _PlaylistScreenState extends ConsumerState { } if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Added $addedCount tracks to Loved')), + SnackBar( + content: Text( + context.l10n.snackbarAddedTracksToLoved(addedCount), + ), + ), ); } } diff --git a/lib/screens/search_screen.dart b/lib/screens/search_screen.dart index 09b2f656..42118769 100644 --- a/lib/screens/search_screen.dart +++ b/lib/screens/search_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/services/cover_cache_manager.dart'; import 'package:spotiflac_android/models/track.dart'; import 'package:spotiflac_android/providers/track_provider.dart'; @@ -52,7 +53,7 @@ class _SearchScreenState extends ConsumerState { .addToQueue(track, settings.defaultService); ScaffoldMessenger.of( context, - ).showSnackBar(SnackBar(content: Text('Added "${track.name}" to queue'))); + ).showSnackBar(SnackBar(content: Text(context.l10n.snackbarAddedToQueue(track.name)))); } @override diff --git a/lib/screens/settings/cache_management_page.dart b/lib/screens/settings/cache_management_page.dart index edb3eed3..e8b43677 100644 --- a/lib/screens/settings/cache_management_page.dart +++ b/lib/screens/settings/cache_management_page.dart @@ -56,7 +56,7 @@ class _CacheManagementPageState extends ConsumerState { setState(() => _isLoading = false); ScaffoldMessenger.of( context, - ).showSnackBar(SnackBar(content: Text('Error: $e'))); + ).showSnackBar(SnackBar(content: Text(context.l10n.snackbarError(e.toString())))); } } @@ -282,7 +282,7 @@ class _CacheManagementPageState extends ConsumerState { if (!mounted) return; ScaffoldMessenger.of( context, - ).showSnackBar(SnackBar(content: Text('Error: $e'))); + ).showSnackBar(SnackBar(content: Text(context.l10n.snackbarError(e.toString())))); } finally { if (mounted) { setState(() => _busyAction = null); @@ -394,7 +394,7 @@ class _CacheManagementPageState extends ConsumerState { ), actions: [ IconButton( - tooltip: 'Refresh', + tooltip: context.l10n.cacheRefresh, onPressed: _isBusy ? null : _refreshOverview, icon: const Icon(Icons.refresh), ), diff --git a/lib/screens/settings/download_settings_page.dart b/lib/screens/settings/download_settings_page.dart index e856c0a2..c502d142 100644 --- a/lib/screens/settings/download_settings_page.dart +++ b/lib/screens/settings/download_settings_page.dart @@ -376,7 +376,7 @@ class _DownloadSettingsPageState extends ConsumerState { title: context.l10n.downloadAskBeforeDownload, subtitle: isBuiltInService ? context.l10n.downloadAskQualitySubtitle - : 'Select a built-in service to enable', + : context.l10n.downloadSelectServiceToEnable, value: settings.askQualityBeforeDownload, enabled: isBuiltInService, onChanged: (value) => ref @@ -413,7 +413,7 @@ class _DownloadSettingsPageState extends ConsumerState { // Lossy 320kbps option (Tidal only) - downloads M4A, converts to MP3/Opus if (isTidalService) _QualityOption( - title: 'Lossy 320kbps', + title: context.l10n.downloadLossy320, subtitle: _getTidalHighFormatLabel( settings.tidalHighFormat, ), @@ -426,7 +426,7 @@ class _DownloadSettingsPageState extends ConsumerState { if (isTidalService && settings.audioQuality == 'HIGH') SettingsItem( icon: Icons.tune, - title: 'Lossy Format', + title: context.l10n.downloadLossyFormat, subtitle: _getTidalHighFormatLabel( settings.tidalHighFormat, ), @@ -451,7 +451,7 @@ class _DownloadSettingsPageState extends ConsumerState { const SizedBox(width: 8), Expanded( child: Text( - 'Select Tidal or Qobuz above to configure quality', + context.l10n.downloadSelectTidalQobuz, style: Theme.of(context).textTheme.bodySmall ?.copyWith( color: colorScheme.onSurfaceVariant, @@ -504,7 +504,7 @@ class _DownloadSettingsPageState extends ConsumerState { title: context.l10n.optionsEmbedLyrics, subtitle: settings.embedMetadata ? context.l10n.optionsEmbedLyricsSubtitle - : 'Disabled while Embed Metadata is turned off', + : context.l10n.downloadEmbedLyricsDisabled, value: settings.embedLyrics, enabled: settings.embedMetadata, onChanged: (value) => ref @@ -528,7 +528,7 @@ class _DownloadSettingsPageState extends ConsumerState { ), SettingsItem( icon: Icons.source_outlined, - title: 'Lyrics Providers', + title: context.l10n.lyricsProvidersTitle, subtitle: _getLyricsProvidersSubtitle( settings.lyricsProviders, ), @@ -541,10 +541,10 @@ class _DownloadSettingsPageState extends ConsumerState { ), SettingsSwitchItem( icon: Icons.translate_outlined, - title: 'Netease: Include Translation', + title: context.l10n.downloadNeteaseIncludeTranslation, subtitle: settings.lyricsIncludeTranslationNetease - ? 'Append translated lyrics when available' - : 'Use original lyrics only', + ? context.l10n.downloadNeteaseIncludeTranslationEnabled + : context.l10n.downloadNeteaseIncludeTranslationDisabled, value: settings.lyricsIncludeTranslationNetease, onChanged: (value) => ref .read(settingsProvider.notifier) @@ -552,10 +552,10 @@ class _DownloadSettingsPageState extends ConsumerState { ), SettingsSwitchItem( icon: Icons.text_fields_outlined, - title: 'Netease: Include Romanization', + title: context.l10n.downloadNeteaseIncludeRomanization, subtitle: settings.lyricsIncludeRomanizationNetease - ? 'Append romanized lyrics when available' - : 'Disabled', + ? context.l10n.downloadNeteaseIncludeRomanizationEnabled + : context.l10n.downloadNeteaseIncludeRomanizationDisabled, value: settings.lyricsIncludeRomanizationNetease, onChanged: (value) => ref .read(settingsProvider.notifier) @@ -563,10 +563,10 @@ class _DownloadSettingsPageState extends ConsumerState { ), SettingsSwitchItem( icon: Icons.record_voice_over_outlined, - title: 'Apple/QQ Multi-Person Word-by-Word', + title: context.l10n.downloadAppleQqMultiPerson, subtitle: settings.lyricsMultiPersonWordByWord - ? 'Enable v1/v2 speaker and [bg:] tags' - : 'Simplified word-by-word formatting', + ? context.l10n.downloadAppleQqMultiPersonEnabled + : context.l10n.downloadAppleQqMultiPersonDisabled, value: settings.lyricsMultiPersonWordByWord, onChanged: (value) => ref .read(settingsProvider.notifier) @@ -574,9 +574,9 @@ class _DownloadSettingsPageState extends ConsumerState { ), SettingsItem( icon: Icons.language_outlined, - title: 'Musixmatch Language', + title: context.l10n.downloadMusixmatchLanguage, subtitle: settings.musixmatchLanguage.isEmpty - ? 'Auto (original)' + ? context.l10n.downloadMusixmatchLanguageAuto : settings.musixmatchLanguage.toUpperCase(), onTap: () => _showMusixmatchLanguagePicker( context, @@ -622,8 +622,8 @@ class _DownloadSettingsPageState extends ConsumerState { icon: Icons.library_music_outlined, title: context.l10n.downloadSeparateSinglesFolder, subtitle: settings.separateSingles - ? 'Albums/ and Singles/ folders' - : 'All files in same structure', + ? context.l10n.downloadSeparateSinglesEnabled + : context.l10n.downloadSeparateSinglesDisabled, value: settings.separateSingles, onChanged: (value) => ref .read(settingsProvider.notifier) @@ -670,9 +670,9 @@ class _DownloadSettingsPageState extends ConsumerState { .read(settingsProvider.notifier) .setUseAlbumArtistForFolders(value), ), - SettingsItem( + SettingsItem( icon: Icons.filter_alt_outlined, - title: 'Artist Name Filters', + title: context.l10n.downloadArtistNameFilters, subtitle: _getArtistFolderFilterSubtitle( context, usePrimaryArtistOnly: settings.usePrimaryArtistOnly, @@ -707,28 +707,16 @@ class _DownloadSettingsPageState extends ConsumerState { if (_artistFolderFiltersExpanded) SettingsSwitchItem( icon: Icons.group_remove_outlined, - title: 'Filter contributing artists in Album Artist', + title: context.l10n.downloadFilterContributing, subtitle: settings.filterContributingArtistsInAlbumArtist - ? 'Album Artist metadata uses primary artist only' - : 'Keep full Album Artist metadata value', + ? context.l10n.downloadFilterContributingEnabled + : context.l10n.downloadFilterContributingDisabled, value: settings.filterContributingArtistsInAlbumArtist, onChanged: (value) => ref .read(settingsProvider.notifier) .setFilterContributingArtistsInAlbumArtist(value), showDivider: false, ), - 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), - showDivider: false, - ), ], ), ), @@ -753,7 +741,7 @@ class _DownloadSettingsPageState extends ConsumerState { ), SettingsItem( icon: Icons.public, - title: 'SongLink Region', + title: context.l10n.downloadSongLinkRegion, subtitle: _getSongLinkRegionLabel(settings.songLinkRegion), onTap: () => _showSongLinkRegionPicker( context, @@ -763,10 +751,10 @@ class _DownloadSettingsPageState extends ConsumerState { ), SettingsSwitchItem( icon: Icons.security_outlined, - title: 'Network compatibility mode', + title: context.l10n.downloadNetworkCompatibilityMode, subtitle: settings.networkCompatibilityMode - ? 'Enabled: try HTTP + accept invalid TLS certificates (unsafe)' - : 'Off: strict HTTPS certificate validation (recommended)', + ? context.l10n.downloadNetworkCompatibilityModeEnabled + : context.l10n.downloadNetworkCompatibilityModeDisabled, value: settings.networkCompatibilityMode, onChanged: (value) { ref @@ -1045,7 +1033,7 @@ class _DownloadSettingsPageState extends ConsumerState { ), const SizedBox(height: 8), Text( - 'Customize how your files are named.', + context.l10n.downloadFilenameDescription, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), @@ -1070,7 +1058,7 @@ class _DownloadSettingsPageState extends ConsumerState { const SizedBox(height: 24), Text( - 'Tap to insert tag:', + context.l10n.downloadFilenameInsertTag, style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, ), @@ -1238,7 +1226,7 @@ class _DownloadSettingsPageState extends ConsumerState { Padding( padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), child: Text( - 'Download Location', + context.l10n.setupDownloadLocationTitle, style: Theme.of( ctx, ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), @@ -1247,7 +1235,7 @@ class _DownloadSettingsPageState extends ConsumerState { Padding( padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), child: Text( - 'Choose storage mode for downloaded files.', + context.l10n.downloadLocationSubtitle, style: Theme.of(ctx).textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), @@ -1255,8 +1243,8 @@ class _DownloadSettingsPageState extends ConsumerState { ), ListTile( leading: Icon(Icons.folder_special, color: colorScheme.primary), - title: const Text('App folder (non-SAF)'), - subtitle: const Text('Use default Music/SpotiFLAC path'), + title: Text(context.l10n.storageModeAppFolder), + subtitle: Text(context.l10n.storageModeAppFolderSubtitle), trailing: !isSafMode ? const Icon(Icons.check) : null, onTap: () async { Navigator.pop(ctx); @@ -1269,10 +1257,8 @@ class _DownloadSettingsPageState extends ConsumerState { ), ListTile( leading: Icon(Icons.folder_open, color: colorScheme.primary), - title: const Text('SAF folder'), - subtitle: const Text( - 'Pick folder via Android Storage Access Framework', - ), + title: Text(context.l10n.storageModeSaf), + subtitle: Text(context.l10n.storageModeSafSubtitle), trailing: isSafMode ? const Icon(Icons.check) : null, onTap: () async { Navigator.pop(ctx); @@ -1546,7 +1532,7 @@ class _DownloadSettingsPageState extends ConsumerState { }; String _getLyricsProvidersSubtitle(List providers) { - if (providers.isEmpty) return 'None enabled'; + if (providers.isEmpty) return context.l10n.downloadProvidersNoneEnabled; return providers.map((p) => _providerDisplayNames[p] ?? p).join(' > '); } @@ -1645,14 +1631,14 @@ class _DownloadSettingsPageState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Musixmatch Language', + context.l10n.downloadMusixmatchLanguage, style: Theme.of( context, ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text( - 'Set preferred language code (example: en, es, ja). Leave empty for auto.', + context.l10n.downloadMusixmatchLanguageDesc, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), @@ -1661,9 +1647,9 @@ class _DownloadSettingsPageState extends ConsumerState { TextField( controller: controller, textInputAction: TextInputAction.done, - decoration: const InputDecoration( - labelText: 'Language code', - hintText: 'auto / en / es / ja', + decoration: InputDecoration( + labelText: context.l10n.downloadMusixmatchLanguageCode, + hintText: context.l10n.downloadMusixmatchLanguageHint, ), ), const SizedBox(height: 16), @@ -1682,7 +1668,7 @@ class _DownloadSettingsPageState extends ConsumerState { .setMusixmatchLanguage(''); Navigator.pop(context); }, - child: const Text('Auto'), + child: Text(context.l10n.downloadMusixmatchAuto), ), const SizedBox(width: 8), FilledButton( @@ -1739,7 +1725,7 @@ class _DownloadSettingsPageState extends ConsumerState { Padding( padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), child: Text( - 'Lossy 320kbps Format', + context.l10n.downloadLossy320Format, style: Theme.of( context, ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), @@ -1748,7 +1734,7 @@ class _DownloadSettingsPageState extends ConsumerState { Padding( padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), child: Text( - 'Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.', + context.l10n.downloadLossy320FormatDesc, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), @@ -1756,8 +1742,8 @@ class _DownloadSettingsPageState extends ConsumerState { ), ListTile( leading: const Icon(Icons.audiotrack), - title: const Text('MP3 320kbps'), - subtitle: const Text('Best compatibility, ~10MB per track'), + title: Text(context.l10n.downloadLossyMp3), + subtitle: Text(context.l10n.downloadLossyMp3Subtitle), trailing: current == 'mp3_320' ? Icon(Icons.check, color: colorScheme.primary) : null, @@ -1770,8 +1756,8 @@ class _DownloadSettingsPageState extends ConsumerState { ), ListTile( leading: const Icon(Icons.graphic_eq), - title: const Text('Opus 256kbps'), - subtitle: const Text('Best quality Opus, ~8MB per track'), + title: Text(context.l10n.downloadLossyOpus256), + subtitle: Text(context.l10n.downloadLossyOpus256Subtitle), trailing: current == 'opus_256' ? Icon(Icons.check, color: colorScheme.primary) : null, @@ -1784,8 +1770,8 @@ class _DownloadSettingsPageState extends ConsumerState { ), ListTile( leading: const Icon(Icons.graphic_eq), - title: const Text('Opus 128kbps'), - subtitle: const Text('Smallest size, ~4MB per track'), + title: Text(context.l10n.downloadLossyOpus128), + subtitle: Text(context.l10n.downloadLossyOpus128Subtitle), trailing: current == 'opus_128' ? Icon(Icons.check, color: colorScheme.primary) : null, @@ -1842,7 +1828,7 @@ class _DownloadSettingsPageState extends ConsumerState { ListTile( leading: const Icon(Icons.signal_cellular_alt), title: Text(context.l10n.settingsDownloadNetworkAny), - subtitle: const Text('WiFi + Mobile Data'), + subtitle: Text(context.l10n.downloadNetworkAnySubtitle), trailing: current == 'any' ? Icon(Icons.check, color: colorScheme.primary) : null, @@ -1856,7 +1842,7 @@ class _DownloadSettingsPageState extends ConsumerState { ListTile( leading: const Icon(Icons.wifi), title: Text(context.l10n.settingsDownloadNetworkWifiOnly), - subtitle: const Text('Pause downloads on mobile data'), + subtitle: Text(context.l10n.downloadNetworkWifiOnlySubtitle), trailing: current == 'wifi_only' ? Icon(Icons.check, color: colorScheme.primary) : null, @@ -1897,17 +1883,17 @@ class _DownloadSettingsPageState extends ConsumerState { children: [ Padding( padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), - child: Text( - 'SongLink Region', - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - ), + child: Text( + context.l10n.downloadSongLinkRegion, + style: Theme.of( + context, + ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), ), - Padding( - padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), - child: Text( - 'Used as userCountry for SongLink API lookup.', + ), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), + child: Text( + context.l10n.downloadSongLinkRegionDesc, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), @@ -1968,12 +1954,12 @@ class _DownloadSettingsPageState extends ConsumerState { children: [ Padding( padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), - child: Text( - 'Folder Organization', - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - ), + child: Text( + context.l10n.downloadFolderOrganization, + style: Theme.of( + context, + ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), + ), ), Padding( padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), diff --git a/lib/screens/settings/extension_detail_page.dart b/lib/screens/settings/extension_detail_page.dart index 0a702946..c6e00b55 100644 --- a/lib/screens/settings/extension_detail_page.dart +++ b/lib/screens/settings/extension_detail_page.dart @@ -801,7 +801,7 @@ class _SettingItemState extends State<_SettingItem> { Future _invokeAction(BuildContext context) async { if (widget.setting.action == null) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No action defined for this button')), + SnackBar(content: Text(context.l10n.snackbarNoActionDefined)), ); return; } @@ -834,7 +834,7 @@ class _SettingItemState extends State<_SettingItem> { if (context.mounted) { ScaffoldMessenger.of( context, - ).showSnackBar(SnackBar(content: Text('Error: $e'))); + ).showSnackBar(SnackBar(content: Text(context.l10n.snackbarError(e.toString())))); } } finally { if (mounted) { diff --git a/lib/screens/settings/lyrics_provider_priority_page.dart b/lib/screens/settings/lyrics_provider_priority_page.dart index 426d82c8..3e3537a2 100644 --- a/lib/screens/settings/lyrics_provider_priority_page.dart +++ b/lib/screens/settings/lyrics_provider_priority_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/widgets/priority_settings_scaffold.dart'; import 'package:spotiflac_android/widgets/settings_group.dart'; @@ -55,18 +56,18 @@ class _LyricsProviderPriorityPageState return PrioritySettingsScaffold( hasChanges: _hasChanges, - title: 'Lyrics Providers', - description: - 'Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.', - infoText: - 'Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.', + title: context.l10n.lyricsProvidersTitle, + description: context.l10n.lyricsProvidersDescription, + infoText: context.l10n.lyricsProvidersInfoText, onSave: _saveChanges, onConfirmDiscard: _confirmDiscard, slivers: [ if (_enabledProviders.isNotEmpty) SliverToBoxAdapter( child: SettingsSectionHeader( - title: 'Enabled (${_enabledProviders.length})', + title: context.l10n.lyricsProvidersEnabledSection( + _enabledProviders.length, + ), ), ), if (_enabledProviders.isNotEmpty) @@ -76,7 +77,7 @@ class _LyricsProviderPriorityPageState itemCount: _enabledProviders.length, itemBuilder: (context, index) { final id = _enabledProviders[index]; - final info = _getLyricsProviderInfo(id); + final info = _getLyricsProviderInfo(id, context); return _EnabledProviderItem( key: ValueKey(id), providerId: id, @@ -99,7 +100,9 @@ class _LyricsProviderPriorityPageState if (disabled.isNotEmpty) SliverToBoxAdapter( child: SettingsSectionHeader( - title: 'Disabled (${disabled.length})', + title: context.l10n.lyricsProvidersDisabledSection( + disabled.length, + ), ), ), if (disabled.isNotEmpty) @@ -108,7 +111,7 @@ class _LyricsProviderPriorityPageState sliver: SliverList( delegate: SliverChildBuilderDelegate((context, index) { final id = disabled[index]; - final info = _getLyricsProviderInfo(id); + final info = _getLyricsProviderInfo(id, context); return _DisabledProviderItem( key: ValueKey(id), providerId: id, @@ -130,8 +133,8 @@ class _LyricsProviderPriorityPageState void _disableProvider(String id) { if (_enabledProviders.length <= 1) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('At least one provider must remain enabled'), + SnackBar( + content: Text(context.l10n.lyricsProvidersAtLeastOne), ), ); return; @@ -150,7 +153,7 @@ class _LyricsProviderPriorityPageState }); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Lyrics provider priority saved')), + SnackBar(content: Text(context.l10n.lyricsProvidersSaved)), ); } } @@ -159,16 +162,16 @@ class _LyricsProviderPriorityPageState final result = await showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Discard changes?'), - content: const Text('You have unsaved changes that will be lost.'), + title: Text(context.l10n.dialogDiscardChanges), + content: Text(context.l10n.lyricsProvidersDiscardContent), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), - child: const Text('Cancel'), + child: Text(context.l10n.dialogCancel), ), FilledButton( onPressed: () => Navigator.pop(context, true), - child: const Text('Discard'), + child: Text(context.l10n.dialogDiscard), ), ], ), @@ -176,48 +179,51 @@ class _LyricsProviderPriorityPageState return result ?? false; } - static _LyricsProviderInfo _getLyricsProviderInfo(String id) { + static _LyricsProviderInfo _getLyricsProviderInfo( + String id, + BuildContext context, + ) { switch (id) { case 'spotify_api': return _LyricsProviderInfo( name: 'Spotify Lyrics API', - description: 'Spotify-sourced synced lyrics via community API', + description: context.l10n.lyricsProviderSpotifyApiDesc, icon: Icons.music_note_outlined, ); case 'lrclib': return _LyricsProviderInfo( name: 'LRCLIB', - description: 'Open-source synced lyrics database', + description: context.l10n.lyricsProviderLrclibDesc, icon: Icons.subtitles_outlined, ); case 'netease': return _LyricsProviderInfo( name: 'Netease', - description: 'NetEase Cloud Music (good for Asian songs)', + description: context.l10n.lyricsProviderNeteaseDesc, icon: Icons.cloud_outlined, ); case 'musixmatch': return _LyricsProviderInfo( name: 'Musixmatch', - description: 'Largest lyrics database (multi-language)', + description: context.l10n.lyricsProviderMusixmatchDesc, icon: Icons.translate, ); case 'apple_music': return _LyricsProviderInfo( name: 'Apple Music', - description: 'Word-by-word synced lyrics (via proxy)', + description: context.l10n.lyricsProviderAppleMusicDesc, icon: Icons.music_note, ); case 'qqmusic': return _LyricsProviderInfo( name: 'QQ Music', - description: 'QQ Music (good for Chinese songs, via proxy)', + description: context.l10n.lyricsProviderQqMusicDesc, icon: Icons.queue_music, ); default: return _LyricsProviderInfo( name: id, - description: 'Extension provider', + description: context.l10n.lyricsProviderExtensionDesc, icon: Icons.extension, ); } diff --git a/lib/screens/settings/settings_tab.dart b/lib/screens/settings/settings_tab.dart index 140f933b..7e66fc62 100644 --- a/lib/screens/settings/settings_tab.dart +++ b/lib/screens/settings/settings_tab.dart @@ -107,8 +107,8 @@ class SettingsTab extends ConsumerWidget { ), SettingsItem( icon: Icons.favorite_outline, - title: 'Donate', - subtitle: 'Support SpotiFLAC-Mobile development', + title: l10n.settingsDonate, + subtitle: l10n.settingsDonateSubtitle, onTap: () => _navigateTo(context, const DonatePage()), showDivider: false, ), diff --git a/lib/screens/track_metadata_screen.dart b/lib/screens/track_metadata_screen.dart index 4394592f..95f65c44 100644 --- a/lib/screens/track_metadata_screen.dart +++ b/lib/screens/track_metadata_screen.dart @@ -1731,13 +1731,13 @@ class _TrackMetadataScreenState extends ConsumerState { ); success = ok; if (!ok) { - error = 'Failed to write back to storage'; + error = context.l10n.snackbarFailedToWriteStorage; } } else { success = true; } } else { - error = result['error']?.toString() ?? 'Failed to embed lyrics'; + error = result['error']?.toString() ?? context.l10n.snackbarFailedToEmbedLyrics; } } else if (isMp3 || isOpus) { final metadata = _buildFallbackMetadata(); @@ -1783,7 +1783,7 @@ class _TrackMetadataScreenState extends ConsumerState { } if (ffmpegResult == null) { - error = 'Failed to embed lyrics'; + error = context.l10n.snackbarFailedToEmbedLyrics; } else if (_isSafFile) { final ok = await PlatformBridge.writeTempToSaf( ffmpegResult, @@ -1791,13 +1791,13 @@ class _TrackMetadataScreenState extends ConsumerState { ); success = ok; if (!ok) { - error = 'Failed to write back to storage'; + error = context.l10n.snackbarFailedToWriteStorage; } } else { success = true; } } else { - error = 'Unsupported audio format'; + error = context.l10n.snackbarUnsupportedAudioFormat; } if (mounted) { @@ -1812,7 +1812,7 @@ class _TrackMetadataScreenState extends ConsumerState { } else { setState(() => _isEmbedding = false); ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(error ?? 'Failed to embed lyrics')), + SnackBar(content: Text(error ?? context.l10n.snackbarFailedToEmbedLyrics)), ); } } @@ -1821,7 +1821,7 @@ class _TrackMetadataScreenState extends ConsumerState { setState(() => _isEmbedding = false); ScaffoldMessenger.of( context, - ).showSnackBar(SnackBar(content: Text('Error: $e'))); + ).showSnackBar(SnackBar(content: Text(context.l10n.snackbarError(e.toString())))); } } finally { if (coverPath != null) { @@ -2271,7 +2271,7 @@ class _TrackMetadataScreenState extends ConsumerState { SnackBar( content: Text( context.l10n.trackSaveFailed( - 'Failed to write back to storage', + context.l10n.snackbarFailedToWriteStorage, ), ), ), @@ -2873,7 +2873,7 @@ class _TrackMetadataScreenState extends ConsumerState { // Show loading indicator ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Loading CUE sheet...')), + SnackBar(content: Text(context.l10n.snackbarLoadingCueSheet)), ); try { @@ -3644,7 +3644,7 @@ class _TrackMetadataScreenState extends ConsumerState { if (saved == true && mounted) { ScaffoldMessenger.of(this.context).showSnackBar( - const SnackBar(content: Text('Metadata saved successfully')), + SnackBar(content: Text(this.context.l10n.snackbarMetadataSaved)), ); // Re-read metadata from file to refresh the display try { @@ -4066,7 +4066,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { if (!mounted) return; ScaffoldMessenger.of( context, - ).showSnackBar(SnackBar(content: Text('Failed to pick cover: $e'))); + ).showSnackBar(SnackBar(content: Text(context.l10n.snackbarError(e.toString())))); } } @@ -4290,7 +4290,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { if (mounted) { ScaffoldMessenger.of( context, - ).showSnackBar(SnackBar(content: Text('Failed to save metadata: $e'))); + ).showSnackBar(SnackBar(content: Text(context.l10n.snackbarError(e.toString())))); } } finally { if (mounted) setState(() => _saving = false);