diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 00000000..c2fd467a --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,44 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: [main] + paths: + - 'site/**' + - '.github/workflows/pages.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: site + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8603a8ec..8273df1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## [3.6.7] - 2026-02-13 + +### Added + +- "Advanced Filename Templates" - new placeholders for custom track/disc formatting and date patterns + - `{track_raw}` and `{disc_raw}` - unpadded raw numbers + - `{track:N}` and `{disc:N}` - zero-padded to N digits (e.g. `{track:02}` → `01`) + - `{date}` - full release date from metadata + - `{date:%Y-%m-%d}` - date formatting with strftime patterns + - "Show advanced tags" toggle in Settings > Download > Filename Format to reveal these placeholders +- Low-RAM / ARM32-only device profiling - detects constrained devices at startup and reduces image cache (120 items / 24 MiB) and disables overscroll effects for smoother performance +- Responsive selection bar on artist screen - switches to compact stacked layout on narrow screens (< 430dp) or large text scale (> 1.15x) +- Quality picker dialog before downloading individual tracks from artist screen (when "Ask quality before download" is enabled) +- Project website with GitHub Pages deployment workflow + - Mobile burger menu navigation for all site pages +- Go filename template test suite + +### Fixed + +- Fixed ICU plural syntax errors in DE, ES, PT, RU translations - incorrect `=1` clause was causing missing plural forms +- Fixed featured-artist regex incorrectly splitting on `&` character (e.g. "Simon & Garfunkel" was being split) - removed `&` from separator pattern +- Fixed `{date}` placeholder not working in filename templates - release date was not being passed to the template builder across all providers (Amazon, Qobuz, Tidal, YouTube, extensions) + +### Changed + +- Improved Go backend metadata handling - filename builder now supports fallback metadata keys and automatic type conversion for more robust template rendering +- Extension providers now pass full metadata set to filename builder (track, disc, year, date, release_date) +- Updated translations: added filename advanced tags strings (EN, ID), regenerated all locale dart files +- Updated app screenshot assets + +--- + ## [3.6.6] - 2026-02-12 ### Added diff --git a/assets/images/1.jpg b/assets/images/1.jpg index d1ddaf50..286f377d 100644 Binary files a/assets/images/1.jpg and b/assets/images/1.jpg differ diff --git a/assets/images/2.jpg b/assets/images/2.jpg index da279a98..9692fc5e 100644 Binary files a/assets/images/2.jpg and b/assets/images/2.jpg differ diff --git a/assets/images/3.jpg b/assets/images/3.jpg index 7783996e..f4cc9de5 100644 Binary files a/assets/images/3.jpg and b/assets/images/3.jpg differ diff --git a/assets/images/4.jpg b/assets/images/4.jpg index 9b06dfe2..b501f2fa 100644 Binary files a/assets/images/4.jpg and b/assets/images/4.jpg differ diff --git a/go_backend/amazon.go b/go_backend/amazon.go index 75cdac82..80fc9f3c 100644 --- a/go_backend/amazon.go +++ b/go_backend/amazon.go @@ -441,6 +441,7 @@ func downloadFromAmazon(req DownloadRequest) (AmazonDownloadResult, error) { "album": req.AlbumName, "track": req.TrackNumber, "year": extractYear(req.ReleaseDate), + "date": req.ReleaseDate, "disc": req.DiscNumber, }) var outputPath string diff --git a/go_backend/extension_providers.go b/go_backend/extension_providers.go index 1fa0e3f0..a83a704b 100644 --- a/go_backend/extension_providers.go +++ b/go_backend/extension_providers.go @@ -1136,8 +1136,13 @@ func buildOutputPath(req DownloadRequest) string { "artist": req.ArtistName, "album": req.AlbumName, "album_artist": req.AlbumArtist, + "track": req.TrackNumber, "track_number": req.TrackNumber, + "disc": req.DiscNumber, "disc_number": req.DiscNumber, + "year": extractYear(req.ReleaseDate), + "date": req.ReleaseDate, + "release_date": req.ReleaseDate, "isrc": req.ISRC, } diff --git a/go_backend/filename.go b/go_backend/filename.go index 94a17cf8..2d3bc497 100644 --- a/go_backend/filename.go +++ b/go_backend/filename.go @@ -3,28 +3,35 @@ package gobackend import ( "fmt" "regexp" + "strconv" "strings" + "time" ) -var invalidChars = regexp.MustCompile(`[<>:"/\\|?*\x00-\x1f]`) +var ( + invalidChars = regexp.MustCompile(`[<>:"/\\|?*\x00-\x1f]`) + multiUnderscore = regexp.MustCompile(`_+`) + formattedNumberPlaceholderExpr = regexp.MustCompile(`\{(track|disc):([0-9]+)\}`) + dateFormatPlaceholderExpr = regexp.MustCompile(`\{date:([^{}]+)\}`) + yearPattern = regexp.MustCompile(`\d{4}`) +) func sanitizeFilename(filename string) string { sanitized := invalidChars.ReplaceAllString(filename, "_") - + sanitized = strings.TrimSpace(sanitized) sanitized = strings.Trim(sanitized, ".") - - multiUnderscore := regexp.MustCompile(`_+`) + sanitized = multiUnderscore.ReplaceAllString(sanitized, "_") - + if len(sanitized) > 200 { sanitized = sanitized[:200] } - + if sanitized == "" { sanitized = "untitled" } - + return sanitized } @@ -32,45 +39,120 @@ func buildFilenameFromTemplate(template string, metadata map[string]interface{}) if template == "" { template = "{artist} - {title}" } - - result := template - - placeholders := map[string]string{ - "{title}": getString(metadata, "title"), - "{artist}": getString(metadata, "artist"), - "{album}": getString(metadata, "album"), - "{track}": formatTrackNumber(getInt(metadata, "track")), - "{year}": getString(metadata, "year"), - "{disc}": formatDiscNumber(getInt(metadata, "disc")), + + result := replaceFormattedNumberPlaceholders(template, metadata) + result = replaceDateFormatPlaceholders(result, metadata) + + dateValue := getDateValue(metadata) + yearValue := getString(metadata, "year") + if yearValue == "" { + yearValue = extractYear(dateValue) } - + + placeholders := map[string]string{ + "{title}": getString(metadata, "title"), + "{artist}": getString(metadata, "artist"), + "{album}": getString(metadata, "album"), + "{track}": formatTrackNumber(getInt(metadata, "track")), + "{track_raw}": formatRawNumber(getInt(metadata, "track")), + "{year}": yearValue, + "{date}": dateValue, + "{disc}": formatDiscNumber(getInt(metadata, "disc")), + "{disc_raw}": formatRawNumber(getInt(metadata, "disc")), + } + for placeholder, value := range placeholders { result = strings.ReplaceAll(result, placeholder, value) } - + return result } +func replaceFormattedNumberPlaceholders(template string, metadata map[string]interface{}) string { + return formattedNumberPlaceholderExpr.ReplaceAllStringFunc(template, func(match string) string { + parts := formattedNumberPlaceholderExpr.FindStringSubmatch(match) + if len(parts) != 3 { + return "" + } + + number := getInt(metadata, parts[1]) + width, err := strconv.Atoi(parts[2]) + if err != nil { + return "" + } + + return formatNumberWithWidth(number, width) + }) +} + +func replaceDateFormatPlaceholders(template string, metadata map[string]interface{}) string { + return dateFormatPlaceholderExpr.ReplaceAllStringFunc(template, func(match string) string { + parts := dateFormatPlaceholderExpr.FindStringSubmatch(match) + if len(parts) != 2 { + return "" + } + + return formatDateWithPattern(getDateValue(metadata), parts[1]) + }) +} + +func getDateValue(metadata map[string]interface{}) string { + date := getString(metadata, "date") + if date != "" { + return date + } + + releaseDate := getString(metadata, "release_date") + if releaseDate != "" { + return releaseDate + } + + return getString(metadata, "year") +} + func getString(m map[string]interface{}, key string) string { if v, ok := m[key]; ok { - if s, ok := v.(string); ok { - return strings.TrimSpace(s) + switch value := v.(type) { + case string: + return strings.TrimSpace(value) + case int: + return strconv.Itoa(value) + case int64: + return strconv.FormatInt(value, 10) + case float64: + return strconv.Itoa(int(value)) } } return "" } func getInt(m map[string]interface{}, key string) int { - if v, ok := m[key]; ok { - switch n := v.(type) { - case int: - return n - case int64: - return int(n) - case float64: - return int(n) + candidateKeys := []string{key} + switch key { + case "track": + candidateKeys = append(candidateKeys, "track_number") + case "disc": + candidateKeys = append(candidateKeys, "disc_number") + } + + for _, candidate := range candidateKeys { + if v, ok := m[candidate]; ok { + switch n := v.(type) { + case int: + return n + case int64: + return int(n) + case float64: + return int(n) + case string: + parsed, err := strconv.Atoi(strings.TrimSpace(n)) + if err == nil { + return parsed + } + } } } + return 0 } @@ -88,6 +170,129 @@ func formatDiscNumber(n int) string { return fmt.Sprintf("%d", n) } +func formatRawNumber(n int) string { + if n <= 0 { + return "" + } + return fmt.Sprintf("%d", n) +} + +func formatNumberWithWidth(n int, width int) string { + if n <= 0 || width <= 0 { + return "" + } + if width <= 1 { + return formatRawNumber(n) + } + return fmt.Sprintf("%0*d", width, n) +} + +func formatDateWithPattern(rawDate string, strftimePattern string) string { + if rawDate == "" || strftimePattern == "" { + return "" + } + + parsedDate, ok := parseMetadataDate(rawDate) + if !ok { + return "" + } + + goLayout := convertStrftimeToGoLayout(strftimePattern) + if goLayout == "" { + return "" + } + + return parsedDate.Format(goLayout) +} + +func parseMetadataDate(rawDate string) (time.Time, bool) { + clean := strings.TrimSpace(rawDate) + if clean == "" { + return time.Time{}, false + } + + layouts := []string{ + time.RFC3339Nano, + time.RFC3339, + "2006-01-02", + "2006-01", + "2006", + "2006/01/02", + "2006/01", + "2006.01.02", + "2006.01", + } + + for _, layout := range layouts { + parsed, err := time.Parse(layout, clean) + if err == nil { + return parsed, true + } + } + + if len(clean) >= 10 { + parsed, err := time.Parse("2006-01-02", clean[:10]) + if err == nil { + return parsed, true + } + } + + yearMatch := yearPattern.FindString(clean) + if yearMatch == "" { + return time.Time{}, false + } + + year, err := strconv.Atoi(yearMatch) + if err != nil || year <= 0 { + return time.Time{}, false + } + + return time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC), true +} + +func convertStrftimeToGoLayout(pattern string) string { + if pattern == "" { + return "" + } + + var builder strings.Builder + for i := 0; i < len(pattern); i++ { + ch := pattern[i] + if ch != '%' { + builder.WriteByte(ch) + continue + } + + if i+1 >= len(pattern) { + builder.WriteByte('%') + break + } + + i++ + switch pattern[i] { + case 'Y': + builder.WriteString("2006") + case 'y': + builder.WriteString("06") + case 'm': + builder.WriteString("01") + case 'd': + builder.WriteString("02") + case 'b': + builder.WriteString("Jan") + case 'B': + builder.WriteString("January") + case '%': + builder.WriteByte('%') + default: + builder.WriteByte('%') + builder.WriteByte(pattern[i]) + } + } + + return builder.String() +} + func extractYear(date string) string { if len(date) >= 4 { return date[:4] diff --git a/go_backend/filename_test.go b/go_backend/filename_test.go new file mode 100644 index 00000000..0b7940af --- /dev/null +++ b/go_backend/filename_test.go @@ -0,0 +1,85 @@ +package gobackend + +import "testing" + +func TestBuildFilenameFromTemplate_WithRawTrackAndDisc(t *testing.T) { + metadata := map[string]interface{}{ + "title": "Song Name", + "artist": "Artist Name", + "album": "Album Name", + "track": 1, + "disc": 2, + "year": "2025", + } + + formatted := buildFilenameFromTemplate( + "{artist} - {track} - {track_raw} - d{disc} - d{disc_raw} - {title}", + metadata, + ) + + expected := "Artist Name - 01 - 1 - d2 - d2 - Song Name" + if formatted != expected { + t.Fatalf("expected %q, got %q", expected, formatted) + } +} + +func TestBuildFilenameFromTemplate_RawPlaceholdersEmptyWhenZero(t *testing.T) { + metadata := map[string]interface{}{ + "title": "Song Name", + "artist": "Artist Name", + "track": 0, + "disc": 0, + } + + formatted := buildFilenameFromTemplate("{track_raw}-{disc_raw}-{title}", metadata) + expected := "--Song Name" + if formatted != expected { + t.Fatalf("expected %q, got %q", expected, formatted) + } +} + +func TestBuildFilenameFromTemplate_InlineNumberFormatting(t *testing.T) { + metadata := map[string]interface{}{ + "track": 3, + "disc": 2, + } + + formatted := buildFilenameFromTemplate("{track:1}-{track:02}-{disc:03}", metadata) + expected := "3-03-002" + if formatted != expected { + t.Fatalf("expected %q, got %q", expected, formatted) + } +} + +func TestBuildFilenameFromTemplate_DateStrftimeFormatting(t *testing.T) { + metadata := map[string]interface{}{ + "artist": "Artist Name", + "title": "Song Name", + "release_date": "2024-03-09", + "track_number": 7, + "disc_number": 1, + } + + formatted := buildFilenameFromTemplate( + "{artist} - {track:02} - {title} - {date:%Y-%m-%d} - {year}", + metadata, + ) + expected := "Artist Name - 07 - Song Name - 2024-03-09 - 2024" + if formatted != expected { + t.Fatalf("expected %q, got %q", expected, formatted) + } +} + +func TestBuildFilenameFromTemplate_DateStrftimeFormattingWithYearOnly(t *testing.T) { + metadata := map[string]interface{}{ + "artist": "Artist Name", + "title": "Song Name", + "date": "2019", + } + + formatted := buildFilenameFromTemplate("{date:%Y}-{date:%m}-{date:%d}", metadata) + expected := "2019-01-01" + if formatted != expected { + t.Fatalf("expected %q, got %q", expected, formatted) + } +} diff --git a/go_backend/qobuz.go b/go_backend/qobuz.go index 5336394c..23914f5b 100644 --- a/go_backend/qobuz.go +++ b/go_backend/qobuz.go @@ -1180,6 +1180,7 @@ func downloadFromQobuz(req DownloadRequest) (QobuzDownloadResult, error) { "album": req.AlbumName, "track": req.TrackNumber, "year": extractYear(req.ReleaseDate), + "date": req.ReleaseDate, "disc": req.DiscNumber, }) var outputPath string diff --git a/go_backend/tidal.go b/go_backend/tidal.go index cb6c9f5f..8e52aa8b 100644 --- a/go_backend/tidal.go +++ b/go_backend/tidal.go @@ -1609,6 +1609,7 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) { "album": req.AlbumName, "track": req.TrackNumber, "year": extractYear(req.ReleaseDate), + "date": req.ReleaseDate, "disc": req.DiscNumber, }) diff --git a/go_backend/youtube.go b/go_backend/youtube.go index 2bd02c69..0309321b 100644 --- a/go_backend/youtube.go +++ b/go_backend/youtube.go @@ -500,6 +500,7 @@ func downloadFromYouTube(req DownloadRequest) (YouTubeDownloadResult, error) { "album": req.AlbumName, "track": req.TrackNumber, "year": extractYear(req.ReleaseDate), + "date": req.ReleaseDate, "disc": req.DiscNumber, }) filename = sanitizeFilename(filename) + ext diff --git a/lib/app.dart b/lib/app.dart index 013d59ec..0e005e54 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -10,9 +10,13 @@ import 'package:spotiflac_android/theme/dynamic_color_wrapper.dart'; import 'package:spotiflac_android/l10n/app_localizations.dart'; final _routerProvider = Provider((ref) { - final isFirstLaunch = ref.watch(settingsProvider.select((s) => s.isFirstLaunch)); - final hasCompletedTutorial = ref.watch(settingsProvider.select((s) => s.hasCompletedTutorial)); - + final isFirstLaunch = ref.watch( + settingsProvider.select((s) => s.isFirstLaunch), + ); + final hasCompletedTutorial = ref.watch( + settingsProvider.select((s) => s.hasCompletedTutorial), + ); + // Determine initial location based on app state String initialLocation; if (isFirstLaunch) { @@ -22,18 +26,12 @@ final _routerProvider = Provider((ref) { } else { initialLocation = '/'; } - + return GoRouter( initialLocation: initialLocation, routes: [ - GoRoute( - path: '/', - builder: (context, state) => const MainShell(), - ), - GoRoute( - path: '/setup', - builder: (context, state) => const SetupScreen(), - ), + GoRoute(path: '/', builder: (context, state) => const MainShell()), + GoRoute(path: '/setup', builder: (context, state) => const SetupScreen()), GoRoute( path: '/tutorial', builder: (context, state) => const TutorialScreen(), @@ -43,13 +41,18 @@ final _routerProvider = Provider((ref) { }); class SpotiFLACApp extends ConsumerWidget { - const SpotiFLACApp({super.key}); + final bool disableOverscrollEffects; + + const SpotiFLACApp({super.key, this.disableOverscrollEffects = false}); @override Widget build(BuildContext context, WidgetRef ref) { final router = ref.watch(_routerProvider); final localeString = ref.watch(settingsProvider.select((s) => s.locale)); - + final scrollBehavior = disableOverscrollEffects + ? const MaterialScrollBehavior().copyWith(overscroll: false) + : null; + Locale? locale; if (localeString != 'system') { if (localeString.contains('_')) { @@ -59,7 +62,7 @@ class SpotiFLACApp extends ConsumerWidget { locale = Locale(localeString); } } - + return DynamicColorWrapper( builder: (lightTheme, darkTheme, themeMode) { return MaterialApp.router( @@ -68,6 +71,7 @@ class SpotiFLACApp extends ConsumerWidget { theme: lightTheme, darkTheme: darkTheme, themeMode: themeMode, + scrollBehavior: scrollBehavior, themeAnimationDuration: const Duration(milliseconds: 300), themeAnimationCurve: Curves.easeInOut, routerConfig: router, diff --git a/lib/constants/app_info.dart b/lib/constants/app_info.dart index 2188374a..7a8d737e 100644 --- a/lib/constants/app_info.dart +++ b/lib/constants/app_info.dart @@ -1,8 +1,8 @@ /// App version and info constants /// Update version here only - all other files will reference this class AppInfo { - static const String version = '3.6.6'; - static const String buildNumber = '80'; + static const String version = '3.6.7'; + static const String buildNumber = '81'; static const String fullVersion = '$version+$buildNumber'; diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 34e49062..7013a130 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2152,6 +2152,18 @@ abstract class AppLocalizations { /// **'{artist} - {title}'** String filenameHint(Object artist, Object title); + /// Toggle label for showing advanced filename tags + /// + /// In en, this message translates to: + /// **'Show advanced tags'** + String get filenameShowAdvancedTags; + + /// Description for advanced filename tag toggle + /// + /// In en, this message translates to: + /// **'Enable formatted tags for track padding and date patterns'** + String get filenameShowAdvancedTagsDescription; + /// Setting title - folder structure /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index b405e5b7..a89201ef 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -13,13 +13,13 @@ class AppLocalizationsDe extends AppLocalizations { @override String get appDescription => - 'Laden Sie Spotify-Titel in verlustfreier Qualität von Tidal, Qobuz und Amazon Music herunter.'; + 'Lade Spotify-Titel in verlustfreier Qualität von Tidal, Qobuz und Amazon Music herunter.'; @override String get navHome => 'Startseite'; @override - String get navLibrary => 'Library'; + String get navLibrary => 'Archiv'; @override String get navHistory => 'Verlauf'; @@ -105,7 +105,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get historyNoAlbumsSubtitle => - 'Laden Sie mehrere Titel eines Albums herunter, um sie hier zu sehen'; + 'Lade mehrere Titel eines Albums herunter, um sie hier zu sehen'; @override String get historyNoSingles => 'Keine Einzel-Downloads'; @@ -142,8 +142,7 @@ class AppLocalizationsDe extends AppLocalizations { String get downloadLocation => 'Download-Speicherort'; @override - String get downloadLocationSubtitle => - 'Wählen Sie den Speicherort für Dateien'; + String get downloadLocationSubtitle => 'Wähle den Speicherort der Dateien'; @override String get downloadLocationDefault => 'Standard-Speicherort'; @@ -243,7 +242,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get optionsSwitchBack => - 'Tippen Sie auf Deezer oder Spotify, um von der Erweiterung zurückzuwechseln'; + 'Tippe auf Deezer oder Spotify, um von der Erweiterung zurückzuwechseln'; @override String get optionsAutoFallback => 'Automatischer Fallback'; @@ -268,7 +267,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get optionsEmbedLyricsSubtitle => - 'Synchronisierte Liedtexte in FLAC-Dateien einbetten'; + 'Synchronisierte Lyrics in FLAC-Dateien einbetten'; @override String get optionsMaxQualityCover => 'Maximale Cover-Qualität'; @@ -354,7 +353,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get optionsSpotifyDeprecationWarning => - 'Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.'; + 'Spotify-Suche wird am 3. März 2026 aufgrund von Änderungen der Spotify-API entfernt. Bitte wechsel vorher zu Deezer.'; @override String get extensionsTitle => 'Erweiterungen'; @@ -485,14 +484,14 @@ class AppLocalizationsDe extends AppLocalizations { @override String get aboutSjdonadoDesc => - 'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!'; + 'Ersteller von I Don\'t Have Spotify (IDHS). Der Fallback-Link-Resolver, der den Tag rettete!'; @override String get aboutDoubleDouble => 'DoubleDouble'; @override String get aboutDoubleDoubleDesc => - 'Wundervolle API für Amazon Music Downloads.\nVielen Dank, dass Sie sie kostenlos zur Verfügung stellen!'; + 'Wundervolle API für Amazon Musik-Downloads.'; @override String get aboutDabMusic => 'DAB Music'; @@ -506,7 +505,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get aboutSpotiSaverDesc => - 'Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!'; + 'Tidal Hi-Res FLAC Streaming-Endpunkte. Ein Schlüsselstück des verlustfreien Puzzle!'; @override String get aboutAppDescription => @@ -655,7 +654,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get setupPermissionDeniedMessage => - 'Berechtigung verweigert. Bitte erteilen Sie alle Berechtigungen um fortzufahren.'; + 'Berechtigung verweigert. Bitte erteile alle Berechtigungen um fortzufahren.'; @override String setupPermissionRequired(String permissionType) { @@ -685,7 +684,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get setupDownloadLocationIosMessage => - 'Auf iOS werden Downloads im Dokumentenverzeichnis der App gespeichert. Sie können sie über die Datei-App aufrufen.'; + 'Auf iOS werden Downloads im Dokumentenverzeichnis der App gespeichert. Du kannst sie über die Datei-App aufrufen.'; @override String get setupAppDocumentsFolder => 'App-Dokumentenordner'; @@ -699,15 +698,15 @@ class AppLocalizationsDe extends AppLocalizations { @override String get setupChooseFromFilesSubtitle => - 'Wählen Sie iCloud oder einen anderen Ort'; + 'Wähle iCloud oder einen anderen Speicherort'; @override String get setupIosEmptyFolderWarning => - 'iOS-Einschränkung: Leere Ordner können nicht ausgewählt werden. Wählen Sie einen Ordner mit mindestens einer Datei.'; + 'iOS-Einschränkung: Leere Ordner können nicht ausgewählt werden. Wähle einen Ordner mit mindestens einer Datei.'; @override String get setupIcloudNotSupported => - 'iCloud Drive is not supported. Please use the app Documents folder.'; + 'iCloud Drive wird nicht unterstützt. Bitte verwende den \"Dokumente\" Ordner.'; @override String get setupDownloadInFlac => 'Spotify Titel in FLAC herunterladen'; @@ -756,7 +755,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get setupFolderDescription => - 'Wählen Sie einen Ordner, in dem Ihre heruntergeladene Musik gespeichert wird.'; + 'Wähle einen Ordner, in dem die heruntergeladene Musik gespeichert wird.'; @override String get setupChangeFolder => 'Ordner ändern'; @@ -769,427 +768,430 @@ class AppLocalizationsDe extends AppLocalizations { @override String get setupSpotifyApiDescription => - 'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.'; + 'Füge deine Spotify-API-Zugangsdaten für bessere Suchergebnisse und den Zugriff auf Spotify-exklusive Inhalte hinzu.'; @override - String get setupUseSpotifyApi => 'Use Spotify API'; + String get setupUseSpotifyApi => 'Spotify-API verwenden'; @override - String get setupEnterCredentialsBelow => 'Enter your credentials below'; + String get setupEnterCredentialsBelow => 'Gib deine Anmeldedaten unten ein'; @override - String get setupUsingDeezer => 'Using Deezer (no account needed)'; + String get setupUsingDeezer => 'Deezer verwenden (kein Konto erforderlich)'; @override - String get setupEnterClientId => 'Enter Spotify Client ID'; + String get setupEnterClientId => 'Spotify-Client-ID eingeben'; @override - String get setupEnterClientSecret => 'Enter Spotify Client Secret'; + String get setupEnterClientSecret => 'Spotify Client-Secret eingeben'; @override String get setupGetFreeCredentials => - 'Get your free API credentials from the Spotify Developer Dashboard.'; + 'Hole dir kostenlose API-Anmeldeinformationen aus dem Spotify-Entwickler-Dashboard.'; @override - String get setupEnableNotifications => 'Enable Notifications'; + String get setupEnableNotifications => 'Benachrichtigungen aktivieren'; @override - String get setupProceedToNextStep => 'You can now proceed to the next step.'; + String get setupProceedToNextStep => + 'Du kannst mit dem nächsten Schritt fortfahren.'; @override String get setupNotificationProgressDescription => - 'You will receive download progress notifications.'; + 'Du erhältst Benachrichtigungen über den Download-Fortschritt.'; @override String get setupNotificationBackgroundDescription => - 'Get notified about download progress and completion. This helps you track downloads when the app is in background.'; + 'Werde benachrichtigt über Download-Fortschritt und -Fertigstellung. Dies hilft Ihnen, Downloads zu verfolgen, wenn die App im Hintergrund ist.'; @override - String get setupSkipForNow => 'Skip for now'; + String get setupSkipForNow => 'Vorerst überspringen'; @override - String get setupBack => 'Back'; + String get setupBack => 'Zurück'; @override - String get setupNext => 'Next'; + String get setupNext => 'Weiter'; @override - String get setupGetStarted => 'Get Started'; + String get setupGetStarted => 'Los geht‘s'; @override - String get setupSkipAndStart => 'Skip & Start'; + String get setupSkipAndStart => 'Überspringen & Starten'; @override String get setupAllowAccessToManageFiles => - 'Please enable \"Allow access to manage all files\" in the next screen.'; + 'Bitte aktiviere \"Zugriff auf alle Dateien erlauben\" auf dem nächsten Bildschirm.'; @override String get setupGetCredentialsFromSpotify => - 'Get credentials from developer.spotify.com'; + 'Zugangsdaten von developer.spotify.com erhalten'; @override - String get dialogCancel => 'Cancel'; + String get dialogCancel => 'Abbrechen'; @override String get dialogOk => 'OK'; @override - String get dialogSave => 'Save'; + String get dialogSave => 'Speichern'; @override - String get dialogDelete => 'Delete'; + String get dialogDelete => 'Löschen'; @override - String get dialogRetry => 'Retry'; + String get dialogRetry => 'Wiederholen'; @override - String get dialogClose => 'Close'; + String get dialogClose => 'Schließen'; @override - String get dialogYes => 'Yes'; + String get dialogYes => 'Ja'; @override - String get dialogNo => 'No'; + String get dialogNo => 'Nein'; @override - String get dialogClear => 'Clear'; + String get dialogClear => 'Leeren'; @override - String get dialogConfirm => 'Confirm'; + String get dialogConfirm => 'Bestätigen'; @override - String get dialogDone => 'Done'; + String get dialogDone => 'Fertig'; @override - String get dialogImport => 'Import'; + String get dialogImport => 'Importieren'; @override - String get dialogDiscard => 'Discard'; + String get dialogDiscard => 'Verwerfen'; @override - String get dialogRemove => 'Remove'; + String get dialogRemove => 'Entfernen'; @override - String get dialogUninstall => 'Uninstall'; + String get dialogUninstall => 'Deinstallieren'; @override - String get dialogDiscardChanges => 'Discard Changes?'; + String get dialogDiscardChanges => 'Änderungen verwerfen?'; @override String get dialogUnsavedChanges => - 'You have unsaved changes. Do you want to discard them?'; + 'Hast du noch nicht alle Änderungen gespeichert. Möchtest du die Änderungen verwerfen?'; @override - String get dialogDownloadFailed => 'Download Failed'; + String get dialogDownloadFailed => 'Download fehlgeschlagen'; @override - String get dialogTrackLabel => 'Track:'; + String get dialogTrackLabel => 'Titel:'; @override - String get dialogArtistLabel => 'Artist:'; + String get dialogArtistLabel => 'Künstler:'; @override - String get dialogErrorLabel => 'Error:'; + String get dialogErrorLabel => 'Fehler:'; @override - String get dialogClearAll => 'Clear All'; + String get dialogClearAll => 'Alles löschen'; @override String get dialogClearAllDownloads => - 'Are you sure you want to clear all downloads?'; + 'Bist du dir sicher, dass du alle Downloads löschen möchten?'; @override - String get dialogRemoveFromDevice => 'Remove from device?'; + String get dialogRemoveFromDevice => 'Vom Gerät entfernen?'; @override - String get dialogRemoveExtension => 'Remove Extension'; + String get dialogRemoveExtension => 'Erweiterung entfernen'; @override String get dialogRemoveExtensionMessage => - 'Are you sure you want to remove this extension? This cannot be undone.'; + 'Bist Du sicher, dass Du diese Erweiterung entfernen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'; @override - String get dialogUninstallExtension => 'Uninstall Extension?'; + String get dialogUninstallExtension => 'Erweiterung deinstallieren?'; @override String dialogUninstallExtensionMessage(String extensionName) { - return 'Are you sure you want to remove $extensionName?'; + return 'Bist du dir sicher, dass du $extensionName entfernen möchtest?'; } @override - String get dialogClearHistoryTitle => 'Clear History'; + String get dialogClearHistoryTitle => 'Verlauf löschen'; @override String get dialogClearHistoryMessage => - 'Are you sure you want to clear all download history? This cannot be undone.'; + 'Bist du dir sicher, dass du den gesamten Download verlauf löschen möchten? Dies kann nicht rückgängig gemacht werden.'; @override - String get dialogDeleteSelectedTitle => 'Delete Selected'; + String get dialogDeleteSelectedTitle => 'Ausgewählte löschen'; @override String dialogDeleteSelectedMessage(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'tracks', - one: 'track', + other: 'Tracks', + one: 'Track', ); - return 'Delete $count $_temp0 from history?\n\nThis will also delete the files from storage.'; + return 'Lösche $count $_temp0 aus dem Verlauf?\n\nDies löscht auch die Dateien aus dem Speicher.'; } @override - String get dialogImportPlaylistTitle => 'Import Playlist'; + String get dialogImportPlaylistTitle => 'Wiedergabeliste importieren'; @override String dialogImportPlaylistMessage(int count) { - return 'Found $count tracks in CSV. Add them to download queue?'; + return '$count Titel in CSV gefunden. Zur Warteschlange hinzufügen?'; } @override String csvImportTracks(int count) { - return '$count tracks from CSV'; + return '$count Titel aus CSV'; } @override String snackbarAddedToQueue(String trackName) { - return 'Added \"$trackName\" to queue'; + return '\"$trackName\" zur Warteschlange hinzugefügt'; } @override String snackbarAddedTracksToQueue(int count) { - return 'Added $count tracks to queue'; + return '$count Titel zur Warteschlange hinzugefügt'; } @override String snackbarAlreadyDownloaded(String trackName) { - return '\"$trackName\" already downloaded'; + return '\"$trackName\" bereits heruntergeladen'; } @override String snackbarAlreadyInLibrary(String trackName) { - return '\"$trackName\" already exists in your library'; + return '\"$trackName\" existiert bereits in Ihrer Bibliothek'; } @override - String get snackbarHistoryCleared => 'History cleared'; + String get snackbarHistoryCleared => 'Verlauf gelöscht'; @override - String get snackbarCredentialsSaved => 'Credentials saved'; + String get snackbarCredentialsSaved => 'Anmeldedaten gespeichert'; @override - String get snackbarCredentialsCleared => 'Credentials cleared'; + String get snackbarCredentialsCleared => 'Anmeldedaten gelöscht'; @override String snackbarDeletedTracks(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'tracks', - one: 'track', + other: 'Titel', + one: 'Titel', ); - return 'Deleted $count $_temp0'; + return '$count $_temp0'; } @override String snackbarCannotOpenFile(String error) { - return 'Cannot open file: $error'; + return 'Datei kann nicht geöffnet werden: $error'; } @override - String get snackbarFillAllFields => 'Please fill all fields'; + String get snackbarFillAllFields => 'Bitte fülle alle Felder aus'; @override - String get snackbarViewQueue => 'View Queue'; + String get snackbarViewQueue => 'Warteschlange anzeigen'; @override String snackbarFailedToLoad(String error) { - return 'Failed to load: $error'; + return 'Fehler beim Laden: $error'; } @override String snackbarUrlCopied(String platform) { - return '$platform URL copied to clipboard'; + return '$platform URL in die Zwischenablage kopiert'; } @override - String get snackbarFileNotFound => 'File not found'; + String get snackbarFileNotFound => 'Datei nicht gefunden'; @override - String get snackbarSelectExtFile => 'Please select a .spotiflac-ext file'; + String get snackbarSelectExtFile => 'Bitte wähle eine .spotiflac-ext Datei'; @override - String get snackbarProviderPrioritySaved => 'Provider priority saved'; + String get snackbarProviderPrioritySaved => 'Anbieterpriorität gespeichert'; @override String get snackbarMetadataProviderSaved => - 'Metadata provider priority saved'; + 'Priorität des Metadaten-Anbieters gespeichert'; @override String snackbarExtensionInstalled(String extensionName) { - return '$extensionName installed.'; + return '$extensionName installiert.'; } @override String snackbarExtensionUpdated(String extensionName) { - return '$extensionName updated.'; + return '$extensionName aktualisiert.'; } @override - String get snackbarFailedToInstall => 'Failed to install extension'; + String get snackbarFailedToInstall => + 'Erweiterung konnte nicht installiert werden'; @override - String get snackbarFailedToUpdate => 'Failed to update extension'; + String get snackbarFailedToUpdate => + 'Erweiterung konnte nicht aktualisiert werden'; @override - String get errorRateLimited => 'Rate Limited'; + String get errorRateLimited => 'Anfragelimit überschritten'; @override String get errorRateLimitedMessage => - 'Too many requests. Please wait a moment before searching again.'; + 'Zu viele Anfragen. Bitte warte einen Moment, bevor du es erneut suchst.'; @override String errorFailedToLoad(String item) { - return 'Failed to load $item'; + return 'Fehler beim Laden von: $item'; } @override - String get errorNoTracksFound => 'No tracks found'; + String get errorNoTracksFound => 'Keine Titel gefunden'; @override String errorMissingExtensionSource(String item) { - return 'Cannot load $item: missing extension source'; + return 'Kann $item nicht lade wegen fehlender Erweiterungsquelle'; } @override - String get statusQueued => 'Queued'; + String get statusQueued => 'In der Warteschlange'; @override - String get statusDownloading => 'Downloading'; + String get statusDownloading => 'Wird heruntergeladen'; @override - String get statusFinalizing => 'Finalizing'; + String get statusFinalizing => 'Wird fertiggestellt'; @override - String get statusCompleted => 'Completed'; + String get statusCompleted => 'Beendet'; @override - String get statusFailed => 'Failed'; + String get statusFailed => 'Fehlgeschlagen'; @override - String get statusSkipped => 'Skipped'; + String get statusSkipped => 'Übersprungen'; @override - String get statusPaused => 'Paused'; + String get statusPaused => 'Pausiert'; @override String get actionPause => 'Pause'; @override - String get actionResume => 'Resume'; + String get actionResume => 'Fortfahren'; @override - String get actionCancel => 'Cancel'; + String get actionCancel => 'Abbrechen'; @override - String get actionStop => 'Stop'; + String get actionStop => 'Beenden'; @override - String get actionSelect => 'Select'; + String get actionSelect => 'Wähle'; @override - String get actionSelectAll => 'Select All'; + String get actionSelectAll => 'Alles Auswählen'; @override - String get actionDeselect => 'Deselect'; + String get actionDeselect => 'Alle abwählen'; @override - String get actionPaste => 'Paste'; + String get actionPaste => 'Einfügen'; @override - String get actionImportCsv => 'Import CSV'; + String get actionImportCsv => 'CSV-Datei importieren'; @override - String get actionRemoveCredentials => 'Remove Credentials'; + String get actionRemoveCredentials => 'Anmeldedaten entfernen'; @override - String get actionSaveCredentials => 'Save Credentials'; + String get actionSaveCredentials => 'Anmeldedaten speichern'; @override String selectionSelected(int count) { - return '$count selected'; + return '$count ausgewählt'; } @override - String get selectionAllSelected => 'All tracks selected'; + String get selectionAllSelected => 'Alle Titel sind ausgewählt'; @override - String get selectionTapToSelect => 'Tap tracks to select'; + String get selectionTapToSelect => 'Tippe auf Titel zum Auswählen'; @override String selectionDeleteTracks(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'tracks', - one: 'track', + other: 'Titel', + one: 'Titel', ); - return 'Delete $count $_temp0'; + return 'Lösche $count $_temp0'; } @override - String get selectionSelectToDelete => 'Select tracks to delete'; + String get selectionSelectToDelete => 'Titel zum Löschen auswählen'; @override String progressFetchingMetadata(int current, int total) { - return 'Fetching metadata... $current/$total'; + return 'Lade Metadaten... $current/$total'; } @override - String get progressReadingCsv => 'Reading CSV...'; + String get progressReadingCsv => 'CSV wird gelesen...'; @override - String get searchSongs => 'Songs'; + String get searchSongs => 'Titel'; @override - String get searchArtists => 'Artists'; + String get searchArtists => 'Künstler'; @override String get searchAlbums => 'Albums'; @override - String get searchPlaylists => 'Playlists'; + String get searchPlaylists => 'Playlisten'; @override - String get tooltipPlay => 'Play'; + String get tooltipPlay => 'Abspielen'; @override - String get tooltipCancel => 'Cancel'; + String get tooltipCancel => 'Abbrechen'; @override - String get tooltipStop => 'Stop'; + String get tooltipStop => 'Beenden'; @override - String get tooltipRetry => 'Retry'; + String get tooltipRetry => 'Wiederholen'; @override - String get tooltipRemove => 'Remove'; + String get tooltipRemove => 'Entfernen'; @override - String get tooltipClear => 'Clear'; + String get tooltipClear => 'Leeren'; @override - String get tooltipPaste => 'Paste'; + String get tooltipPaste => 'Einfügen'; @override - String get filenameFormat => 'Filename Format'; + String get filenameFormat => 'Dateinamenformat'; @override String filenameFormatPreview(String preview) { - return 'Preview: $preview'; + return 'Vorschau: $preview'; } @override - String get filenameAvailablePlaceholders => 'Available placeholders:'; + String get filenameAvailablePlaceholders => 'Verfügbare Platzhalter:'; @override String filenameHint(Object artist, Object title) { @@ -1197,238 +1199,249 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get folderOrganization => 'Folder Organization'; + String get filenameShowAdvancedTags => 'Show advanced tags'; @override - String get folderOrganizationNone => 'No organization'; + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; @override - String get folderOrganizationByArtist => 'By Artist'; + String get folderOrganization => 'Ordnerstruktur'; @override - String get folderOrganizationByAlbum => 'By Album'; + String get folderOrganizationNone => 'Keine Organisation'; @override - String get folderOrganizationByArtistAlbum => 'Artist/Album'; + String get folderOrganizationByArtist => 'Nach Künstler'; + + @override + String get folderOrganizationByAlbum => 'Nach Album'; + + @override + String get folderOrganizationByArtistAlbum => 'Künstler/Album'; @override String get folderOrganizationDescription => - 'Organize downloaded files into folders'; + 'Heruntergeladene Dateien in Ordner organisieren'; @override - String get folderOrganizationNoneSubtitle => 'All files in download folder'; + String get folderOrganizationNoneSubtitle => + 'Alle Dateien im Download-Verzeichnis'; @override String get folderOrganizationByArtistSubtitle => - 'Separate folder for each artist'; + 'Trenne Ordner nach Künstler'; @override - String get folderOrganizationByAlbumSubtitle => - 'Separate folder for each album'; + String get folderOrganizationByAlbumSubtitle => 'Trenne Ordner nach Album'; @override String get folderOrganizationByArtistAlbumSubtitle => - 'Nested folders for artist and album'; + 'Verschachtelte Ordner für Künstler und Album'; @override - String get updateAvailable => 'Update Available'; + String get updateAvailable => 'Update verfügbar'; @override String updateNewVersion(String version) { - return 'Version $version is available'; + return 'Version $version ist verfügbar'; } @override - String get updateDownload => 'Download'; + String get updateDownload => 'Herunterladen'; @override - String get updateLater => 'Later'; + String get updateLater => 'Später'; @override - String get updateChangelog => 'Changelog'; + String get updateChangelog => 'Änderungsverlauf'; @override - String get updateStartingDownload => 'Starting download...'; + String get updateStartingDownload => 'Download wird gestartet...'; @override - String get updateDownloadFailed => 'Download failed'; + String get updateDownloadFailed => 'Download fehlgeschlagen'; @override - String get updateFailedMessage => 'Failed to download update'; + String get updateFailedMessage => + 'Das Update konnte nicht heruntergeladen werden'; @override - String get updateNewVersionReady => 'A new version is ready'; + String get updateNewVersionReady => 'Eine neue Version ist verfügbar'; @override - String get updateCurrent => 'Current'; + String get updateCurrent => 'Aktuell'; @override - String get updateNew => 'New'; + String get updateNew => 'Neu'; @override - String get updateDownloading => 'Downloading...'; + String get updateDownloading => 'Wird heruntergeladen...'; @override - String get updateWhatsNew => 'What\'s New'; + String get updateWhatsNew => 'Was ist neu'; @override - String get updateDownloadInstall => 'Download & Install'; + String get updateDownloadInstall => 'Herunterladen & Installieren'; @override - String get updateDontRemind => 'Don\'t remind'; + String get updateDontRemind => 'Nicht erinnern'; @override - String get providerPriority => 'Provider Priority'; + String get providerPriority => 'Anbieterpriorität'; @override - String get providerPrioritySubtitle => 'Drag to reorder download providers'; + String get providerPrioritySubtitle => + 'Ziehen, um Download-Anbieter neu anzuordnen'; @override - String get providerPriorityTitle => 'Provider Priority'; + String get providerPriorityTitle => 'Anbieterpriorität'; @override String get providerPriorityDescription => - 'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.'; + 'Ziehen, um Download-Anbieter neu zu ordnen. Die App versucht Anbieter von oben nach unten, wenn Titel heruntergeladen werden.'; @override String get providerPriorityInfo => - 'If a track is not available on the first provider, the app will automatically try the next one.'; + 'Wenn kein Titel bei dem ersten Anbieter nicht verfügbar ist, wird die App automatisch den nächsten versuchen.'; @override - String get providerBuiltIn => 'Built-in'; + String get providerBuiltIn => 'Integriert'; @override - String get providerExtension => 'Extension'; + String get providerExtension => 'Erweiterung'; @override - String get metadataProviderPriority => 'Metadata Provider Priority'; + String get metadataProviderPriority => 'Priorität des Metadaten-Anbieters'; @override String get metadataProviderPrioritySubtitle => - 'Order used when fetching track metadata'; + 'Reihenfolge beim Abrufen von Titelmetadaten'; @override - String get metadataProviderPriorityTitle => 'Metadata Priority'; + String get metadataProviderPriorityTitle => 'Metadaten Priorität'; @override String get metadataProviderPriorityDescription => - 'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.'; + 'Ziehe, um Metadatenanbieter neu zu ordnen. Die App versucht Anbieter von oben nach unten, wenn sie nach Tracks suchen und Metadaten abrufen.'; @override String get metadataProviderPriorityInfo => - 'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.'; + 'Deezer hat keine Limits und wird als primäre empfohlen. Spotify kann nach vielen Anfragen begrenzen.'; @override - String get metadataNoRateLimits => 'No rate limits'; + String get metadataNoRateLimits => 'Keine Limitierungen'; @override - String get metadataMayRateLimit => 'May rate limit'; + String get metadataMayRateLimit => 'Hat vielleicht Limitierungen'; @override - String get logTitle => 'Logs'; + String get logTitle => 'Protokolle'; @override - String get logCopy => 'Copy Logs'; + String get logCopy => 'Protokolle kopieren'; @override - String get logClear => 'Clear Logs'; + String get logClear => 'Protokolle löschen'; @override - String get logShare => 'Share Logs'; + String get logShare => 'Protokolle teilen'; @override - String get logEmpty => 'No logs yet'; + String get logEmpty => 'Keine Protokolle bisher'; @override - String get logCopied => 'Logs copied to clipboard'; + String get logCopied => 'Protokolle in Zwischenablage kopiert'; @override - String get logSearchHint => 'Search logs...'; + String get logSearchHint => 'Protokolle durchsuchen...'; @override - String get logFilterLevel => 'Level'; + String get logFilterLevel => 'Stufe'; @override String get logFilterSection => 'Filter'; @override - String get logShareLogs => 'Share logs'; + String get logShareLogs => 'Protokolle teilen'; @override - String get logClearLogs => 'Clear logs'; + String get logClearLogs => 'Protokolle löschen'; @override - String get logClearLogsTitle => 'Clear Logs'; + String get logClearLogsTitle => 'Protokolle leeren'; @override - String get logClearLogsMessage => 'Are you sure you want to clear all logs?'; + String get logClearLogsMessage => + 'Bist du dir sicher, dass Sie alle Protokolle löschen möchtest?'; @override - String get logIspBlocking => 'ISP BLOCKING DETECTED'; + String get logIspBlocking => 'ISP BLOCKIERUNG ERKANNT'; @override - String get logRateLimited => 'RATE LIMITED'; + String get logRateLimited => 'LIMIT ERKANNT'; @override - String get logNetworkError => 'NETWORK ERROR'; + String get logNetworkError => 'NETZWERKFEHLER'; @override - String get logTrackNotFound => 'TRACK NOT FOUND'; + String get logTrackNotFound => 'TITEL NICHT GEFUNDEN'; @override - String get logFilterBySeverity => 'Filter logs by severity'; + String get logFilterBySeverity => 'Protokolle nach Schweregrad filtern'; @override - String get logNoLogsYet => 'No logs yet'; + String get logNoLogsYet => 'Keine Protokolle bisher'; @override - String get logNoLogsYetSubtitle => 'Logs will appear here as you use the app'; + String get logNoLogsYetSubtitle => + 'Protokolle werden hier angezeigt, während du die App benutzt'; @override - String get logIssueSummary => 'Issue Summary'; + String get logIssueSummary => 'Problemübersicht'; @override String get logIspBlockingDescription => - 'Your ISP may be blocking access to download services'; + 'Ihr ISP blockiert möglicherweise den Zugriff auf den Download Dienst'; @override String get logIspBlockingSuggestion => - 'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8'; + 'Versuche es einem VPN oder ändere DNS auf 1.1.1.1 oder 8.8.8.8'; @override - String get logRateLimitedDescription => 'Too many requests to the service'; + String get logRateLimitedDescription => 'Zu viele Anfragen an den Dienst'; @override String get logRateLimitedSuggestion => - 'Wait a few minutes before trying again'; + 'Warte ein paar Minuten, bevor du es erneut versuchst'; @override - String get logNetworkErrorDescription => 'Connection issues detected'; + String get logNetworkErrorDescription => 'Verbindungsprobleme erkannt'; @override - String get logNetworkErrorSuggestion => 'Check your internet connection'; + String get logNetworkErrorSuggestion => 'Überprüfe deine Internetverbindung'; @override String get logTrackNotFoundDescription => - 'Some tracks could not be found on download services'; + 'Einige Titel konnten auf Download-Diensten nicht gefunden werden'; @override String get logTrackNotFoundSuggestion => - 'The track may not be available in lossless quality'; + 'Der Titel ist möglicherweise nicht in verlustfreier Qualität verfügbar'; @override String logTotalErrors(int count) { - return 'Total errors: $count'; + return 'Gesamte Fehler: $count'; } @override String logAffected(String domains) { - return 'Affected: $domains'; + return 'Betroffen: $domains'; } @override String logEntriesFiltered(int count) { - return 'Entries ($count filtered)'; + return 'Einträge ($count gefiltert)'; } @override @@ -1441,31 +1454,31 @@ class AppLocalizationsDe extends AppLocalizations { @override String get credentialsDescription => - 'Enter your Client ID and Secret to use your own Spotify application quota.'; + 'Gebe deine Client-ID und Secret ein, um dein eigenes Spotify Anwendungs Limit zu haben.'; @override String get credentialsClientId => 'Client ID'; @override - String get credentialsClientIdHint => 'Paste Client ID'; + String get credentialsClientIdHint => 'Client ID einfügen'; @override String get credentialsClientSecret => 'Client Secret'; @override - String get credentialsClientSecretHint => 'Paste Client Secret'; + String get credentialsClientSecretHint => 'Client Secret einfügen'; @override - String get channelStable => 'Stable'; + String get channelStable => 'Stabil'; @override - String get channelPreview => 'Preview'; + String get channelPreview => 'Vorschau'; @override - String get sectionSearchSource => 'Search Source'; + String get sectionSearchSource => 'Suchquelle'; @override - String get sectionDownload => 'Download'; + String get sectionDownload => 'Herunterladen'; @override String get sectionPerformance => 'Performance'; @@ -1474,63 +1487,64 @@ class AppLocalizationsDe extends AppLocalizations { String get sectionApp => 'App'; @override - String get sectionData => 'Data'; + String get sectionData => 'Daten'; @override String get sectionDebug => 'Debug'; @override - String get sectionService => 'Service'; + String get sectionService => 'Anbieter'; @override - String get sectionAudioQuality => 'Audio Quality'; + String get sectionAudioQuality => 'Audioqualität'; @override - String get sectionFileSettings => 'File Settings'; + String get sectionFileSettings => 'Datei-Einstellungen'; @override String get sectionLyrics => 'Lyrics'; @override - String get lyricsMode => 'Lyrics Mode'; + String get lyricsMode => 'Lyrics-Modus'; @override String get lyricsModeDescription => - 'Choose how lyrics are saved with your downloads'; + 'Wähle wie Songtexte mit deinen Downloads gespeichert werden'; @override - String get lyricsModeEmbed => 'Embed in file'; + String get lyricsModeEmbed => 'In Datei einbinden'; @override - String get lyricsModeEmbedSubtitle => 'Lyrics stored inside FLAC metadata'; + String get lyricsModeEmbedSubtitle => 'Lyrics in FLAC Metadaten gespeichert'; @override - String get lyricsModeExternal => 'External .lrc file'; + String get lyricsModeExternal => 'Externe .lrc Datei'; @override String get lyricsModeExternalSubtitle => - 'Separate .lrc file for players like Samsung Music'; + 'Separate .lrc Datei für Player wie Samsung Music'; @override - String get lyricsModeBoth => 'Both'; + String get lyricsModeBoth => 'Beides'; @override - String get lyricsModeBothSubtitle => 'Embed and save .lrc file'; + String get lyricsModeBothSubtitle => + 'Lyrics einbinden und als .lrc speichern'; @override - String get sectionColor => 'Color'; + String get sectionColor => 'Farbe'; @override - String get sectionTheme => 'Theme'; + String get sectionTheme => 'Design'; @override String get sectionLayout => 'Layout'; @override - String get sectionLanguage => 'Language'; + String get sectionLanguage => 'Sprache'; @override - String get appearanceLanguage => 'App Language'; + String get appearanceLanguage => 'App Sprache'; @override String get appearanceLanguageSubtitle => 'Choose your preferred language'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f8c318ff..2f3eefea 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1182,6 +1182,13 @@ class AppLocalizationsEn extends AppLocalizations { return '$artist - $title'; } + @override + String get filenameShowAdvancedTags => 'Show advanced tags'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; + @override String get folderOrganization => 'Folder Organization'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index cfd26679..75613f96 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1182,6 +1182,13 @@ class AppLocalizationsEs extends AppLocalizations { return '$artist - $title'; } + @override + String get filenameShowAdvancedTags => 'Show advanced tags'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; + @override String get folderOrganization => 'Folder Organization'; @@ -2951,6 +2958,9 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get navHome => 'Inicio'; + @override + String get navLibrary => 'Biblioteca'; + @override String get navHistory => 'Historial'; @@ -3044,6 +3054,9 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get historyNoSinglesSubtitle => 'Las descargas de una sola pista aparecerán aquí'; + @override + String get historySearchHint => 'Buscar en historial...'; + @override String get settingsTitle => 'Ajustes'; @@ -3278,6 +3291,10 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get optionsSpotifyWarning => 'Spotify requiere tus propias credenciales API. Obténgalas gratis de developer.spotify.com'; + @override + String get optionsSpotifyDeprecationWarning => + 'Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.'; + @override String get extensionsTitle => 'Extensiones'; @@ -3344,6 +3361,9 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get aboutLogoArtist => '¡El talentoso artista que creó nuestro hermoso logo!'; + @override + String get aboutTranslators => 'Traductores'; + @override String get aboutSpecialThanks => 'Agradecimientos especiales'; @@ -3370,6 +3390,21 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get aboutFeatureRequestSubtitle => 'Sugerir nuevas funciones para la aplicación'; + @override + String get aboutTelegramChannel => 'Canal de Telegram'; + + @override + String get aboutTelegramChannelSubtitle => 'Anuncios y actualizaciones'; + + @override + String get aboutTelegramChat => 'Comunidad de Telegram'; + + @override + String get aboutTelegramChatSubtitle => 'Chatear con otros usuarios'; + + @override + String get aboutSocial => 'Redes sociales'; + @override String get aboutSupport => 'Soporte'; @@ -3387,6 +3422,10 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get aboutSachinsenalDesc => 'El creador original del proyecto Hi-Fi. ¡La base de la integración de Tidal!'; + @override + String get aboutSjdonadoDesc => + 'Creador de I No tengo Spotify (IDHS). ¡La solución de enlace de reserva que salva el día!'; + @override String get aboutDoubleDouble => 'DoubleDouble'; @@ -3401,6 +3440,13 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get aboutDabMusicDesc => 'La mejor API de streaming de Qobuz. ¡Las descargas de Hi-Res no serían posibles sin esto!'; + @override + String get aboutSpotiSaver => 'SpotiSaver'; + + @override + String get aboutSpotiSaverDesc => + 'Tidal de transmisión Hi-Res FLAC. ¡Una pieza clave del rompecabezas sin pérdida!'; + @override String get aboutAppDescription => 'Descarga pistas de Spotify con calidad sin pérdida de Tidal, Qobuz y Amazon Music.'; @@ -3598,6 +3644,10 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get setupIosEmptyFolderWarning => 'Limitación de iOS: No se pueden seleccionar carpetas vacías. Elige una carpeta con al menos un archivo.'; + @override + String get setupIcloudNotSupported => + 'iCloud Drive no es compatible. Utilice la carpeta Documentos de la aplicación.'; + @override String get setupDownloadInFlac => 'Descargar pistas de Spotify en FLAC'; @@ -3836,6 +3886,11 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { return 'Se han encontrado pistas $count en CSV. ¿Añadirlas para descargar la cola?'; } + @override + String csvImportTracks(int count) { + return '$count pistas de CSV'; + } + @override String snackbarAddedToQueue(String trackName) { return 'Añadido \"$trackName\" a la cola'; @@ -3851,6 +3906,11 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { return '\"$trackName\" ya descargado'; } + @override + String snackbarAlreadyInLibrary(String trackName) { + return '\"$trackName\" ya existe en tu biblioteca'; + } + @override String get snackbarHistoryCleared => 'Historial borrado'; @@ -4374,6 +4434,36 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get sectionFileSettings => 'Ajustes del archivo'; + @override + String get sectionLyrics => 'Letras'; + + @override + String get lyricsMode => 'Modo Letras'; + + @override + String get lyricsModeDescription => + 'Elige cómo se guardan las letras de tus descargas'; + + @override + String get lyricsModeEmbed => 'Insertar en archivo'; + + @override + String get lyricsModeEmbedSubtitle => + 'Letras almacenadas en los metadatos FLAC'; + + @override + String get lyricsModeExternal => 'Archivo .lrc externo'; + + @override + String get lyricsModeExternalSubtitle => + 'Archivo .lrc separado para reproductores como Samsung Music'; + + @override + String get lyricsModeBoth => 'Ambos'; + + @override + String get lyricsModeBothSubtitle => 'Insertar y guardar archivo .lrc'; + @override String get sectionColor => 'Colores'; @@ -4490,6 +4580,15 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get trackReleaseDate => 'Fecha de lanzamiento'; + @override + String get trackGenre => 'Género'; + + @override + String get trackLabel => 'Etiqueta'; + + @override + String get trackCopyright => 'Derechos de autor'; + @override String get trackDownloaded => 'Descargado'; @@ -4506,6 +4605,15 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get trackLyricsLoadFailed => 'Error al cargar la letra'; + @override + String get trackEmbedLyrics => 'Incrustar Letras'; + + @override + String get trackLyricsEmbedded => 'Letra incrustada con éxito'; + + @override + String get trackInstrumental => 'Pista intrumental'; + @override String get trackCopiedToClipboard => 'Copiado al portapapeles'; @@ -4737,10 +4845,47 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get qualityHiResFlacMaxSubtitle => '24 bits / hasta 192kHz'; + @override + String get qualityLossy => 'Con pérdidas'; + + @override + String get qualityLossyMp3Subtitle => 'MP3 320kbps (convertido desde FLAC)'; + + @override + String get qualityLossyOpusSubtitle => 'Opus 128kbps (convertido de FLAC)'; + + @override + String get enableLossyOption => 'Habilitar opción con pérdida'; + + @override + String get enableLossyOptionSubtitleOn => + 'La opción de calidad con pérdida está disponible'; + + @override + String get enableLossyOptionSubtitleOff => + 'Descargas FLAC y luego se convierten en formato con pérdida'; + + @override + String get lossyFormat => 'Formato con Perdido'; + + @override + String get lossyFormatDescription => + 'Elegir el formato con pérdida para la conversión'; + + @override + String get lossyFormatMp3Subtitle => '320kbps, mejor compatibilidad'; + + @override + String get lossyFormatOpusSubtitle => '128kbps, mejor calidad a menor tamaño'; + @override String get qualityNote => 'La calidad real depende de la disponibilidad de la pista del servicio'; + @override + String get youtubeQualityNote => + 'YouTube provides lossy audio only. Not part of lossless fallback.'; + @override String get downloadAskBeforeDownload => 'Preguntar antes de descargar'; @@ -4753,6 +4898,28 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get downloadAlbumFolderStructure => 'Estructura de carpeta del álbum'; + @override + String get downloadUseAlbumArtistForFolders => 'Use Album Artist for folders'; + + @override + String get downloadUseAlbumArtistForFoldersAlbumSubtitle => + 'Artist folders use Album Artist when available'; + + @override + String get downloadUseAlbumArtistForFoldersTrackSubtitle => + 'Artist folders use Track Artist only'; + + @override + String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders'; + + @override + String get downloadUsePrimaryArtistOnlyEnabled => + 'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)'; + + @override + String get downloadUsePrimaryArtistOnlyDisabled => + 'Full artist string used for folder name'; + @override String get downloadSaveFormat => 'Guardar Formato'; @@ -4834,6 +5001,39 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String get queueClearAllMessage => '¿Estás seguro de que quieres borrar todas las descargas?'; + @override + String get queueExportFailed => 'Exportar'; + + @override + String get queueExportFailedSuccess => + 'Descarga fallida exportada al archivo TXT'; + + @override + String get queueExportFailedClear => 'Limpieza Fallida'; + + @override + String get queueExportFailedError => 'Error al exportar descargas'; + + @override + String get settingsAutoExportFailed => 'Autoexportar descargas fallidas'; + + @override + String get settingsAutoExportFailedSubtitle => + 'Guardar descargas fallidas en el archivo TXT automáticamente'; + + @override + String get settingsDownloadNetwork => 'Red de descarga'; + + @override + String get settingsDownloadNetworkAny => 'WiFi + Datos móviles'; + + @override + String get settingsDownloadNetworkWifiOnly => 'Iniciar solo por Wifi'; + + @override + String get settingsDownloadNetworkSubtitle => + 'Elegir qué red usar para descargas. Cuando se establece en WiFi solamente, las descargas se detendrán en los datos móviles.'; + @override String get queueEmpty => 'No hay descargas en cola'; @@ -4884,6 +5084,13 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get albumFolderYearAlbumSubtitle => 'Álbumes/[2005] Nombre del Álbum/'; + @override + String get albumFolderArtistAlbumSingles => 'Artista / Álbum + Pistas'; + + @override + String get albumFolderArtistAlbumSinglesSubtitle => + 'Artista/Álbum/ y Artista/pistas/'; + @override String get downloadedAlbumDeleteSelected => 'Borrar Seleccionados'; @@ -4931,6 +5138,11 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get downloadedAlbumSelectToDelete => 'Seleccionar pistas a eliminar'; + @override + String downloadedAlbumDiscHeader(int discNumber) { + return 'Disco $discNumber'; + } + @override String get utilityFunctions => 'Funciones de utilidad'; @@ -4946,6 +5158,12 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String get recentTypePlaylist => 'Lista de reproducción'; + @override + String get recentEmpty => 'No recent items yet'; + + @override + String get recentShowAllDownloads => 'Show All Downloads'; + @override String recentPlaylistInfo(String name) { return 'Lista de reproducción: $name'; @@ -4955,4 +5173,739 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { String errorGeneric(String message) { return 'Error: $message'; } + + @override + String get discographyDownload => 'Descargar Discografía'; + + @override + String get discographyDownloadAll => 'Descargar Todo'; + + @override + String discographyDownloadAllSubtitle(int count, int albumCount) { + return '$count pistas de $albumCount lanzamientos'; + } + + @override + String get discographyAlbumsOnly => 'Sólo álbumes'; + + @override + String discographyAlbumsOnlySubtitle(int count, int albumCount) { + return '$count pistas de $albumCount álbumes'; + } + + @override + String get discographySinglesOnly => 'Solo sencillos & EPs '; + + @override + String discographySinglesOnlySubtitle(int count, int albumCount) { + return '$count tracks from $albumCount singles'; + } + + @override + String get discographySelectAlbums => 'Select Albums...'; + + @override + String get discographySelectAlbumsSubtitle => + 'Choose specific albums or singles'; + + @override + String get discographyFetchingTracks => 'Fetching tracks...'; + + @override + String discographyFetchingAlbum(int current, int total) { + return 'Fetching $current of $total...'; + } + + @override + String discographySelectedCount(int count) { + return '$count selected'; + } + + @override + String get discographyDownloadSelected => 'Download Selected'; + + @override + String discographyAddedToQueue(int count) { + return 'Added $count tracks to queue'; + } + + @override + String discographySkippedDownloaded(int added, int skipped) { + return '$added added, $skipped already downloaded'; + } + + @override + String get discographyNoAlbums => 'No albums available'; + + @override + String get discographyFailedToFetch => 'Failed to fetch some albums'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; + + @override + String get settingsLocalLibrary => 'Local Library'; + + @override + String get settingsLocalLibrarySubtitle => 'Scan music & detect duplicates'; + + @override + String get settingsCache => 'Storage & Cache'; + + @override + String get settingsCacheSubtitle => 'View size and clear cached data'; + + @override + String get libraryTitle => 'Local Library'; + + @override + String get libraryStatus => 'Library Status'; + + @override + String get libraryScanSettings => 'Scan Settings'; + + @override + String get libraryEnableLocalLibrary => 'Enable Local Library'; + + @override + String get libraryEnableLocalLibrarySubtitle => + 'Scan and track your existing music'; + + @override + String get libraryFolder => 'Library Folder'; + + @override + String get libraryFolderHint => 'Tap to select folder'; + + @override + String get libraryShowDuplicateIndicator => 'Show Duplicate Indicator'; + + @override + String get libraryShowDuplicateIndicatorSubtitle => + 'Show when searching for existing tracks'; + + @override + String get libraryActions => 'Actions'; + + @override + String get libraryScan => 'Scan Library'; + + @override + String get libraryScanSubtitle => 'Scan for audio files'; + + @override + String get libraryScanSelectFolderFirst => 'Select a folder first'; + + @override + String get libraryCleanupMissingFiles => 'Cleanup Missing Files'; + + @override + String get libraryCleanupMissingFilesSubtitle => + 'Remove entries for files that no longer exist'; + + @override + String get libraryClear => 'Clear Library'; + + @override + String get libraryClearSubtitle => 'Remove all scanned tracks'; + + @override + String get libraryClearConfirmTitle => 'Clear Library'; + + @override + String get libraryClearConfirmMessage => + 'This will remove all scanned tracks from your library. Your actual music files will not be deleted.'; + + @override + String get libraryAbout => 'About Local Library'; + + @override + String get libraryAboutDescription => + 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.'; + + @override + String libraryTracksCount(int count) { + return '$count tracks'; + } + + @override + String libraryLastScanned(String time) { + return 'Last scanned: $time'; + } + + @override + String get libraryLastScannedNever => 'Never'; + + @override + String get libraryScanning => 'Scanning...'; + + @override + String libraryScanProgress(String progress, int total) { + return '$progress% of $total files'; + } + + @override + String get libraryInLibrary => 'In Library'; + + @override + String libraryRemovedMissingFiles(int count) { + return 'Removed $count missing files from library'; + } + + @override + String get libraryCleared => 'Library cleared'; + + @override + String get libraryStorageAccessRequired => 'Storage Access Required'; + + @override + String get libraryStorageAccessMessage => + 'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.'; + + @override + String get libraryFolderNotExist => 'Selected folder does not exist'; + + @override + String get librarySourceDownloaded => 'Downloaded'; + + @override + String get librarySourceLocal => 'Local'; + + @override + String get libraryFilterAll => 'All'; + + @override + String get libraryFilterDownloaded => 'Downloaded'; + + @override + String get libraryFilterLocal => 'Local'; + + @override + String get libraryFilterTitle => 'Filters'; + + @override + String get libraryFilterReset => 'Reset'; + + @override + String get libraryFilterApply => 'Apply'; + + @override + String get libraryFilterSource => 'Source'; + + @override + String get libraryFilterQuality => 'Quality'; + + @override + String get libraryFilterQualityHiRes => 'Hi-Res (24bit)'; + + @override + String get libraryFilterQualityCD => 'CD (16bit)'; + + @override + String get libraryFilterQualityLossy => 'Lossy'; + + @override + String get libraryFilterFormat => 'Format'; + + @override + String get libraryFilterDate => 'Date Added'; + + @override + String get libraryFilterDateToday => 'Today'; + + @override + String get libraryFilterDateWeek => 'This Week'; + + @override + String get libraryFilterDateMonth => 'This Month'; + + @override + String get libraryFilterDateYear => 'This Year'; + + @override + String get libraryFilterSort => 'Sort'; + + @override + String get libraryFilterSortLatest => 'Latest'; + + @override + String get libraryFilterSortOldest => 'Oldest'; + + @override + String libraryFilterActive(int count) { + return '$count filter(s) active'; + } + + @override + String get timeJustNow => 'Just now'; + + @override + String timeMinutesAgo(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count minutes ago', + one: '1 minute ago', + ); + return '$_temp0'; + } + + @override + String timeHoursAgo(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count hours ago', + one: '1 hour ago', + ); + return '$_temp0'; + } + + @override + String get storageSwitchTitle => 'Switch Storage Mode'; + + @override + String get storageSwitchToSafTitle => 'Switch to SAF Storage?'; + + @override + String get storageSwitchToAppTitle => 'Switch to App Storage?'; + + @override + String get storageSwitchToSafMessage => + 'Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.'; + + @override + String get storageSwitchToAppMessage => + 'Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.'; + + @override + String get storageSwitchExistingDownloads => 'Existing Downloads'; + + @override + String storageSwitchExistingDownloadsInfo(int count, String mode) { + return '$count tracks in $mode storage'; + } + + @override + String get storageSwitchNewDownloads => 'New Downloads'; + + @override + String storageSwitchNewDownloadsLocation(String location) { + return 'Will be saved to: $location'; + } + + @override + String get storageSwitchContinue => 'Continue'; + + @override + String get storageSwitchSelectFolder => 'Select SAF Folder'; + + @override + String get storageAppStorage => 'App Storage'; + + @override + String get storageSafStorage => 'SAF Storage'; + + @override + String storageModeBadge(String mode) { + return 'Storage: $mode'; + } + + @override + String get storageStatsTitle => 'Storage Statistics'; + + @override + String storageStatsAppCount(int count) { + return '$count tracks in App Storage'; + } + + @override + String storageStatsSafCount(int count) { + return '$count tracks in SAF Storage'; + } + + @override + String get storageModeInfo => 'Your files are stored in multiple locations'; + + @override + String get tutorialWelcomeTitle => 'Welcome to SpotiFLAC!'; + + @override + String get tutorialWelcomeDesc => + 'Let\'s learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.'; + + @override + String get tutorialWelcomeTip1 => + 'Download music from Spotify, Deezer, or paste any supported URL'; + + @override + String get tutorialWelcomeTip2 => + 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + + @override + String get tutorialWelcomeTip3 => + 'Automatic metadata, cover art, and lyrics embedding'; + + @override + String get tutorialSearchTitle => 'Finding Music'; + + @override + String get tutorialSearchDesc => + 'There are two easy ways to find music you want to download.'; + + @override + String get tutorialSearchTip1 => + 'Paste a Spotify or Deezer URL directly in the search box'; + + @override + String get tutorialSearchTip2 => + 'Or type the song name, artist, or album to search'; + + @override + String get tutorialSearchTip3 => + 'Supports tracks, albums, playlists, and artist pages'; + + @override + String get tutorialDownloadTitle => 'Downloading Music'; + + @override + String get tutorialDownloadDesc => + 'Downloading music is simple and fast. Here\'s how it works.'; + + @override + String get tutorialDownloadTip1 => + 'Tap the download button next to any track to start downloading'; + + @override + String get tutorialDownloadTip2 => + 'Choose your preferred quality (FLAC, Hi-Res, or MP3)'; + + @override + String get tutorialDownloadTip3 => + 'Download entire albums or playlists with one tap'; + + @override + String get tutorialLibraryTitle => 'Your Library'; + + @override + String get tutorialLibraryDesc => + 'All your downloaded music is organized in the Library tab.'; + + @override + String get tutorialLibraryTip1 => + 'View download progress and queue in the Library tab'; + + @override + String get tutorialLibraryTip2 => + 'Tap any track to play it with your music player'; + + @override + String get tutorialLibraryTip3 => + 'Switch between list and grid view for better browsing'; + + @override + String get tutorialExtensionsTitle => 'Extensions'; + + @override + String get tutorialExtensionsDesc => + 'Extend the app\'s capabilities with community extensions.'; + + @override + String get tutorialExtensionsTip1 => + 'Browse the Store tab to discover useful extensions'; + + @override + String get tutorialExtensionsTip2 => + 'Add new download providers or search sources'; + + @override + String get tutorialExtensionsTip3 => + 'Get lyrics, enhanced metadata, and more features'; + + @override + String get tutorialSettingsTitle => 'Customize Your Experience'; + + @override + String get tutorialSettingsDesc => + 'Personalize the app in Settings to match your preferences.'; + + @override + String get tutorialSettingsTip1 => + 'Change download location and folder organization'; + + @override + String get tutorialSettingsTip2 => + 'Set default audio quality and format preferences'; + + @override + String get tutorialSettingsTip3 => 'Customize app theme and appearance'; + + @override + String get tutorialReadyMessage => + 'You\'re all set! Start downloading your favorite music now.'; + + @override + String get tutorialExample => 'EXAMPLE'; + + @override + String get libraryForceFullScan => 'Force Full Scan'; + + @override + String get libraryForceFullScanSubtitle => 'Rescan all files, ignoring cache'; + + @override + String get cleanupOrphanedDownloads => 'Cleanup Orphaned Downloads'; + + @override + String get cleanupOrphanedDownloadsSubtitle => + 'Remove history entries for files that no longer exist'; + + @override + String cleanupOrphanedDownloadsResult(int count) { + return 'Removed $count orphaned entries from history'; + } + + @override + String get cleanupOrphanedDownloadsNone => 'No orphaned entries found'; + + @override + String get cacheTitle => 'Storage & Cache'; + + @override + String get cacheSummaryTitle => 'Cache overview'; + + @override + String get cacheSummarySubtitle => + 'Clearing cache will not remove downloaded music files.'; + + @override + String cacheEstimatedTotal(String size) { + return 'Estimated cache usage: $size'; + } + + @override + String get cacheSectionStorage => 'Cached Data'; + + @override + String get cacheSectionMaintenance => 'Maintenance'; + + @override + String get cacheAppDirectory => 'App cache directory'; + + @override + String get cacheAppDirectoryDesc => + 'HTTP responses, WebView data, and other temporary app data.'; + + @override + String get cacheTempDirectory => 'Temporary directory'; + + @override + String get cacheTempDirectoryDesc => + 'Temporary files from downloads and audio conversion.'; + + @override + String get cacheCoverImage => 'Cover image cache'; + + @override + String get cacheCoverImageDesc => + 'Downloaded album and track cover art. Will re-download when viewed.'; + + @override + String get cacheLibraryCover => 'Library cover cache'; + + @override + String get cacheLibraryCoverDesc => + 'Cover art extracted from local music files. Will re-extract on next scan.'; + + @override + String get cacheExploreFeed => 'Explore feed cache'; + + @override + String get cacheExploreFeedDesc => + 'Explore tab content (new releases, trending). Will refresh on next visit.'; + + @override + String get cacheTrackLookup => 'Track lookup cache'; + + @override + String get cacheTrackLookupDesc => + 'Spotify/Deezer track ID lookups. Clearing may slow next few searches.'; + + @override + String get cacheCleanupUnusedDesc => + 'Remove orphaned download history and library entries for missing files.'; + + @override + String get cacheNoData => 'No cached data'; + + @override + String cacheSizeWithFiles(String size, int count) { + return '$size in $count files'; + } + + @override + String cacheSizeOnly(String size) { + return '$size'; + } + + @override + String cacheEntries(int count) { + return '$count entries'; + } + + @override + String cacheClearSuccess(String target) { + return 'Cleared: $target'; + } + + @override + String get cacheClearConfirmTitle => 'Clear cache?'; + + @override + String cacheClearConfirmMessage(String target) { + return 'This will clear cached data for $target. Downloaded music files will not be deleted.'; + } + + @override + String get cacheClearAllConfirmTitle => 'Clear all cache?'; + + @override + String get cacheClearAllConfirmMessage => + 'This will clear all cache categories on this page. Downloaded music files will not be deleted.'; + + @override + String get cacheClearAll => 'Clear all cache'; + + @override + String get cacheCleanupUnused => 'Cleanup unused data'; + + @override + String get cacheCleanupUnusedSubtitle => + 'Remove orphaned download history and missing library entries'; + + @override + String cacheCleanupResult(int downloadCount, int libraryCount) { + return 'Cleanup completed: $downloadCount orphaned downloads, $libraryCount missing library entries'; + } + + @override + String get cacheRefreshStats => 'Refresh stats'; + + @override + String get trackSaveCoverArt => 'Save Cover Art'; + + @override + String get trackSaveCoverArtSubtitle => 'Save album art as .jpg file'; + + @override + String get trackSaveLyrics => 'Save Lyrics (.lrc)'; + + @override + String get trackSaveLyricsSubtitle => 'Fetch and save lyrics as .lrc file'; + + @override + String get trackSaveLyricsProgress => 'Saving lyrics...'; + + @override + String get trackReEnrich => 'Re-enrich Metadata'; + + @override + String get trackReEnrichSubtitle => + 'Re-embed metadata without re-downloading'; + + @override + String get trackReEnrichOnlineSubtitle => + 'Search metadata online and embed into file'; + + @override + String get trackEditMetadata => 'Edit Metadata'; + + @override + String trackCoverSaved(String fileName) { + return 'Cover art saved to $fileName'; + } + + @override + String get trackCoverNoSource => 'No cover art source available'; + + @override + String trackLyricsSaved(String fileName) { + return 'Lyrics saved to $fileName'; + } + + @override + String get trackReEnrichProgress => 'Re-enriching metadata...'; + + @override + String get trackReEnrichSearching => 'Searching metadata online...'; + + @override + String get trackReEnrichSuccess => 'Metadata re-enriched successfully'; + + @override + String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed'; + + @override + String trackSaveFailed(String error) { + return 'Failed: $error'; + } + + @override + String get trackConvertFormat => 'Convert Format'; + + @override + String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + + @override + String get trackConvertTitle => 'Convert Audio'; + + @override + String get trackConvertTargetFormat => 'Target Format'; + + @override + String get trackConvertBitrate => 'Bitrate'; + + @override + String get trackConvertConfirmTitle => 'Confirm Conversion'; + + @override + String trackConvertConfirmMessage( + String sourceFormat, + String targetFormat, + String bitrate, + ) { + return 'Convert from $sourceFormat to $targetFormat at $bitrate?\n\nThe original file will be deleted after conversion.'; + } + + @override + String get trackConvertConverting => 'Converting audio...'; + + @override + String trackConvertSuccess(String format) { + return 'Converted to $format successfully'; + } + + @override + String get trackConvertFailed => 'Conversion failed'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index d07bc20f..4e381dee 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -13,62 +13,62 @@ class AppLocalizationsFr extends AppLocalizations { @override String get appDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + 'Téléchargez des pistes Spotify en qualité sans perte de Tidal, Qobuz et Amazon Music.'; @override - String get navHome => 'Home'; + String get navHome => 'Accueil'; @override - String get navLibrary => 'Library'; + String get navLibrary => 'Bibliothèques'; @override - String get navHistory => 'History'; + String get navHistory => 'Historique'; @override - String get navSettings => 'Settings'; + String get navSettings => 'Paramètres'; @override - String get navStore => 'Store'; + String get navStore => 'Magasin'; @override - String get homeTitle => 'Home'; + String get homeTitle => 'Accueil'; @override - String get homeSearchHint => 'Paste Spotify URL or search...'; + String get homeSearchHint => 'Coller l\'URL Spotify ou rechercher...'; @override String homeSearchHintExtension(String extensionName) { - return 'Search with $extensionName...'; + return 'Rechercher avec $extensionName...'; } @override - String get homeSubtitle => 'Paste a Spotify link or search by name'; + String get homeSubtitle => 'Coller un lien Spotify ou rechercher par nom'; @override - String get homeSupports => 'Supports: Track, Album, Playlist, Artist URLs'; + String get homeSupports => 'Supports: Piste, Album, Playlist, Artiste URLs'; @override - String get homeRecent => 'Recent'; + String get homeRecent => 'Récent'; @override - String get historyTitle => 'History'; + String get historyTitle => 'Historique'; @override String historyDownloading(int count) { - return 'Downloading ($count)'; + return 'Téléchargement ($count)'; } @override - String get historyDownloaded => 'Downloaded'; + String get historyDownloaded => 'Téléchargé'; @override - String get historyFilterAll => 'All'; + String get historyFilterAll => 'Tous'; @override String get historyFilterAlbums => 'Albums'; @override - String get historyFilterSingles => 'Singles'; + String get historyFilterSingles => 'Titres'; @override String historyTracksCount(int count) { @@ -93,36 +93,37 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get historyNoDownloads => 'No download history'; + String get historyNoDownloads => 'Pas d\'historique de téléchargement'; @override - String get historyNoDownloadsSubtitle => 'Downloaded tracks will appear here'; + String get historyNoDownloadsSubtitle => + 'Les pistes téléchargées apparaîtront ici'; @override - String get historyNoAlbums => 'No album downloads'; + String get historyNoAlbums => 'Pas de téléchargement d\'album'; @override String get historyNoAlbumsSubtitle => - 'Download multiple tracks from an album to see them here'; + 'Téléchargez plusieurs titres d\'un album pour les voir ici'; @override - String get historyNoSingles => 'No single downloads'; + String get historyNoSingles => 'Pas de téléchargements uniques'; @override String get historyNoSinglesSubtitle => - 'Single track downloads will appear here'; + 'Les téléchargements de pistes uniques apparaîtront ici'; @override - String get historySearchHint => 'Search history...'; + String get historySearchHint => 'Historique de recherche...'; @override - String get settingsTitle => 'Settings'; + String get settingsTitle => 'Paramètres'; @override - String get settingsDownload => 'Download'; + String get settingsDownload => 'Télécharger'; @override - String get settingsAppearance => 'Appearance'; + String get settingsAppearance => 'Apparence'; @override String get settingsOptions => 'Options'; @@ -131,51 +132,54 @@ class AppLocalizationsFr extends AppLocalizations { String get settingsExtensions => 'Extensions'; @override - String get settingsAbout => 'About'; + String get settingsAbout => 'À propos'; @override - String get downloadTitle => 'Download'; + String get downloadTitle => 'Télécharger'; @override - String get downloadLocation => 'Download Location'; + String get downloadLocation => 'Télécharger Localisation'; @override - String get downloadLocationSubtitle => 'Choose where to save files'; + String get downloadLocationSubtitle => + 'Choisissez où enregistrer des fichiers'; @override - String get downloadLocationDefault => 'Default location'; + String get downloadLocationDefault => 'Localisation par défaut'; @override - String get downloadDefaultService => 'Default Service'; + String get downloadDefaultService => 'Service par défaut'; @override - String get downloadDefaultServiceSubtitle => 'Service used for downloads'; + String get downloadDefaultServiceSubtitle => + 'Service utilisé pour les téléchargements'; @override - String get downloadDefaultQuality => 'Default Quality'; + String get downloadDefaultQuality => 'Qualité par défaut'; @override - String get downloadAskQuality => 'Ask Quality Before Download'; + String get downloadAskQuality => + 'Demandez La Qualité Avant Le Téléchargement'; @override String get downloadAskQualitySubtitle => - 'Show quality picker for each download'; + 'Afficher le sélecteur de qualité pour chaque téléchargement'; @override - String get downloadFilenameFormat => 'Filename Format'; + String get downloadFilenameFormat => 'Nom du fichier'; @override - String get downloadFolderOrganization => 'Folder Organization'; + String get downloadFolderOrganization => 'Organisation du dossier'; @override - String get downloadSeparateSingles => 'Separate Singles'; + String get downloadSeparateSingles => 'Titres séparés'; @override String get downloadSeparateSinglesSubtitle => - 'Put single tracks in a separate folder'; + 'Mettre des pistes uniques dans un dossier séparé'; @override - String get qualityBest => 'Best Available'; + String get qualityBest => 'Meilleur Disponible'; @override String get qualityFlac => 'FLAC'; @@ -187,69 +191,71 @@ class AppLocalizationsFr extends AppLocalizations { String get quality128 => '128 kbps'; @override - String get appearanceTitle => 'Appearance'; + String get appearanceTitle => 'Apparence'; @override - String get appearanceTheme => 'Theme'; + String get appearanceTheme => 'Thème'; @override - String get appearanceThemeSystem => 'System'; + String get appearanceThemeSystem => 'Système'; @override - String get appearanceThemeLight => 'Light'; + String get appearanceThemeLight => 'Clair'; @override - String get appearanceThemeDark => 'Dark'; + String get appearanceThemeDark => 'Sombre'; @override - String get appearanceDynamicColor => 'Dynamic Color'; + String get appearanceDynamicColor => 'Couleur dynamique'; @override - String get appearanceDynamicColorSubtitle => 'Use colors from your wallpaper'; + String get appearanceDynamicColorSubtitle => + 'Utilisez les couleurs de votre fond d\'écran'; @override - String get appearanceAccentColor => 'Accent Color'; + String get appearanceAccentColor => 'Couleur d\'accent'; @override - String get appearanceHistoryView => 'History View'; + String get appearanceHistoryView => 'Historique Vue'; @override - String get appearanceHistoryViewList => 'List'; + String get appearanceHistoryViewList => ''; @override - String get appearanceHistoryViewGrid => 'Grid'; + String get appearanceHistoryViewGrid => 'Grille'; @override String get optionsTitle => 'Options'; @override - String get optionsSearchSource => 'Search Source'; + String get optionsSearchSource => 'Recherche Source'; @override - String get optionsPrimaryProvider => 'Primary Provider'; + String get optionsPrimaryProvider => 'Fournisseur principal'; @override String get optionsPrimaryProviderSubtitle => - 'Service used when searching by track name.'; + 'Service utilisé lors de la recherche par nom de piste.'; @override String optionsUsingExtension(String extensionName) { - return 'Using extension: $extensionName'; + return 'Utilisation de l\'extension: $extensionName'; } @override String get optionsSwitchBack => - 'Tap Deezer or Spotify to switch back from extension'; + 'Appuyez sur Deezer ou Spotify pour revenir à l\'extension'; @override String get optionsAutoFallback => 'Auto Fallback'; @override String get optionsAutoFallbackSubtitle => - 'Try other services if download fails'; + 'Essayez d\'autres services si le téléchargement échoue'; @override - String get optionsUseExtensionProviders => 'Use Extension Providers'; + String get optionsUseExtensionProviders => + 'Utiliser des fournisseurs d\'extension'; @override String get optionsUseExtensionProvidersOn => 'Extensions will be tried first'; @@ -376,16 +382,16 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get extensionsUninstall => 'Uninstall'; + String get extensionsUninstall => 'Désinstaller'; @override - String get extensionsSetAsSearch => 'Set as Search Provider'; + String get extensionsSetAsSearch => 'Défini comme fournisseur de recherche'; @override - String get storeTitle => 'Extension Store'; + String get storeTitle => 'Magasin d\'extension'; @override - String get storeSearch => 'Search extensions...'; + String get storeSearch => 'Recherche d\'extensions...'; @override String get storeInstall => 'Install'; @@ -567,7 +573,7 @@ class AppLocalizationsFr extends AppLocalizations { String get trackMetadataDuration => 'Duration'; @override - String get trackMetadataQuality => 'Quality'; + String get trackMetadataQuality => ''; @override String get trackMetadataPath => 'File Path'; @@ -579,38 +585,38 @@ class AppLocalizationsFr extends AppLocalizations { String get trackMetadataService => 'Service'; @override - String get trackMetadataPlay => 'Play'; + String get trackMetadataPlay => 'Jouer'; @override - String get trackMetadataShare => 'Share'; + String get trackMetadataShare => 'Partager'; @override - String get trackMetadataDelete => 'Delete'; + String get trackMetadataDelete => 'Supprimer'; @override - String get trackMetadataRedownload => 'Re-download'; + String get trackMetadataRedownload => 'Re-télécharger'; @override - String get trackMetadataOpenFolder => 'Open Folder'; + String get trackMetadataOpenFolder => 'Dossier ouvert'; @override - String get setupTitle => 'Welcome to SpotiFLAC'; + String get setupTitle => 'Bienvenue chez SpotiFLAC'; @override - String get setupSubtitle => 'Let\'s get you started'; + String get setupSubtitle => 'On va commencer'; @override - String get setupStoragePermission => 'Storage Permission'; + String get setupStoragePermission => 'Permission de stockage'; @override String get setupStoragePermissionSubtitle => - 'Required to save downloaded files'; + 'Requis pour enregistrer les fichiers téléchargés'; @override - String get setupStoragePermissionGranted => 'Permission granted'; + String get setupStoragePermissionGranted => 'Permission accordée'; @override - String get setupStoragePermissionDenied => 'Permission denied'; + String get setupStoragePermissionDenied => 'Permission refusée'; @override String get setupGrantPermission => 'Grant Permission'; @@ -735,14 +741,14 @@ class AppLocalizationsFr extends AppLocalizations { 'Get notified when downloads complete or require attention.'; @override - String get setupFolderSelected => 'Download Folder Selected!'; + String get setupFolderSelected => 'Dossier de téléchargement sélectionné!'; @override - String get setupFolderChoose => 'Choose Download Folder'; + String get setupFolderChoose => 'Choisissez le dossier pour télécharger'; @override String get setupFolderDescription => - 'Select a folder where your downloaded music will be saved.'; + 'Sélectionnez un dossier dans lequel votre musique téléchargée sera enregistrée.'; @override String get setupChangeFolder => 'Change Folder'; @@ -1182,6 +1188,13 @@ class AppLocalizationsFr extends AppLocalizations { return '$artist - $title'; } + @override + String get filenameShowAdvancedTags => 'Show advanced tags'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; + @override String get folderOrganization => 'Folder Organization'; diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index 4391e763..3afb4c16 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -1182,6 +1182,13 @@ class AppLocalizationsHi extends AppLocalizations { return '$artist - $title'; } + @override + String get filenameShowAdvancedTags => 'Show advanced tags'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; + @override String get folderOrganization => 'Folder Organization'; diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index ae16675d..16fde0a6 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -349,7 +349,7 @@ class AppLocalizationsId extends AppLocalizations { @override String get optionsSpotifyDeprecationWarning => - 'Pencarian Spotify akan dihentikan pada 3 Maret 2026 karena perubahan API Spotify. Silakan beralih ke Deezer.'; + 'Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.'; @override String get extensionsTitle => 'Ekstensi'; @@ -1188,6 +1188,13 @@ class AppLocalizationsId extends AppLocalizations { return '$artist - $title'; } + @override + String get filenameShowAdvancedTags => 'Tampilkan tag lanjutan'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Aktifkan tag format untuk padding nomor lagu dan pola tanggal'; + @override String get folderOrganization => 'Organisasi Folder'; @@ -1941,27 +1948,26 @@ class AppLocalizationsId extends AppLocalizations { String get downloadAlbumFolderStructure => 'Struktur Folder Album'; @override - String get downloadUseAlbumArtistForFolders => - 'Gunakan Album Artist untuk folder'; + String get downloadUseAlbumArtistForFolders => 'Use Album Artist for folders'; @override String get downloadUseAlbumArtistForFoldersAlbumSubtitle => - 'Folder artis memakai Album Artist jika tersedia'; + 'Artist folders use Album Artist when available'; @override String get downloadUseAlbumArtistForFoldersTrackSubtitle => - 'Folder artis hanya memakai Track Artist'; + 'Artist folders use Track Artist only'; @override - String get downloadUsePrimaryArtistOnly => 'Hanya artis utama untuk folder'; + String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders'; @override String get downloadUsePrimaryArtistOnlyEnabled => - 'Featured artist dihapus dari nama folder (misal Justin Bieber, Quavo → Justin Bieber)'; + 'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)'; @override String get downloadUsePrimaryArtistOnlyDisabled => - 'Nama artis lengkap dipakai untuk folder'; + 'Full artist string used for folder name'; @override String get downloadSaveFormat => 'Simpan Format'; @@ -2200,10 +2206,10 @@ class AppLocalizationsId extends AppLocalizations { String get recentTypePlaylist => 'Playlist'; @override - String get recentEmpty => 'Belum ada item terbaru'; + String get recentEmpty => 'No recent items yet'; @override - String get recentShowAllDownloads => 'Tampilkan Semua Download'; + String get recentShowAllDownloads => 'Show All Downloads'; @override String recentPlaylistInfo(String name) { @@ -2312,10 +2318,10 @@ class AppLocalizationsId extends AppLocalizations { String get settingsLocalLibrarySubtitle => 'Scan music & detect duplicates'; @override - String get settingsCache => 'Penyimpanan & Cache'; + String get settingsCache => 'Storage & Cache'; @override - String get settingsCacheSubtitle => 'Lihat ukuran dan bersihkan data cache'; + String get settingsCacheSubtitle => 'View size and clear cached data'; @override String get libraryTitle => 'Local Library'; @@ -2590,221 +2596,219 @@ class AppLocalizationsId extends AppLocalizations { String get storageModeInfo => 'Your files are stored in multiple locations'; @override - String get tutorialWelcomeTitle => 'Selamat Datang di SpotiFLAC!'; + String get tutorialWelcomeTitle => 'Welcome to SpotiFLAC!'; @override String get tutorialWelcomeDesc => - 'Mari pelajari cara mengunduh musik favorit Anda dalam kualitas lossless. Tutorial singkat ini akan menunjukkan dasar-dasarnya.'; + 'Let\'s learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.'; @override String get tutorialWelcomeTip1 => - 'Unduh musik dari Spotify, Deezer, atau tempel URL yang didukung'; + 'Download music from Spotify, Deezer, or paste any supported URL'; @override String get tutorialWelcomeTip2 => - 'Dapatkan audio kualitas FLAC dari Tidal, Qobuz, atau Amazon Music'; + 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; @override String get tutorialWelcomeTip3 => - 'Metadata, cover art, dan lirik otomatis tertanam'; + 'Automatic metadata, cover art, and lyrics embedding'; @override - String get tutorialSearchTitle => 'Mencari Musik'; + String get tutorialSearchTitle => 'Finding Music'; @override String get tutorialSearchDesc => - 'Ada dua cara mudah untuk menemukan musik yang ingin Anda unduh.'; + 'There are two easy ways to find music you want to download.'; @override String get tutorialSearchTip1 => - 'Tempel URL Spotify atau Deezer langsung di kotak pencarian'; + 'Paste a Spotify or Deezer URL directly in the search box'; @override String get tutorialSearchTip2 => - 'Atau ketik nama lagu, artis, atau album untuk mencari'; + 'Or type the song name, artist, or album to search'; @override String get tutorialSearchTip3 => - 'Mendukung lagu, album, playlist, dan halaman artis'; + 'Supports tracks, albums, playlists, and artist pages'; @override - String get tutorialDownloadTitle => 'Mengunduh Musik'; + String get tutorialDownloadTitle => 'Downloading Music'; @override String get tutorialDownloadDesc => - 'Mengunduh musik itu mudah dan cepat. Begini caranya.'; + 'Downloading music is simple and fast. Here\'s how it works.'; @override String get tutorialDownloadTip1 => - 'Ketuk tombol unduh di samping lagu mana pun untuk mulai mengunduh'; + 'Tap the download button next to any track to start downloading'; @override String get tutorialDownloadTip2 => - 'Pilih kualitas yang Anda inginkan (FLAC, Hi-Res, atau MP3)'; + 'Choose your preferred quality (FLAC, Hi-Res, or MP3)'; @override String get tutorialDownloadTip3 => - 'Unduh seluruh album atau playlist dengan satu ketukan'; + 'Download entire albums or playlists with one tap'; @override - String get tutorialLibraryTitle => 'Perpustakaan Anda'; + String get tutorialLibraryTitle => 'Your Library'; @override String get tutorialLibraryDesc => - 'Semua musik yang Anda unduh terorganisir di tab Perpustakaan.'; + 'All your downloaded music is organized in the Library tab.'; @override String get tutorialLibraryTip1 => - 'Lihat progres unduhan dan antrian di tab Perpustakaan'; + 'View download progress and queue in the Library tab'; @override String get tutorialLibraryTip2 => - 'Ketuk lagu mana pun untuk memutarnya dengan pemutar musik'; + 'Tap any track to play it with your music player'; @override String get tutorialLibraryTip3 => - 'Beralih antara tampilan daftar dan grid untuk penjelajahan lebih baik'; + 'Switch between list and grid view for better browsing'; @override - String get tutorialExtensionsTitle => 'Ekstensi'; + String get tutorialExtensionsTitle => 'Extensions'; @override String get tutorialExtensionsDesc => - 'Tingkatkan kemampuan aplikasi dengan ekstensi komunitas.'; + 'Extend the app\'s capabilities with community extensions.'; @override String get tutorialExtensionsTip1 => - 'Jelajahi tab Toko untuk menemukan ekstensi berguna'; + 'Browse the Store tab to discover useful extensions'; @override String get tutorialExtensionsTip2 => - 'Tambahkan provider unduhan atau sumber pencarian baru'; + 'Add new download providers or search sources'; @override String get tutorialExtensionsTip3 => - 'Dapatkan lirik, metadata lebih baik, dan fitur lainnya'; + 'Get lyrics, enhanced metadata, and more features'; @override - String get tutorialSettingsTitle => 'Sesuaikan Pengalaman Anda'; + String get tutorialSettingsTitle => 'Customize Your Experience'; @override String get tutorialSettingsDesc => - 'Personalisasi aplikasi di Pengaturan sesuai preferensi Anda.'; + 'Personalize the app in Settings to match your preferences.'; @override String get tutorialSettingsTip1 => - 'Ubah lokasi unduhan dan organisasi folder'; + 'Change download location and folder organization'; @override String get tutorialSettingsTip2 => - 'Atur kualitas audio dan preferensi format default'; + 'Set default audio quality and format preferences'; @override - String get tutorialSettingsTip3 => 'Sesuaikan tema dan tampilan aplikasi'; + String get tutorialSettingsTip3 => 'Customize app theme and appearance'; @override String get tutorialReadyMessage => - 'Anda siap! Mulai unduh musik favorit Anda sekarang.'; + 'You\'re all set! Start downloading your favorite music now.'; @override - String get tutorialExample => 'CONTOH'; + String get tutorialExample => 'EXAMPLE'; @override - String get libraryForceFullScan => 'Pindai Ulang Penuh'; + String get libraryForceFullScan => 'Force Full Scan'; @override - String get libraryForceFullScanSubtitle => - 'Pindai ulang semua file, abaikan cache'; + String get libraryForceFullScanSubtitle => 'Rescan all files, ignoring cache'; @override - String get cleanupOrphanedDownloads => 'Bersihkan Entri Unduhan Tidak Valid'; + String get cleanupOrphanedDownloads => 'Cleanup Orphaned Downloads'; @override String get cleanupOrphanedDownloadsSubtitle => - 'Hapus entri riwayat untuk file yang tidak ada lagi'; + 'Remove history entries for files that no longer exist'; @override String cleanupOrphanedDownloadsResult(int count) { - return 'Menghapus $count entri unduhan tidak valid dari riwayat'; + return 'Removed $count orphaned entries from history'; } @override - String get cleanupOrphanedDownloadsNone => - 'Tidak ada entri unduhan tidak valid'; + String get cleanupOrphanedDownloadsNone => 'No orphaned entries found'; @override - String get cacheTitle => 'Penyimpanan & Cache'; + String get cacheTitle => 'Storage & Cache'; @override - String get cacheSummaryTitle => 'Ringkasan cache'; + String get cacheSummaryTitle => 'Cache overview'; @override String get cacheSummarySubtitle => - 'Membersihkan cache tidak akan menghapus file musik yang sudah diunduh.'; + 'Clearing cache will not remove downloaded music files.'; @override String cacheEstimatedTotal(String size) { - return 'Estimasi penggunaan cache: $size'; + return 'Estimated cache usage: $size'; } @override - String get cacheSectionStorage => 'Data Cache'; + String get cacheSectionStorage => 'Cached Data'; @override - String get cacheSectionMaintenance => 'Perawatan'; + String get cacheSectionMaintenance => 'Maintenance'; @override - String get cacheAppDirectory => 'Direktori cache aplikasi'; + String get cacheAppDirectory => 'App cache directory'; @override String get cacheAppDirectoryDesc => - 'Respons HTTP, data WebView, dan data sementara aplikasi.'; + 'HTTP responses, WebView data, and other temporary app data.'; @override - String get cacheTempDirectory => 'Direktori sementara'; + String get cacheTempDirectory => 'Temporary directory'; @override String get cacheTempDirectoryDesc => - 'File sementara dari proses download dan konversi audio.'; + 'Temporary files from downloads and audio conversion.'; @override - String get cacheCoverImage => 'Cache gambar cover'; + String get cacheCoverImage => 'Cover image cache'; @override String get cacheCoverImageDesc => - 'Gambar cover album dan lagu yang diunduh. Akan diunduh ulang saat dilihat.'; + 'Downloaded album and track cover art. Will re-download when viewed.'; @override - String get cacheLibraryCover => 'Cache cover library'; + String get cacheLibraryCover => 'Library cover cache'; @override String get cacheLibraryCoverDesc => - 'Cover dari file musik lokal. Akan diekstrak ulang saat scan berikutnya.'; + 'Cover art extracted from local music files. Will re-extract on next scan.'; @override - String get cacheExploreFeed => 'Cache feed Explore'; + String get cacheExploreFeed => 'Explore feed cache'; @override String get cacheExploreFeedDesc => - 'Konten tab Explore (rilis baru, trending). Akan dimuat ulang saat dikunjungi.'; + 'Explore tab content (new releases, trending). Will refresh on next visit.'; @override - String get cacheTrackLookup => 'Cache pencocokan lagu'; + String get cacheTrackLookup => 'Track lookup cache'; @override String get cacheTrackLookupDesc => - 'Cache pencarian ID lagu Spotify/Deezer. Menghapus mungkin memperlambat beberapa pencarian.'; + 'Spotify/Deezer track ID lookups. Clearing may slow next few searches.'; @override String get cacheCleanupUnusedDesc => - 'Hapus entri riwayat download dan library yang filenya sudah tidak ada.'; + 'Remove orphaned download history and library entries for missing files.'; @override - String get cacheNoData => 'Tidak ada data cache'; + String get cacheNoData => 'No cached data'; @override String cacheSizeWithFiles(String size, int count) { - return '$size dalam $count file'; + return '$size in $count files'; } @override @@ -2814,126 +2818,123 @@ class AppLocalizationsId extends AppLocalizations { @override String cacheEntries(int count) { - return '$count entri'; + return '$count entries'; } @override String cacheClearSuccess(String target) { - return 'Berhasil dibersihkan: $target'; + return 'Cleared: $target'; } @override - String get cacheClearConfirmTitle => 'Bersihkan cache?'; + String get cacheClearConfirmTitle => 'Clear cache?'; @override String cacheClearConfirmMessage(String target) { - return 'Ini akan membersihkan data cache untuk $target. File musik yang sudah diunduh tidak akan dihapus.'; + return 'This will clear cached data for $target. Downloaded music files will not be deleted.'; } @override - String get cacheClearAllConfirmTitle => 'Bersihkan semua cache?'; + String get cacheClearAllConfirmTitle => 'Clear all cache?'; @override String get cacheClearAllConfirmMessage => - 'Ini akan membersihkan semua kategori cache di halaman ini. File musik yang sudah diunduh tidak akan dihapus.'; + 'This will clear all cache categories on this page. Downloaded music files will not be deleted.'; @override - String get cacheClearAll => 'Bersihkan semua cache'; + String get cacheClearAll => 'Clear all cache'; @override - String get cacheCleanupUnused => 'Bersihkan data tidak terpakai'; + String get cacheCleanupUnused => 'Cleanup unused data'; @override String get cacheCleanupUnusedSubtitle => - 'Hapus riwayat unduhan yatim dan entri library yang file-nya hilang'; + 'Remove orphaned download history and missing library entries'; @override String cacheCleanupResult(int downloadCount, int libraryCount) { - return 'Pembersihan selesai: $downloadCount unduhan yatim, $libraryCount entri library hilang'; + return 'Cleanup completed: $downloadCount orphaned downloads, $libraryCount missing library entries'; } @override - String get cacheRefreshStats => 'Segarkan statistik'; + String get cacheRefreshStats => 'Refresh stats'; @override - String get trackSaveCoverArt => 'Simpan Cover Art'; + String get trackSaveCoverArt => 'Save Cover Art'; @override - String get trackSaveCoverArtSubtitle => - 'Simpan cover album sebagai file .jpg'; + String get trackSaveCoverArtSubtitle => 'Save album art as .jpg file'; @override - String get trackSaveLyrics => 'Simpan Lirik (.lrc)'; + String get trackSaveLyrics => 'Save Lyrics (.lrc)'; @override - String get trackSaveLyricsSubtitle => - 'Ambil dan simpan lirik sebagai file .lrc'; + String get trackSaveLyricsSubtitle => 'Fetch and save lyrics as .lrc file'; @override - String get trackSaveLyricsProgress => 'Menyimpan lirik...'; + String get trackSaveLyricsProgress => 'Saving lyrics...'; @override - String get trackReEnrich => 'Perkaya Ulang Metadata'; + String get trackReEnrich => 'Re-enrich Metadata'; @override String get trackReEnrichSubtitle => - 'Tanamkan ulang metadata tanpa mengunduh ulang'; + 'Re-embed metadata without re-downloading'; @override String get trackReEnrichOnlineSubtitle => - 'Cari metadata dari internet dan tanamkan ke file'; + 'Search metadata online and embed into file'; @override String get trackEditMetadata => 'Edit Metadata'; @override String trackCoverSaved(String fileName) { - return 'Cover art disimpan ke $fileName'; + return 'Cover art saved to $fileName'; } @override - String get trackCoverNoSource => 'Tidak ada sumber cover art'; + String get trackCoverNoSource => 'No cover art source available'; @override String trackLyricsSaved(String fileName) { - return 'Lirik disimpan ke $fileName'; + return 'Lyrics saved to $fileName'; } @override - String get trackReEnrichProgress => 'Memperkaya ulang metadata...'; + String get trackReEnrichProgress => 'Re-enriching metadata...'; @override - String get trackReEnrichSearching => 'Mencari metadata dari internet...'; + String get trackReEnrichSearching => 'Searching metadata online...'; @override - String get trackReEnrichSuccess => 'Metadata berhasil diperkaya ulang'; + String get trackReEnrichSuccess => 'Metadata re-enriched successfully'; @override - String get trackReEnrichFfmpegFailed => - 'Gagal menanamkan metadata via FFmpeg'; + String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed'; @override String trackSaveFailed(String error) { - return 'Gagal: $error'; + return 'Failed: $error'; } @override - String get trackConvertFormat => 'Konversi Format'; + String get trackConvertFormat => 'Convert Format'; @override - String get trackConvertFormatSubtitle => 'Konversi ke MP3 atau Opus'; + String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; @override - String get trackConvertTitle => 'Konversi Audio'; + String get trackConvertTitle => 'Convert Audio'; @override - String get trackConvertTargetFormat => 'Format Tujuan'; + String get trackConvertTargetFormat => 'Target Format'; @override String get trackConvertBitrate => 'Bitrate'; @override - String get trackConvertConfirmTitle => 'Konfirmasi Konversi'; + String get trackConvertConfirmTitle => 'Confirm Conversion'; @override String trackConvertConfirmMessage( @@ -2941,17 +2942,17 @@ class AppLocalizationsId extends AppLocalizations { String targetFormat, String bitrate, ) { - return 'Konversi dari $sourceFormat ke $targetFormat pada $bitrate?\n\nFile asli akan dihapus setelah konversi.'; + return 'Convert from $sourceFormat to $targetFormat at $bitrate?\n\nThe original file will be deleted after conversion.'; } @override - String get trackConvertConverting => 'Mengkonversi audio...'; + String get trackConvertConverting => 'Converting audio...'; @override String trackConvertSuccess(String format) { - return 'Berhasil dikonversi ke $format'; + return 'Converted to $format successfully'; } @override - String get trackConvertFailed => 'Konversi gagal'; + String get trackConvertFailed => 'Conversion failed'; } diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 23d2d69c..370018ad 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1176,6 +1176,13 @@ class AppLocalizationsJa extends AppLocalizations { return '$artist - $title'; } + @override + String get filenameShowAdvancedTags => 'Show advanced tags'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; + @override String get folderOrganization => 'フォルダ構成'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 74b97adb..e97ac06c 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -13,7 +13,7 @@ class AppLocalizationsKo extends AppLocalizations { @override String get appDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + 'Spotify 트랙을 Tidal, Qobuz, Amazon Music에서 무손실 음질로 다운로드하세요.'; @override String get navHome => 'Home'; @@ -34,32 +34,32 @@ class AppLocalizationsKo extends AppLocalizations { String get homeTitle => 'Home'; @override - String get homeSearchHint => 'Paste Spotify URL or search...'; + String get homeSearchHint => 'Spotify URL을 붙여 넣거나 검색'; @override String homeSearchHintExtension(String extensionName) { - return 'Search with $extensionName...'; + return '$extensionName에서 검색'; } @override - String get homeSubtitle => 'Paste a Spotify link or search by name'; + String get homeSubtitle => 'Spotify URL을 붙여 넣거나 검색'; @override - String get homeSupports => 'Supports: Track, Album, Playlist, Artist URLs'; + String get homeSupports => '지원 항목: 트랙, 앨범, 플레이리스트, 아티스트 URLs'; @override - String get homeRecent => 'Recent'; + String get homeRecent => '최근 기록'; @override - String get historyTitle => 'History'; + String get historyTitle => '기록'; @override String historyDownloading(int count) { - return 'Downloading ($count)'; + return '다운로드 중... $count'; } @override - String get historyDownloaded => 'Downloaded'; + String get historyDownloaded => '다운로드 목록'; @override String get historyFilterAll => 'All'; @@ -75,7 +75,7 @@ class AppLocalizationsKo extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count tracks', + other: '${count}tracks', one: '1 track', ); return '$_temp0'; @@ -245,14 +245,13 @@ class AppLocalizationsKo extends AppLocalizations { String get optionsAutoFallback => 'Auto Fallback'; @override - String get optionsAutoFallbackSubtitle => - 'Try other services if download fails'; + String get optionsAutoFallbackSubtitle => '다운로드가 실패한 경우, 다른 서비스로 재시도'; @override String get optionsUseExtensionProviders => 'Use Extension Providers'; @override - String get optionsUseExtensionProvidersOn => 'Extensions will be tried first'; + String get optionsUseExtensionProvidersOn => '확장 기능을 우선적으로 사용합니다'; @override String get optionsUseExtensionProvidersOff => 'Using built-in providers only'; @@ -1182,6 +1181,13 @@ class AppLocalizationsKo extends AppLocalizations { return '$artist - $title'; } + @override + String get filenameShowAdvancedTags => 'Show advanced tags'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; + @override String get folderOrganization => 'Folder Organization'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 2d2e32cf..b0bd7b60 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1182,6 +1182,13 @@ class AppLocalizationsNl extends AppLocalizations { return '$artist - $title'; } + @override + String get filenameShowAdvancedTags => 'Show advanced tags'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; + @override String get folderOrganization => 'Folder Organization'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 5d2c37a9..3a946c0a 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1182,6 +1182,13 @@ class AppLocalizationsPt extends AppLocalizations { return '$artist - $title'; } + @override + String get filenameShowAdvancedTags => 'Show advanced tags'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; + @override String get folderOrganization => 'Folder Organization'; @@ -2951,6 +2958,9 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get navHome => 'Início'; + @override + String get navLibrary => 'Library'; + @override String get navHistory => 'Histórico'; @@ -3043,6 +3053,9 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get historyNoSinglesSubtitle => 'Os downloads de faixa individuais aparecerão aqui'; + @override + String get historySearchHint => 'Pesquisar histórico...'; + @override String get settingsTitle => 'Configurações'; @@ -3277,6 +3290,10 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get optionsSpotifyWarning => 'O Spotify requer as suas próprias credenciais de API. Consiga gratuitamente em developer.spotify.com'; + @override + String get optionsSpotifyDeprecationWarning => + 'Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.'; + @override String get extensionsTitle => 'Extensões'; @@ -3343,6 +3360,9 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get aboutLogoArtist => 'O artista talentoso que criou o nosso lindo logotipo do aplicativo!'; + @override + String get aboutTranslators => 'Tradutores'; + @override String get aboutSpecialThanks => 'Agradecimentos Especiais'; @@ -3369,6 +3389,21 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get aboutFeatureRequestSubtitle => 'Sugira novos recursos para o aplicativo'; + @override + String get aboutTelegramChannel => 'Canal do Telegram'; + + @override + String get aboutTelegramChannelSubtitle => 'Anúncios e atualizações'; + + @override + String get aboutTelegramChat => 'Comunidade do Telegram'; + + @override + String get aboutTelegramChatSubtitle => 'Converse com outros usuários'; + + @override + String get aboutSocial => 'Social'; + @override String get aboutSupport => 'Apoiar'; @@ -3386,6 +3421,10 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get aboutSachinsenalDesc => 'O criador original do projeto HiFi. A base da integração do Tidal!'; + @override + String get aboutSjdonadoDesc => + 'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!'; + @override String get aboutDoubleDouble => 'DoubleDouble'; @@ -3400,6 +3439,13 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get aboutDabMusicDesc => 'A melhor API de streaming do Qobuz. Downloads de alta resolução não seriam possíveis sem isso!'; + @override + String get aboutSpotiSaver => 'SpotiSaver'; + + @override + String get aboutSpotiSaverDesc => + 'Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!'; + @override String get aboutAppDescription => 'Baixe faixas do Spotify em qualidade sem perdas do Tidal, Qobuz e Amazon Music.'; @@ -3598,7 +3644,11 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { 'Limitação do iOS: Pastas vazias não podem ser selecionadas. Escolha uma pasta com pelo menos um arquivo.'; @override - String get setupDownloadInFlac => 'Baixe faixas do Spotify em FLAC'; + String get setupIcloudNotSupported => + 'iCloud Drive is not supported. Please use the app Documents folder.'; + + @override + String get setupDownloadInFlac => 'Baixar faixas do Spotify em FLAC'; @override String get setupStepStorage => 'Armazenamento'; @@ -3801,7 +3851,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String dialogUninstallExtensionMessage(String extensionName) { - return 'Tem certeza de que deseja remover $extensionName?'; + return 'Tem certeza que deseja remover $extensionName?'; } @override @@ -3809,7 +3859,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get dialogClearHistoryMessage => - 'Tem certeza de que deseja limpar todo o histórico de downloads? Isso não pode ser desfeito.'; + 'Tem certeza que deseja limpar todo o histórico de downloads? Isso não pode ser desfeito.'; @override String get dialogDeleteSelectedTitle => 'Apagar Selecionados'; @@ -3830,7 +3880,12 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String dialogImportPlaylistMessage(int count) { - return 'Encontradas $count faixas no CSV. Adicionar à fila de download?'; + return '$count Faixas encontradas em CSV. Adicioná-las à lista de downloads?'; + } + + @override + String csvImportTracks(int count) { + return '$count faixas do CSV'; } @override @@ -3848,6 +3903,11 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { return '\"$trackName\" já foi baixada'; } + @override + String snackbarAlreadyInLibrary(String trackName) { + return '\"$trackName\" already exists in your library'; + } + @override String get snackbarHistoryCleared => 'Histórico limpo'; @@ -3855,7 +3915,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get snackbarCredentialsSaved => 'Credenciais salvas'; @override - String get snackbarCredentialsCleared => 'Credenciais removidas'; + String get snackbarCredentialsCleared => 'Credenciais limpas'; @override String snackbarDeletedTracks(int count) { @@ -3886,7 +3946,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String snackbarUrlCopied(String platform) { - return 'URL do $platform copiada para a área de transferência'; + return 'URL do $platform copiado para a área de transferência'; } @override @@ -3901,7 +3961,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get snackbarMetadataProviderSaved => - 'Prioridade de provedor de metadados salva'; + 'Prioridade do provedor de metadados salva'; @override String snackbarExtensionInstalled(String extensionName) { @@ -3914,13 +3974,13 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { } @override - String get snackbarFailedToInstall => 'Falha ao instalar extensão'; + String get snackbarFailedToInstall => 'Falha ao instalar a extensão'; @override - String get snackbarFailedToUpdate => 'Falha ao atualizar extensão'; + String get snackbarFailedToUpdate => 'Falha ao atualizar a extensão'; @override - String get errorRateLimited => 'Taxa Limitada'; + String get errorRateLimited => 'Tráfico Limitado (Rate Limited)'; @override String get errorRateLimitedMessage => @@ -3936,7 +3996,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String errorMissingExtensionSource(String item) { - return 'Não foi possível carregar $item: fonte de extensão ausente'; + return 'Não é possível carregar $item: faltando a fonte da extensão'; } @override @@ -4289,8 +4349,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get logNetworkErrorDescription => 'Problemas de conexão detectados'; @override - String get logNetworkErrorSuggestion => - 'Verifique a sua conexão com a internet'; + String get logNetworkErrorSuggestion => 'Verifique sua conexão de internet'; @override String get logTrackNotFoundDescription => @@ -4298,7 +4357,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get logTrackNotFoundSuggestion => - 'A faixa pode não estar disponível em qualidade lossless'; + 'A faixa pode não estar disponível em qualidade sem perdas'; @override String logTotalErrors(int count) { @@ -4307,7 +4366,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String logAffected(String domains) { - return 'Afetados: $domains'; + return 'Afetado(s): $domains'; } @override @@ -4325,7 +4384,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get credentialsDescription => - 'Insira o seu Client ID e Secret para usar a sua própria cota de aplicativo do Spotify.'; + 'Digite a sua Client ID e Secret para usar a sua própria cota de aplicativo do Spotify.'; @override String get credentialsClientId => 'Client ID'; @@ -4372,6 +4431,36 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get sectionFileSettings => 'Configurações de Arquivo'; + @override + String get sectionLyrics => 'Letras'; + + @override + String get lyricsMode => 'Modo de Letras'; + + @override + String get lyricsModeDescription => + 'Escolha como as letras são salvas com os seus downloads'; + + @override + String get lyricsModeEmbed => 'Incorporar no arquivo'; + + @override + String get lyricsModeEmbedSubtitle => + 'Letra armazenada nos metadados da FLAC'; + + @override + String get lyricsModeExternal => 'Arquivo .lrc externo'; + + @override + String get lyricsModeExternalSubtitle => + 'Arquivo .lrc separado para reprodutores como o Samsung Music'; + + @override + String get lyricsModeBoth => 'Ambos'; + + @override + String get lyricsModeBothSubtitle => 'Incorporar e salvar arquivo .lrc'; + @override String get sectionColor => 'Cor'; @@ -4486,22 +4575,39 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get trackReleaseDate => 'Data de lançamento'; + @override + String get trackGenre => 'Género'; + + @override + String get trackLabel => 'Gravadora'; + + @override + String get trackCopyright => 'Direitos Autorais'; + @override String get trackDownloaded => 'Baixado'; @override - String get trackCopyLyrics => 'Copiar letras'; + String get trackCopyLyrics => 'Copiar letra'; @override - String get trackLyricsNotAvailable => - 'Letras não disponíveis para esta faixa'; + String get trackLyricsNotAvailable => 'Letra não disponível para esta faixa'; @override String get trackLyricsTimeout => 'A solicitação expirou. Tente novamente mais tarde.'; @override - String get trackLyricsLoadFailed => 'Falha ao carregar letras'; + String get trackLyricsLoadFailed => 'Falha ao carregar a letra'; + + @override + String get trackEmbedLyrics => 'Incorporar Letras'; + + @override + String get trackLyricsEmbedded => 'Letras incorporadas com sucesso'; + + @override + String get trackInstrumental => 'Faixa de instrumentais'; @override String get trackCopiedToClipboard => 'Copiado para a área de transferência'; @@ -4511,7 +4617,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get trackDeleteConfirmMessage => - 'Isso apagará permanentemente o arquivo baixado e o removerá do seu histórico.'; + 'Isto irá excluir o arquivo baixado permanentemente e removê-lo do seu histórico.'; @override String trackCannotOpen(String message) { @@ -4526,17 +4632,17 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String dateDaysAgo(int count) { - return 'Há $count dias'; + return '$count dias atrás'; } @override String dateWeeksAgo(int count) { - return 'Há $count semanas'; + return '$count semanas atrás'; } @override String dateMonthsAgo(int count) { - return 'Há $count meses'; + return '$count meses atrás'; } @override @@ -4549,10 +4655,10 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get concurrentParallel3 => '3 Paralelos'; @override - String get tapToSeeError => 'Toque para ver detalhes do erro'; + String get tapToSeeError => 'Toque para ver os detalhes do erro'; @override - String get storeFilterAll => 'Todos'; + String get storeFilterAll => 'Tudo'; @override String get storeFilterMetadata => 'Metadados'; @@ -4561,7 +4667,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get storeFilterDownload => 'Download'; @override - String get storeFilterUtility => 'Utilitário'; + String get storeFilterUtility => 'Utilidade'; @override String get storeFilterLyrics => 'Letras'; @@ -4597,7 +4703,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get extensionError => 'Erro'; @override - String get extensionCapabilities => 'Capacidades'; + String get extensionCapabilities => 'Funcionalidades'; @override String get extensionMetadataProvider => 'Provedor de Metadados'; @@ -4609,7 +4715,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get extensionLyricsProvider => 'Provedor de Letras'; @override - String get extensionUrlHandler => 'Manipulador de URL'; + String get extensionUrlHandler => 'Gerenciador de URL'; @override String get extensionQualityOptions => 'Opções de Qualidade'; @@ -4734,10 +4840,46 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get qualityHiResFlacMaxSubtitle => '24-bit / até 192kHz'; + @override + String get qualityLossy => 'Lossy'; + + @override + String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)'; + + @override + String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)'; + + @override + String get enableLossyOption => 'Enable Lossy Option'; + + @override + String get enableLossyOptionSubtitleOn => 'Lossy quality option is available'; + + @override + String get enableLossyOptionSubtitleOff => + 'Downloads FLAC then converts to lossy format'; + + @override + String get lossyFormat => 'Lossy Format'; + + @override + String get lossyFormatDescription => 'Choose the lossy format for conversion'; + + @override + String get lossyFormatMp3Subtitle => '320kbps, best compatibility'; + + @override + String get lossyFormatOpusSubtitle => + '128kbps, better quality at smaller size'; + @override String get qualityNote => 'A qualidade real depende da faixa que estiver disponível no serviço'; + @override + String get youtubeQualityNote => + 'YouTube provides lossy audio only. Not part of lossless fallback.'; + @override String get downloadAskBeforeDownload => 'Perguntar qualidade antes de baixar'; @@ -4750,6 +4892,28 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get downloadAlbumFolderStructure => 'Estrutura da Pasta de Álbum'; + @override + String get downloadUseAlbumArtistForFolders => 'Use Album Artist for folders'; + + @override + String get downloadUseAlbumArtistForFoldersAlbumSubtitle => + 'Artist folders use Album Artist when available'; + + @override + String get downloadUseAlbumArtistForFoldersTrackSubtitle => + 'Artist folders use Track Artist only'; + + @override + String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders'; + + @override + String get downloadUsePrimaryArtistOnlyEnabled => + 'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)'; + + @override + String get downloadUsePrimaryArtistOnlyDisabled => + 'Full artist string used for folder name'; + @override String get downloadSaveFormat => 'Formato para Salvar'; @@ -4769,7 +4933,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get downloadBestAvailable => 'Melhor Disponível'; @override - String get folderNone => 'Nenhum'; + String get folderNone => 'Nenhuma'; @override String get folderNoneSubtitle => @@ -4779,20 +4943,20 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get folderArtist => 'Artista'; @override - String get folderArtistSubtitle => 'Nome do Artista/nome do arquivo'; + String get folderArtistSubtitle => 'Nome do Artista/arquivo'; @override String get folderAlbum => 'Álbum'; @override - String get folderAlbumSubtitle => 'Nome do Álbum/nome do arquivo'; + String get folderAlbumSubtitle => 'Nome do Álbum/arquivo'; @override String get folderArtistAlbum => 'Artista/Álbum'; @override String get folderArtistAlbumSubtitle => - 'Nome do Artista/Nome do Álbum/nome do arquivo'; + 'Nome do Artista/Nome do Álbum/arquivo'; @override String get serviceTidal => 'Tidal'; @@ -4810,16 +4974,16 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String get serviceSpotify => 'Spotify'; @override - String get appearanceAmoledDark => 'AMOLED Escuro'; + String get appearanceAmoledDark => 'Escuro AMOLED'; @override String get appearanceAmoledDarkSubtitle => 'Fundo preto puro'; @override - String get appearanceChooseAccentColor => 'Escolher Cor de Destaque'; + String get appearanceChooseAccentColor => 'Escolha a Cor de Destaque'; @override - String get appearanceChooseTheme => 'Modo de Tema'; + String get appearanceChooseTheme => 'Modo do Tema'; @override String get queueTitle => 'Fila de Download'; @@ -4829,7 +4993,40 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get queueClearAllMessage => - 'Tem certeza de que deseja limpar todos os downloads?'; + 'Você tem certeza que deseja limpar todos os downloads?'; + + @override + String get queueExportFailed => 'Export'; + + @override + String get queueExportFailedSuccess => + 'Failed downloads exported to TXT file'; + + @override + String get queueExportFailedClear => 'Clear Failed'; + + @override + String get queueExportFailedError => 'Failed to export downloads'; + + @override + String get settingsAutoExportFailed => 'Auto-export failed downloads'; + + @override + String get settingsAutoExportFailedSubtitle => + 'Save failed downloads to TXT file automatically'; + + @override + String get settingsDownloadNetwork => 'Download Network'; + + @override + String get settingsDownloadNetworkAny => 'WiFi + Mobile Data'; + + @override + String get settingsDownloadNetworkWifiOnly => 'WiFi Only'; + + @override + String get settingsDownloadNetworkSubtitle => + 'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.'; @override String get queueEmpty => 'Nenhum download na fila'; @@ -4870,10 +5067,10 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { 'Álbuns/Nome do Artista/[2005] Nome do Álbum/'; @override - String get albumFolderAlbumOnly => 'Apenas Álbum'; + String get albumFolderAlbumOnly => 'Somente Álbum'; @override - String get albumFolderAlbumOnlySubtitle => 'Álbuns/Nome do Álbum/'; + String get albumFolderAlbumOnlySubtitle => 'Albums/Nome do Álbum/'; @override String get albumFolderYearAlbum => '[Ano] Álbum'; @@ -4881,6 +5078,13 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get albumFolderYearAlbumSubtitle => 'Álbuns/[2005] Nome do Álbum/'; + @override + String get albumFolderArtistAlbumSingles => 'Artista / Álbum + Singles'; + + @override + String get albumFolderArtistAlbumSinglesSubtitle => + 'Artista/Álbum/ e Artista/Singles/'; + @override String get downloadedAlbumDeleteSelected => 'Apagar Selecionados'; @@ -4892,7 +5096,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { other: 'faixas', one: 'faixa', ); - return 'Apagar $count $_temp0 deste álbum?\n\nIsso também apagará os arquivos do armazenamento.'; + return 'Excluir $count $_temp0 deste álbum?\n\nIsso também excluirá os arquivos do armazenamento.'; } @override @@ -4900,12 +5104,12 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String downloadedAlbumDownloadedCount(int count) { - return '$count baixadas'; + return '$count baixado(s)'; } @override String downloadedAlbumSelectedCount(int count) { - return '$count selecionadas'; + return '$count selecionado(s)'; } @override @@ -4926,7 +5130,12 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { } @override - String get downloadedAlbumSelectToDelete => 'Selecione faixas para apagar'; + String get downloadedAlbumSelectToDelete => 'Selecione as faixas para apagar'; + + @override + String downloadedAlbumDiscHeader(int discNumber) { + return 'Disco $discNumber'; + } @override String get utilityFunctions => 'Funções Utilitárias'; @@ -4943,6 +5152,12 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String get recentTypePlaylist => 'Playlist'; + @override + String get recentEmpty => 'No recent items yet'; + + @override + String get recentShowAllDownloads => 'Show All Downloads'; + @override String recentPlaylistInfo(String name) { return 'Playlist: $name'; @@ -4952,4 +5167,739 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { String errorGeneric(String message) { return 'Erro: $message'; } + + @override + String get discographyDownload => 'Baixar Discografia'; + + @override + String get discographyDownloadAll => 'Baixar Tudo'; + + @override + String discographyDownloadAllSubtitle(int count, int albumCount) { + return '$count faixas de $albumCount lançamentos'; + } + + @override + String get discographyAlbumsOnly => 'Somente Álbuns'; + + @override + String discographyAlbumsOnlySubtitle(int count, int albumCount) { + return '$count faixas de $albumCount álbuns'; + } + + @override + String get discographySinglesOnly => 'Somente Singles e EPs'; + + @override + String discographySinglesOnlySubtitle(int count, int albumCount) { + return '$count faixas de $albumCount singles'; + } + + @override + String get discographySelectAlbums => 'Selecione Álbuns...'; + + @override + String get discographySelectAlbumsSubtitle => + 'Escolher álbuns ou singles específicos'; + + @override + String get discographyFetchingTracks => 'Buscando faixas...'; + + @override + String discographyFetchingAlbum(int current, int total) { + return 'Buscando $current de $total...'; + } + + @override + String discographySelectedCount(int count) { + return '$count selecionado(s)'; + } + + @override + String get discographyDownloadSelected => 'Baixar Selecionados'; + + @override + String discographyAddedToQueue(int count) { + return '$count faixas adicionadas à fila'; + } + + @override + String discographySkippedDownloaded(int added, int skipped) { + return '$added adicionada(s), $skipped já baixada(s)'; + } + + @override + String get discographyNoAlbums => 'Nenhum álbum disponível'; + + @override + String get discographyFailedToFetch => 'Falha ao obter alguns álbuns'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; + + @override + String get settingsLocalLibrary => 'Local Library'; + + @override + String get settingsLocalLibrarySubtitle => 'Scan music & detect duplicates'; + + @override + String get settingsCache => 'Storage & Cache'; + + @override + String get settingsCacheSubtitle => 'View size and clear cached data'; + + @override + String get libraryTitle => 'Local Library'; + + @override + String get libraryStatus => 'Library Status'; + + @override + String get libraryScanSettings => 'Scan Settings'; + + @override + String get libraryEnableLocalLibrary => 'Enable Local Library'; + + @override + String get libraryEnableLocalLibrarySubtitle => + 'Scan and track your existing music'; + + @override + String get libraryFolder => 'Library Folder'; + + @override + String get libraryFolderHint => 'Tap to select folder'; + + @override + String get libraryShowDuplicateIndicator => 'Show Duplicate Indicator'; + + @override + String get libraryShowDuplicateIndicatorSubtitle => + 'Show when searching for existing tracks'; + + @override + String get libraryActions => 'Actions'; + + @override + String get libraryScan => 'Scan Library'; + + @override + String get libraryScanSubtitle => 'Scan for audio files'; + + @override + String get libraryScanSelectFolderFirst => 'Select a folder first'; + + @override + String get libraryCleanupMissingFiles => 'Cleanup Missing Files'; + + @override + String get libraryCleanupMissingFilesSubtitle => + 'Remove entries for files that no longer exist'; + + @override + String get libraryClear => 'Clear Library'; + + @override + String get libraryClearSubtitle => 'Remove all scanned tracks'; + + @override + String get libraryClearConfirmTitle => 'Clear Library'; + + @override + String get libraryClearConfirmMessage => + 'This will remove all scanned tracks from your library. Your actual music files will not be deleted.'; + + @override + String get libraryAbout => 'About Local Library'; + + @override + String get libraryAboutDescription => + 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.'; + + @override + String libraryTracksCount(int count) { + return '$count tracks'; + } + + @override + String libraryLastScanned(String time) { + return 'Last scanned: $time'; + } + + @override + String get libraryLastScannedNever => 'Never'; + + @override + String get libraryScanning => 'Scanning...'; + + @override + String libraryScanProgress(String progress, int total) { + return '$progress% of $total files'; + } + + @override + String get libraryInLibrary => 'In Library'; + + @override + String libraryRemovedMissingFiles(int count) { + return 'Removed $count missing files from library'; + } + + @override + String get libraryCleared => 'Library cleared'; + + @override + String get libraryStorageAccessRequired => 'Storage Access Required'; + + @override + String get libraryStorageAccessMessage => + 'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.'; + + @override + String get libraryFolderNotExist => 'Selected folder does not exist'; + + @override + String get librarySourceDownloaded => 'Downloaded'; + + @override + String get librarySourceLocal => 'Local'; + + @override + String get libraryFilterAll => 'All'; + + @override + String get libraryFilterDownloaded => 'Downloaded'; + + @override + String get libraryFilterLocal => 'Local'; + + @override + String get libraryFilterTitle => 'Filters'; + + @override + String get libraryFilterReset => 'Reset'; + + @override + String get libraryFilterApply => 'Apply'; + + @override + String get libraryFilterSource => 'Source'; + + @override + String get libraryFilterQuality => 'Quality'; + + @override + String get libraryFilterQualityHiRes => 'Hi-Res (24bit)'; + + @override + String get libraryFilterQualityCD => 'CD (16bit)'; + + @override + String get libraryFilterQualityLossy => 'Lossy'; + + @override + String get libraryFilterFormat => 'Format'; + + @override + String get libraryFilterDate => 'Date Added'; + + @override + String get libraryFilterDateToday => 'Today'; + + @override + String get libraryFilterDateWeek => 'This Week'; + + @override + String get libraryFilterDateMonth => 'This Month'; + + @override + String get libraryFilterDateYear => 'This Year'; + + @override + String get libraryFilterSort => 'Sort'; + + @override + String get libraryFilterSortLatest => 'Latest'; + + @override + String get libraryFilterSortOldest => 'Oldest'; + + @override + String libraryFilterActive(int count) { + return '$count filter(s) active'; + } + + @override + String get timeJustNow => 'Just now'; + + @override + String timeMinutesAgo(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count minutes ago', + one: '1 minute ago', + ); + return '$_temp0'; + } + + @override + String timeHoursAgo(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count hours ago', + one: '1 hour ago', + ); + return '$_temp0'; + } + + @override + String get storageSwitchTitle => 'Switch Storage Mode'; + + @override + String get storageSwitchToSafTitle => 'Switch to SAF Storage?'; + + @override + String get storageSwitchToAppTitle => 'Switch to App Storage?'; + + @override + String get storageSwitchToSafMessage => + 'Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.'; + + @override + String get storageSwitchToAppMessage => + 'Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.'; + + @override + String get storageSwitchExistingDownloads => 'Existing Downloads'; + + @override + String storageSwitchExistingDownloadsInfo(int count, String mode) { + return '$count tracks in $mode storage'; + } + + @override + String get storageSwitchNewDownloads => 'New Downloads'; + + @override + String storageSwitchNewDownloadsLocation(String location) { + return 'Will be saved to: $location'; + } + + @override + String get storageSwitchContinue => 'Continue'; + + @override + String get storageSwitchSelectFolder => 'Select SAF Folder'; + + @override + String get storageAppStorage => 'App Storage'; + + @override + String get storageSafStorage => 'SAF Storage'; + + @override + String storageModeBadge(String mode) { + return 'Storage: $mode'; + } + + @override + String get storageStatsTitle => 'Storage Statistics'; + + @override + String storageStatsAppCount(int count) { + return '$count tracks in App Storage'; + } + + @override + String storageStatsSafCount(int count) { + return '$count tracks in SAF Storage'; + } + + @override + String get storageModeInfo => 'Your files are stored in multiple locations'; + + @override + String get tutorialWelcomeTitle => 'Welcome to SpotiFLAC!'; + + @override + String get tutorialWelcomeDesc => + 'Let\'s learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.'; + + @override + String get tutorialWelcomeTip1 => + 'Download music from Spotify, Deezer, or paste any supported URL'; + + @override + String get tutorialWelcomeTip2 => + 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + + @override + String get tutorialWelcomeTip3 => + 'Automatic metadata, cover art, and lyrics embedding'; + + @override + String get tutorialSearchTitle => 'Finding Music'; + + @override + String get tutorialSearchDesc => + 'There are two easy ways to find music you want to download.'; + + @override + String get tutorialSearchTip1 => + 'Paste a Spotify or Deezer URL directly in the search box'; + + @override + String get tutorialSearchTip2 => + 'Or type the song name, artist, or album to search'; + + @override + String get tutorialSearchTip3 => + 'Supports tracks, albums, playlists, and artist pages'; + + @override + String get tutorialDownloadTitle => 'Downloading Music'; + + @override + String get tutorialDownloadDesc => + 'Downloading music is simple and fast. Here\'s how it works.'; + + @override + String get tutorialDownloadTip1 => + 'Tap the download button next to any track to start downloading'; + + @override + String get tutorialDownloadTip2 => + 'Choose your preferred quality (FLAC, Hi-Res, or MP3)'; + + @override + String get tutorialDownloadTip3 => + 'Download entire albums or playlists with one tap'; + + @override + String get tutorialLibraryTitle => 'Your Library'; + + @override + String get tutorialLibraryDesc => + 'All your downloaded music is organized in the Library tab.'; + + @override + String get tutorialLibraryTip1 => + 'View download progress and queue in the Library tab'; + + @override + String get tutorialLibraryTip2 => + 'Tap any track to play it with your music player'; + + @override + String get tutorialLibraryTip3 => + 'Switch between list and grid view for better browsing'; + + @override + String get tutorialExtensionsTitle => 'Extensions'; + + @override + String get tutorialExtensionsDesc => + 'Extend the app\'s capabilities with community extensions.'; + + @override + String get tutorialExtensionsTip1 => + 'Browse the Store tab to discover useful extensions'; + + @override + String get tutorialExtensionsTip2 => + 'Add new download providers or search sources'; + + @override + String get tutorialExtensionsTip3 => + 'Get lyrics, enhanced metadata, and more features'; + + @override + String get tutorialSettingsTitle => 'Customize Your Experience'; + + @override + String get tutorialSettingsDesc => + 'Personalize the app in Settings to match your preferences.'; + + @override + String get tutorialSettingsTip1 => + 'Change download location and folder organization'; + + @override + String get tutorialSettingsTip2 => + 'Set default audio quality and format preferences'; + + @override + String get tutorialSettingsTip3 => 'Customize app theme and appearance'; + + @override + String get tutorialReadyMessage => + 'You\'re all set! Start downloading your favorite music now.'; + + @override + String get tutorialExample => 'EXAMPLE'; + + @override + String get libraryForceFullScan => 'Force Full Scan'; + + @override + String get libraryForceFullScanSubtitle => 'Rescan all files, ignoring cache'; + + @override + String get cleanupOrphanedDownloads => 'Cleanup Orphaned Downloads'; + + @override + String get cleanupOrphanedDownloadsSubtitle => + 'Remove history entries for files that no longer exist'; + + @override + String cleanupOrphanedDownloadsResult(int count) { + return 'Removed $count orphaned entries from history'; + } + + @override + String get cleanupOrphanedDownloadsNone => 'No orphaned entries found'; + + @override + String get cacheTitle => 'Storage & Cache'; + + @override + String get cacheSummaryTitle => 'Cache overview'; + + @override + String get cacheSummarySubtitle => + 'Clearing cache will not remove downloaded music files.'; + + @override + String cacheEstimatedTotal(String size) { + return 'Estimated cache usage: $size'; + } + + @override + String get cacheSectionStorage => 'Cached Data'; + + @override + String get cacheSectionMaintenance => 'Maintenance'; + + @override + String get cacheAppDirectory => 'App cache directory'; + + @override + String get cacheAppDirectoryDesc => + 'HTTP responses, WebView data, and other temporary app data.'; + + @override + String get cacheTempDirectory => 'Temporary directory'; + + @override + String get cacheTempDirectoryDesc => + 'Temporary files from downloads and audio conversion.'; + + @override + String get cacheCoverImage => 'Cover image cache'; + + @override + String get cacheCoverImageDesc => + 'Downloaded album and track cover art. Will re-download when viewed.'; + + @override + String get cacheLibraryCover => 'Library cover cache'; + + @override + String get cacheLibraryCoverDesc => + 'Cover art extracted from local music files. Will re-extract on next scan.'; + + @override + String get cacheExploreFeed => 'Explore feed cache'; + + @override + String get cacheExploreFeedDesc => + 'Explore tab content (new releases, trending). Will refresh on next visit.'; + + @override + String get cacheTrackLookup => 'Track lookup cache'; + + @override + String get cacheTrackLookupDesc => + 'Spotify/Deezer track ID lookups. Clearing may slow next few searches.'; + + @override + String get cacheCleanupUnusedDesc => + 'Remove orphaned download history and library entries for missing files.'; + + @override + String get cacheNoData => 'No cached data'; + + @override + String cacheSizeWithFiles(String size, int count) { + return '$size in $count files'; + } + + @override + String cacheSizeOnly(String size) { + return '$size'; + } + + @override + String cacheEntries(int count) { + return '$count entries'; + } + + @override + String cacheClearSuccess(String target) { + return 'Cleared: $target'; + } + + @override + String get cacheClearConfirmTitle => 'Clear cache?'; + + @override + String cacheClearConfirmMessage(String target) { + return 'This will clear cached data for $target. Downloaded music files will not be deleted.'; + } + + @override + String get cacheClearAllConfirmTitle => 'Clear all cache?'; + + @override + String get cacheClearAllConfirmMessage => + 'This will clear all cache categories on this page. Downloaded music files will not be deleted.'; + + @override + String get cacheClearAll => 'Clear all cache'; + + @override + String get cacheCleanupUnused => 'Cleanup unused data'; + + @override + String get cacheCleanupUnusedSubtitle => + 'Remove orphaned download history and missing library entries'; + + @override + String cacheCleanupResult(int downloadCount, int libraryCount) { + return 'Cleanup completed: $downloadCount orphaned downloads, $libraryCount missing library entries'; + } + + @override + String get cacheRefreshStats => 'Refresh stats'; + + @override + String get trackSaveCoverArt => 'Save Cover Art'; + + @override + String get trackSaveCoverArtSubtitle => 'Save album art as .jpg file'; + + @override + String get trackSaveLyrics => 'Save Lyrics (.lrc)'; + + @override + String get trackSaveLyricsSubtitle => 'Fetch and save lyrics as .lrc file'; + + @override + String get trackSaveLyricsProgress => 'Saving lyrics...'; + + @override + String get trackReEnrich => 'Re-enrich Metadata'; + + @override + String get trackReEnrichSubtitle => + 'Re-embed metadata without re-downloading'; + + @override + String get trackReEnrichOnlineSubtitle => + 'Search metadata online and embed into file'; + + @override + String get trackEditMetadata => 'Edit Metadata'; + + @override + String trackCoverSaved(String fileName) { + return 'Cover art saved to $fileName'; + } + + @override + String get trackCoverNoSource => 'No cover art source available'; + + @override + String trackLyricsSaved(String fileName) { + return 'Lyrics saved to $fileName'; + } + + @override + String get trackReEnrichProgress => 'Re-enriching metadata...'; + + @override + String get trackReEnrichSearching => 'Searching metadata online...'; + + @override + String get trackReEnrichSuccess => 'Metadata re-enriched successfully'; + + @override + String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed'; + + @override + String trackSaveFailed(String error) { + return 'Failed: $error'; + } + + @override + String get trackConvertFormat => 'Convert Format'; + + @override + String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + + @override + String get trackConvertTitle => 'Convert Audio'; + + @override + String get trackConvertTargetFormat => 'Target Format'; + + @override + String get trackConvertBitrate => 'Bitrate'; + + @override + String get trackConvertConfirmTitle => 'Confirm Conversion'; + + @override + String trackConvertConfirmMessage( + String sourceFormat, + String targetFormat, + String bitrate, + ) { + return 'Convert from $sourceFormat to $targetFormat at $bitrate?\n\nThe original file will be deleted after conversion.'; + } + + @override + String get trackConvertConverting => 'Converting audio...'; + + @override + String trackConvertSuccess(String format) { + return 'Converted to $format successfully'; + } + + @override + String get trackConvertFailed => 'Conversion failed'; } diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index a491c0e3..306ad760 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -19,7 +19,7 @@ class AppLocalizationsRu extends AppLocalizations { String get navHome => 'Главная'; @override - String get navLibrary => 'Library'; + String get navLibrary => 'Библиотека'; @override String get navHistory => 'История'; @@ -356,7 +356,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get optionsSpotifyDeprecationWarning => - 'Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.'; + 'Поиск Spotify устареет 3 марта 2026 года из-за изменений Spotify API. Пожалуйста, перейдите на Deezer.'; @override String get extensionsTitle => 'Расширения'; @@ -486,7 +486,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get aboutSjdonadoDesc => - 'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!'; + 'Создатель I Don\'t Have Spotify (IDHS). Резервный резолвер ссылки'; @override String get aboutDoubleDouble => 'DoubleDouble'; @@ -507,7 +507,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get aboutSpotiSaverDesc => - 'Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!'; + 'Потоковая передача Tidal Hi-Res FLAC. Ключевая часть lossless головоломки!'; @override String get aboutAppDescription => @@ -712,7 +712,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get setupIcloudNotSupported => - 'iCloud Drive is not supported. Please use the app Documents folder.'; + 'iCloud Drive не поддерживается. Пожалуйста, используйте папку Документы.'; @override String get setupDownloadInFlac => 'Скачать Spotify треки во FLAC'; @@ -975,7 +975,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String snackbarAlreadyInLibrary(String trackName) { - return '\"$trackName\" already exists in your library'; + return '\"$trackName\" уже есть в вашей библиотеке'; } @override @@ -1209,6 +1209,13 @@ class AppLocalizationsRu extends AppLocalizations { return '$artist - $title'; } + @override + String get filenameShowAdvancedTags => 'Show advanced tags'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; + @override String get folderOrganization => 'Организация папок'; @@ -1918,33 +1925,35 @@ class AppLocalizationsRu extends AppLocalizations { String get qualityLossy => 'Lossy'; @override - String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)'; + String get qualityLossyMp3Subtitle => + 'Opus 320 кбит/с (конвертировать из FLAC)'; @override - String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)'; + String get qualityLossyOpusSubtitle => + 'Opus 128 кбит/с (конвертировать из FLAC)'; @override - String get enableLossyOption => 'Enable Lossy Option'; + String get enableLossyOption => 'Включить опцию Lossy'; @override - String get enableLossyOptionSubtitleOn => 'Lossy quality option is available'; + String get enableLossyOptionSubtitleOn => 'Доступно качество с потерями'; @override String get enableLossyOptionSubtitleOff => - 'Downloads FLAC then converts to lossy format'; + 'Скачивать FLAC и конвертировать в MP3 320 кбит/с'; @override - String get lossyFormat => 'Lossy Format'; + String get lossyFormat => 'Формат с потерями'; @override - String get lossyFormatDescription => 'Choose the lossy format for conversion'; + String get lossyFormatDescription => 'Выберите Lossy формат для конвертации'; @override - String get lossyFormatMp3Subtitle => '320kbps, best compatibility'; + String get lossyFormatMp3Subtitle => '320Кбит/с, лучшая совместимость'; @override String get lossyFormatOpusSubtitle => - '128kbps, better quality at smaller size'; + '128кбит/с, лучшее качество при меньших размерах'; @override String get qualityNote => @@ -1952,7 +1961,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get youtubeQualityNote => - 'YouTube provides lossy audio only. Not part of lossless fallback.'; + 'YouTube обеспечивает только звук с потерями(Lossy).'; @override String get downloadAskBeforeDownload => 'Спрашивать перед скачиванием'; @@ -1967,7 +1976,8 @@ class AppLocalizationsRu extends AppLocalizations { String get downloadAlbumFolderStructure => 'Структура папок альбома'; @override - String get downloadUseAlbumArtistForFolders => 'Use Album Artist for folders'; + String get downloadUseAlbumArtistForFolders => + 'Использовать исполнителя альбома для папок'; @override String get downloadUseAlbumArtistForFoldersAlbumSubtitle => @@ -1975,7 +1985,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get downloadUseAlbumArtistForFoldersTrackSubtitle => - 'Artist folders use Track Artist only'; + 'Папки исполнителя используют только трек исполнителя'; @override String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders'; @@ -2069,37 +2079,37 @@ class AppLocalizationsRu extends AppLocalizations { 'Вы уверены, что хотите очистить все загрузки?'; @override - String get queueExportFailed => 'Export'; + String get queueExportFailed => 'Экспорт'; @override String get queueExportFailedSuccess => - 'Failed downloads exported to TXT file'; + 'Сбой при экспорте загрузок в файл TXT'; @override - String get queueExportFailedClear => 'Clear Failed'; + String get queueExportFailedClear => 'Не удалось очистить'; @override - String get queueExportFailedError => 'Failed to export downloads'; + String get queueExportFailedError => 'Не удалось экспортировать загрузки'; @override - String get settingsAutoExportFailed => 'Auto-export failed downloads'; + String get settingsAutoExportFailed => 'Автоэкспорт неудачных загрузок'; @override String get settingsAutoExportFailedSubtitle => - 'Save failed downloads to TXT file automatically'; + 'Автоматическое сохранение неудачных загрузок в TXT файл'; @override - String get settingsDownloadNetwork => 'Download Network'; + String get settingsDownloadNetwork => 'Сеть для скачивания'; @override - String get settingsDownloadNetworkAny => 'WiFi + Mobile Data'; + String get settingsDownloadNetworkAny => 'WiFi и мобильная сеть'; @override - String get settingsDownloadNetworkWifiOnly => 'WiFi Only'; + String get settingsDownloadNetworkWifiOnly => 'Только WiFi'; @override String get settingsDownloadNetworkSubtitle => - 'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.'; + 'Выберите, какую сеть использовать для скачивания. Когда установлено значение только WiFi — скачивания через мобильную сеть будут приостановлены.'; @override String get queueEmpty => 'Нет загрузок в очереди'; @@ -2231,10 +2241,10 @@ class AppLocalizationsRu extends AppLocalizations { String get recentTypePlaylist => 'Плейлист'; @override - String get recentEmpty => 'No recent items yet'; + String get recentEmpty => 'Нет недавних элементов'; @override - String get recentShowAllDownloads => 'Show All Downloads'; + String get recentShowAllDownloads => 'Показать все загрузки'; @override String recentPlaylistInfo(String name) { @@ -2314,234 +2324,254 @@ class AppLocalizationsRu extends AppLocalizations { 'Не удалось получить некоторые альбомы'; @override - String get sectionStorageAccess => 'Storage Access'; + String get sectionStorageAccess => 'Доступ к хранилищу'; @override - String get allFilesAccess => 'All Files Access'; + String get allFilesAccess => 'Доступ ко всем файлам'; @override - String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + String get allFilesAccessEnabledSubtitle => 'Можно записать в любую папку'; @override - String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + String get allFilesAccessDisabledSubtitle => + 'Ограничено только папками медиа'; @override String get allFilesAccessDescription => - 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + 'Включите, если вы сталкиваетесь с ошибками записи при сохранении в пользовательские папки. Android 13+ по умолчанию ограничивает доступ к определенным папкам.'; @override String get allFilesAccessDeniedMessage => - 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + 'В разрешении отказано. Пожалуйста, включите функцию «Доступ ко всем файлам» в настройках системы.'; @override String get allFilesAccessDisabledMessage => - 'All Files Access disabled. The app will use limited storage access.'; + 'Доступ ко всем файлам отключен. Приложение будет использовать ограниченный доступ к хранилищу.'; @override - String get settingsLocalLibrary => 'Local Library'; + String get settingsLocalLibrary => 'Локальная библиотека'; @override - String get settingsLocalLibrarySubtitle => 'Scan music & detect duplicates'; + String get settingsLocalLibrarySubtitle => + 'Сканировать и обнаружить дубликаты'; @override - String get settingsCache => 'Storage & Cache'; + String get settingsCache => 'Хранилище и кэш'; @override - String get settingsCacheSubtitle => 'View size and clear cached data'; + String get settingsCacheSubtitle => 'Просмотреть размер и очистить кэш'; @override - String get libraryTitle => 'Local Library'; + String get libraryTitle => 'Локальная библиотека'; @override - String get libraryStatus => 'Library Status'; + String get libraryStatus => 'Статус Библиотеки'; @override - String get libraryScanSettings => 'Scan Settings'; + String get libraryScanSettings => 'Настройки сканирования'; @override - String get libraryEnableLocalLibrary => 'Enable Local Library'; + String get libraryEnableLocalLibrary => 'Включить локальную библиотеку'; @override String get libraryEnableLocalLibrarySubtitle => - 'Scan and track your existing music'; + 'Сканировать и отслеживать вашу существующую музыку'; @override - String get libraryFolder => 'Library Folder'; + String get libraryFolder => 'Папка библиотеки'; @override - String get libraryFolderHint => 'Tap to select folder'; + String get libraryFolderHint => 'Нажмите, чтобы выбрать папку'; @override - String get libraryShowDuplicateIndicator => 'Show Duplicate Indicator'; + String get libraryShowDuplicateIndicator => 'Показать индикатор дубликатов'; @override String get libraryShowDuplicateIndicatorSubtitle => - 'Show when searching for existing tracks'; + 'Показать при поиске существующих треков'; @override - String get libraryActions => 'Actions'; + String get libraryActions => 'Действия'; @override - String get libraryScan => 'Scan Library'; + String get libraryScan => 'Сканировать библиотеку'; @override - String get libraryScanSubtitle => 'Scan for audio files'; + String get libraryScanSubtitle => 'Сканировать аудио файлы'; @override - String get libraryScanSelectFolderFirst => 'Select a folder first'; + String get libraryScanSelectFolderFirst => 'Сначала выберите папку'; @override - String get libraryCleanupMissingFiles => 'Cleanup Missing Files'; + String get libraryCleanupMissingFiles => 'Очистка отсутствующих файлов'; @override String get libraryCleanupMissingFilesSubtitle => - 'Remove entries for files that no longer exist'; + 'Удалить записи для файлов, которых больше не существует'; @override - String get libraryClear => 'Clear Library'; + String get libraryClear => 'Очистить библиотеку'; @override - String get libraryClearSubtitle => 'Remove all scanned tracks'; + String get libraryClearSubtitle => 'Удалить все сканированные треки'; @override - String get libraryClearConfirmTitle => 'Clear Library'; + String get libraryClearConfirmTitle => 'Очистить библиотеку'; @override String get libraryClearConfirmMessage => - 'This will remove all scanned tracks from your library. Your actual music files will not be deleted.'; + 'Это удалит все сканированные треки из вашей библиотеки. Ваши фактические файлы не будут удалены.'; @override - String get libraryAbout => 'About Local Library'; + String get libraryAbout => 'О локальной библиотеке'; @override String get libraryAboutDescription => - 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.'; + 'Сканирует существующую коллекцию музыки для обнаружения дубликатов при загрузке. Поддерживает форматы FLAC, M4A, MP3, Opus и OGG. Метаданные читаются из тегов файлов, если доступны.'; @override String libraryTracksCount(int count) { - return '$count tracks'; + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'треков', + many: 'треков', + few: 'трека', + one: 'трек', + ); + return '$count $_temp0'; } @override String libraryLastScanned(String time) { - return 'Last scanned: $time'; + return 'Последнее сканирование: $time'; } @override - String get libraryLastScannedNever => 'Never'; + String get libraryLastScannedNever => 'Никогда'; @override - String get libraryScanning => 'Scanning...'; + String get libraryScanning => 'Сканирование...'; @override String libraryScanProgress(String progress, int total) { - return '$progress% of $total files'; + return '$progress% из $total файлов'; } @override - String get libraryInLibrary => 'In Library'; + String get libraryInLibrary => 'В библиотеке'; @override String libraryRemovedMissingFiles(int count) { - return 'Removed $count missing files from library'; + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'отсутствующих файлов', + many: 'отсутствующих файлов', + few: 'трека', + one: 'отсутствующий файл', + ); + return 'Удалено $count $_temp0 в библиотеке'; } @override - String get libraryCleared => 'Library cleared'; + String get libraryCleared => 'Библиотека очищена'; @override - String get libraryStorageAccessRequired => 'Storage Access Required'; + String get libraryStorageAccessRequired => 'Требуется доступ к хранилищу'; @override String get libraryStorageAccessMessage => - 'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.'; + 'SpotiFLAC требуется доступ к хранилищу для сканирования вашей библиотеки музыки. Пожалуйста, предоставьте разрешение в настройках.'; @override - String get libraryFolderNotExist => 'Selected folder does not exist'; + String get libraryFolderNotExist => 'Выбранной папки не существует'; @override - String get librarySourceDownloaded => 'Downloaded'; + String get librarySourceDownloaded => 'Скачанные'; @override - String get librarySourceLocal => 'Local'; + String get librarySourceLocal => 'Локальные'; @override - String get libraryFilterAll => 'All'; + String get libraryFilterAll => 'Все'; @override - String get libraryFilterDownloaded => 'Downloaded'; + String get libraryFilterDownloaded => 'Скачанные'; @override - String get libraryFilterLocal => 'Local'; + String get libraryFilterLocal => 'Локальные'; @override - String get libraryFilterTitle => 'Filters'; + String get libraryFilterTitle => 'Фильтры'; @override - String get libraryFilterReset => 'Reset'; + String get libraryFilterReset => 'Сброс'; @override - String get libraryFilterApply => 'Apply'; + String get libraryFilterApply => 'Применить'; @override - String get libraryFilterSource => 'Source'; + String get libraryFilterSource => 'Источник'; @override - String get libraryFilterQuality => 'Quality'; + String get libraryFilterQuality => 'Качество'; @override - String get libraryFilterQualityHiRes => 'Hi-Res (24bit)'; + String get libraryFilterQualityHiRes => 'Hi-Res (24 бит)'; @override - String get libraryFilterQualityCD => 'CD (16bit)'; + String get libraryFilterQualityCD => 'CD (16 бит)'; @override - String get libraryFilterQualityLossy => 'Lossy'; + String get libraryFilterQualityLossy => 'С потерями'; @override - String get libraryFilterFormat => 'Format'; + String get libraryFilterFormat => 'Формат'; @override - String get libraryFilterDate => 'Date Added'; + String get libraryFilterDate => 'Дата добавления'; @override - String get libraryFilterDateToday => 'Today'; + String get libraryFilterDateToday => 'Сегодня'; @override - String get libraryFilterDateWeek => 'This Week'; + String get libraryFilterDateWeek => 'На этой неделе'; @override - String get libraryFilterDateMonth => 'This Month'; + String get libraryFilterDateMonth => 'В этом месяце'; @override - String get libraryFilterDateYear => 'This Year'; + String get libraryFilterDateYear => 'В этом году'; @override - String get libraryFilterSort => 'Sort'; + String get libraryFilterSort => 'Сортировка'; @override - String get libraryFilterSortLatest => 'Latest'; + String get libraryFilterSortLatest => 'Последние'; @override - String get libraryFilterSortOldest => 'Oldest'; + String get libraryFilterSortOldest => 'Старые'; @override String libraryFilterActive(int count) { - return '$count filter(s) active'; + return '$count фильтр(-ов) активно'; } @override - String get timeJustNow => 'Just now'; + String get timeJustNow => 'Только что'; @override String timeMinutesAgo(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count minutes ago', - one: '1 minute ago', + other: '$count минут', + many: '$count минут', + few: '$count минуты', + one: '$count минуту', ); - return '$_temp0'; + return '$_temp0 назад'; } @override @@ -2549,160 +2579,186 @@ class AppLocalizationsRu extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: '$count hours ago', - one: '1 hour ago', + other: '$count часов', + many: '$count часов', + few: '$count часа', + one: '$count час', ); - return '$_temp0'; + return '$_temp0 назад'; } @override - String get storageSwitchTitle => 'Switch Storage Mode'; + String get storageSwitchTitle => 'Сменить режим хранения'; @override - String get storageSwitchToSafTitle => 'Switch to SAF Storage?'; + String get storageSwitchToSafTitle => 'Переключиться на SAF хранилище?'; @override - String get storageSwitchToAppTitle => 'Switch to App Storage?'; + String get storageSwitchToAppTitle => 'Переключиться хранилище приложения?'; @override String get storageSwitchToSafMessage => - 'Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.'; + 'Ваши скачанные файлы останутся в текущем расположении и будут доступны.\n\nНовые файлы будут сохранены в выбранной вами папке SAF.'; @override String get storageSwitchToAppMessage => - 'Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.'; + 'Ваши скачанные файлы останутся в текущем выбранной вами папке SAF.\n\nНовые файлы будут сохранены в папке Music/SpotiFLAC.'; @override - String get storageSwitchExistingDownloads => 'Existing Downloads'; + String get storageSwitchExistingDownloads => 'Существующие загрузки'; @override String storageSwitchExistingDownloadsInfo(int count, String mode) { - return '$count tracks in $mode storage'; + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count треков', + many: '$count треков', + few: '$count трека', + one: '$count трек', + ); + return '$_temp0 в $mode хранилище'; } @override - String get storageSwitchNewDownloads => 'New Downloads'; + String get storageSwitchNewDownloads => 'Новые загрузки'; @override String storageSwitchNewDownloadsLocation(String location) { - return 'Will be saved to: $location'; + return 'Будет сохранено в: $location'; } @override - String get storageSwitchContinue => 'Continue'; + String get storageSwitchContinue => 'Продолжить'; @override - String get storageSwitchSelectFolder => 'Select SAF Folder'; + String get storageSwitchSelectFolder => 'Выберите папку SAF'; @override - String get storageAppStorage => 'App Storage'; + String get storageAppStorage => 'Хранилище приложения'; @override - String get storageSafStorage => 'SAF Storage'; + String get storageSafStorage => 'Хранилище SAF'; @override String storageModeBadge(String mode) { - return 'Storage: $mode'; + return 'Хранилище: $mode'; } @override - String get storageStatsTitle => 'Storage Statistics'; + String get storageStatsTitle => 'Статистика хранилища'; @override String storageStatsAppCount(int count) { - return '$count tracks in App Storage'; + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count треков', + many: '$count треков', + few: '$count трека', + one: '$count трек', + ); + return '$_temp0 в хранилище приложения'; } @override String storageStatsSafCount(int count) { - return '$count tracks in SAF Storage'; + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count треков', + many: '$count треков', + few: '$count трека', + one: '$count трек', + ); + return '$_temp0 в вашей папке в SAF'; } @override - String get storageModeInfo => 'Your files are stored in multiple locations'; + String get storageModeInfo => 'Ваши файлы хранятся в нескольких местах'; @override - String get tutorialWelcomeTitle => 'Welcome to SpotiFLAC!'; + String get tutorialWelcomeTitle => 'Добро пожаловать в SpotiFLAC!'; @override String get tutorialWelcomeDesc => - 'Let\'s learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.'; + 'Давайте научимся скачивать свою любимую музыку в качестве без потерь. В этом кратком руководстве мы покажем вам основы.'; @override String get tutorialWelcomeTip1 => - 'Download music from Spotify, Deezer, or paste any supported URL'; + 'Скачивайте музыку из Spotify, Deezer, или вставьте любой поддерживаемый URL'; @override String get tutorialWelcomeTip2 => - 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + 'Скачайте FLAC с Tidal, Qobuz или Amazon Music'; @override String get tutorialWelcomeTip3 => - 'Automatic metadata, cover art, and lyrics embedding'; + 'Автоматическое встраивание метаданных, обложек и текстов песен'; @override - String get tutorialSearchTitle => 'Finding Music'; + String get tutorialSearchTitle => 'Поиск музыки'; @override String get tutorialSearchDesc => - 'There are two easy ways to find music you want to download.'; + 'Есть два простых способа найти музыку, которую вы хотите скачать.'; @override String get tutorialSearchTip1 => - 'Paste a Spotify or Deezer URL directly in the search box'; + 'Вставьте ссылку Spotify или Deezer прямо в поле поиска'; @override String get tutorialSearchTip2 => - 'Or type the song name, artist, or album to search'; + 'Или введите название песни, исполнителя или альбом для поиска'; @override String get tutorialSearchTip3 => - 'Supports tracks, albums, playlists, and artist pages'; + 'Поддержка треков, альбомов, плейлистов и страниц исполнителей'; @override - String get tutorialDownloadTitle => 'Downloading Music'; + String get tutorialDownloadTitle => 'Скачивание музыки'; @override String get tutorialDownloadDesc => - 'Downloading music is simple and fast. Here\'s how it works.'; + 'Скачивание музыки просто и быстро. Вот как это работает.'; @override String get tutorialDownloadTip1 => - 'Tap the download button next to any track to start downloading'; + 'Нажмите кнопку скачать рядом с любым треком, чтобы начать скачивание'; @override String get tutorialDownloadTip2 => - 'Choose your preferred quality (FLAC, Hi-Res, or MP3)'; + 'Выберите предпочитаемое качество (FLAC, Hi-Res или MP3)'; @override String get tutorialDownloadTip3 => - 'Download entire albums or playlists with one tap'; + 'Скачать все альбомы или плейлисты одним нажатием'; @override - String get tutorialLibraryTitle => 'Your Library'; + String get tutorialLibraryTitle => 'Ваша библиотека'; @override String get tutorialLibraryDesc => - 'All your downloaded music is organized in the Library tab.'; + 'Вся скачанная музыка организована во вкладке Библиотека.'; @override String get tutorialLibraryTip1 => - 'View download progress and queue in the Library tab'; + 'Просмотр прогресса загрузки и очереди на вкладке Библиотека'; @override String get tutorialLibraryTip2 => - 'Tap any track to play it with your music player'; + 'Нажмите на любой трек, чтобы воспроизвести его с помощью вашего музыкального плеера'; @override String get tutorialLibraryTip3 => - 'Switch between list and grid view for better browsing'; + 'Переключение между списком и сеткой для лучшего просмотра'; @override - String get tutorialExtensionsTitle => 'Extensions'; + String get tutorialExtensionsTitle => 'Расширения'; @override String get tutorialExtensionsDesc => - 'Extend the app\'s capabilities with community extensions.'; + 'Расширьте возможности приложения с расширениями от сообщества.'; @override String get tutorialExtensionsTip1 => @@ -2710,14 +2766,14 @@ class AppLocalizationsRu extends AppLocalizations { @override String get tutorialExtensionsTip2 => - 'Add new download providers or search sources'; + 'Добавить новых поставщиков загрузок или поиска'; @override String get tutorialExtensionsTip3 => 'Get lyrics, enhanced metadata, and more features'; @override - String get tutorialSettingsTitle => 'Customize Your Experience'; + String get tutorialSettingsTitle => 'Настройте приложение под себя'; @override String get tutorialSettingsDesc => @@ -2725,27 +2781,28 @@ class AppLocalizationsRu extends AppLocalizations { @override String get tutorialSettingsTip1 => - 'Change download location and folder organization'; + 'Изменить местоположение и организацию папок для скачивания'; @override String get tutorialSettingsTip2 => - 'Set default audio quality and format preferences'; + 'Настройте качество и формата аудиофайла по умолчанию'; @override - String get tutorialSettingsTip3 => 'Customize app theme and appearance'; + String get tutorialSettingsTip3 => 'Настроить тему и внешний вид приложения'; @override String get tutorialReadyMessage => - 'You\'re all set! Start downloading your favorite music now.'; + 'Всё готово! Начните загружать любимую музыку прямо сейчас.'; @override String get tutorialExample => 'EXAMPLE'; @override - String get libraryForceFullScan => 'Force Full Scan'; + String get libraryForceFullScan => 'Полное сканирование'; @override - String get libraryForceFullScanSubtitle => 'Rescan all files, ignoring cache'; + String get libraryForceFullScanSubtitle => + 'Пересканировать все файлы, игнорировать кэш'; @override String get cleanupOrphanedDownloads => 'Cleanup Orphaned Downloads'; @@ -2763,10 +2820,10 @@ class AppLocalizationsRu extends AppLocalizations { String get cleanupOrphanedDownloadsNone => 'No orphaned entries found'; @override - String get cacheTitle => 'Storage & Cache'; + String get cacheTitle => 'Хранилище и кэш'; @override - String get cacheSummaryTitle => 'Cache overview'; + String get cacheSummaryTitle => 'Просмотр кэша'; @override String get cacheSummarySubtitle => @@ -2778,13 +2835,13 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get cacheSectionStorage => 'Cached Data'; + String get cacheSectionStorage => 'Кэшированные данные'; @override String get cacheSectionMaintenance => 'Maintenance'; @override - String get cacheAppDirectory => 'App cache directory'; + String get cacheAppDirectory => 'Папка кэша приложения'; @override String get cacheAppDirectoryDesc => @@ -2830,11 +2887,11 @@ class AppLocalizationsRu extends AppLocalizations { 'Remove orphaned download history and library entries for missing files.'; @override - String get cacheNoData => 'No cached data'; + String get cacheNoData => 'Нет кэшированных данных'; @override String cacheSizeWithFiles(String size, int count) { - return '$size in $count files'; + return '$size в $count файлах'; } @override @@ -2849,11 +2906,11 @@ class AppLocalizationsRu extends AppLocalizations { @override String cacheClearSuccess(String target) { - return 'Cleared: $target'; + return 'Очищено: $target'; } @override - String get cacheClearConfirmTitle => 'Clear cache?'; + String get cacheClearConfirmTitle => 'Очистить кэш?'; @override String cacheClearConfirmMessage(String target) { @@ -2861,17 +2918,17 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get cacheClearAllConfirmTitle => 'Clear all cache?'; + String get cacheClearAllConfirmTitle => 'Очистить весь кэш?'; @override String get cacheClearAllConfirmMessage => - 'This will clear all cache categories on this page. Downloaded music files will not be deleted.'; + 'Это очистит все категории кэша на этой странице. Скачанные музыкальные файлы не будут удалены.'; @override - String get cacheClearAll => 'Clear all cache'; + String get cacheClearAll => 'Очистить весь кэш'; @override - String get cacheCleanupUnused => 'Cleanup unused data'; + String get cacheCleanupUnused => 'Очистка неиспользуемых данных'; @override String get cacheCleanupUnusedSubtitle => @@ -2883,19 +2940,20 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get cacheRefreshStats => 'Refresh stats'; + String get cacheRefreshStats => 'Обновить статистику'; @override - String get trackSaveCoverArt => 'Save Cover Art'; + String get trackSaveCoverArt => 'Сохранить обложку'; @override - String get trackSaveCoverArtSubtitle => 'Save album art as .jpg file'; + String get trackSaveCoverArtSubtitle => 'Сохранить обложку как файл .jpg'; @override - String get trackSaveLyrics => 'Save Lyrics (.lrc)'; + String get trackSaveLyrics => 'Сохранить текст (.lrc)'; @override - String get trackSaveLyricsSubtitle => 'Fetch and save lyrics as .lrc file'; + String get trackSaveLyricsSubtitle => + 'Получить и сохранить текст песни в формате .lrc'; @override String get trackSaveLyricsProgress => 'Saving lyrics...'; @@ -2912,36 +2970,37 @@ class AppLocalizationsRu extends AppLocalizations { 'Search metadata online and embed into file'; @override - String get trackEditMetadata => 'Edit Metadata'; + String get trackEditMetadata => 'Редактировать метаданные'; @override String trackCoverSaved(String fileName) { - return 'Cover art saved to $fileName'; + return 'Обложка сохранена в $fileName'; } @override - String get trackCoverNoSource => 'No cover art source available'; + String get trackCoverNoSource => 'Нет доступных источников обложки'; @override String trackLyricsSaved(String fileName) { - return 'Lyrics saved to $fileName'; + return 'Текст песни сохранен в $fileName'; } @override String get trackReEnrichProgress => 'Re-enriching metadata...'; @override - String get trackReEnrichSearching => 'Searching metadata online...'; + String get trackReEnrichSearching => 'Поиск метаданных в сети...'; @override String get trackReEnrichSuccess => 'Metadata re-enriched successfully'; @override - String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed'; + String get trackReEnrichFfmpegFailed => + 'Ошибка встраивания метаданных FFmpeg'; @override String trackSaveFailed(String error) { - return 'Failed: $error'; + return 'Ошибка: $error'; } @override diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index b5fad48e..fc66ca2b 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -1189,6 +1189,13 @@ class AppLocalizationsTr extends AppLocalizations { return '$artist - $title'; } + @override + String get filenameShowAdvancedTags => 'Show advanced tags'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; + @override String get folderOrganization => 'Klasör Organizasyonu'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 65a694d2..e1d656eb 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1182,6 +1182,2946 @@ class AppLocalizationsZh extends AppLocalizations { return '$artist - $title'; } + @override + String get filenameShowAdvancedTags => 'Show advanced tags'; + + @override + String get filenameShowAdvancedTagsDescription => + 'Enable formatted tags for track padding and date patterns'; + + @override + String get folderOrganization => 'Folder Organization'; + + @override + String get folderOrganizationNone => 'No organization'; + + @override + String get folderOrganizationByArtist => 'By Artist'; + + @override + String get folderOrganizationByAlbum => 'By Album'; + + @override + String get folderOrganizationByArtistAlbum => 'Artist/Album'; + + @override + String get folderOrganizationDescription => + 'Organize downloaded files into folders'; + + @override + String get folderOrganizationNoneSubtitle => 'All files in download folder'; + + @override + String get folderOrganizationByArtistSubtitle => + 'Separate folder for each artist'; + + @override + String get folderOrganizationByAlbumSubtitle => + 'Separate folder for each album'; + + @override + String get folderOrganizationByArtistAlbumSubtitle => + 'Nested folders for artist and album'; + + @override + String get updateAvailable => 'Update Available'; + + @override + String updateNewVersion(String version) { + return 'Version $version is available'; + } + + @override + String get updateDownload => 'Download'; + + @override + String get updateLater => 'Later'; + + @override + String get updateChangelog => 'Changelog'; + + @override + String get updateStartingDownload => 'Starting download...'; + + @override + String get updateDownloadFailed => 'Download failed'; + + @override + String get updateFailedMessage => 'Failed to download update'; + + @override + String get updateNewVersionReady => 'A new version is ready'; + + @override + String get updateCurrent => 'Current'; + + @override + String get updateNew => 'New'; + + @override + String get updateDownloading => 'Downloading...'; + + @override + String get updateWhatsNew => 'What\'s New'; + + @override + String get updateDownloadInstall => 'Download & Install'; + + @override + String get updateDontRemind => 'Don\'t remind'; + + @override + String get providerPriority => 'Provider Priority'; + + @override + String get providerPrioritySubtitle => 'Drag to reorder download providers'; + + @override + String get providerPriorityTitle => 'Provider Priority'; + + @override + String get providerPriorityDescription => + 'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.'; + + @override + String get providerPriorityInfo => + 'If a track is not available on the first provider, the app will automatically try the next one.'; + + @override + String get providerBuiltIn => 'Built-in'; + + @override + String get providerExtension => 'Extension'; + + @override + String get metadataProviderPriority => 'Metadata Provider Priority'; + + @override + String get metadataProviderPrioritySubtitle => + 'Order used when fetching track metadata'; + + @override + String get metadataProviderPriorityTitle => 'Metadata Priority'; + + @override + String get metadataProviderPriorityDescription => + 'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.'; + + @override + String get metadataProviderPriorityInfo => + 'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.'; + + @override + String get metadataNoRateLimits => 'No rate limits'; + + @override + String get metadataMayRateLimit => 'May rate limit'; + + @override + String get logTitle => 'Logs'; + + @override + String get logCopy => 'Copy Logs'; + + @override + String get logClear => 'Clear Logs'; + + @override + String get logShare => 'Share Logs'; + + @override + String get logEmpty => 'No logs yet'; + + @override + String get logCopied => 'Logs copied to clipboard'; + + @override + String get logSearchHint => 'Search logs...'; + + @override + String get logFilterLevel => 'Level'; + + @override + String get logFilterSection => 'Filter'; + + @override + String get logShareLogs => 'Share logs'; + + @override + String get logClearLogs => 'Clear logs'; + + @override + String get logClearLogsTitle => 'Clear Logs'; + + @override + String get logClearLogsMessage => 'Are you sure you want to clear all logs?'; + + @override + String get logIspBlocking => 'ISP BLOCKING DETECTED'; + + @override + String get logRateLimited => 'RATE LIMITED'; + + @override + String get logNetworkError => 'NETWORK ERROR'; + + @override + String get logTrackNotFound => 'TRACK NOT FOUND'; + + @override + String get logFilterBySeverity => 'Filter logs by severity'; + + @override + String get logNoLogsYet => 'No logs yet'; + + @override + String get logNoLogsYetSubtitle => 'Logs will appear here as you use the app'; + + @override + String get logIssueSummary => 'Issue Summary'; + + @override + String get logIspBlockingDescription => + 'Your ISP may be blocking access to download services'; + + @override + String get logIspBlockingSuggestion => + 'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8'; + + @override + String get logRateLimitedDescription => 'Too many requests to the service'; + + @override + String get logRateLimitedSuggestion => + 'Wait a few minutes before trying again'; + + @override + String get logNetworkErrorDescription => 'Connection issues detected'; + + @override + String get logNetworkErrorSuggestion => 'Check your internet connection'; + + @override + String get logTrackNotFoundDescription => + 'Some tracks could not be found on download services'; + + @override + String get logTrackNotFoundSuggestion => + 'The track may not be available in lossless quality'; + + @override + String logTotalErrors(int count) { + return 'Total errors: $count'; + } + + @override + String logAffected(String domains) { + return 'Affected: $domains'; + } + + @override + String logEntriesFiltered(int count) { + return 'Entries ($count filtered)'; + } + + @override + String logEntries(int count) { + return 'Entries ($count)'; + } + + @override + String get credentialsTitle => 'Spotify Credentials'; + + @override + String get credentialsDescription => + 'Enter your Client ID and Secret to use your own Spotify application quota.'; + + @override + String get credentialsClientId => 'Client ID'; + + @override + String get credentialsClientIdHint => 'Paste Client ID'; + + @override + String get credentialsClientSecret => 'Client Secret'; + + @override + String get credentialsClientSecretHint => 'Paste Client Secret'; + + @override + String get channelStable => 'Stable'; + + @override + String get channelPreview => 'Preview'; + + @override + String get sectionSearchSource => 'Search Source'; + + @override + String get sectionDownload => 'Download'; + + @override + String get sectionPerformance => 'Performance'; + + @override + String get sectionApp => 'App'; + + @override + String get sectionData => 'Data'; + + @override + String get sectionDebug => 'Debug'; + + @override + String get sectionService => 'Service'; + + @override + String get sectionAudioQuality => 'Audio Quality'; + + @override + String get sectionFileSettings => 'File Settings'; + + @override + String get sectionLyrics => 'Lyrics'; + + @override + String get lyricsMode => 'Lyrics Mode'; + + @override + String get lyricsModeDescription => + 'Choose how lyrics are saved with your downloads'; + + @override + String get lyricsModeEmbed => 'Embed in file'; + + @override + String get lyricsModeEmbedSubtitle => 'Lyrics stored inside FLAC metadata'; + + @override + String get lyricsModeExternal => 'External .lrc file'; + + @override + String get lyricsModeExternalSubtitle => + 'Separate .lrc file for players like Samsung Music'; + + @override + String get lyricsModeBoth => 'Both'; + + @override + String get lyricsModeBothSubtitle => 'Embed and save .lrc file'; + + @override + String get sectionColor => 'Color'; + + @override + String get sectionTheme => 'Theme'; + + @override + String get sectionLayout => 'Layout'; + + @override + String get sectionLanguage => 'Language'; + + @override + String get appearanceLanguage => 'App Language'; + + @override + String get appearanceLanguageSubtitle => 'Choose your preferred language'; + + @override + String get settingsAppearanceSubtitle => 'Theme, colors, display'; + + @override + String get settingsDownloadSubtitle => 'Service, quality, filename format'; + + @override + String get settingsOptionsSubtitle => 'Fallback, lyrics, cover art, updates'; + + @override + String get settingsExtensionsSubtitle => 'Manage download providers'; + + @override + String get settingsLogsSubtitle => 'View app logs for debugging'; + + @override + String get loadingSharedLink => 'Loading shared link...'; + + @override + String get pressBackAgainToExit => 'Press back again to exit'; + + @override + String get tracksHeader => 'Tracks'; + + @override + String downloadAllCount(int count) { + return 'Download All ($count)'; + } + + @override + String tracksCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count tracks', + one: '1 track', + ); + return '$_temp0'; + } + + @override + String get trackCopyFilePath => 'Copy file path'; + + @override + String get trackRemoveFromDevice => 'Remove from device'; + + @override + String get trackLoadLyrics => 'Load Lyrics'; + + @override + String get trackMetadata => 'Metadata'; + + @override + String get trackFileInfo => 'File Info'; + + @override + String get trackLyrics => 'Lyrics'; + + @override + String get trackFileNotFound => 'File not found'; + + @override + String get trackOpenInDeezer => 'Open in Deezer'; + + @override + String get trackOpenInSpotify => 'Open in Spotify'; + + @override + String get trackTrackName => 'Track name'; + + @override + String get trackArtist => 'Artist'; + + @override + String get trackAlbumArtist => 'Album artist'; + + @override + String get trackAlbum => 'Album'; + + @override + String get trackTrackNumber => 'Track number'; + + @override + String get trackDiscNumber => 'Disc number'; + + @override + String get trackDuration => 'Duration'; + + @override + String get trackAudioQuality => 'Audio quality'; + + @override + String get trackReleaseDate => 'Release date'; + + @override + String get trackGenre => 'Genre'; + + @override + String get trackLabel => 'Label'; + + @override + String get trackCopyright => 'Copyright'; + + @override + String get trackDownloaded => 'Downloaded'; + + @override + String get trackCopyLyrics => 'Copy lyrics'; + + @override + String get trackLyricsNotAvailable => 'Lyrics not available for this track'; + + @override + String get trackLyricsTimeout => 'Request timed out. Try again later.'; + + @override + String get trackLyricsLoadFailed => 'Failed to load lyrics'; + + @override + String get trackEmbedLyrics => 'Embed Lyrics'; + + @override + String get trackLyricsEmbedded => 'Lyrics embedded successfully'; + + @override + String get trackInstrumental => 'Instrumental track'; + + @override + String get trackCopiedToClipboard => 'Copied to clipboard'; + + @override + String get trackDeleteConfirmTitle => 'Remove from device?'; + + @override + String get trackDeleteConfirmMessage => + 'This will permanently delete the downloaded file and remove it from your history.'; + + @override + String trackCannotOpen(String message) { + return 'Cannot open: $message'; + } + + @override + String get dateToday => 'Today'; + + @override + String get dateYesterday => 'Yesterday'; + + @override + String dateDaysAgo(int count) { + return '$count days ago'; + } + + @override + String dateWeeksAgo(int count) { + return '$count weeks ago'; + } + + @override + String dateMonthsAgo(int count) { + return '$count months ago'; + } + + @override + String get concurrentSequential => 'Sequential'; + + @override + String get concurrentParallel2 => '2 Parallel'; + + @override + String get concurrentParallel3 => '3 Parallel'; + + @override + String get tapToSeeError => 'Tap to see error details'; + + @override + String get storeFilterAll => 'All'; + + @override + String get storeFilterMetadata => 'Metadata'; + + @override + String get storeFilterDownload => 'Download'; + + @override + String get storeFilterUtility => 'Utility'; + + @override + String get storeFilterLyrics => 'Lyrics'; + + @override + String get storeFilterIntegration => 'Integration'; + + @override + String get storeClearFilters => 'Clear filters'; + + @override + String get storeNoResults => 'No extensions found'; + + @override + String get extensionProviderPriority => 'Provider Priority'; + + @override + String get extensionInstallButton => 'Install Extension'; + + @override + String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; + + @override + String get extensionDefaultProviderSubtitle => 'Use built-in search'; + + @override + String get extensionAuthor => 'Author'; + + @override + String get extensionId => 'ID'; + + @override + String get extensionError => 'Error'; + + @override + String get extensionCapabilities => 'Capabilities'; + + @override + String get extensionMetadataProvider => 'Metadata Provider'; + + @override + String get extensionDownloadProvider => 'Download Provider'; + + @override + String get extensionLyricsProvider => 'Lyrics Provider'; + + @override + String get extensionUrlHandler => 'URL Handler'; + + @override + String get extensionQualityOptions => 'Quality Options'; + + @override + String get extensionPostProcessingHooks => 'Post-Processing Hooks'; + + @override + String get extensionPermissions => 'Permissions'; + + @override + String get extensionSettings => 'Settings'; + + @override + String get extensionRemoveButton => 'Remove Extension'; + + @override + String get extensionUpdated => 'Updated'; + + @override + String get extensionMinAppVersion => 'Min App Version'; + + @override + String get extensionCustomTrackMatching => 'Custom Track Matching'; + + @override + String get extensionPostProcessing => 'Post-Processing'; + + @override + String extensionHooksAvailable(int count) { + return '$count hook(s) available'; + } + + @override + String extensionPatternsCount(int count) { + return '$count pattern(s)'; + } + + @override + String extensionStrategy(String strategy) { + return 'Strategy: $strategy'; + } + + @override + String get extensionsProviderPrioritySection => 'Provider Priority'; + + @override + String get extensionsInstalledSection => 'Installed Extensions'; + + @override + String get extensionsNoExtensions => 'No extensions installed'; + + @override + String get extensionsNoExtensionsSubtitle => + 'Install .spotiflac-ext files to add new providers'; + + @override + String get extensionsInstallButton => 'Install Extension'; + + @override + String get extensionsInfoTip => + 'Extensions can add new metadata and download providers. Only install extensions from trusted sources.'; + + @override + String get extensionsInstalledSuccess => 'Extension installed successfully'; + + @override + String get extensionsDownloadPriority => 'Download Priority'; + + @override + String get extensionsDownloadPrioritySubtitle => 'Set download service order'; + + @override + String get extensionsNoDownloadProvider => + 'No extensions with download provider'; + + @override + String get extensionsMetadataPriority => 'Metadata Priority'; + + @override + String get extensionsMetadataPrioritySubtitle => + 'Set search & metadata source order'; + + @override + String get extensionsNoMetadataProvider => + 'No extensions with metadata provider'; + + @override + String get extensionsSearchProvider => 'Search Provider'; + + @override + String get extensionsNoCustomSearch => 'No extensions with custom search'; + + @override + String get extensionsSearchProviderDescription => + 'Choose which service to use for searching tracks'; + + @override + String get extensionsCustomSearch => 'Custom search'; + + @override + String get extensionsErrorLoading => 'Error loading extension'; + + @override + String get qualityFlacLossless => 'FLAC Lossless'; + + @override + String get qualityFlacLosslessSubtitle => '16-bit / 44.1kHz'; + + @override + String get qualityHiResFlac => 'Hi-Res FLAC'; + + @override + String get qualityHiResFlacSubtitle => '24-bit / up to 96kHz'; + + @override + String get qualityHiResFlacMax => 'Hi-Res FLAC Max'; + + @override + String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz'; + + @override + String get qualityLossy => 'Lossy'; + + @override + String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)'; + + @override + String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)'; + + @override + String get enableLossyOption => 'Enable Lossy Option'; + + @override + String get enableLossyOptionSubtitleOn => 'Lossy quality option is available'; + + @override + String get enableLossyOptionSubtitleOff => + 'Downloads FLAC then converts to lossy format'; + + @override + String get lossyFormat => 'Lossy Format'; + + @override + String get lossyFormatDescription => 'Choose the lossy format for conversion'; + + @override + String get lossyFormatMp3Subtitle => '320kbps, best compatibility'; + + @override + String get lossyFormatOpusSubtitle => + '128kbps, better quality at smaller size'; + + @override + String get qualityNote => + 'Actual quality depends on track availability from the service'; + + @override + String get youtubeQualityNote => + 'YouTube provides lossy audio only. Not part of lossless fallback.'; + + @override + String get downloadAskBeforeDownload => 'Ask Before Download'; + + @override + String get downloadDirectory => 'Download Directory'; + + @override + String get downloadSeparateSinglesFolder => 'Separate Singles Folder'; + + @override + String get downloadAlbumFolderStructure => 'Album Folder Structure'; + + @override + String get downloadUseAlbumArtistForFolders => 'Use Album Artist for folders'; + + @override + String get downloadUseAlbumArtistForFoldersAlbumSubtitle => + 'Artist folders use Album Artist when available'; + + @override + String get downloadUseAlbumArtistForFoldersTrackSubtitle => + 'Artist folders use Track Artist only'; + + @override + String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders'; + + @override + String get downloadUsePrimaryArtistOnlyEnabled => + 'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)'; + + @override + String get downloadUsePrimaryArtistOnlyDisabled => + 'Full artist string used for folder name'; + + @override + String get downloadSaveFormat => 'Save Format'; + + @override + String get downloadSelectService => 'Select Service'; + + @override + String get downloadSelectQuality => 'Select Quality'; + + @override + String get downloadFrom => 'Download From'; + + @override + String get downloadDefaultQualityLabel => 'Default Quality'; + + @override + String get downloadBestAvailable => 'Best available'; + + @override + String get folderNone => 'None'; + + @override + String get folderNoneSubtitle => 'Save all files directly to download folder'; + + @override + String get folderArtist => 'Artist'; + + @override + String get folderArtistSubtitle => 'Artist Name/filename'; + + @override + String get folderAlbum => 'Album'; + + @override + String get folderAlbumSubtitle => 'Album Name/filename'; + + @override + String get folderArtistAlbum => 'Artist/Album'; + + @override + String get folderArtistAlbumSubtitle => 'Artist Name/Album Name/filename'; + + @override + String get serviceTidal => 'Tidal'; + + @override + String get serviceQobuz => 'Qobuz'; + + @override + String get serviceAmazon => 'Amazon'; + + @override + String get serviceDeezer => 'Deezer'; + + @override + String get serviceSpotify => 'Spotify'; + + @override + String get appearanceAmoledDark => 'AMOLED Dark'; + + @override + String get appearanceAmoledDarkSubtitle => 'Pure black background'; + + @override + String get appearanceChooseAccentColor => 'Choose Accent Color'; + + @override + String get appearanceChooseTheme => 'Theme Mode'; + + @override + String get queueTitle => 'Download Queue'; + + @override + String get queueClearAll => 'Clear All'; + + @override + String get queueClearAllMessage => + 'Are you sure you want to clear all downloads?'; + + @override + String get queueExportFailed => 'Export'; + + @override + String get queueExportFailedSuccess => + 'Failed downloads exported to TXT file'; + + @override + String get queueExportFailedClear => 'Clear Failed'; + + @override + String get queueExportFailedError => 'Failed to export downloads'; + + @override + String get settingsAutoExportFailed => 'Auto-export failed downloads'; + + @override + String get settingsAutoExportFailedSubtitle => + 'Save failed downloads to TXT file automatically'; + + @override + String get settingsDownloadNetwork => 'Download Network'; + + @override + String get settingsDownloadNetworkAny => 'WiFi + Mobile Data'; + + @override + String get settingsDownloadNetworkWifiOnly => 'WiFi Only'; + + @override + String get settingsDownloadNetworkSubtitle => + 'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.'; + + @override + String get queueEmpty => 'No downloads in queue'; + + @override + String get queueEmptySubtitle => 'Add tracks from the home screen'; + + @override + String get queueClearCompleted => 'Clear completed'; + + @override + String get queueDownloadFailed => 'Download Failed'; + + @override + String get queueTrackLabel => 'Track:'; + + @override + String get queueArtistLabel => 'Artist:'; + + @override + String get queueErrorLabel => 'Error:'; + + @override + String get queueUnknownError => 'Unknown error'; + + @override + String get albumFolderArtistAlbum => 'Artist / Album'; + + @override + String get albumFolderArtistAlbumSubtitle => 'Albums/Artist Name/Album Name/'; + + @override + String get albumFolderArtistYearAlbum => 'Artist / [Year] Album'; + + @override + String get albumFolderArtistYearAlbumSubtitle => + 'Albums/Artist Name/[2005] Album Name/'; + + @override + String get albumFolderAlbumOnly => 'Album Only'; + + @override + String get albumFolderAlbumOnlySubtitle => 'Albums/Album Name/'; + + @override + String get albumFolderYearAlbum => '[Year] Album'; + + @override + String get albumFolderYearAlbumSubtitle => 'Albums/[2005] Album Name/'; + + @override + String get albumFolderArtistAlbumSingles => 'Artist / Album + Singles'; + + @override + String get albumFolderArtistAlbumSinglesSubtitle => + 'Artist/Album/ and Artist/Singles/'; + + @override + String get downloadedAlbumDeleteSelected => 'Delete Selected'; + + @override + String downloadedAlbumDeleteMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Delete $count $_temp0 from this album?\n\nThis will also delete the files from storage.'; + } + + @override + String get downloadedAlbumTracksHeader => 'Tracks'; + + @override + String downloadedAlbumDownloadedCount(int count) { + return '$count downloaded'; + } + + @override + String downloadedAlbumSelectedCount(int count) { + return '$count selected'; + } + + @override + String get downloadedAlbumAllSelected => 'All tracks selected'; + + @override + String get downloadedAlbumTapToSelect => 'Tap tracks to select'; + + @override + String downloadedAlbumDeleteCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Delete $count $_temp0'; + } + + @override + String get downloadedAlbumSelectToDelete => 'Select tracks to delete'; + + @override + String downloadedAlbumDiscHeader(int discNumber) { + return 'Disc $discNumber'; + } + + @override + String get utilityFunctions => 'Utility Functions'; + + @override + String get recentTypeArtist => 'Artist'; + + @override + String get recentTypeAlbum => 'Album'; + + @override + String get recentTypeSong => 'Song'; + + @override + String get recentTypePlaylist => 'Playlist'; + + @override + String get recentEmpty => 'No recent items yet'; + + @override + String get recentShowAllDownloads => 'Show All Downloads'; + + @override + String recentPlaylistInfo(String name) { + return 'Playlist: $name'; + } + + @override + String errorGeneric(String message) { + return 'Error: $message'; + } + + @override + String get discographyDownload => 'Download Discography'; + + @override + String get discographyDownloadAll => 'Download All'; + + @override + String discographyDownloadAllSubtitle(int count, int albumCount) { + return '$count tracks from $albumCount releases'; + } + + @override + String get discographyAlbumsOnly => 'Albums Only'; + + @override + String discographyAlbumsOnlySubtitle(int count, int albumCount) { + return '$count tracks from $albumCount albums'; + } + + @override + String get discographySinglesOnly => 'Singles & EPs Only'; + + @override + String discographySinglesOnlySubtitle(int count, int albumCount) { + return '$count tracks from $albumCount singles'; + } + + @override + String get discographySelectAlbums => 'Select Albums...'; + + @override + String get discographySelectAlbumsSubtitle => + 'Choose specific albums or singles'; + + @override + String get discographyFetchingTracks => 'Fetching tracks...'; + + @override + String discographyFetchingAlbum(int current, int total) { + return 'Fetching $current of $total...'; + } + + @override + String discographySelectedCount(int count) { + return '$count selected'; + } + + @override + String get discographyDownloadSelected => 'Download Selected'; + + @override + String discographyAddedToQueue(int count) { + return 'Added $count tracks to queue'; + } + + @override + String discographySkippedDownloaded(int added, int skipped) { + return '$added added, $skipped already downloaded'; + } + + @override + String get discographyNoAlbums => 'No albums available'; + + @override + String get discographyFailedToFetch => 'Failed to fetch some albums'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; + + @override + String get settingsLocalLibrary => 'Local Library'; + + @override + String get settingsLocalLibrarySubtitle => 'Scan music & detect duplicates'; + + @override + String get settingsCache => 'Storage & Cache'; + + @override + String get settingsCacheSubtitle => 'View size and clear cached data'; + + @override + String get libraryTitle => 'Local Library'; + + @override + String get libraryStatus => 'Library Status'; + + @override + String get libraryScanSettings => 'Scan Settings'; + + @override + String get libraryEnableLocalLibrary => 'Enable Local Library'; + + @override + String get libraryEnableLocalLibrarySubtitle => + 'Scan and track your existing music'; + + @override + String get libraryFolder => 'Library Folder'; + + @override + String get libraryFolderHint => 'Tap to select folder'; + + @override + String get libraryShowDuplicateIndicator => 'Show Duplicate Indicator'; + + @override + String get libraryShowDuplicateIndicatorSubtitle => + 'Show when searching for existing tracks'; + + @override + String get libraryActions => 'Actions'; + + @override + String get libraryScan => 'Scan Library'; + + @override + String get libraryScanSubtitle => 'Scan for audio files'; + + @override + String get libraryScanSelectFolderFirst => 'Select a folder first'; + + @override + String get libraryCleanupMissingFiles => 'Cleanup Missing Files'; + + @override + String get libraryCleanupMissingFilesSubtitle => + 'Remove entries for files that no longer exist'; + + @override + String get libraryClear => 'Clear Library'; + + @override + String get libraryClearSubtitle => 'Remove all scanned tracks'; + + @override + String get libraryClearConfirmTitle => 'Clear Library'; + + @override + String get libraryClearConfirmMessage => + 'This will remove all scanned tracks from your library. Your actual music files will not be deleted.'; + + @override + String get libraryAbout => 'About Local Library'; + + @override + String get libraryAboutDescription => + 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.'; + + @override + String libraryTracksCount(int count) { + return '$count tracks'; + } + + @override + String libraryLastScanned(String time) { + return 'Last scanned: $time'; + } + + @override + String get libraryLastScannedNever => 'Never'; + + @override + String get libraryScanning => 'Scanning...'; + + @override + String libraryScanProgress(String progress, int total) { + return '$progress% of $total files'; + } + + @override + String get libraryInLibrary => 'In Library'; + + @override + String libraryRemovedMissingFiles(int count) { + return 'Removed $count missing files from library'; + } + + @override + String get libraryCleared => 'Library cleared'; + + @override + String get libraryStorageAccessRequired => 'Storage Access Required'; + + @override + String get libraryStorageAccessMessage => + 'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.'; + + @override + String get libraryFolderNotExist => 'Selected folder does not exist'; + + @override + String get librarySourceDownloaded => 'Downloaded'; + + @override + String get librarySourceLocal => 'Local'; + + @override + String get libraryFilterAll => 'All'; + + @override + String get libraryFilterDownloaded => 'Downloaded'; + + @override + String get libraryFilterLocal => 'Local'; + + @override + String get libraryFilterTitle => 'Filters'; + + @override + String get libraryFilterReset => 'Reset'; + + @override + String get libraryFilterApply => 'Apply'; + + @override + String get libraryFilterSource => 'Source'; + + @override + String get libraryFilterQuality => 'Quality'; + + @override + String get libraryFilterQualityHiRes => 'Hi-Res (24bit)'; + + @override + String get libraryFilterQualityCD => 'CD (16bit)'; + + @override + String get libraryFilterQualityLossy => 'Lossy'; + + @override + String get libraryFilterFormat => 'Format'; + + @override + String get libraryFilterDate => 'Date Added'; + + @override + String get libraryFilterDateToday => 'Today'; + + @override + String get libraryFilterDateWeek => 'This Week'; + + @override + String get libraryFilterDateMonth => 'This Month'; + + @override + String get libraryFilterDateYear => 'This Year'; + + @override + String get libraryFilterSort => 'Sort'; + + @override + String get libraryFilterSortLatest => 'Latest'; + + @override + String get libraryFilterSortOldest => 'Oldest'; + + @override + String libraryFilterActive(int count) { + return '$count filter(s) active'; + } + + @override + String get timeJustNow => 'Just now'; + + @override + String timeMinutesAgo(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count minutes ago', + one: '1 minute ago', + ); + return '$_temp0'; + } + + @override + String timeHoursAgo(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count hours ago', + one: '1 hour ago', + ); + return '$_temp0'; + } + + @override + String get storageSwitchTitle => 'Switch Storage Mode'; + + @override + String get storageSwitchToSafTitle => 'Switch to SAF Storage?'; + + @override + String get storageSwitchToAppTitle => 'Switch to App Storage?'; + + @override + String get storageSwitchToSafMessage => + 'Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.'; + + @override + String get storageSwitchToAppMessage => + 'Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.'; + + @override + String get storageSwitchExistingDownloads => 'Existing Downloads'; + + @override + String storageSwitchExistingDownloadsInfo(int count, String mode) { + return '$count tracks in $mode storage'; + } + + @override + String get storageSwitchNewDownloads => 'New Downloads'; + + @override + String storageSwitchNewDownloadsLocation(String location) { + return 'Will be saved to: $location'; + } + + @override + String get storageSwitchContinue => 'Continue'; + + @override + String get storageSwitchSelectFolder => 'Select SAF Folder'; + + @override + String get storageAppStorage => 'App Storage'; + + @override + String get storageSafStorage => 'SAF Storage'; + + @override + String storageModeBadge(String mode) { + return 'Storage: $mode'; + } + + @override + String get storageStatsTitle => 'Storage Statistics'; + + @override + String storageStatsAppCount(int count) { + return '$count tracks in App Storage'; + } + + @override + String storageStatsSafCount(int count) { + return '$count tracks in SAF Storage'; + } + + @override + String get storageModeInfo => 'Your files are stored in multiple locations'; + + @override + String get tutorialWelcomeTitle => 'Welcome to SpotiFLAC!'; + + @override + String get tutorialWelcomeDesc => + 'Let\'s learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.'; + + @override + String get tutorialWelcomeTip1 => + 'Download music from Spotify, Deezer, or paste any supported URL'; + + @override + String get tutorialWelcomeTip2 => + 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + + @override + String get tutorialWelcomeTip3 => + 'Automatic metadata, cover art, and lyrics embedding'; + + @override + String get tutorialSearchTitle => 'Finding Music'; + + @override + String get tutorialSearchDesc => + 'There are two easy ways to find music you want to download.'; + + @override + String get tutorialSearchTip1 => + 'Paste a Spotify or Deezer URL directly in the search box'; + + @override + String get tutorialSearchTip2 => + 'Or type the song name, artist, or album to search'; + + @override + String get tutorialSearchTip3 => + 'Supports tracks, albums, playlists, and artist pages'; + + @override + String get tutorialDownloadTitle => 'Downloading Music'; + + @override + String get tutorialDownloadDesc => + 'Downloading music is simple and fast. Here\'s how it works.'; + + @override + String get tutorialDownloadTip1 => + 'Tap the download button next to any track to start downloading'; + + @override + String get tutorialDownloadTip2 => + 'Choose your preferred quality (FLAC, Hi-Res, or MP3)'; + + @override + String get tutorialDownloadTip3 => + 'Download entire albums or playlists with one tap'; + + @override + String get tutorialLibraryTitle => 'Your Library'; + + @override + String get tutorialLibraryDesc => + 'All your downloaded music is organized in the Library tab.'; + + @override + String get tutorialLibraryTip1 => + 'View download progress and queue in the Library tab'; + + @override + String get tutorialLibraryTip2 => + 'Tap any track to play it with your music player'; + + @override + String get tutorialLibraryTip3 => + 'Switch between list and grid view for better browsing'; + + @override + String get tutorialExtensionsTitle => 'Extensions'; + + @override + String get tutorialExtensionsDesc => + 'Extend the app\'s capabilities with community extensions.'; + + @override + String get tutorialExtensionsTip1 => + 'Browse the Store tab to discover useful extensions'; + + @override + String get tutorialExtensionsTip2 => + 'Add new download providers or search sources'; + + @override + String get tutorialExtensionsTip3 => + 'Get lyrics, enhanced metadata, and more features'; + + @override + String get tutorialSettingsTitle => 'Customize Your Experience'; + + @override + String get tutorialSettingsDesc => + 'Personalize the app in Settings to match your preferences.'; + + @override + String get tutorialSettingsTip1 => + 'Change download location and folder organization'; + + @override + String get tutorialSettingsTip2 => + 'Set default audio quality and format preferences'; + + @override + String get tutorialSettingsTip3 => 'Customize app theme and appearance'; + + @override + String get tutorialReadyMessage => + 'You\'re all set! Start downloading your favorite music now.'; + + @override + String get tutorialExample => 'EXAMPLE'; + + @override + String get libraryForceFullScan => 'Force Full Scan'; + + @override + String get libraryForceFullScanSubtitle => 'Rescan all files, ignoring cache'; + + @override + String get cleanupOrphanedDownloads => 'Cleanup Orphaned Downloads'; + + @override + String get cleanupOrphanedDownloadsSubtitle => + 'Remove history entries for files that no longer exist'; + + @override + String cleanupOrphanedDownloadsResult(int count) { + return 'Removed $count orphaned entries from history'; + } + + @override + String get cleanupOrphanedDownloadsNone => 'No orphaned entries found'; + + @override + String get cacheTitle => 'Storage & Cache'; + + @override + String get cacheSummaryTitle => 'Cache overview'; + + @override + String get cacheSummarySubtitle => + 'Clearing cache will not remove downloaded music files.'; + + @override + String cacheEstimatedTotal(String size) { + return 'Estimated cache usage: $size'; + } + + @override + String get cacheSectionStorage => 'Cached Data'; + + @override + String get cacheSectionMaintenance => 'Maintenance'; + + @override + String get cacheAppDirectory => 'App cache directory'; + + @override + String get cacheAppDirectoryDesc => + 'HTTP responses, WebView data, and other temporary app data.'; + + @override + String get cacheTempDirectory => 'Temporary directory'; + + @override + String get cacheTempDirectoryDesc => + 'Temporary files from downloads and audio conversion.'; + + @override + String get cacheCoverImage => 'Cover image cache'; + + @override + String get cacheCoverImageDesc => + 'Downloaded album and track cover art. Will re-download when viewed.'; + + @override + String get cacheLibraryCover => 'Library cover cache'; + + @override + String get cacheLibraryCoverDesc => + 'Cover art extracted from local music files. Will re-extract on next scan.'; + + @override + String get cacheExploreFeed => 'Explore feed cache'; + + @override + String get cacheExploreFeedDesc => + 'Explore tab content (new releases, trending). Will refresh on next visit.'; + + @override + String get cacheTrackLookup => 'Track lookup cache'; + + @override + String get cacheTrackLookupDesc => + 'Spotify/Deezer track ID lookups. Clearing may slow next few searches.'; + + @override + String get cacheCleanupUnusedDesc => + 'Remove orphaned download history and library entries for missing files.'; + + @override + String get cacheNoData => 'No cached data'; + + @override + String cacheSizeWithFiles(String size, int count) { + return '$size in $count files'; + } + + @override + String cacheSizeOnly(String size) { + return '$size'; + } + + @override + String cacheEntries(int count) { + return '$count entries'; + } + + @override + String cacheClearSuccess(String target) { + return 'Cleared: $target'; + } + + @override + String get cacheClearConfirmTitle => 'Clear cache?'; + + @override + String cacheClearConfirmMessage(String target) { + return 'This will clear cached data for $target. Downloaded music files will not be deleted.'; + } + + @override + String get cacheClearAllConfirmTitle => 'Clear all cache?'; + + @override + String get cacheClearAllConfirmMessage => + 'This will clear all cache categories on this page. Downloaded music files will not be deleted.'; + + @override + String get cacheClearAll => 'Clear all cache'; + + @override + String get cacheCleanupUnused => 'Cleanup unused data'; + + @override + String get cacheCleanupUnusedSubtitle => + 'Remove orphaned download history and missing library entries'; + + @override + String cacheCleanupResult(int downloadCount, int libraryCount) { + return 'Cleanup completed: $downloadCount orphaned downloads, $libraryCount missing library entries'; + } + + @override + String get cacheRefreshStats => 'Refresh stats'; + + @override + String get trackSaveCoverArt => 'Save Cover Art'; + + @override + String get trackSaveCoverArtSubtitle => 'Save album art as .jpg file'; + + @override + String get trackSaveLyrics => 'Save Lyrics (.lrc)'; + + @override + String get trackSaveLyricsSubtitle => 'Fetch and save lyrics as .lrc file'; + + @override + String get trackSaveLyricsProgress => 'Saving lyrics...'; + + @override + String get trackReEnrich => 'Re-enrich Metadata'; + + @override + String get trackReEnrichSubtitle => + 'Re-embed metadata without re-downloading'; + + @override + String get trackReEnrichOnlineSubtitle => + 'Search metadata online and embed into file'; + + @override + String get trackEditMetadata => 'Edit Metadata'; + + @override + String trackCoverSaved(String fileName) { + return 'Cover art saved to $fileName'; + } + + @override + String get trackCoverNoSource => 'No cover art source available'; + + @override + String trackLyricsSaved(String fileName) { + return 'Lyrics saved to $fileName'; + } + + @override + String get trackReEnrichProgress => 'Re-enriching metadata...'; + + @override + String get trackReEnrichSearching => 'Searching metadata online...'; + + @override + String get trackReEnrichSuccess => 'Metadata re-enriched successfully'; + + @override + String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed'; + + @override + String trackSaveFailed(String error) { + return 'Failed: $error'; + } + + @override + String get trackConvertFormat => 'Convert Format'; + + @override + String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + + @override + String get trackConvertTitle => 'Convert Audio'; + + @override + String get trackConvertTargetFormat => 'Target Format'; + + @override + String get trackConvertBitrate => 'Bitrate'; + + @override + String get trackConvertConfirmTitle => 'Confirm Conversion'; + + @override + String trackConvertConfirmMessage( + String sourceFormat, + String targetFormat, + String bitrate, + ) { + return 'Convert from $sourceFormat to $targetFormat at $bitrate?\n\nThe original file will be deleted after conversion.'; + } + + @override + String get trackConvertConverting => 'Converting audio...'; + + @override + String trackConvertSuccess(String format) { + return 'Converted to $format successfully'; + } + + @override + String get trackConvertFailed => 'Conversion failed'; +} + +/// The translations for Chinese, as used in China (`zh_CN`). +class AppLocalizationsZhCn extends AppLocalizationsZh { + AppLocalizationsZhCn() : super('zh_CN'); + + @override + String get appName => 'SpotiFLAC'; + + @override + String get appDescription => + 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + + @override + String get navHome => 'Home'; + + @override + String get navLibrary => 'Library'; + + @override + String get navHistory => 'History'; + + @override + String get navSettings => 'Settings'; + + @override + String get navStore => 'Store'; + + @override + String get homeTitle => 'Home'; + + @override + String get homeSearchHint => 'Paste Spotify URL or search...'; + + @override + String homeSearchHintExtension(String extensionName) { + return 'Search with $extensionName...'; + } + + @override + String get homeSubtitle => 'Paste a Spotify link or search by name'; + + @override + String get homeSupports => 'Supports: Track, Album, Playlist, Artist URLs'; + + @override + String get homeRecent => 'Recent'; + + @override + String get historyTitle => 'History'; + + @override + String historyDownloading(int count) { + return 'Downloading ($count)'; + } + + @override + String get historyDownloaded => 'Downloaded'; + + @override + String get historyFilterAll => 'All'; + + @override + String get historyFilterAlbums => 'Albums'; + + @override + String get historyFilterSingles => 'Singles'; + + @override + String historyTracksCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count tracks', + one: '1 track', + ); + return '$_temp0'; + } + + @override + String historyAlbumsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count albums', + one: '1 album', + ); + return '$_temp0'; + } + + @override + String get historyNoDownloads => 'No download history'; + + @override + String get historyNoDownloadsSubtitle => 'Downloaded tracks will appear here'; + + @override + String get historyNoAlbums => 'No album downloads'; + + @override + String get historyNoAlbumsSubtitle => + 'Download multiple tracks from an album to see them here'; + + @override + String get historyNoSingles => 'No single downloads'; + + @override + String get historyNoSinglesSubtitle => + 'Single track downloads will appear here'; + + @override + String get historySearchHint => 'Search history...'; + + @override + String get settingsTitle => 'Settings'; + + @override + String get settingsDownload => 'Download'; + + @override + String get settingsAppearance => 'Appearance'; + + @override + String get settingsOptions => 'Options'; + + @override + String get settingsExtensions => 'Extensions'; + + @override + String get settingsAbout => 'About'; + + @override + String get downloadTitle => 'Download'; + + @override + String get downloadLocation => 'Download Location'; + + @override + String get downloadLocationSubtitle => 'Choose where to save files'; + + @override + String get downloadLocationDefault => 'Default location'; + + @override + String get downloadDefaultService => 'Default Service'; + + @override + String get downloadDefaultServiceSubtitle => 'Service used for downloads'; + + @override + String get downloadDefaultQuality => 'Default Quality'; + + @override + String get downloadAskQuality => 'Ask Quality Before Download'; + + @override + String get downloadAskQualitySubtitle => + 'Show quality picker for each download'; + + @override + String get downloadFilenameFormat => 'Filename Format'; + + @override + String get downloadFolderOrganization => 'Folder Organization'; + + @override + String get downloadSeparateSingles => 'Separate Singles'; + + @override + String get downloadSeparateSinglesSubtitle => + 'Put single tracks in a separate folder'; + + @override + String get qualityBest => 'Best Available'; + + @override + String get qualityFlac => 'FLAC'; + + @override + String get quality320 => '320 kbps'; + + @override + String get quality128 => '128 kbps'; + + @override + String get appearanceTitle => 'Appearance'; + + @override + String get appearanceTheme => 'Theme'; + + @override + String get appearanceThemeSystem => 'System'; + + @override + String get appearanceThemeLight => 'Light'; + + @override + String get appearanceThemeDark => 'Dark'; + + @override + String get appearanceDynamicColor => 'Dynamic Color'; + + @override + String get appearanceDynamicColorSubtitle => 'Use colors from your wallpaper'; + + @override + String get appearanceAccentColor => 'Accent Color'; + + @override + String get appearanceHistoryView => 'History View'; + + @override + String get appearanceHistoryViewList => 'List'; + + @override + String get appearanceHistoryViewGrid => 'Grid'; + + @override + String get optionsTitle => 'Options'; + + @override + String get optionsSearchSource => 'Search Source'; + + @override + String get optionsPrimaryProvider => 'Primary Provider'; + + @override + String get optionsPrimaryProviderSubtitle => + 'Service used when searching by track name.'; + + @override + String optionsUsingExtension(String extensionName) { + return 'Using extension: $extensionName'; + } + + @override + String get optionsSwitchBack => + 'Tap Deezer or Spotify to switch back from extension'; + + @override + String get optionsAutoFallback => 'Auto Fallback'; + + @override + String get optionsAutoFallbackSubtitle => + 'Try other services if download fails'; + + @override + String get optionsUseExtensionProviders => 'Use Extension Providers'; + + @override + String get optionsUseExtensionProvidersOn => 'Extensions will be tried first'; + + @override + String get optionsUseExtensionProvidersOff => 'Using built-in providers only'; + + @override + String get optionsEmbedLyrics => 'Embed Lyrics'; + + @override + String get optionsEmbedLyricsSubtitle => + 'Embed synced lyrics into FLAC files'; + + @override + String get optionsMaxQualityCover => 'Max Quality Cover'; + + @override + String get optionsMaxQualityCoverSubtitle => + 'Download highest resolution cover art'; + + @override + String get optionsConcurrentDownloads => 'Concurrent Downloads'; + + @override + String get optionsConcurrentSequential => 'Sequential (1 at a time)'; + + @override + String optionsConcurrentParallel(int count) { + return '$count parallel downloads'; + } + + @override + String get optionsConcurrentWarning => + 'Parallel downloads may trigger rate limiting'; + + @override + String get optionsExtensionStore => 'Extension Store'; + + @override + String get optionsExtensionStoreSubtitle => 'Show Store tab in navigation'; + + @override + String get optionsCheckUpdates => 'Check for Updates'; + + @override + String get optionsCheckUpdatesSubtitle => + 'Notify when new version is available'; + + @override + String get optionsUpdateChannel => 'Update Channel'; + + @override + String get optionsUpdateChannelStable => 'Stable releases only'; + + @override + String get optionsUpdateChannelPreview => 'Get preview releases'; + + @override + String get optionsUpdateChannelWarning => + 'Preview may contain bugs or incomplete features'; + + @override + String get optionsClearHistory => 'Clear Download History'; + + @override + String get optionsClearHistorySubtitle => + 'Remove all downloaded tracks from history'; + + @override + String get optionsDetailedLogging => 'Detailed Logging'; + + @override + String get optionsDetailedLoggingOn => 'Detailed logs are being recorded'; + + @override + String get optionsDetailedLoggingOff => 'Enable for bug reports'; + + @override + String get optionsSpotifyCredentials => 'Spotify Credentials'; + + @override + String optionsSpotifyCredentialsConfigured(String clientId) { + return 'Client ID: $clientId...'; + } + + @override + String get optionsSpotifyCredentialsRequired => 'Required - tap to configure'; + + @override + String get optionsSpotifyWarning => + 'Spotify requires your own API credentials. Get them free from developer.spotify.com'; + + @override + String get optionsSpotifyDeprecationWarning => + 'Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.'; + + @override + String get extensionsTitle => 'Extensions'; + + @override + String get extensionsInstalled => 'Installed Extensions'; + + @override + String get extensionsNone => 'No extensions installed'; + + @override + String get extensionsNoneSubtitle => 'Install extensions from the Store tab'; + + @override + String get extensionsEnabled => 'Enabled'; + + @override + String get extensionsDisabled => 'Disabled'; + + @override + String extensionsVersion(String version) { + return 'Version $version'; + } + + @override + String extensionsAuthor(String author) { + return 'by $author'; + } + + @override + String get extensionsUninstall => 'Uninstall'; + + @override + String get extensionsSetAsSearch => 'Set as Search Provider'; + + @override + String get storeTitle => 'Extension Store'; + + @override + String get storeSearch => 'Search extensions...'; + + @override + String get storeInstall => 'Install'; + + @override + String get storeInstalled => 'Installed'; + + @override + String get storeUpdate => 'Update'; + + @override + String get aboutTitle => 'About'; + + @override + String get aboutContributors => 'Contributors'; + + @override + String get aboutMobileDeveloper => 'Mobile version developer'; + + @override + String get aboutOriginalCreator => 'Creator of the original SpotiFLAC'; + + @override + String get aboutLogoArtist => + 'The talented artist who created our beautiful app logo!'; + + @override + String get aboutTranslators => 'Translators'; + + @override + String get aboutSpecialThanks => 'Special Thanks'; + + @override + String get aboutLinks => 'Links'; + + @override + String get aboutMobileSource => 'Mobile source code'; + + @override + String get aboutPCSource => 'PC source code'; + + @override + String get aboutReportIssue => 'Report an issue'; + + @override + String get aboutReportIssueSubtitle => 'Report any problems you encounter'; + + @override + String get aboutFeatureRequest => 'Feature request'; + + @override + String get aboutFeatureRequestSubtitle => 'Suggest new features for the app'; + + @override + String get aboutTelegramChannel => 'Telegram Channel'; + + @override + String get aboutTelegramChannelSubtitle => 'Announcements and updates'; + + @override + String get aboutTelegramChat => 'Telegram Community'; + + @override + String get aboutTelegramChatSubtitle => 'Chat with other users'; + + @override + String get aboutSocial => 'Social'; + + @override + String get aboutSupport => 'Support'; + + @override + String get aboutApp => 'App'; + + @override + String get aboutVersion => 'Version'; + + @override + String get aboutBinimumDesc => + 'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!'; + + @override + String get aboutSachinsenalDesc => + 'The original HiFi project creator. The foundation of Tidal integration!'; + + @override + String get aboutSjdonadoDesc => + 'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!'; + + @override + String get aboutDoubleDouble => 'DoubleDouble'; + + @override + String get aboutDoubleDoubleDesc => + 'Amazing API for Amazon Music downloads. Thank you for making it free!'; + + @override + String get aboutDabMusic => 'DAB Music'; + + @override + String get aboutDabMusicDesc => + 'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!'; + + @override + String get aboutSpotiSaver => 'SpotiSaver'; + + @override + String get aboutSpotiSaverDesc => + 'Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!'; + + @override + String get aboutAppDescription => + 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; + + @override + String get albumTitle => 'Album'; + + @override + String albumTracks(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count tracks', + one: '1 track', + ); + return '$_temp0'; + } + + @override + String get albumDownloadAll => 'Download All'; + + @override + String get albumDownloadRemaining => 'Download Remaining'; + + @override + String get playlistTitle => 'Playlist'; + + @override + String get artistTitle => 'Artist'; + + @override + String get artistAlbums => 'Albums'; + + @override + String get artistSingles => 'Singles & EPs'; + + @override + String get artistCompilations => 'Compilations'; + + @override + String artistReleases(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count releases', + one: '1 release', + ); + return '$_temp0'; + } + + @override + String get artistPopular => 'Popular'; + + @override + String artistMonthlyListeners(String count) { + return '$count monthly listeners'; + } + + @override + String get trackMetadataTitle => 'Track Info'; + + @override + String get trackMetadataArtist => 'Artist'; + + @override + String get trackMetadataAlbum => 'Album'; + + @override + String get trackMetadataDuration => 'Duration'; + + @override + String get trackMetadataQuality => 'Quality'; + + @override + String get trackMetadataPath => 'File Path'; + + @override + String get trackMetadataDownloadedAt => 'Downloaded'; + + @override + String get trackMetadataService => 'Service'; + + @override + String get trackMetadataPlay => 'Play'; + + @override + String get trackMetadataShare => 'Share'; + + @override + String get trackMetadataDelete => 'Delete'; + + @override + String get trackMetadataRedownload => 'Re-download'; + + @override + String get trackMetadataOpenFolder => 'Open Folder'; + + @override + String get setupTitle => 'Welcome to SpotiFLAC'; + + @override + String get setupSubtitle => 'Let\'s get you started'; + + @override + String get setupStoragePermission => 'Storage Permission'; + + @override + String get setupStoragePermissionSubtitle => + 'Required to save downloaded files'; + + @override + String get setupStoragePermissionGranted => 'Permission granted'; + + @override + String get setupStoragePermissionDenied => 'Permission denied'; + + @override + String get setupGrantPermission => 'Grant Permission'; + + @override + String get setupDownloadLocation => 'Download Location'; + + @override + String get setupChooseFolder => 'Choose Folder'; + + @override + String get setupContinue => 'Continue'; + + @override + String get setupSkip => 'Skip for now'; + + @override + String get setupStorageAccessRequired => 'Storage Access Required'; + + @override + String get setupStorageAccessMessage => + 'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.'; + + @override + String get setupStorageAccessMessageAndroid11 => + 'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.'; + + @override + String get setupOpenSettings => 'Open Settings'; + + @override + String get setupPermissionDeniedMessage => + 'Permission denied. Please grant all permissions to continue.'; + + @override + String setupPermissionRequired(String permissionType) { + return '$permissionType Permission Required'; + } + + @override + String setupPermissionRequiredMessage(String permissionType) { + return '$permissionType permission is required for the best experience. You can change this later in Settings.'; + } + + @override + String get setupSelectDownloadFolder => 'Select Download Folder'; + + @override + String get setupUseDefaultFolder => 'Use Default Folder?'; + + @override + String get setupNoFolderSelected => + 'No folder selected. Would you like to use the default Music folder?'; + + @override + String get setupUseDefault => 'Use Default'; + + @override + String get setupDownloadLocationTitle => 'Download Location'; + + @override + String get setupDownloadLocationIosMessage => + 'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.'; + + @override + String get setupAppDocumentsFolder => 'App Documents Folder'; + + @override + String get setupAppDocumentsFolderSubtitle => + 'Recommended - accessible via Files app'; + + @override + String get setupChooseFromFiles => 'Choose from Files'; + + @override + String get setupChooseFromFilesSubtitle => 'Select iCloud or other location'; + + @override + String get setupIosEmptyFolderWarning => + 'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.'; + + @override + String get setupIcloudNotSupported => + 'iCloud Drive is not supported. Please use the app Documents folder.'; + + @override + String get setupDownloadInFlac => 'Download Spotify tracks in FLAC'; + + @override + String get setupStepStorage => 'Storage'; + + @override + String get setupStepNotification => 'Notification'; + + @override + String get setupStepFolder => 'Folder'; + + @override + String get setupStepSpotify => 'Spotify'; + + @override + String get setupStepPermission => 'Permission'; + + @override + String get setupStorageGranted => 'Storage Permission Granted!'; + + @override + String get setupStorageRequired => 'Storage Permission Required'; + + @override + String get setupStorageDescription => + 'SpotiFLAC needs storage permission to save your downloaded music files.'; + + @override + String get setupNotificationGranted => 'Notification Permission Granted!'; + + @override + String get setupNotificationEnable => 'Enable Notifications'; + + @override + String get setupNotificationDescription => + 'Get notified when downloads complete or require attention.'; + + @override + String get setupFolderSelected => 'Download Folder Selected!'; + + @override + String get setupFolderChoose => 'Choose Download Folder'; + + @override + String get setupFolderDescription => + 'Select a folder where your downloaded music will be saved.'; + + @override + String get setupChangeFolder => 'Change Folder'; + + @override + String get setupSelectFolder => 'Select Folder'; + + @override + String get setupSpotifyApiOptional => 'Spotify API (Optional)'; + + @override + String get setupSpotifyApiDescription => + 'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.'; + + @override + String get setupUseSpotifyApi => 'Use Spotify API'; + + @override + String get setupEnterCredentialsBelow => 'Enter your credentials below'; + + @override + String get setupUsingDeezer => 'Using Deezer (no account needed)'; + + @override + String get setupEnterClientId => 'Enter Spotify Client ID'; + + @override + String get setupEnterClientSecret => 'Enter Spotify Client Secret'; + + @override + String get setupGetFreeCredentials => + 'Get your free API credentials from the Spotify Developer Dashboard.'; + + @override + String get setupEnableNotifications => 'Enable Notifications'; + + @override + String get setupProceedToNextStep => 'You can now proceed to the next step.'; + + @override + String get setupNotificationProgressDescription => + 'You will receive download progress notifications.'; + + @override + String get setupNotificationBackgroundDescription => + 'Get notified about download progress and completion. This helps you track downloads when the app is in background.'; + + @override + String get setupSkipForNow => 'Skip for now'; + + @override + String get setupBack => 'Back'; + + @override + String get setupNext => 'Next'; + + @override + String get setupGetStarted => 'Get Started'; + + @override + String get setupSkipAndStart => 'Skip & Start'; + + @override + String get setupAllowAccessToManageFiles => + 'Please enable \"Allow access to manage all files\" in the next screen.'; + + @override + String get setupGetCredentialsFromSpotify => + 'Get credentials from developer.spotify.com'; + + @override + String get dialogCancel => 'Cancel'; + + @override + String get dialogOk => 'OK'; + + @override + String get dialogSave => 'Save'; + + @override + String get dialogDelete => 'Delete'; + + @override + String get dialogRetry => 'Retry'; + + @override + String get dialogClose => 'Close'; + + @override + String get dialogYes => 'Yes'; + + @override + String get dialogNo => 'No'; + + @override + String get dialogClear => 'Clear'; + + @override + String get dialogConfirm => 'Confirm'; + + @override + String get dialogDone => 'Done'; + + @override + String get dialogImport => 'Import'; + + @override + String get dialogDiscard => 'Discard'; + + @override + String get dialogRemove => 'Remove'; + + @override + String get dialogUninstall => 'Uninstall'; + + @override + String get dialogDiscardChanges => 'Discard Changes?'; + + @override + String get dialogUnsavedChanges => + 'You have unsaved changes. Do you want to discard them?'; + + @override + String get dialogDownloadFailed => 'Download Failed'; + + @override + String get dialogTrackLabel => 'Track:'; + + @override + String get dialogArtistLabel => 'Artist:'; + + @override + String get dialogErrorLabel => 'Error:'; + + @override + String get dialogClearAll => 'Clear All'; + + @override + String get dialogClearAllDownloads => + 'Are you sure you want to clear all downloads?'; + + @override + String get dialogRemoveFromDevice => 'Remove from device?'; + + @override + String get dialogRemoveExtension => 'Remove Extension'; + + @override + String get dialogRemoveExtensionMessage => + 'Are you sure you want to remove this extension? This cannot be undone.'; + + @override + String get dialogUninstallExtension => 'Uninstall Extension?'; + + @override + String dialogUninstallExtensionMessage(String extensionName) { + return 'Are you sure you want to remove $extensionName?'; + } + + @override + String get dialogClearHistoryTitle => 'Clear History'; + + @override + String get dialogClearHistoryMessage => + 'Are you sure you want to clear all download history? This cannot be undone.'; + + @override + String get dialogDeleteSelectedTitle => 'Delete Selected'; + + @override + String dialogDeleteSelectedMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Delete $count $_temp0 from history?\n\nThis will also delete the files from storage.'; + } + + @override + String get dialogImportPlaylistTitle => 'Import Playlist'; + + @override + String dialogImportPlaylistMessage(int count) { + return 'Found $count tracks in CSV. Add them to download queue?'; + } + + @override + String csvImportTracks(int count) { + return '$count tracks from CSV'; + } + + @override + String snackbarAddedToQueue(String trackName) { + return 'Added \"$trackName\" to queue'; + } + + @override + String snackbarAddedTracksToQueue(int count) { + return 'Added $count tracks to queue'; + } + + @override + String snackbarAlreadyDownloaded(String trackName) { + return '\"$trackName\" already downloaded'; + } + + @override + String snackbarAlreadyInLibrary(String trackName) { + return '\"$trackName\" already exists in your library'; + } + + @override + String get snackbarHistoryCleared => 'History cleared'; + + @override + String get snackbarCredentialsSaved => 'Credentials saved'; + + @override + String get snackbarCredentialsCleared => 'Credentials cleared'; + + @override + String snackbarDeletedTracks(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Deleted $count $_temp0'; + } + + @override + String snackbarCannotOpenFile(String error) { + return 'Cannot open file: $error'; + } + + @override + String get snackbarFillAllFields => 'Please fill all fields'; + + @override + String get snackbarViewQueue => 'View Queue'; + + @override + String snackbarFailedToLoad(String error) { + return 'Failed to load: $error'; + } + + @override + String snackbarUrlCopied(String platform) { + return '$platform URL copied to clipboard'; + } + + @override + String get snackbarFileNotFound => 'File not found'; + + @override + String get snackbarSelectExtFile => 'Please select a .spotiflac-ext file'; + + @override + String get snackbarProviderPrioritySaved => 'Provider priority saved'; + + @override + String get snackbarMetadataProviderSaved => + 'Metadata provider priority saved'; + + @override + String snackbarExtensionInstalled(String extensionName) { + return '$extensionName installed.'; + } + + @override + String snackbarExtensionUpdated(String extensionName) { + return '$extensionName updated.'; + } + + @override + String get snackbarFailedToInstall => 'Failed to install extension'; + + @override + String get snackbarFailedToUpdate => 'Failed to update extension'; + + @override + String get errorRateLimited => 'Rate Limited'; + + @override + String get errorRateLimitedMessage => + 'Too many requests. Please wait a moment before searching again.'; + + @override + String errorFailedToLoad(String item) { + return 'Failed to load $item'; + } + + @override + String get errorNoTracksFound => 'No tracks found'; + + @override + String errorMissingExtensionSource(String item) { + return 'Cannot load $item: missing extension source'; + } + + @override + String get statusQueued => 'Queued'; + + @override + String get statusDownloading => 'Downloading'; + + @override + String get statusFinalizing => 'Finalizing'; + + @override + String get statusCompleted => 'Completed'; + + @override + String get statusFailed => 'Failed'; + + @override + String get statusSkipped => 'Skipped'; + + @override + String get statusPaused => 'Paused'; + + @override + String get actionPause => 'Pause'; + + @override + String get actionResume => 'Resume'; + + @override + String get actionCancel => 'Cancel'; + + @override + String get actionStop => 'Stop'; + + @override + String get actionSelect => 'Select'; + + @override + String get actionSelectAll => 'Select All'; + + @override + String get actionDeselect => 'Deselect'; + + @override + String get actionPaste => 'Paste'; + + @override + String get actionImportCsv => 'Import CSV'; + + @override + String get actionRemoveCredentials => 'Remove Credentials'; + + @override + String get actionSaveCredentials => 'Save Credentials'; + + @override + String selectionSelected(int count) { + return '$count selected'; + } + + @override + String get selectionAllSelected => 'All tracks selected'; + + @override + String get selectionTapToSelect => 'Tap tracks to select'; + + @override + String selectionDeleteTracks(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'tracks', + one: 'track', + ); + return 'Delete $count $_temp0'; + } + + @override + String get selectionSelectToDelete => 'Select tracks to delete'; + + @override + String progressFetchingMetadata(int current, int total) { + return 'Fetching metadata... $current/$total'; + } + + @override + String get progressReadingCsv => 'Reading CSV...'; + + @override + String get searchSongs => 'Songs'; + + @override + String get searchArtists => 'Artists'; + + @override + String get searchAlbums => 'Albums'; + + @override + String get searchPlaylists => 'Playlists'; + + @override + String get tooltipPlay => 'Play'; + + @override + String get tooltipCancel => 'Cancel'; + + @override + String get tooltipStop => 'Stop'; + + @override + String get tooltipRetry => 'Retry'; + + @override + String get tooltipRemove => 'Remove'; + + @override + String get tooltipClear => 'Clear'; + + @override + String get tooltipPaste => 'Paste'; + + @override + String get filenameFormat => 'Filename Format'; + + @override + String filenameFormatPreview(String preview) { + return 'Preview: $preview'; + } + + @override + String get filenameAvailablePlaceholders => 'Available placeholders:'; + + @override + String filenameHint(Object artist, Object title) { + return '$artist - $title'; + } + @override String get folderOrganization => 'Folder Organization'; @@ -2937,2146 +5877,6 @@ class AppLocalizationsZh extends AppLocalizations { String get trackConvertFailed => 'Conversion failed'; } -/// The translations for Chinese, as used in China (`zh_CN`). -class AppLocalizationsZhCn extends AppLocalizationsZh { - AppLocalizationsZhCn() : super('zh_CN'); - - @override - String get appName => 'SpotiFLAC'; - - @override - String get appDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; - - @override - String get navHome => 'Home'; - - @override - String get navHistory => 'History'; - - @override - String get navSettings => 'Settings'; - - @override - String get navStore => 'Store'; - - @override - String get homeTitle => 'Home'; - - @override - String get homeSearchHint => 'Paste Spotify URL or search...'; - - @override - String homeSearchHintExtension(String extensionName) { - return 'Search with $extensionName...'; - } - - @override - String get homeSubtitle => 'Paste a Spotify link or search by name'; - - @override - String get homeSupports => 'Supports: Track, Album, Playlist, Artist URLs'; - - @override - String get homeRecent => 'Recent'; - - @override - String get historyTitle => 'History'; - - @override - String historyDownloading(int count) { - return 'Downloading ($count)'; - } - - @override - String get historyDownloaded => 'Downloaded'; - - @override - String get historyFilterAll => 'All'; - - @override - String get historyFilterAlbums => 'Albums'; - - @override - String get historyFilterSingles => 'Singles'; - - @override - String historyTracksCount(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: '$count tracks', - one: '1 track', - ); - return '$_temp0'; - } - - @override - String historyAlbumsCount(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: '$count albums', - one: '1 album', - ); - return '$_temp0'; - } - - @override - String get historyNoDownloads => 'No download history'; - - @override - String get historyNoDownloadsSubtitle => 'Downloaded tracks will appear here'; - - @override - String get historyNoAlbums => 'No album downloads'; - - @override - String get historyNoAlbumsSubtitle => - 'Download multiple tracks from an album to see them here'; - - @override - String get historyNoSingles => 'No single downloads'; - - @override - String get historyNoSinglesSubtitle => - 'Single track downloads will appear here'; - - @override - String get historySearchHint => 'Search history...'; - - @override - String get settingsTitle => 'Settings'; - - @override - String get settingsDownload => 'Download'; - - @override - String get settingsAppearance => 'Appearance'; - - @override - String get settingsOptions => 'Options'; - - @override - String get settingsExtensions => 'Extensions'; - - @override - String get settingsAbout => 'About'; - - @override - String get downloadTitle => 'Download'; - - @override - String get downloadLocation => 'Download Location'; - - @override - String get downloadLocationSubtitle => 'Choose where to save files'; - - @override - String get downloadLocationDefault => 'Default location'; - - @override - String get downloadDefaultService => 'Default Service'; - - @override - String get downloadDefaultServiceSubtitle => 'Service used for downloads'; - - @override - String get downloadDefaultQuality => 'Default Quality'; - - @override - String get downloadAskQuality => 'Ask Quality Before Download'; - - @override - String get downloadAskQualitySubtitle => - 'Show quality picker for each download'; - - @override - String get downloadFilenameFormat => 'Filename Format'; - - @override - String get downloadFolderOrganization => 'Folder Organization'; - - @override - String get downloadSeparateSingles => 'Separate Singles'; - - @override - String get downloadSeparateSinglesSubtitle => - 'Put single tracks in a separate folder'; - - @override - String get qualityBest => 'Best Available'; - - @override - String get qualityFlac => 'FLAC'; - - @override - String get quality320 => '320 kbps'; - - @override - String get quality128 => '128 kbps'; - - @override - String get appearanceTitle => 'Appearance'; - - @override - String get appearanceTheme => 'Theme'; - - @override - String get appearanceThemeSystem => 'System'; - - @override - String get appearanceThemeLight => 'Light'; - - @override - String get appearanceThemeDark => 'Dark'; - - @override - String get appearanceDynamicColor => 'Dynamic Color'; - - @override - String get appearanceDynamicColorSubtitle => 'Use colors from your wallpaper'; - - @override - String get appearanceAccentColor => 'Accent Color'; - - @override - String get appearanceHistoryView => 'History View'; - - @override - String get appearanceHistoryViewList => 'List'; - - @override - String get appearanceHistoryViewGrid => 'Grid'; - - @override - String get optionsTitle => 'Options'; - - @override - String get optionsSearchSource => 'Search Source'; - - @override - String get optionsPrimaryProvider => 'Primary Provider'; - - @override - String get optionsPrimaryProviderSubtitle => - 'Service used when searching by track name.'; - - @override - String optionsUsingExtension(String extensionName) { - return 'Using extension: $extensionName'; - } - - @override - String get optionsSwitchBack => - 'Tap Deezer or Spotify to switch back from extension'; - - @override - String get optionsAutoFallback => 'Auto Fallback'; - - @override - String get optionsAutoFallbackSubtitle => - 'Try other services if download fails'; - - @override - String get optionsUseExtensionProviders => 'Use Extension Providers'; - - @override - String get optionsUseExtensionProvidersOn => 'Extensions will be tried first'; - - @override - String get optionsUseExtensionProvidersOff => 'Using built-in providers only'; - - @override - String get optionsEmbedLyrics => 'Embed Lyrics'; - - @override - String get optionsEmbedLyricsSubtitle => - 'Embed synced lyrics into FLAC files'; - - @override - String get optionsMaxQualityCover => 'Max Quality Cover'; - - @override - String get optionsMaxQualityCoverSubtitle => - 'Download highest resolution cover art'; - - @override - String get optionsConcurrentDownloads => 'Concurrent Downloads'; - - @override - String get optionsConcurrentSequential => 'Sequential (1 at a time)'; - - @override - String optionsConcurrentParallel(int count) { - return '$count parallel downloads'; - } - - @override - String get optionsConcurrentWarning => - 'Parallel downloads may trigger rate limiting'; - - @override - String get optionsExtensionStore => 'Extension Store'; - - @override - String get optionsExtensionStoreSubtitle => 'Show Store tab in navigation'; - - @override - String get optionsCheckUpdates => 'Check for Updates'; - - @override - String get optionsCheckUpdatesSubtitle => - 'Notify when new version is available'; - - @override - String get optionsUpdateChannel => 'Update Channel'; - - @override - String get optionsUpdateChannelStable => 'Stable releases only'; - - @override - String get optionsUpdateChannelPreview => 'Get preview releases'; - - @override - String get optionsUpdateChannelWarning => - 'Preview may contain bugs or incomplete features'; - - @override - String get optionsClearHistory => 'Clear Download History'; - - @override - String get optionsClearHistorySubtitle => - 'Remove all downloaded tracks from history'; - - @override - String get optionsDetailedLogging => 'Detailed Logging'; - - @override - String get optionsDetailedLoggingOn => 'Detailed logs are being recorded'; - - @override - String get optionsDetailedLoggingOff => 'Enable for bug reports'; - - @override - String get optionsSpotifyCredentials => 'Spotify Credentials'; - - @override - String optionsSpotifyCredentialsConfigured(String clientId) { - return 'Client ID: $clientId...'; - } - - @override - String get optionsSpotifyCredentialsRequired => 'Required - tap to configure'; - - @override - String get optionsSpotifyWarning => - 'Spotify requires your own API credentials. Get them free from developer.spotify.com'; - - @override - String get extensionsTitle => 'Extensions'; - - @override - String get extensionsInstalled => 'Installed Extensions'; - - @override - String get extensionsNone => 'No extensions installed'; - - @override - String get extensionsNoneSubtitle => 'Install extensions from the Store tab'; - - @override - String get extensionsEnabled => 'Enabled'; - - @override - String get extensionsDisabled => 'Disabled'; - - @override - String extensionsVersion(String version) { - return 'Version $version'; - } - - @override - String extensionsAuthor(String author) { - return 'by $author'; - } - - @override - String get extensionsUninstall => 'Uninstall'; - - @override - String get extensionsSetAsSearch => 'Set as Search Provider'; - - @override - String get storeTitle => 'Extension Store'; - - @override - String get storeSearch => 'Search extensions...'; - - @override - String get storeInstall => 'Install'; - - @override - String get storeInstalled => 'Installed'; - - @override - String get storeUpdate => 'Update'; - - @override - String get aboutTitle => 'About'; - - @override - String get aboutContributors => 'Contributors'; - - @override - String get aboutMobileDeveloper => 'Mobile version developer'; - - @override - String get aboutOriginalCreator => 'Creator of the original SpotiFLAC'; - - @override - String get aboutLogoArtist => - 'The talented artist who created our beautiful app logo!'; - - @override - String get aboutTranslators => 'Translators'; - - @override - String get aboutSpecialThanks => 'Special Thanks'; - - @override - String get aboutLinks => 'Links'; - - @override - String get aboutMobileSource => 'Mobile source code'; - - @override - String get aboutPCSource => 'PC source code'; - - @override - String get aboutReportIssue => 'Report an issue'; - - @override - String get aboutReportIssueSubtitle => 'Report any problems you encounter'; - - @override - String get aboutFeatureRequest => 'Feature request'; - - @override - String get aboutFeatureRequestSubtitle => 'Suggest new features for the app'; - - @override - String get aboutTelegramChannel => 'Telegram Channel'; - - @override - String get aboutTelegramChannelSubtitle => 'Announcements and updates'; - - @override - String get aboutTelegramChat => 'Telegram Community'; - - @override - String get aboutTelegramChatSubtitle => 'Chat with other users'; - - @override - String get aboutSocial => 'Social'; - - @override - String get aboutSupport => 'Support'; - - @override - String get aboutApp => 'App'; - - @override - String get aboutVersion => 'Version'; - - @override - String get aboutBinimumDesc => - 'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!'; - - @override - String get aboutSachinsenalDesc => - 'The original HiFi project creator. The foundation of Tidal integration!'; - - @override - String get aboutDoubleDouble => 'DoubleDouble'; - - @override - String get aboutDoubleDoubleDesc => - 'Amazing API for Amazon Music downloads. Thank you for making it free!'; - - @override - String get aboutDabMusic => 'DAB Music'; - - @override - String get aboutDabMusicDesc => - 'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!'; - - @override - String get aboutAppDescription => - 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; - - @override - String get albumTitle => 'Album'; - - @override - String albumTracks(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: '$count tracks', - one: '1 track', - ); - return '$_temp0'; - } - - @override - String get albumDownloadAll => 'Download All'; - - @override - String get albumDownloadRemaining => 'Download Remaining'; - - @override - String get playlistTitle => 'Playlist'; - - @override - String get artistTitle => 'Artist'; - - @override - String get artistAlbums => 'Albums'; - - @override - String get artistSingles => 'Singles & EPs'; - - @override - String get artistCompilations => 'Compilations'; - - @override - String artistReleases(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: '$count releases', - one: '1 release', - ); - return '$_temp0'; - } - - @override - String get artistPopular => 'Popular'; - - @override - String artistMonthlyListeners(String count) { - return '$count monthly listeners'; - } - - @override - String get trackMetadataTitle => 'Track Info'; - - @override - String get trackMetadataArtist => 'Artist'; - - @override - String get trackMetadataAlbum => 'Album'; - - @override - String get trackMetadataDuration => 'Duration'; - - @override - String get trackMetadataQuality => 'Quality'; - - @override - String get trackMetadataPath => 'File Path'; - - @override - String get trackMetadataDownloadedAt => 'Downloaded'; - - @override - String get trackMetadataService => 'Service'; - - @override - String get trackMetadataPlay => 'Play'; - - @override - String get trackMetadataShare => 'Share'; - - @override - String get trackMetadataDelete => 'Delete'; - - @override - String get trackMetadataRedownload => 'Re-download'; - - @override - String get trackMetadataOpenFolder => 'Open Folder'; - - @override - String get setupTitle => 'Welcome to SpotiFLAC'; - - @override - String get setupSubtitle => 'Let\'s get you started'; - - @override - String get setupStoragePermission => 'Storage Permission'; - - @override - String get setupStoragePermissionSubtitle => - 'Required to save downloaded files'; - - @override - String get setupStoragePermissionGranted => 'Permission granted'; - - @override - String get setupStoragePermissionDenied => 'Permission denied'; - - @override - String get setupGrantPermission => 'Grant Permission'; - - @override - String get setupDownloadLocation => 'Download Location'; - - @override - String get setupChooseFolder => 'Choose Folder'; - - @override - String get setupContinue => 'Continue'; - - @override - String get setupSkip => 'Skip for now'; - - @override - String get setupStorageAccessRequired => 'Storage Access Required'; - - @override - String get setupStorageAccessMessage => - 'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.'; - - @override - String get setupStorageAccessMessageAndroid11 => - 'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.'; - - @override - String get setupOpenSettings => 'Open Settings'; - - @override - String get setupPermissionDeniedMessage => - 'Permission denied. Please grant all permissions to continue.'; - - @override - String setupPermissionRequired(String permissionType) { - return '$permissionType Permission Required'; - } - - @override - String setupPermissionRequiredMessage(String permissionType) { - return '$permissionType permission is required for the best experience. You can change this later in Settings.'; - } - - @override - String get setupSelectDownloadFolder => 'Select Download Folder'; - - @override - String get setupUseDefaultFolder => 'Use Default Folder?'; - - @override - String get setupNoFolderSelected => - 'No folder selected. Would you like to use the default Music folder?'; - - @override - String get setupUseDefault => 'Use Default'; - - @override - String get setupDownloadLocationTitle => 'Download Location'; - - @override - String get setupDownloadLocationIosMessage => - 'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.'; - - @override - String get setupAppDocumentsFolder => 'App Documents Folder'; - - @override - String get setupAppDocumentsFolderSubtitle => - 'Recommended - accessible via Files app'; - - @override - String get setupChooseFromFiles => 'Choose from Files'; - - @override - String get setupChooseFromFilesSubtitle => 'Select iCloud or other location'; - - @override - String get setupIosEmptyFolderWarning => - 'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.'; - - @override - String get setupDownloadInFlac => 'Download Spotify tracks in FLAC'; - - @override - String get setupStepStorage => 'Storage'; - - @override - String get setupStepNotification => 'Notification'; - - @override - String get setupStepFolder => 'Folder'; - - @override - String get setupStepSpotify => 'Spotify'; - - @override - String get setupStepPermission => 'Permission'; - - @override - String get setupStorageGranted => 'Storage Permission Granted!'; - - @override - String get setupStorageRequired => 'Storage Permission Required'; - - @override - String get setupStorageDescription => - 'SpotiFLAC needs storage permission to save your downloaded music files.'; - - @override - String get setupNotificationGranted => 'Notification Permission Granted!'; - - @override - String get setupNotificationEnable => 'Enable Notifications'; - - @override - String get setupNotificationDescription => - 'Get notified when downloads complete or require attention.'; - - @override - String get setupFolderSelected => 'Download Folder Selected!'; - - @override - String get setupFolderChoose => 'Choose Download Folder'; - - @override - String get setupFolderDescription => - 'Select a folder where your downloaded music will be saved.'; - - @override - String get setupChangeFolder => 'Change Folder'; - - @override - String get setupSelectFolder => 'Select Folder'; - - @override - String get setupSpotifyApiOptional => 'Spotify API (Optional)'; - - @override - String get setupSpotifyApiDescription => - 'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.'; - - @override - String get setupUseSpotifyApi => 'Use Spotify API'; - - @override - String get setupEnterCredentialsBelow => 'Enter your credentials below'; - - @override - String get setupUsingDeezer => 'Using Deezer (no account needed)'; - - @override - String get setupEnterClientId => 'Enter Spotify Client ID'; - - @override - String get setupEnterClientSecret => 'Enter Spotify Client Secret'; - - @override - String get setupGetFreeCredentials => - 'Get your free API credentials from the Spotify Developer Dashboard.'; - - @override - String get setupEnableNotifications => 'Enable Notifications'; - - @override - String get setupProceedToNextStep => 'You can now proceed to the next step.'; - - @override - String get setupNotificationProgressDescription => - 'You will receive download progress notifications.'; - - @override - String get setupNotificationBackgroundDescription => - 'Get notified about download progress and completion. This helps you track downloads when the app is in background.'; - - @override - String get setupSkipForNow => 'Skip for now'; - - @override - String get setupBack => 'Back'; - - @override - String get setupNext => 'Next'; - - @override - String get setupGetStarted => 'Get Started'; - - @override - String get setupSkipAndStart => 'Skip & Start'; - - @override - String get setupAllowAccessToManageFiles => - 'Please enable \"Allow access to manage all files\" in the next screen.'; - - @override - String get setupGetCredentialsFromSpotify => - 'Get credentials from developer.spotify.com'; - - @override - String get dialogCancel => 'Cancel'; - - @override - String get dialogOk => 'OK'; - - @override - String get dialogSave => 'Save'; - - @override - String get dialogDelete => 'Delete'; - - @override - String get dialogRetry => 'Retry'; - - @override - String get dialogClose => 'Close'; - - @override - String get dialogYes => 'Yes'; - - @override - String get dialogNo => 'No'; - - @override - String get dialogClear => 'Clear'; - - @override - String get dialogConfirm => 'Confirm'; - - @override - String get dialogDone => 'Done'; - - @override - String get dialogImport => 'Import'; - - @override - String get dialogDiscard => 'Discard'; - - @override - String get dialogRemove => 'Remove'; - - @override - String get dialogUninstall => 'Uninstall'; - - @override - String get dialogDiscardChanges => 'Discard Changes?'; - - @override - String get dialogUnsavedChanges => - 'You have unsaved changes. Do you want to discard them?'; - - @override - String get dialogDownloadFailed => 'Download Failed'; - - @override - String get dialogTrackLabel => 'Track:'; - - @override - String get dialogArtistLabel => 'Artist:'; - - @override - String get dialogErrorLabel => 'Error:'; - - @override - String get dialogClearAll => 'Clear All'; - - @override - String get dialogClearAllDownloads => - 'Are you sure you want to clear all downloads?'; - - @override - String get dialogRemoveFromDevice => 'Remove from device?'; - - @override - String get dialogRemoveExtension => 'Remove Extension'; - - @override - String get dialogRemoveExtensionMessage => - 'Are you sure you want to remove this extension? This cannot be undone.'; - - @override - String get dialogUninstallExtension => 'Uninstall Extension?'; - - @override - String dialogUninstallExtensionMessage(String extensionName) { - return 'Are you sure you want to remove $extensionName?'; - } - - @override - String get dialogClearHistoryTitle => 'Clear History'; - - @override - String get dialogClearHistoryMessage => - 'Are you sure you want to clear all download history? This cannot be undone.'; - - @override - String get dialogDeleteSelectedTitle => 'Delete Selected'; - - @override - String dialogDeleteSelectedMessage(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: 'tracks', - one: 'track', - ); - return 'Delete $count $_temp0 from history?\n\nThis will also delete the files from storage.'; - } - - @override - String get dialogImportPlaylistTitle => 'Import Playlist'; - - @override - String dialogImportPlaylistMessage(int count) { - return 'Found $count tracks in CSV. Add them to download queue?'; - } - - @override - String csvImportTracks(int count) { - return '$count tracks from CSV'; - } - - @override - String snackbarAddedToQueue(String trackName) { - return 'Added \"$trackName\" to queue'; - } - - @override - String snackbarAddedTracksToQueue(int count) { - return 'Added $count tracks to queue'; - } - - @override - String snackbarAlreadyDownloaded(String trackName) { - return '\"$trackName\" already downloaded'; - } - - @override - String get snackbarHistoryCleared => 'History cleared'; - - @override - String get snackbarCredentialsSaved => 'Credentials saved'; - - @override - String get snackbarCredentialsCleared => 'Credentials cleared'; - - @override - String snackbarDeletedTracks(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: 'tracks', - one: 'track', - ); - return 'Deleted $count $_temp0'; - } - - @override - String snackbarCannotOpenFile(String error) { - return 'Cannot open file: $error'; - } - - @override - String get snackbarFillAllFields => 'Please fill all fields'; - - @override - String get snackbarViewQueue => 'View Queue'; - - @override - String snackbarFailedToLoad(String error) { - return 'Failed to load: $error'; - } - - @override - String snackbarUrlCopied(String platform) { - return '$platform URL copied to clipboard'; - } - - @override - String get snackbarFileNotFound => 'File not found'; - - @override - String get snackbarSelectExtFile => 'Please select a .spotiflac-ext file'; - - @override - String get snackbarProviderPrioritySaved => 'Provider priority saved'; - - @override - String get snackbarMetadataProviderSaved => - 'Metadata provider priority saved'; - - @override - String snackbarExtensionInstalled(String extensionName) { - return '$extensionName installed.'; - } - - @override - String snackbarExtensionUpdated(String extensionName) { - return '$extensionName updated.'; - } - - @override - String get snackbarFailedToInstall => 'Failed to install extension'; - - @override - String get snackbarFailedToUpdate => 'Failed to update extension'; - - @override - String get errorRateLimited => 'Rate Limited'; - - @override - String get errorRateLimitedMessage => - 'Too many requests. Please wait a moment before searching again.'; - - @override - String errorFailedToLoad(String item) { - return 'Failed to load $item'; - } - - @override - String get errorNoTracksFound => 'No tracks found'; - - @override - String errorMissingExtensionSource(String item) { - return 'Cannot load $item: missing extension source'; - } - - @override - String get statusQueued => 'Queued'; - - @override - String get statusDownloading => 'Downloading'; - - @override - String get statusFinalizing => 'Finalizing'; - - @override - String get statusCompleted => 'Completed'; - - @override - String get statusFailed => 'Failed'; - - @override - String get statusSkipped => 'Skipped'; - - @override - String get statusPaused => 'Paused'; - - @override - String get actionPause => 'Pause'; - - @override - String get actionResume => 'Resume'; - - @override - String get actionCancel => 'Cancel'; - - @override - String get actionStop => 'Stop'; - - @override - String get actionSelect => 'Select'; - - @override - String get actionSelectAll => 'Select All'; - - @override - String get actionDeselect => 'Deselect'; - - @override - String get actionPaste => 'Paste'; - - @override - String get actionImportCsv => 'Import CSV'; - - @override - String get actionRemoveCredentials => 'Remove Credentials'; - - @override - String get actionSaveCredentials => 'Save Credentials'; - - @override - String selectionSelected(int count) { - return '$count selected'; - } - - @override - String get selectionAllSelected => 'All tracks selected'; - - @override - String get selectionTapToSelect => 'Tap tracks to select'; - - @override - String selectionDeleteTracks(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: 'tracks', - one: 'track', - ); - return 'Delete $count $_temp0'; - } - - @override - String get selectionSelectToDelete => 'Select tracks to delete'; - - @override - String progressFetchingMetadata(int current, int total) { - return 'Fetching metadata... $current/$total'; - } - - @override - String get progressReadingCsv => 'Reading CSV...'; - - @override - String get searchSongs => 'Songs'; - - @override - String get searchArtists => 'Artists'; - - @override - String get searchAlbums => 'Albums'; - - @override - String get searchPlaylists => 'Playlists'; - - @override - String get tooltipPlay => 'Play'; - - @override - String get tooltipCancel => 'Cancel'; - - @override - String get tooltipStop => 'Stop'; - - @override - String get tooltipRetry => 'Retry'; - - @override - String get tooltipRemove => 'Remove'; - - @override - String get tooltipClear => 'Clear'; - - @override - String get tooltipPaste => 'Paste'; - - @override - String get filenameFormat => 'Filename Format'; - - @override - String filenameFormatPreview(String preview) { - return 'Preview: $preview'; - } - - @override - String get filenameAvailablePlaceholders => 'Available placeholders:'; - - @override - String filenameHint(Object artist, Object title) { - return '$artist - $title'; - } - - @override - String get folderOrganization => 'Folder Organization'; - - @override - String get folderOrganizationNone => 'No organization'; - - @override - String get folderOrganizationByArtist => 'By Artist'; - - @override - String get folderOrganizationByAlbum => 'By Album'; - - @override - String get folderOrganizationByArtistAlbum => 'Artist/Album'; - - @override - String get folderOrganizationDescription => - 'Organize downloaded files into folders'; - - @override - String get folderOrganizationNoneSubtitle => 'All files in download folder'; - - @override - String get folderOrganizationByArtistSubtitle => - 'Separate folder for each artist'; - - @override - String get folderOrganizationByAlbumSubtitle => - 'Separate folder for each album'; - - @override - String get folderOrganizationByArtistAlbumSubtitle => - 'Nested folders for artist and album'; - - @override - String get updateAvailable => 'Update Available'; - - @override - String updateNewVersion(String version) { - return 'Version $version is available'; - } - - @override - String get updateDownload => 'Download'; - - @override - String get updateLater => 'Later'; - - @override - String get updateChangelog => 'Changelog'; - - @override - String get updateStartingDownload => 'Starting download...'; - - @override - String get updateDownloadFailed => 'Download failed'; - - @override - String get updateFailedMessage => 'Failed to download update'; - - @override - String get updateNewVersionReady => 'A new version is ready'; - - @override - String get updateCurrent => 'Current'; - - @override - String get updateNew => 'New'; - - @override - String get updateDownloading => 'Downloading...'; - - @override - String get updateWhatsNew => 'What\'s New'; - - @override - String get updateDownloadInstall => 'Download & Install'; - - @override - String get updateDontRemind => 'Don\'t remind'; - - @override - String get providerPriority => 'Provider Priority'; - - @override - String get providerPrioritySubtitle => 'Drag to reorder download providers'; - - @override - String get providerPriorityTitle => 'Provider Priority'; - - @override - String get providerPriorityDescription => - 'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.'; - - @override - String get providerPriorityInfo => - 'If a track is not available on the first provider, the app will automatically try the next one.'; - - @override - String get providerBuiltIn => 'Built-in'; - - @override - String get providerExtension => 'Extension'; - - @override - String get metadataProviderPriority => 'Metadata Provider Priority'; - - @override - String get metadataProviderPrioritySubtitle => - 'Order used when fetching track metadata'; - - @override - String get metadataProviderPriorityTitle => 'Metadata Priority'; - - @override - String get metadataProviderPriorityDescription => - 'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.'; - - @override - String get metadataProviderPriorityInfo => - 'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.'; - - @override - String get metadataNoRateLimits => 'No rate limits'; - - @override - String get metadataMayRateLimit => 'May rate limit'; - - @override - String get logTitle => 'Logs'; - - @override - String get logCopy => 'Copy Logs'; - - @override - String get logClear => 'Clear Logs'; - - @override - String get logShare => 'Share Logs'; - - @override - String get logEmpty => 'No logs yet'; - - @override - String get logCopied => 'Logs copied to clipboard'; - - @override - String get logSearchHint => 'Search logs...'; - - @override - String get logFilterLevel => 'Level'; - - @override - String get logFilterSection => 'Filter'; - - @override - String get logShareLogs => 'Share logs'; - - @override - String get logClearLogs => 'Clear logs'; - - @override - String get logClearLogsTitle => 'Clear Logs'; - - @override - String get logClearLogsMessage => 'Are you sure you want to clear all logs?'; - - @override - String get logIspBlocking => 'ISP BLOCKING DETECTED'; - - @override - String get logRateLimited => 'RATE LIMITED'; - - @override - String get logNetworkError => 'NETWORK ERROR'; - - @override - String get logTrackNotFound => 'TRACK NOT FOUND'; - - @override - String get logFilterBySeverity => 'Filter logs by severity'; - - @override - String get logNoLogsYet => 'No logs yet'; - - @override - String get logNoLogsYetSubtitle => 'Logs will appear here as you use the app'; - - @override - String get logIssueSummary => 'Issue Summary'; - - @override - String get logIspBlockingDescription => - 'Your ISP may be blocking access to download services'; - - @override - String get logIspBlockingSuggestion => - 'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8'; - - @override - String get logRateLimitedDescription => 'Too many requests to the service'; - - @override - String get logRateLimitedSuggestion => - 'Wait a few minutes before trying again'; - - @override - String get logNetworkErrorDescription => 'Connection issues detected'; - - @override - String get logNetworkErrorSuggestion => 'Check your internet connection'; - - @override - String get logTrackNotFoundDescription => - 'Some tracks could not be found on download services'; - - @override - String get logTrackNotFoundSuggestion => - 'The track may not be available in lossless quality'; - - @override - String logTotalErrors(int count) { - return 'Total errors: $count'; - } - - @override - String logAffected(String domains) { - return 'Affected: $domains'; - } - - @override - String logEntriesFiltered(int count) { - return 'Entries ($count filtered)'; - } - - @override - String logEntries(int count) { - return 'Entries ($count)'; - } - - @override - String get credentialsTitle => 'Spotify Credentials'; - - @override - String get credentialsDescription => - 'Enter your Client ID and Secret to use your own Spotify application quota.'; - - @override - String get credentialsClientId => 'Client ID'; - - @override - String get credentialsClientIdHint => 'Paste Client ID'; - - @override - String get credentialsClientSecret => 'Client Secret'; - - @override - String get credentialsClientSecretHint => 'Paste Client Secret'; - - @override - String get channelStable => 'Stable'; - - @override - String get channelPreview => 'Preview'; - - @override - String get sectionSearchSource => 'Search Source'; - - @override - String get sectionDownload => 'Download'; - - @override - String get sectionPerformance => 'Performance'; - - @override - String get sectionApp => 'App'; - - @override - String get sectionData => 'Data'; - - @override - String get sectionDebug => 'Debug'; - - @override - String get sectionService => 'Service'; - - @override - String get sectionAudioQuality => 'Audio Quality'; - - @override - String get sectionFileSettings => 'File Settings'; - - @override - String get sectionLyrics => 'Lyrics'; - - @override - String get lyricsMode => 'Lyrics Mode'; - - @override - String get lyricsModeDescription => - 'Choose how lyrics are saved with your downloads'; - - @override - String get lyricsModeEmbed => 'Embed in file'; - - @override - String get lyricsModeEmbedSubtitle => 'Lyrics stored inside FLAC metadata'; - - @override - String get lyricsModeExternal => 'External .lrc file'; - - @override - String get lyricsModeExternalSubtitle => - 'Separate .lrc file for players like Samsung Music'; - - @override - String get lyricsModeBoth => 'Both'; - - @override - String get lyricsModeBothSubtitle => 'Embed and save .lrc file'; - - @override - String get sectionColor => 'Color'; - - @override - String get sectionTheme => 'Theme'; - - @override - String get sectionLayout => 'Layout'; - - @override - String get sectionLanguage => 'Language'; - - @override - String get appearanceLanguage => 'App Language'; - - @override - String get appearanceLanguageSubtitle => 'Choose your preferred language'; - - @override - String get settingsAppearanceSubtitle => 'Theme, colors, display'; - - @override - String get settingsDownloadSubtitle => 'Service, quality, filename format'; - - @override - String get settingsOptionsSubtitle => 'Fallback, lyrics, cover art, updates'; - - @override - String get settingsExtensionsSubtitle => 'Manage download providers'; - - @override - String get settingsLogsSubtitle => 'View app logs for debugging'; - - @override - String get loadingSharedLink => 'Loading shared link...'; - - @override - String get pressBackAgainToExit => 'Press back again to exit'; - - @override - String get tracksHeader => 'Tracks'; - - @override - String downloadAllCount(int count) { - return 'Download All ($count)'; - } - - @override - String tracksCount(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: '$count tracks', - one: '1 track', - ); - return '$_temp0'; - } - - @override - String get trackCopyFilePath => 'Copy file path'; - - @override - String get trackRemoveFromDevice => 'Remove from device'; - - @override - String get trackLoadLyrics => 'Load Lyrics'; - - @override - String get trackMetadata => 'Metadata'; - - @override - String get trackFileInfo => 'File Info'; - - @override - String get trackLyrics => 'Lyrics'; - - @override - String get trackFileNotFound => 'File not found'; - - @override - String get trackOpenInDeezer => 'Open in Deezer'; - - @override - String get trackOpenInSpotify => 'Open in Spotify'; - - @override - String get trackTrackName => 'Track name'; - - @override - String get trackArtist => 'Artist'; - - @override - String get trackAlbumArtist => 'Album artist'; - - @override - String get trackAlbum => 'Album'; - - @override - String get trackTrackNumber => 'Track number'; - - @override - String get trackDiscNumber => 'Disc number'; - - @override - String get trackDuration => 'Duration'; - - @override - String get trackAudioQuality => 'Audio quality'; - - @override - String get trackReleaseDate => 'Release date'; - - @override - String get trackGenre => 'Genre'; - - @override - String get trackLabel => 'Label'; - - @override - String get trackCopyright => 'Copyright'; - - @override - String get trackDownloaded => 'Downloaded'; - - @override - String get trackCopyLyrics => 'Copy lyrics'; - - @override - String get trackLyricsNotAvailable => 'Lyrics not available for this track'; - - @override - String get trackLyricsTimeout => 'Request timed out. Try again later.'; - - @override - String get trackLyricsLoadFailed => 'Failed to load lyrics'; - - @override - String get trackEmbedLyrics => 'Embed Lyrics'; - - @override - String get trackLyricsEmbedded => 'Lyrics embedded successfully'; - - @override - String get trackInstrumental => 'Instrumental track'; - - @override - String get trackCopiedToClipboard => 'Copied to clipboard'; - - @override - String get trackDeleteConfirmTitle => 'Remove from device?'; - - @override - String get trackDeleteConfirmMessage => - 'This will permanently delete the downloaded file and remove it from your history.'; - - @override - String trackCannotOpen(String message) { - return 'Cannot open: $message'; - } - - @override - String get dateToday => 'Today'; - - @override - String get dateYesterday => 'Yesterday'; - - @override - String dateDaysAgo(int count) { - return '$count days ago'; - } - - @override - String dateWeeksAgo(int count) { - return '$count weeks ago'; - } - - @override - String dateMonthsAgo(int count) { - return '$count months ago'; - } - - @override - String get concurrentSequential => 'Sequential'; - - @override - String get concurrentParallel2 => '2 Parallel'; - - @override - String get concurrentParallel3 => '3 Parallel'; - - @override - String get tapToSeeError => 'Tap to see error details'; - - @override - String get storeFilterAll => 'All'; - - @override - String get storeFilterMetadata => 'Metadata'; - - @override - String get storeFilterDownload => 'Download'; - - @override - String get storeFilterUtility => 'Utility'; - - @override - String get storeFilterLyrics => 'Lyrics'; - - @override - String get storeFilterIntegration => 'Integration'; - - @override - String get storeClearFilters => 'Clear filters'; - - @override - String get storeNoResults => 'No extensions found'; - - @override - String get extensionProviderPriority => 'Provider Priority'; - - @override - String get extensionInstallButton => 'Install Extension'; - - @override - String get extensionDefaultProvider => 'Default (Deezer/Spotify)'; - - @override - String get extensionDefaultProviderSubtitle => 'Use built-in search'; - - @override - String get extensionAuthor => 'Author'; - - @override - String get extensionId => 'ID'; - - @override - String get extensionError => 'Error'; - - @override - String get extensionCapabilities => 'Capabilities'; - - @override - String get extensionMetadataProvider => 'Metadata Provider'; - - @override - String get extensionDownloadProvider => 'Download Provider'; - - @override - String get extensionLyricsProvider => 'Lyrics Provider'; - - @override - String get extensionUrlHandler => 'URL Handler'; - - @override - String get extensionQualityOptions => 'Quality Options'; - - @override - String get extensionPostProcessingHooks => 'Post-Processing Hooks'; - - @override - String get extensionPermissions => 'Permissions'; - - @override - String get extensionSettings => 'Settings'; - - @override - String get extensionRemoveButton => 'Remove Extension'; - - @override - String get extensionUpdated => 'Updated'; - - @override - String get extensionMinAppVersion => 'Min App Version'; - - @override - String get extensionCustomTrackMatching => 'Custom Track Matching'; - - @override - String get extensionPostProcessing => 'Post-Processing'; - - @override - String extensionHooksAvailable(int count) { - return '$count hook(s) available'; - } - - @override - String extensionPatternsCount(int count) { - return '$count pattern(s)'; - } - - @override - String extensionStrategy(String strategy) { - return 'Strategy: $strategy'; - } - - @override - String get extensionsProviderPrioritySection => 'Provider Priority'; - - @override - String get extensionsInstalledSection => 'Installed Extensions'; - - @override - String get extensionsNoExtensions => 'No extensions installed'; - - @override - String get extensionsNoExtensionsSubtitle => - 'Install .spotiflac-ext files to add new providers'; - - @override - String get extensionsInstallButton => 'Install Extension'; - - @override - String get extensionsInfoTip => - 'Extensions can add new metadata and download providers. Only install extensions from trusted sources.'; - - @override - String get extensionsInstalledSuccess => 'Extension installed successfully'; - - @override - String get extensionsDownloadPriority => 'Download Priority'; - - @override - String get extensionsDownloadPrioritySubtitle => 'Set download service order'; - - @override - String get extensionsNoDownloadProvider => - 'No extensions with download provider'; - - @override - String get extensionsMetadataPriority => 'Metadata Priority'; - - @override - String get extensionsMetadataPrioritySubtitle => - 'Set search & metadata source order'; - - @override - String get extensionsNoMetadataProvider => - 'No extensions with metadata provider'; - - @override - String get extensionsSearchProvider => 'Search Provider'; - - @override - String get extensionsNoCustomSearch => 'No extensions with custom search'; - - @override - String get extensionsSearchProviderDescription => - 'Choose which service to use for searching tracks'; - - @override - String get extensionsCustomSearch => 'Custom search'; - - @override - String get extensionsErrorLoading => 'Error loading extension'; - - @override - String get qualityFlacLossless => 'FLAC Lossless'; - - @override - String get qualityFlacLosslessSubtitle => '16-bit / 44.1kHz'; - - @override - String get qualityHiResFlac => 'Hi-Res FLAC'; - - @override - String get qualityHiResFlacSubtitle => '24-bit / up to 96kHz'; - - @override - String get qualityHiResFlacMax => 'Hi-Res FLAC Max'; - - @override - String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz'; - - @override - String get qualityNote => - 'Actual quality depends on track availability from the service'; - - @override - String get downloadAskBeforeDownload => 'Ask Before Download'; - - @override - String get downloadDirectory => 'Download Directory'; - - @override - String get downloadSeparateSinglesFolder => 'Separate Singles Folder'; - - @override - String get downloadAlbumFolderStructure => 'Album Folder Structure'; - - @override - String get downloadSaveFormat => 'Save Format'; - - @override - String get downloadSelectService => 'Select Service'; - - @override - String get downloadSelectQuality => 'Select Quality'; - - @override - String get downloadFrom => 'Download From'; - - @override - String get downloadDefaultQualityLabel => 'Default Quality'; - - @override - String get downloadBestAvailable => 'Best available'; - - @override - String get folderNone => 'None'; - - @override - String get folderNoneSubtitle => 'Save all files directly to download folder'; - - @override - String get folderArtist => 'Artist'; - - @override - String get folderArtistSubtitle => 'Artist Name/filename'; - - @override - String get folderAlbum => 'Album'; - - @override - String get folderAlbumSubtitle => 'Album Name/filename'; - - @override - String get folderArtistAlbum => 'Artist/Album'; - - @override - String get folderArtistAlbumSubtitle => 'Artist Name/Album Name/filename'; - - @override - String get serviceTidal => 'Tidal'; - - @override - String get serviceQobuz => 'Qobuz'; - - @override - String get serviceAmazon => 'Amazon'; - - @override - String get serviceDeezer => 'Deezer'; - - @override - String get serviceSpotify => 'Spotify'; - - @override - String get appearanceAmoledDark => 'AMOLED Dark'; - - @override - String get appearanceAmoledDarkSubtitle => 'Pure black background'; - - @override - String get appearanceChooseAccentColor => 'Choose Accent Color'; - - @override - String get appearanceChooseTheme => 'Theme Mode'; - - @override - String get queueTitle => 'Download Queue'; - - @override - String get queueClearAll => 'Clear All'; - - @override - String get queueClearAllMessage => - 'Are you sure you want to clear all downloads?'; - - @override - String get queueEmpty => 'No downloads in queue'; - - @override - String get queueEmptySubtitle => 'Add tracks from the home screen'; - - @override - String get queueClearCompleted => 'Clear completed'; - - @override - String get queueDownloadFailed => 'Download Failed'; - - @override - String get queueTrackLabel => 'Track:'; - - @override - String get queueArtistLabel => 'Artist:'; - - @override - String get queueErrorLabel => 'Error:'; - - @override - String get queueUnknownError => 'Unknown error'; - - @override - String get albumFolderArtistAlbum => 'Artist / Album'; - - @override - String get albumFolderArtistAlbumSubtitle => 'Albums/Artist Name/Album Name/'; - - @override - String get albumFolderArtistYearAlbum => 'Artist / [Year] Album'; - - @override - String get albumFolderArtistYearAlbumSubtitle => - 'Albums/Artist Name/[2005] Album Name/'; - - @override - String get albumFolderAlbumOnly => 'Album Only'; - - @override - String get albumFolderAlbumOnlySubtitle => 'Albums/Album Name/'; - - @override - String get albumFolderYearAlbum => '[Year] Album'; - - @override - String get albumFolderYearAlbumSubtitle => 'Albums/[2005] Album Name/'; - - @override - String get albumFolderArtistAlbumSingles => 'Artist / Album + Singles'; - - @override - String get albumFolderArtistAlbumSinglesSubtitle => - 'Artist/Album/ and Artist/Singles/'; - - @override - String get downloadedAlbumDeleteSelected => 'Delete Selected'; - - @override - String downloadedAlbumDeleteMessage(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: 'tracks', - one: 'track', - ); - return 'Delete $count $_temp0 from this album?\n\nThis will also delete the files from storage.'; - } - - @override - String get downloadedAlbumTracksHeader => 'Tracks'; - - @override - String downloadedAlbumDownloadedCount(int count) { - return '$count downloaded'; - } - - @override - String downloadedAlbumSelectedCount(int count) { - return '$count selected'; - } - - @override - String get downloadedAlbumAllSelected => 'All tracks selected'; - - @override - String get downloadedAlbumTapToSelect => 'Tap tracks to select'; - - @override - String downloadedAlbumDeleteCount(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: 'tracks', - one: 'track', - ); - return 'Delete $count $_temp0'; - } - - @override - String get downloadedAlbumSelectToDelete => 'Select tracks to delete'; - - @override - String downloadedAlbumDiscHeader(int discNumber) { - return 'Disc $discNumber'; - } - - @override - String get utilityFunctions => 'Utility Functions'; - - @override - String get recentTypeArtist => 'Artist'; - - @override - String get recentTypeAlbum => 'Album'; - - @override - String get recentTypeSong => 'Song'; - - @override - String get recentTypePlaylist => 'Playlist'; - - @override - String recentPlaylistInfo(String name) { - return 'Playlist: $name'; - } - - @override - String errorGeneric(String message) { - return 'Error: $message'; - } - - @override - String get discographyDownload => 'Download Discography'; - - @override - String get discographyDownloadAll => 'Download All'; - - @override - String discographyDownloadAllSubtitle(int count, int albumCount) { - return '$count tracks from $albumCount releases'; - } - - @override - String get discographyAlbumsOnly => 'Albums Only'; - - @override - String discographyAlbumsOnlySubtitle(int count, int albumCount) { - return '$count tracks from $albumCount albums'; - } - - @override - String get discographySinglesOnly => 'Singles & EPs Only'; - - @override - String discographySinglesOnlySubtitle(int count, int albumCount) { - return '$count tracks from $albumCount singles'; - } - - @override - String get discographySelectAlbums => 'Select Albums...'; - - @override - String get discographySelectAlbumsSubtitle => - 'Choose specific albums or singles'; - - @override - String get discographyFetchingTracks => 'Fetching tracks...'; - - @override - String discographyFetchingAlbum(int current, int total) { - return 'Fetching $current of $total...'; - } - - @override - String discographySelectedCount(int count) { - return '$count selected'; - } - - @override - String get discographyDownloadSelected => 'Download Selected'; - - @override - String discographyAddedToQueue(int count) { - return 'Added $count tracks to queue'; - } - - @override - String discographySkippedDownloaded(int added, int skipped) { - return '$added added, $skipped already downloaded'; - } - - @override - String get discographyNoAlbums => 'No albums available'; - - @override - String get discographyFailedToFetch => 'Failed to fetch some albums'; -} - /// The translations for Chinese, as used in Taiwan (`zh_TW`). class AppLocalizationsZhTw extends AppLocalizationsZh { AppLocalizationsZhTw() : super('zh_TW'); @@ -5091,6 +5891,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get navHome => 'Home'; + @override + String get navLibrary => 'Library'; + @override String get navHistory => 'History'; @@ -5413,6 +6216,10 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get optionsSpotifyWarning => 'Spotify requires your own API credentials. Get them free from developer.spotify.com'; + @override + String get optionsSpotifyDeprecationWarning => + 'Spotify search will be deprecated on March 3, 2026 due to Spotify API changes. Please switch to Deezer.'; + @override String get extensionsTitle => 'Extensions'; @@ -5537,6 +6344,10 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get aboutSachinsenalDesc => 'The original HiFi project creator. The foundation of Tidal integration!'; + @override + String get aboutSjdonadoDesc => + 'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!'; + @override String get aboutDoubleDouble => 'DoubleDouble'; @@ -5551,6 +6362,13 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get aboutDabMusicDesc => 'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!'; + @override + String get aboutSpotiSaver => 'SpotiSaver'; + + @override + String get aboutSpotiSaverDesc => + 'Tidal Hi-Res FLAC streaming endpoints. A key piece of the lossless puzzle!'; + @override String get aboutAppDescription => 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'; @@ -5747,6 +6565,10 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get setupIosEmptyFolderWarning => 'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.'; + @override + String get setupIcloudNotSupported => + 'iCloud Drive is not supported. Please use the app Documents folder.'; + @override String get setupDownloadInFlac => 'Download Spotify tracks in FLAC'; @@ -6002,6 +6824,11 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { return '\"$trackName\" already downloaded'; } + @override + String snackbarAlreadyInLibrary(String trackName) { + return '\"$trackName\" already exists in your library'; + } + @override String get snackbarHistoryCleared => 'History cleared'; @@ -6922,10 +7749,46 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz'; + @override + String get qualityLossy => 'Lossy'; + + @override + String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)'; + + @override + String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)'; + + @override + String get enableLossyOption => 'Enable Lossy Option'; + + @override + String get enableLossyOptionSubtitleOn => 'Lossy quality option is available'; + + @override + String get enableLossyOptionSubtitleOff => + 'Downloads FLAC then converts to lossy format'; + + @override + String get lossyFormat => 'Lossy Format'; + + @override + String get lossyFormatDescription => 'Choose the lossy format for conversion'; + + @override + String get lossyFormatMp3Subtitle => '320kbps, best compatibility'; + + @override + String get lossyFormatOpusSubtitle => + '128kbps, better quality at smaller size'; + @override String get qualityNote => 'Actual quality depends on track availability from the service'; + @override + String get youtubeQualityNote => + 'YouTube provides lossy audio only. Not part of lossless fallback.'; + @override String get downloadAskBeforeDownload => 'Ask Before Download'; @@ -6938,6 +7801,28 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get downloadAlbumFolderStructure => 'Album Folder Structure'; + @override + String get downloadUseAlbumArtistForFolders => 'Use Album Artist for folders'; + + @override + String get downloadUseAlbumArtistForFoldersAlbumSubtitle => + 'Artist folders use Album Artist when available'; + + @override + String get downloadUseAlbumArtistForFoldersTrackSubtitle => + 'Artist folders use Track Artist only'; + + @override + String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders'; + + @override + String get downloadUsePrimaryArtistOnlyEnabled => + 'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)'; + + @override + String get downloadUsePrimaryArtistOnlyDisabled => + 'Full artist string used for folder name'; + @override String get downloadSaveFormat => 'Save Format'; @@ -7017,6 +7902,39 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get queueClearAllMessage => 'Are you sure you want to clear all downloads?'; + @override + String get queueExportFailed => 'Export'; + + @override + String get queueExportFailedSuccess => + 'Failed downloads exported to TXT file'; + + @override + String get queueExportFailedClear => 'Clear Failed'; + + @override + String get queueExportFailedError => 'Failed to export downloads'; + + @override + String get settingsAutoExportFailed => 'Auto-export failed downloads'; + + @override + String get settingsAutoExportFailedSubtitle => + 'Save failed downloads to TXT file automatically'; + + @override + String get settingsDownloadNetwork => 'Download Network'; + + @override + String get settingsDownloadNetworkAny => 'WiFi + Mobile Data'; + + @override + String get settingsDownloadNetworkWifiOnly => 'WiFi Only'; + + @override + String get settingsDownloadNetworkSubtitle => + 'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.'; + @override String get queueEmpty => 'No downloads in queue'; @@ -7140,6 +8058,12 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get recentTypePlaylist => 'Playlist'; + @override + String get recentEmpty => 'No recent items yet'; + + @override + String get recentShowAllDownloads => 'Show All Downloads'; + @override String recentPlaylistInfo(String name) { return 'Playlist: $name'; @@ -7215,4 +8139,673 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String get discographyFailedToFetch => 'Failed to fetch some albums'; + + @override + String get sectionStorageAccess => 'Storage Access'; + + @override + String get allFilesAccess => 'All Files Access'; + + @override + String get allFilesAccessEnabledSubtitle => 'Can write to any folder'; + + @override + String get allFilesAccessDisabledSubtitle => 'Limited to media folders only'; + + @override + String get allFilesAccessDescription => + 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.'; + + @override + String get allFilesAccessDeniedMessage => + 'Permission was denied. Please enable \'All files access\' manually in system settings.'; + + @override + String get allFilesAccessDisabledMessage => + 'All Files Access disabled. The app will use limited storage access.'; + + @override + String get settingsLocalLibrary => 'Local Library'; + + @override + String get settingsLocalLibrarySubtitle => 'Scan music & detect duplicates'; + + @override + String get settingsCache => 'Storage & Cache'; + + @override + String get settingsCacheSubtitle => 'View size and clear cached data'; + + @override + String get libraryTitle => 'Local Library'; + + @override + String get libraryStatus => 'Library Status'; + + @override + String get libraryScanSettings => 'Scan Settings'; + + @override + String get libraryEnableLocalLibrary => 'Enable Local Library'; + + @override + String get libraryEnableLocalLibrarySubtitle => + 'Scan and track your existing music'; + + @override + String get libraryFolder => 'Library Folder'; + + @override + String get libraryFolderHint => 'Tap to select folder'; + + @override + String get libraryShowDuplicateIndicator => 'Show Duplicate Indicator'; + + @override + String get libraryShowDuplicateIndicatorSubtitle => + 'Show when searching for existing tracks'; + + @override + String get libraryActions => 'Actions'; + + @override + String get libraryScan => 'Scan Library'; + + @override + String get libraryScanSubtitle => 'Scan for audio files'; + + @override + String get libraryScanSelectFolderFirst => 'Select a folder first'; + + @override + String get libraryCleanupMissingFiles => 'Cleanup Missing Files'; + + @override + String get libraryCleanupMissingFilesSubtitle => + 'Remove entries for files that no longer exist'; + + @override + String get libraryClear => 'Clear Library'; + + @override + String get libraryClearSubtitle => 'Remove all scanned tracks'; + + @override + String get libraryClearConfirmTitle => 'Clear Library'; + + @override + String get libraryClearConfirmMessage => + 'This will remove all scanned tracks from your library. Your actual music files will not be deleted.'; + + @override + String get libraryAbout => 'About Local Library'; + + @override + String get libraryAboutDescription => + 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.'; + + @override + String libraryTracksCount(int count) { + return '$count tracks'; + } + + @override + String libraryLastScanned(String time) { + return 'Last scanned: $time'; + } + + @override + String get libraryLastScannedNever => 'Never'; + + @override + String get libraryScanning => 'Scanning...'; + + @override + String libraryScanProgress(String progress, int total) { + return '$progress% of $total files'; + } + + @override + String get libraryInLibrary => 'In Library'; + + @override + String libraryRemovedMissingFiles(int count) { + return 'Removed $count missing files from library'; + } + + @override + String get libraryCleared => 'Library cleared'; + + @override + String get libraryStorageAccessRequired => 'Storage Access Required'; + + @override + String get libraryStorageAccessMessage => + 'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.'; + + @override + String get libraryFolderNotExist => 'Selected folder does not exist'; + + @override + String get librarySourceDownloaded => 'Downloaded'; + + @override + String get librarySourceLocal => 'Local'; + + @override + String get libraryFilterAll => 'All'; + + @override + String get libraryFilterDownloaded => 'Downloaded'; + + @override + String get libraryFilterLocal => 'Local'; + + @override + String get libraryFilterTitle => 'Filters'; + + @override + String get libraryFilterReset => 'Reset'; + + @override + String get libraryFilterApply => 'Apply'; + + @override + String get libraryFilterSource => 'Source'; + + @override + String get libraryFilterQuality => 'Quality'; + + @override + String get libraryFilterQualityHiRes => 'Hi-Res (24bit)'; + + @override + String get libraryFilterQualityCD => 'CD (16bit)'; + + @override + String get libraryFilterQualityLossy => 'Lossy'; + + @override + String get libraryFilterFormat => 'Format'; + + @override + String get libraryFilterDate => 'Date Added'; + + @override + String get libraryFilterDateToday => 'Today'; + + @override + String get libraryFilterDateWeek => 'This Week'; + + @override + String get libraryFilterDateMonth => 'This Month'; + + @override + String get libraryFilterDateYear => 'This Year'; + + @override + String get libraryFilterSort => 'Sort'; + + @override + String get libraryFilterSortLatest => 'Latest'; + + @override + String get libraryFilterSortOldest => 'Oldest'; + + @override + String libraryFilterActive(int count) { + return '$count filter(s) active'; + } + + @override + String get timeJustNow => 'Just now'; + + @override + String timeMinutesAgo(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count minutes ago', + one: '1 minute ago', + ); + return '$_temp0'; + } + + @override + String timeHoursAgo(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count hours ago', + one: '1 hour ago', + ); + return '$_temp0'; + } + + @override + String get storageSwitchTitle => 'Switch Storage Mode'; + + @override + String get storageSwitchToSafTitle => 'Switch to SAF Storage?'; + + @override + String get storageSwitchToAppTitle => 'Switch to App Storage?'; + + @override + String get storageSwitchToSafMessage => + 'Your existing downloads will remain in the current location and stay accessible.\n\nNew downloads will be saved to your selected SAF folder.'; + + @override + String get storageSwitchToAppMessage => + 'Your existing downloads will remain in the current SAF location and stay accessible.\n\nNew downloads will be saved to Music/SpotiFLAC folder.'; + + @override + String get storageSwitchExistingDownloads => 'Existing Downloads'; + + @override + String storageSwitchExistingDownloadsInfo(int count, String mode) { + return '$count tracks in $mode storage'; + } + + @override + String get storageSwitchNewDownloads => 'New Downloads'; + + @override + String storageSwitchNewDownloadsLocation(String location) { + return 'Will be saved to: $location'; + } + + @override + String get storageSwitchContinue => 'Continue'; + + @override + String get storageSwitchSelectFolder => 'Select SAF Folder'; + + @override + String get storageAppStorage => 'App Storage'; + + @override + String get storageSafStorage => 'SAF Storage'; + + @override + String storageModeBadge(String mode) { + return 'Storage: $mode'; + } + + @override + String get storageStatsTitle => 'Storage Statistics'; + + @override + String storageStatsAppCount(int count) { + return '$count tracks in App Storage'; + } + + @override + String storageStatsSafCount(int count) { + return '$count tracks in SAF Storage'; + } + + @override + String get storageModeInfo => 'Your files are stored in multiple locations'; + + @override + String get tutorialWelcomeTitle => 'Welcome to SpotiFLAC!'; + + @override + String get tutorialWelcomeDesc => + 'Let\'s learn how to download your favorite music in lossless quality. This quick tutorial will show you the basics.'; + + @override + String get tutorialWelcomeTip1 => + 'Download music from Spotify, Deezer, or paste any supported URL'; + + @override + String get tutorialWelcomeTip2 => + 'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'; + + @override + String get tutorialWelcomeTip3 => + 'Automatic metadata, cover art, and lyrics embedding'; + + @override + String get tutorialSearchTitle => 'Finding Music'; + + @override + String get tutorialSearchDesc => + 'There are two easy ways to find music you want to download.'; + + @override + String get tutorialSearchTip1 => + 'Paste a Spotify or Deezer URL directly in the search box'; + + @override + String get tutorialSearchTip2 => + 'Or type the song name, artist, or album to search'; + + @override + String get tutorialSearchTip3 => + 'Supports tracks, albums, playlists, and artist pages'; + + @override + String get tutorialDownloadTitle => 'Downloading Music'; + + @override + String get tutorialDownloadDesc => + 'Downloading music is simple and fast. Here\'s how it works.'; + + @override + String get tutorialDownloadTip1 => + 'Tap the download button next to any track to start downloading'; + + @override + String get tutorialDownloadTip2 => + 'Choose your preferred quality (FLAC, Hi-Res, or MP3)'; + + @override + String get tutorialDownloadTip3 => + 'Download entire albums or playlists with one tap'; + + @override + String get tutorialLibraryTitle => 'Your Library'; + + @override + String get tutorialLibraryDesc => + 'All your downloaded music is organized in the Library tab.'; + + @override + String get tutorialLibraryTip1 => + 'View download progress and queue in the Library tab'; + + @override + String get tutorialLibraryTip2 => + 'Tap any track to play it with your music player'; + + @override + String get tutorialLibraryTip3 => + 'Switch between list and grid view for better browsing'; + + @override + String get tutorialExtensionsTitle => 'Extensions'; + + @override + String get tutorialExtensionsDesc => + 'Extend the app\'s capabilities with community extensions.'; + + @override + String get tutorialExtensionsTip1 => + 'Browse the Store tab to discover useful extensions'; + + @override + String get tutorialExtensionsTip2 => + 'Add new download providers or search sources'; + + @override + String get tutorialExtensionsTip3 => + 'Get lyrics, enhanced metadata, and more features'; + + @override + String get tutorialSettingsTitle => 'Customize Your Experience'; + + @override + String get tutorialSettingsDesc => + 'Personalize the app in Settings to match your preferences.'; + + @override + String get tutorialSettingsTip1 => + 'Change download location and folder organization'; + + @override + String get tutorialSettingsTip2 => + 'Set default audio quality and format preferences'; + + @override + String get tutorialSettingsTip3 => 'Customize app theme and appearance'; + + @override + String get tutorialReadyMessage => + 'You\'re all set! Start downloading your favorite music now.'; + + @override + String get tutorialExample => 'EXAMPLE'; + + @override + String get libraryForceFullScan => 'Force Full Scan'; + + @override + String get libraryForceFullScanSubtitle => 'Rescan all files, ignoring cache'; + + @override + String get cleanupOrphanedDownloads => 'Cleanup Orphaned Downloads'; + + @override + String get cleanupOrphanedDownloadsSubtitle => + 'Remove history entries for files that no longer exist'; + + @override + String cleanupOrphanedDownloadsResult(int count) { + return 'Removed $count orphaned entries from history'; + } + + @override + String get cleanupOrphanedDownloadsNone => 'No orphaned entries found'; + + @override + String get cacheTitle => 'Storage & Cache'; + + @override + String get cacheSummaryTitle => 'Cache overview'; + + @override + String get cacheSummarySubtitle => + 'Clearing cache will not remove downloaded music files.'; + + @override + String cacheEstimatedTotal(String size) { + return 'Estimated cache usage: $size'; + } + + @override + String get cacheSectionStorage => 'Cached Data'; + + @override + String get cacheSectionMaintenance => 'Maintenance'; + + @override + String get cacheAppDirectory => 'App cache directory'; + + @override + String get cacheAppDirectoryDesc => + 'HTTP responses, WebView data, and other temporary app data.'; + + @override + String get cacheTempDirectory => 'Temporary directory'; + + @override + String get cacheTempDirectoryDesc => + 'Temporary files from downloads and audio conversion.'; + + @override + String get cacheCoverImage => 'Cover image cache'; + + @override + String get cacheCoverImageDesc => + 'Downloaded album and track cover art. Will re-download when viewed.'; + + @override + String get cacheLibraryCover => 'Library cover cache'; + + @override + String get cacheLibraryCoverDesc => + 'Cover art extracted from local music files. Will re-extract on next scan.'; + + @override + String get cacheExploreFeed => 'Explore feed cache'; + + @override + String get cacheExploreFeedDesc => + 'Explore tab content (new releases, trending). Will refresh on next visit.'; + + @override + String get cacheTrackLookup => 'Track lookup cache'; + + @override + String get cacheTrackLookupDesc => + 'Spotify/Deezer track ID lookups. Clearing may slow next few searches.'; + + @override + String get cacheCleanupUnusedDesc => + 'Remove orphaned download history and library entries for missing files.'; + + @override + String get cacheNoData => 'No cached data'; + + @override + String cacheSizeWithFiles(String size, int count) { + return '$size in $count files'; + } + + @override + String cacheSizeOnly(String size) { + return '$size'; + } + + @override + String cacheEntries(int count) { + return '$count entries'; + } + + @override + String cacheClearSuccess(String target) { + return 'Cleared: $target'; + } + + @override + String get cacheClearConfirmTitle => 'Clear cache?'; + + @override + String cacheClearConfirmMessage(String target) { + return 'This will clear cached data for $target. Downloaded music files will not be deleted.'; + } + + @override + String get cacheClearAllConfirmTitle => 'Clear all cache?'; + + @override + String get cacheClearAllConfirmMessage => + 'This will clear all cache categories on this page. Downloaded music files will not be deleted.'; + + @override + String get cacheClearAll => 'Clear all cache'; + + @override + String get cacheCleanupUnused => 'Cleanup unused data'; + + @override + String get cacheCleanupUnusedSubtitle => + 'Remove orphaned download history and missing library entries'; + + @override + String cacheCleanupResult(int downloadCount, int libraryCount) { + return 'Cleanup completed: $downloadCount orphaned downloads, $libraryCount missing library entries'; + } + + @override + String get cacheRefreshStats => 'Refresh stats'; + + @override + String get trackSaveCoverArt => 'Save Cover Art'; + + @override + String get trackSaveCoverArtSubtitle => 'Save album art as .jpg file'; + + @override + String get trackSaveLyrics => 'Save Lyrics (.lrc)'; + + @override + String get trackSaveLyricsSubtitle => 'Fetch and save lyrics as .lrc file'; + + @override + String get trackSaveLyricsProgress => 'Saving lyrics...'; + + @override + String get trackReEnrich => 'Re-enrich Metadata'; + + @override + String get trackReEnrichSubtitle => + 'Re-embed metadata without re-downloading'; + + @override + String get trackReEnrichOnlineSubtitle => + 'Search metadata online and embed into file'; + + @override + String get trackEditMetadata => 'Edit Metadata'; + + @override + String trackCoverSaved(String fileName) { + return 'Cover art saved to $fileName'; + } + + @override + String get trackCoverNoSource => 'No cover art source available'; + + @override + String trackLyricsSaved(String fileName) { + return 'Lyrics saved to $fileName'; + } + + @override + String get trackReEnrichProgress => 'Re-enriching metadata...'; + + @override + String get trackReEnrichSearching => 'Searching metadata online...'; + + @override + String get trackReEnrichSuccess => 'Metadata re-enriched successfully'; + + @override + String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed'; + + @override + String trackSaveFailed(String error) { + return 'Failed: $error'; + } + + @override + String get trackConvertFormat => 'Convert Format'; + + @override + String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus'; + + @override + String get trackConvertTitle => 'Convert Audio'; + + @override + String get trackConvertTargetFormat => 'Target Format'; + + @override + String get trackConvertBitrate => 'Bitrate'; + + @override + String get trackConvertConfirmTitle => 'Confirm Conversion'; + + @override + String trackConvertConfirmMessage( + String sourceFormat, + String targetFormat, + String bitrate, + ) { + return 'Convert from $sourceFormat to $targetFormat at $bitrate?\n\nThe original file will be deleted after conversion.'; + } + + @override + String get trackConvertConverting => 'Converting audio...'; + + @override + String trackConvertSuccess(String format) { + return 'Converted to $format successfully'; + } + + @override + String get trackConvertFailed => 'Conversion failed'; } diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 8dfbd9d9..9f356f1b 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -1152,7 +1152,7 @@ "@dialogDeleteSelectedTitle": { "description": "Dialog title - delete selected items" }, - "dialogDeleteSelectedMessage": "Lösche {count} {count, plural, one {}=1{Track} other{Tracks}} aus dem Verlauf?\n\nDies löscht auch die Dateien aus dem Speicher.", + "dialogDeleteSelectedMessage": "Lösche {count} {count, plural, one {Track} other{Tracks}} aus dem Verlauf?\n\nDies löscht auch die Dateien aus dem Speicher.", "@dialogDeleteSelectedMessage": { "description": "Dialog message - delete selected tracks", "placeholders": { @@ -1231,7 +1231,7 @@ "@snackbarCredentialsCleared": { "description": "Snackbar - Spotify credentials removed" }, - "snackbarDeletedTracks": "{count} {count, plural, one {}=1{Titel} other{Titel}}", + "snackbarDeletedTracks": "{count} {count, plural, one {Titel} other{Titel}}", "@snackbarDeletedTracks": { "description": "Snackbar - tracks deleted", "placeholders": { @@ -1438,7 +1438,7 @@ "@selectionTapToSelect": { "description": "Hint - how to select items" }, - "selectionDeleteTracks": "Lösche {count} {count, plural, one {}=1{Titel}other{Titel}}", + "selectionDeleteTracks": "Lösche {count} {count, plural, one {Titel}other{Titel}}", "@selectionDeleteTracks": { "description": "Delete button with count", "placeholders": { diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index ecf0e423..2ff691cb 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -874,6 +874,14 @@ "@filenameAvailablePlaceholders": {"description": "Label for placeholder list"}, "filenameHint": "{artist} - {title}", "@filenameHint": {"description": "Default filename format hint"}, + "filenameShowAdvancedTags": "Show advanced tags", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Enable formatted tags for track padding and date patterns", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, "folderOrganization": "Folder Organization", "@folderOrganization": {"description": "Setting title - folder structure"}, diff --git a/lib/l10n/arb/app_es_ES.arb b/lib/l10n/arb/app_es_ES.arb index 8f9268df..4e0dfd9b 100644 --- a/lib/l10n/arb/app_es_ES.arb +++ b/lib/l10n/arb/app_es_ES.arb @@ -89,7 +89,7 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, one {}=1{1 pista} other{{count} pistas}}", + "historyTracksCount": "{count, plural, one {1 pista} other{{count} pistas}}", "@historyTracksCount": { "description": "Track count with plural form", "placeholders": { @@ -98,7 +98,7 @@ } } }, - "historyAlbumsCount": "{count, plural, one {}=1{1 álbum} other{{count} álbumes}}", + "historyAlbumsCount": "{count, plural, one {1 álbum} other{{count} álbumes}}", "@historyAlbumsCount": { "description": "Album count with plural form", "placeholders": { @@ -636,7 +636,7 @@ "@albumTitle": { "description": "Album screen title" }, - "albumTracks": "{count, plural, one {}=1{1 pista} other{{count} pistas}}", + "albumTracks": "{count, plural, one {1 pista} other{{count} pistas}}", "@albumTracks": { "description": "Album track count", "placeholders": { @@ -673,7 +673,7 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, one {}=1{1 lanzamiento} other{{count} lanzamientos}}", + "artistReleases": "{count, plural, one {1 lanzamiento} other{{count} lanzamientos}}", "@artistReleases": { "description": "Artist release count", "placeholders": { @@ -1152,7 +1152,7 @@ "@dialogDeleteSelectedTitle": { "description": "Dialog title - delete selected items" }, - "dialogDeleteSelectedMessage": "¿Eliminar {count} {count, plural, one {}=1{pista} other{pistas}} del historial?\n\nEsto también eliminará los archivos del almacenamiento.", + "dialogDeleteSelectedMessage": "¿Eliminar {count} {count, plural, one {pista} other{pistas}} del historial?\n\nEsto también eliminará los archivos del almacenamiento.", "@dialogDeleteSelectedMessage": { "description": "Dialog message - delete selected tracks", "placeholders": { @@ -1231,7 +1231,7 @@ "@snackbarCredentialsCleared": { "description": "Snackbar - Spotify credentials removed" }, - "snackbarDeletedTracks": "Eliminado {count} {count, plural, one {}=1{pista} other{pistas}}", + "snackbarDeletedTracks": "Eliminado {count} {count, plural, one {pista} other{pistas}}", "@snackbarDeletedTracks": { "description": "Snackbar - tracks deleted", "placeholders": { @@ -1438,7 +1438,7 @@ "@selectionTapToSelect": { "description": "Hint - how to select items" }, - "selectionDeleteTracks": "¡Eliminar {count} {count, plural, one {}=1{pista} other{pistas}}", + "selectionDeleteTracks": "¡Eliminar {count} {count, plural, one {pista} other{pistas}}", "@selectionDeleteTracks": { "description": "Delete button with count", "placeholders": { @@ -2014,7 +2014,7 @@ } } }, - "tracksCount": "{count, plural, one {}=1{1 pista} other{{count} pistas}}", + "tracksCount": "{count, plural, one {1 pista} other{{count} pistas}}", "@tracksCount": { "description": "Track count display", "placeholders": { @@ -2758,7 +2758,7 @@ "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" }, - "downloadedAlbumDeleteMessage": "¿Eliminar {count} {count, plural, one {}=1{pista} other{pistas}} del historial?\n\nEsto también eliminará los archivos del almacenamiento.", + "downloadedAlbumDeleteMessage": "¿Eliminar {count} {count, plural, one {pista} other{pistas}} del historial?\n\nEsto también eliminará los archivos del almacenamiento.", "@downloadedAlbumDeleteMessage": { "description": "Delete confirmation with count", "placeholders": { @@ -2797,7 +2797,7 @@ "@downloadedAlbumTapToSelect": { "description": "Selection hint" }, - "downloadedAlbumDeleteCount": "¡Eliminar {count} {count, plural, one {}=1{pista} other{pistas}}", + "downloadedAlbumDeleteCount": "¡Eliminar {count} {count, plural, one {pista} other{pistas}}", "@downloadedAlbumDeleteCount": { "description": "Delete button text with count", "placeholders": { diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index b26b5441..72f43405 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -1524,15 +1524,23 @@ } } }, - "filenameAvailablePlaceholders": "Placeholder yang tersedia:", - "@filenameAvailablePlaceholders": { - "description": "Label for placeholder list" - }, - "filenameHint": "{artist} - {title}", - "@filenameHint": { - "description": "Default filename format hint" - }, - "folderOrganization": "Organisasi Folder", + "filenameAvailablePlaceholders": "Placeholder yang tersedia:", + "@filenameAvailablePlaceholders": { + "description": "Label for placeholder list" + }, + "filenameHint": "{artist} - {title}", + "@filenameHint": { + "description": "Default filename format hint" + }, + "filenameShowAdvancedTags": "Tampilkan tag lanjutan", + "@filenameShowAdvancedTags": { + "description": "Toggle label for showing advanced filename tags" + }, + "filenameShowAdvancedTagsDescription": "Aktifkan tag format untuk padding nomor lagu dan pola tanggal", + "@filenameShowAdvancedTagsDescription": { + "description": "Description for advanced filename tag toggle" + }, + "folderOrganization": "Organisasi Folder", "@folderOrganization": { "description": "Setting title - folder structure" }, @@ -3869,4 +3877,4 @@ "@trackConvertFailed": { "description": "Snackbar when conversion fails" } -} \ No newline at end of file +} diff --git a/lib/l10n/arb/app_pt_PT.arb b/lib/l10n/arb/app_pt_PT.arb index 8945597d..2caeebcb 100644 --- a/lib/l10n/arb/app_pt_PT.arb +++ b/lib/l10n/arb/app_pt_PT.arb @@ -89,7 +89,7 @@ "@historyFilterSingles": { "description": "Filter chip - show singles only" }, - "historyTracksCount": "{count, plural, one {}=1{1 faixa} other{{count} faixas}}", + "historyTracksCount": "{count, plural, one {1 faixa} other{{count} faixas}}", "@historyTracksCount": { "description": "Track count with plural form", "placeholders": { @@ -98,7 +98,7 @@ } } }, - "historyAlbumsCount": "{count, plural, one {}=1{1 álbum} other{{count} álbuns}}", + "historyAlbumsCount": "{count, plural, one {1 álbum} other{{count} álbuns}}", "@historyAlbumsCount": { "description": "Album count with plural form", "placeholders": { @@ -636,7 +636,7 @@ "@albumTitle": { "description": "Album screen title" }, - "albumTracks": "{count, plural, one {}=1{1 faixa} other{{count} faixas}}", + "albumTracks": "{count, plural, one {1 faixa} other{{count} faixas}}", "@albumTracks": { "description": "Album track count", "placeholders": { @@ -673,7 +673,7 @@ "@artistCompilations": { "description": "Section header for compilations" }, - "artistReleases": "{count, plural, one {}=1{1 lançamento} other{{count} lançamentos}}", + "artistReleases": "{count, plural, one {1 lançamento} other{{count} lançamentos}}", "@artistReleases": { "description": "Artist release count", "placeholders": { @@ -1152,7 +1152,7 @@ "@dialogDeleteSelectedTitle": { "description": "Dialog title - delete selected items" }, - "dialogDeleteSelectedMessage": "Apagar {count} {count, plural, one {}=1{faixa} other{faixas}} do histórico?\n\nIsso também apagará os arquivos do armazenamento.", + "dialogDeleteSelectedMessage": "Apagar {count} {count, plural, one {faixa} other{faixas}} do histórico?\n\nIsso também apagará os arquivos do armazenamento.", "@dialogDeleteSelectedMessage": { "description": "Dialog message - delete selected tracks", "placeholders": { @@ -1231,7 +1231,7 @@ "@snackbarCredentialsCleared": { "description": "Snackbar - Spotify credentials removed" }, - "snackbarDeletedTracks": "{count} {count, plural, one {}=1{faixa apagada} other{faixas apagadas}}", + "snackbarDeletedTracks": "{count} {count, plural, one {faixa apagada} other{faixas apagadas}}", "@snackbarDeletedTracks": { "description": "Snackbar - tracks deleted", "placeholders": { @@ -1438,7 +1438,7 @@ "@selectionTapToSelect": { "description": "Hint - how to select items" }, - "selectionDeleteTracks": "Apagar {count} {count, plural, one {}=1{faixa} other{faixas}}", + "selectionDeleteTracks": "Apagar {count} {count, plural, one {faixa} other{faixas}}", "@selectionDeleteTracks": { "description": "Delete button with count", "placeholders": { @@ -2014,7 +2014,7 @@ } } }, - "tracksCount": "{count, plural, one {}=1{1 faixa} other{{count} faixas}}", + "tracksCount": "{count, plural, one {1 faixa} other{{count} faixas}}", "@tracksCount": { "description": "Track count display", "placeholders": { @@ -2758,7 +2758,7 @@ "@downloadedAlbumDeleteSelected": { "description": "Button - delete selected tracks" }, - "downloadedAlbumDeleteMessage": "Excluir {count} {count, plural, one {}=1{faixa} other{faixas}} deste álbum?\n\nIsso também excluirá os arquivos do armazenamento.", + "downloadedAlbumDeleteMessage": "Excluir {count} {count, plural, one {faixa} other{faixas}} deste álbum?\n\nIsso também excluirá os arquivos do armazenamento.", "@downloadedAlbumDeleteMessage": { "description": "Delete confirmation with count", "placeholders": { @@ -2797,7 +2797,7 @@ "@downloadedAlbumTapToSelect": { "description": "Selection hint" }, - "downloadedAlbumDeleteCount": "Apagar {count} {count, plural, one {}=1{faixa} other{faixas}}", + "downloadedAlbumDeleteCount": "Apagar {count} {count, plural, one {faixa} other{faixas}}", "@downloadedAlbumDeleteCount": { "description": "Delete button text with count", "placeholders": { diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 6080e5e9..2025e802 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -3114,7 +3114,7 @@ "@libraryAboutDescription": { "description": "Description of local library feature" }, - "libraryTracksCount": "{count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}", + "libraryTracksCount": "{count} {count, plural, one {трек} few {трека} many {треков} other{треков}}", "@libraryTracksCount": { "description": "Track count in library", "placeholders": { @@ -3156,7 +3156,7 @@ "@libraryInLibrary": { "description": "Badge shown on tracks that exist in local library" }, - "libraryRemovedMissingFiles": "Удалено {count} {count, plural, one {отсутствующий файл} few {трека} many {отсутствующих файлов} =1{отсутствующий файл} other{отсутствующих файлов}} в библиотеке", + "libraryRemovedMissingFiles": "Удалено {count} {count, plural, one {отсутствующий файл} few {трека} many {отсутствующих файлов} other{отсутствующих файлов}} в библиотеке", "@libraryRemovedMissingFiles": { "description": "Snackbar after cleanup", "placeholders": { @@ -3282,7 +3282,7 @@ "@timeJustNow": { "description": "Relative time - less than a minute ago" }, - "timeMinutesAgo": "{count, plural, one {{count} минуту} few {{count} минуты} many {{count} минут} =1 {1 минуту} other {{count} минут}} назад", + "timeMinutesAgo": "{count, plural, one {{count} минуту} few {{count} минуты} many {{count} минут} other {{count} минут}} назад", "@timeMinutesAgo": { "description": "Relative time - minutes ago", "placeholders": { @@ -3291,7 +3291,7 @@ } } }, - "timeHoursAgo": "{count, plural, one {{count} час} few {{count} часа} many {{count} часов} =1 {1 час} other {{count} часов}} назад", + "timeHoursAgo": "{count, plural, one {{count} час} few {{count} часа} many {{count} часов} other {{count} часов}} назад", "@timeHoursAgo": { "description": "Relative time - hours ago", "placeholders": { @@ -3324,7 +3324,7 @@ "@storageSwitchExistingDownloads": { "description": "Section header for existing downloads info" }, - "storageSwitchExistingDownloadsInfo": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}} в {mode} хранилище", + "storageSwitchExistingDownloadsInfo": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} other {{count} треков}} в {mode} хранилище", "@storageSwitchExistingDownloadsInfo": { "description": "Info about existing downloads count", "placeholders": { @@ -3378,7 +3378,7 @@ "@storageStatsTitle": { "description": "Section title for storage stats" }, - "storageStatsAppCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}} в хранилище приложения", + "storageStatsAppCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} other {{count} треков}} в хранилище приложения", "@storageStatsAppCount": { "description": "Count of tracks in app storage", "placeholders": { @@ -3387,7 +3387,7 @@ } } }, - "storageStatsSafCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}} в вашей папке в SAF", + "storageStatsSafCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} other {{count} треков}} в вашей папке в SAF", "@storageStatsSafCount": { "description": "Count of tracks in SAF storage", "placeholders": { diff --git a/lib/main.dart b/lib/main.dart index 2e19a7a8..1fc79299 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; @@ -11,19 +12,68 @@ import 'package:spotiflac_android/services/cover_cache_manager.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - _configureImageCache(); + final runtimeProfile = await _resolveRuntimeProfile(); + _configureImageCache(runtimeProfile); runApp( - ProviderScope(child: const _EagerInitialization(child: SpotiFLACApp())), + ProviderScope( + child: _EagerInitialization( + child: SpotiFLACApp( + disableOverscrollEffects: runtimeProfile.disableOverscrollEffects, + ), + ), + ), ); } -void _configureImageCache() { +Future<_RuntimeProfile> _resolveRuntimeProfile() async { + const defaults = _RuntimeProfile( + imageCacheMaximumSize: 240, + imageCacheMaximumSizeBytes: 60 << 20, + disableOverscrollEffects: false, + ); + + if (!Platform.isAndroid) return defaults; + + try { + final androidInfo = await DeviceInfoPlugin().androidInfo; + final isArm32Only = androidInfo.supported64BitAbis.isEmpty; + final isLowRamDevice = + androidInfo.isLowRamDevice || androidInfo.physicalRamSize <= 2500; + + if (!isArm32Only && !isLowRamDevice) { + return defaults; + } + + return _RuntimeProfile( + imageCacheMaximumSize: 120, + imageCacheMaximumSizeBytes: 24 << 20, + disableOverscrollEffects: true, + ); + } catch (e) { + debugPrint('Failed to resolve runtime profile: $e'); + return defaults; + } +} + +void _configureImageCache(_RuntimeProfile runtimeProfile) { final imageCache = PaintingBinding.instance.imageCache; // Keep memory cache bounded so cover-heavy pages don't retain too many // full-resolution images simultaneously. - imageCache.maximumSize = 240; - imageCache.maximumSizeBytes = 60 << 20; // 60 MiB + imageCache.maximumSize = runtimeProfile.imageCacheMaximumSize; + imageCache.maximumSizeBytes = runtimeProfile.imageCacheMaximumSizeBytes; +} + +class _RuntimeProfile { + final int imageCacheMaximumSize; + final int imageCacheMaximumSizeBytes; + final bool disableOverscrollEffects; + + const _RuntimeProfile({ + required this.imageCacheMaximumSize, + required this.imageCacheMaximumSizeBytes, + required this.disableOverscrollEffects, + }); } /// Widget to eagerly initialize providers that need to load data on startup diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index 0e59a08b..35871a09 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -1304,7 +1304,7 @@ class DownloadQueueNotifier extends Notifier { } static final _featuredArtistPattern = RegExp( - r'\s*[,;&]\s*|\s+(?:feat\.?|ft\.?|featuring|with|x)\s+', + r'\s*[,;]\s*|\s+(?:feat\.?|ft\.?|featuring|with|x)\s+', caseSensitive: false, ); @@ -2813,6 +2813,7 @@ class DownloadQueueNotifier extends Notifier { 'track': trackToDownload.trackNumber ?? 0, 'disc': trackToDownload.discNumber ?? 0, 'year': _extractYear(trackToDownload.releaseDate) ?? '', + 'date': trackToDownload.releaseDate ?? '', }); final sanitized = await PlatformBridge.sanitizeFilename(baseName); safBaseName = sanitized; diff --git a/lib/screens/artist_screen.dart b/lib/screens/artist_screen.dart index f007ab8f..97737649 100644 --- a/lib/screens/artist_screen.dart +++ b/lib/screens/artist_screen.dart @@ -490,6 +490,9 @@ class _ArtistScreenState extends ConsumerState { 0, (sum, a) => sum + a.totalTracks, ); + final textScale = MediaQuery.textScalerOf(context).scale(1.0); + final compactLayout = + MediaQuery.sizeOf(context).width < 430 || textScale > 1.15; return Positioned( left: 0, @@ -510,53 +513,145 @@ class _ArtistScreenState extends ConsumerState { top: false, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Row( - children: [ - IconButton( - onPressed: _exitSelectionMode, - icon: const Icon(Icons.close), - tooltip: context.l10n.dialogCancel, - ), - const SizedBox(width: 8), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: compactLayout + ? Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - context.l10n.discographySelectedCount(selectedCount), - style: Theme.of(context).textTheme.titleMedium - ?.copyWith(fontWeight: FontWeight.w600), + Row( + children: [ + IconButton( + onPressed: _exitSelectionMode, + icon: const Icon(Icons.close), + tooltip: context.l10n.dialogCancel, + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + context.l10n.discographySelectedCount( + selectedCount, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.titleMedium + ?.copyWith(fontWeight: FontWeight.w600), + ), + if (selectedCount > 0) + Text( + context.l10n.tracksCount(totalTracks), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodySmall + ?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + ], ), - if (selectedCount > 0) - Text( - context.l10n.tracksCount(totalTracks), - style: Theme.of(context).textTheme.bodySmall - ?.copyWith(color: colorScheme.onSurfaceVariant), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: allSelected + ? _deselectAll + : () => _selectAll(allAlbums), + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + allSelected + ? context.l10n.actionDeselect + : context.l10n.actionSelectAll, + ), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: FilledButton( + onPressed: selectedCount > 0 + ? () => _downloadSelectedAlbums( + context, + selectedAlbums, + ) + : null, + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + context.l10n.discographyDownloadSelected, + ), + ), + ), + ), + ], + ), + ], + ) + : Row( + children: [ + IconButton( + onPressed: _exitSelectionMode, + icon: const Icon(Icons.close), + tooltip: context.l10n.dialogCancel, + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + context.l10n.discographySelectedCount( + selectedCount, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.titleMedium + ?.copyWith(fontWeight: FontWeight.w600), + ), + if (selectedCount > 0) + Text( + context.l10n.tracksCount(totalTracks), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodySmall + ?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ], ), + ), + TextButton( + onPressed: allSelected + ? _deselectAll + : () => _selectAll(allAlbums), + child: Text( + allSelected + ? context.l10n.actionDeselect + : context.l10n.actionSelectAll, + ), + ), + const SizedBox(width: 8), + FilledButton.icon( + onPressed: selectedCount > 0 + ? () => _downloadSelectedAlbums( + context, + selectedAlbums, + ) + : null, + icon: const Icon(Icons.download, size: 18), + label: Text(context.l10n.discographyDownloadSelected), + ), ], ), - ), - TextButton( - onPressed: allSelected - ? _deselectAll - : () => _selectAll(allAlbums), - child: Text( - allSelected - ? context.l10n.actionDeselect - : context.l10n.actionSelectAll, - ), - ), - const SizedBox(width: 8), - FilledButton.icon( - onPressed: selectedCount > 0 - ? () => _downloadSelectedAlbums(context, selectedAlbums) - : null, - icon: const Icon(Icons.download, size: 18), - label: Text(context.l10n.discographyDownloadSelected), - ), - ], - ), ), ), ), @@ -1427,15 +1522,31 @@ class _ArtistScreenState extends ConsumerState { void _downloadTrack(Track track) { final settings = ref.read(settingsProvider); ref.read(settingsProvider.notifier).setHasSearchedBefore(); - ref - .read(downloadQueueProvider.notifier) - .addToQueue(track, settings.defaultService); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.snackbarAddedToQueue(track.name)), - duration: const Duration(seconds: 2), - ), - ); + + void enqueue(String service, {String? quality}) { + ref + .read(downloadQueueProvider.notifier) + .addToQueue(track, service, qualityOverride: quality); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(context.l10n.snackbarAddedToQueue(track.name)), + duration: const Duration(seconds: 2), + ), + ); + } + + if (settings.askQualityBeforeDownload) { + DownloadServicePicker.show( + context, + onSelect: (quality, service) { + if (!mounted) return; + enqueue(service, quality: quality); + }, + ); + return; + } + + enqueue(settings.defaultService); } Widget _buildAlbumSection( @@ -1468,7 +1579,12 @@ class _ArtistScreenState extends ConsumerState { final album = albums[index]; return KeyedSubtree( key: ValueKey(album.id), - child: _buildAlbumCard(album, colorScheme, tileSize: tileSize, sectionHeight: sectionHeight), + child: _buildAlbumCard( + album, + colorScheme, + tileSize: tileSize, + sectionHeight: sectionHeight, + ), ); }, ), @@ -1601,9 +1717,9 @@ class _ArtistScreenState extends ConsumerState { Flexible( child: Text( album.name, - style: Theme.of( - context, - ).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + ), maxLines: 2, overflow: TextOverflow.ellipsis, ), diff --git a/lib/screens/settings/download_settings_page.dart b/lib/screens/settings/download_settings_page.dart index da350dda..51b9be18 100644 --- a/lib/screens/settings/download_settings_page.dart +++ b/lib/screens/settings/download_settings_page.dart @@ -620,14 +620,28 @@ class _DownloadSettingsPageState extends ConsumerState { final controller = TextEditingController(text: current); final colorScheme = Theme.of(context).colorScheme; - final tags = [ + final basicTags = [ '{artist}', '{title}', '{album}', '{track}', '{year}', + '{date}', '{disc}', ]; + final advancedTags = [ + '{track_raw}', + '{track:02}', + '{track:1}', + '{date:%Y}', + '{date:%Y-%m-%d}', + '{disc_raw}', + '{disc:02}', + ]; + var showAdvancedTags = RegExp( + r'\{(?:track_raw|disc_raw|track:\d+|disc:\d+|date:[^}]+)\}', + caseSensitive: false, + ).hasMatch(current); void insertTag(String tag) { final text = controller.text; @@ -659,130 +673,164 @@ class _DownloadSettingsPageState extends ConsumerState { shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(28)), ), - builder: (context) => Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: SingleChildScrollView( - child: SafeArea( - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Center( - child: Container( - width: 32, - height: 4, - margin: const EdgeInsets.only(bottom: 24), - decoration: BoxDecoration( - color: colorScheme.outlineVariant, - borderRadius: BorderRadius.circular(2), - ), - ), - ), - Text( - context.l10n.filenameFormat, - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - 'Customize how your files are named.', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 24), - - TextField( - controller: controller, - decoration: InputDecoration( - hintText: '{artist} - {title}', - filled: true, - fillColor: colorScheme.surfaceContainerHighest.withValues( - alpha: 0.3, - ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: BorderSide.none, - ), - ), - autofocus: true, - ), - const SizedBox(height: 24), - - Text( - 'Tap to insert tag:', - style: Theme.of(context).textTheme.titleSmall?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 12), - Wrap( - spacing: 8, - runSpacing: 8, - children: tags.map((tag) { - return ActionChip( - label: Text(tag), - onPressed: () => insertTag(tag), - backgroundColor: colorScheme.surfaceContainerHighest - .withValues(alpha: 0.5), - side: BorderSide.none, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + builder: (context) => StatefulBuilder( + builder: (context, setModalState) => Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: SingleChildScrollView( + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: Container( + width: 32, + height: 4, + margin: const EdgeInsets.only(bottom: 24), + decoration: BoxDecoration( + color: colorScheme.outlineVariant, + borderRadius: BorderRadius.circular(2), ), - labelStyle: TextStyle( - color: colorScheme.onSurface, - fontWeight: FontWeight.w500, + ), + ), + Text( + context.l10n.filenameFormat, + style: Theme.of(context).textTheme.headlineSmall + ?.copyWith(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + 'Customize how your files are named.', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + + TextField( + controller: controller, + decoration: InputDecoration( + hintText: '{artist} - {title}', + filled: true, + fillColor: colorScheme.surfaceContainerHighest + .withValues(alpha: 0.3), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide.none, ), - ); - }).toList(), - ), + ), + autofocus: true, + ), + const SizedBox(height: 24), - const SizedBox(height: 32), - - Row( - children: [ - Expanded( - child: TextButton( - onPressed: () => Navigator.pop(context), - style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), + Text( + 'Tap to insert tag:', + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 12), + Wrap( + spacing: 8, + runSpacing: 8, + children: basicTags.map((tag) { + return ActionChip( + label: Text(tag), + onPressed: () => insertTag(tag), + backgroundColor: colorScheme.surfaceContainerHighest + .withValues(alpha: 0.5), + side: BorderSide.none, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), ), - child: Text(context.l10n.dialogCancel), - ), - ), - const SizedBox(width: 12), - Expanded( - flex: 2, - child: FilledButton( - onPressed: () { - ref - .read(settingsProvider.notifier) - .setFilenameFormat(controller.text); - Navigator.pop(context); - }, - style: FilledButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), + labelStyle: TextStyle( + color: colorScheme.onSurface, + fontWeight: FontWeight.w500, ), - child: Text(context.l10n.dialogSave), - ), + ); + }).toList(), + ), + const SizedBox(height: 12), + SwitchListTile( + value: showAdvancedTags, + onChanged: (value) => + setModalState(() => showAdvancedTags = value), + contentPadding: EdgeInsets.zero, + title: Text(context.l10n.filenameShowAdvancedTags), + subtitle: Text( + context.l10n.filenameShowAdvancedTagsDescription, + ), + ), + if (showAdvancedTags) ...[ + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 8, + children: advancedTags.map((tag) { + return ActionChip( + label: Text(tag), + onPressed: () => insertTag(tag), + backgroundColor: colorScheme.surfaceContainerHighest + .withValues(alpha: 0.5), + side: BorderSide.none, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + labelStyle: TextStyle( + color: colorScheme.onSurface, + fontWeight: FontWeight.w500, + ), + ); + }).toList(), ), ], - ), - const SizedBox(height: 8), - ], + + const SizedBox(height: 32), + + Row( + children: [ + Expanded( + child: TextButton( + onPressed: () => Navigator.pop(context), + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: Text(context.l10n.dialogCancel), + ), + ), + const SizedBox(width: 12), + Expanded( + flex: 2, + child: FilledButton( + onPressed: () { + ref + .read(settingsProvider.notifier) + .setFilenameFormat(controller.text); + Navigator.pop(context); + }, + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: Text(context.l10n.dialogSave), + ), + ), + ], + ), + const SizedBox(height: 8), + ], + ), ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index f1e379fc..948feea3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: spotiflac_android description: Download Spotify tracks in FLAC from Tidal, Qobuz & Amazon Music publish_to: "none" -version: 3.6.6+80 +version: 3.6.7+81 environment: sdk: ^3.10.0 diff --git a/site/downloads.html b/site/downloads.html new file mode 100644 index 00000000..a95e31d2 --- /dev/null +++ b/site/downloads.html @@ -0,0 +1,484 @@ + + + + + + Downloads - SpotiFLAC Mobile + + + + + + + + + + + + + + +
+ + + + +
+
+
+ Loading latest release... +
+
+ + + + + + + + + + + diff --git a/site/icon.png b/site/icon.png new file mode 100644 index 00000000..565fab55 Binary files /dev/null and b/site/icon.png differ diff --git a/site/images/1.jpg b/site/images/1.jpg new file mode 100644 index 00000000..286f377d Binary files /dev/null and b/site/images/1.jpg differ diff --git a/site/images/2.jpg b/site/images/2.jpg new file mode 100644 index 00000000..9692fc5e Binary files /dev/null and b/site/images/2.jpg differ diff --git a/site/images/3.jpg b/site/images/3.jpg new file mode 100644 index 00000000..f4cc9de5 Binary files /dev/null and b/site/images/3.jpg differ diff --git a/site/images/4.jpg b/site/images/4.jpg new file mode 100644 index 00000000..b501f2fa Binary files /dev/null and b/site/images/4.jpg differ diff --git a/site/index.html b/site/index.html new file mode 100644 index 00000000..94673e9d --- /dev/null +++ b/site/index.html @@ -0,0 +1,465 @@ + + + + + + SpotiFLAC Mobile - Lossless Music Downloader + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+

SpotiFLAC Mobile

+

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

+
+ + + Android 7.0+ + + + + iOS 14.0+ + + + + Open Source + +
+ +
+
Search
+
Home screen
+
History
+
+
+ + +
+
+

Features

+

Everything you need to build a high-quality music library on your phone.

+
+
+
+ +
+

True Lossless FLAC

+

Download in up to 24-bit/192kHz quality. No transcoding, no quality loss. Pure studio-grade audio files.

+
+
+
+ +
+

Multiple Providers

+

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

+
+
+
+ +
+

Extensions

+

Community-built extensions add new music sources and features. Install from the built-in Store with one tap.

+
+
+
+ +
+

Search by Link or Name

+

Paste a Spotify, Tidal, Qobuz, or Deezer link. Or just search by song name — it handles the rest.

+
+
+
+ +
+

Batch & Playlist Download

+

Download entire albums and playlists at once. Smart queue management with concurrent downloads.

+
+
+
+ +
+

Rich Metadata

+

Full metadata embedding — album art, lyrics, genre, label, copyright, and more. All embedded in the FLAC file.

+
+
+
+
+ + + +
+
+

FAQ

+

Common questions about SpotiFLAC Mobile.

+
+
+ Why is my download failing with "Song not found"? +
The track may not be available on Tidal, Qobuz, or Amazon Music. Try enabling more download services in Settings > Download > Provider Priority, or install additional extensions from the Store.
+
+
+ Why are some tracks downloading in lower quality? +
Quality depends on what's available from the streaming service. Tidal offers up to 24-bit/192kHz, Qobuz up to 24-bit/192kHz, and Amazon up to 24-bit/48kHz. The app automatically selects the best available quality.
+
+
+ Can I download entire playlists? +
Yes! Just paste the playlist URL in the search bar. The app will fetch all tracks and queue them for download.
+
+
+ Why do I need to grant storage permission? +
The app needs permission to save downloaded files to your device. On Android 13+, you may need to grant "All files access" in Settings > Apps > SpotiFLAC > Permissions.
+
+
+ Is this app safe? +
Yes, the app is fully open source. You can verify the code yourself on GitHub. Each release is scanned with VirusTotal.
+
+
+ Download not working in my country? +
Some countries have restricted access to certain streaming service APIs. If downloads are failing, try using a VPN to connect through a different region.
+
+
+ How do I create my own extension? +
Check out the Extension Development Guide for complete documentation on building custom extensions.
+
+
+
+
+ + + + + + + + + diff --git a/site/partners.html b/site/partners.html new file mode 100644 index 00000000..4f63b8ea --- /dev/null +++ b/site/partners.html @@ -0,0 +1,516 @@ + + + + + + Partners & Services - SpotiFLAC Mobile + + + + + + + + + + + + + + +
+ + + + + +
+
+ +

APIs & Tools

+

The services that handle link resolution, lyrics, audio extraction, and more.

+ +
+ + + + +
+
+ +
+
+
Odesli / song.link
+
Cross-platform link resolution. Translates any Spotify, Deezer, or streaming URL into matching Tidal, Qobuz, Amazon, and YouTube IDs — enabling SpotiFLAC to find the best lossless source for every track.
+ + odesli.co + + +
+
+ + +
+
+ +
+
+
I Don't Have Spotify
+
Fallback link resolution service. When Odesli is rate-limited or unavailable, IDHS provides an alternative way to match Spotify links to Tidal, Qobuz, and other streaming platforms.
+ + sjdonado/idonthavespotify + + +
+
+ + +
+
+ +
+
+
LRCLIB
+
Open synced lyrics database. Provides time-stamped lyrics that get embedded directly into downloaded FLAC files, so your music player can display lyrics in sync with the music.
+ + tranxuanthang/lrclib + + +
+
+ + + + +
+
+ +
+
+
hifi-api / Binimum
+
Primary Tidal lossless stream API. Accepts a track ID and quality parameter, returns hi-res download URLs and DASH manifests. Also deployed at music.binimum.org.
+ + binimum/hifi-api + + +
+
+ + +
+
+ +
+
+
QQDL
+
Redundant Tidal API mirror cluster. Operates five parallel endpoints (vogel, maus, hund, katze, wolf) for high-availability lossless track downloads across the API pool.
+ + qqdl.site + + +
+
+ + +
+
+ +
+
+
Squid
+
Dual-purpose download API serving both Tidal and Qobuz streams. Supports multi-region retrieval (US/FR fallback for Qobuz) to maximize track availability across catalogs.
+ + squid.wtf + + +
+
+ + +
+
+ +
+
+
SpotiSaver
+
Tidal hi-fi download endpoints. Hosts two parallel instances (hifi-one, hifi-two) that provide additional redundancy in the 10-API parallel race pool.
+ + spotisaver.net + + +
+
+ + + + +
+
+ +
+
+
DabMusic
+
Primary Qobuz lossless stream API. Provides direct download URLs for FLAC audio at up to 24-bit/192kHz quality. Queried in parallel alongside squid.wtf for fastest response.
+ + dabmusic.xyz + + +
+
+ + +
+
+ +
+
+
Jumo DL
+
Qobuz final fallback. A Cloudflare Pages worker tried after all standard Qobuz APIs fail, with automatic quality downgrade cascade (hi-res → CD → MP3) to maximize success rate.
+ + jumo-dl.pages.dev + + +
+
+ + + + +
+
+ +
+
+
AfkarXYZ
+
Sole Amazon Music download API with stream decryption support. Also provides a SpotFetch-compatible Spotify metadata proxy used when direct API access is blocked.
+ + afkarxyz + + +
+
+ + + + +
+
+ +
+
+
Cobalt
+
Privacy-focused media extraction tool. The core engine behind YouTube Music downloads — accepts a video URL and returns a tunnel URL to the audio stream in opus or mp3 format.
+ + imputnet/cobalt + + +
+
+ + +
+
+ +
+
+
Qwkuns
+
Cobalt-compatible API for YouTube audio extraction. Serves as the fallback download engine when the primary SpotubeDL proxy is unavailable, using the standard Cobalt protocol.
+ + qwkuns.me + + +
+
+ + +
+
+ +
+
+
SpotubeDL
+
Primary YouTube download proxy. Handles authentication to Cobalt download instances and serves as the first-choice engine for YouTube Music audio extraction.
+ + spotubedl.com + + +
+
+ +
+
+
+ + +
+

SpotiFLAC Mobile is not affiliated with, endorsed by, or connected to any of the services listed above. All trademarks and logos belong to their respective owners. This page is meant to acknowledge and appreciate the platforms that make this project possible.

+
+ + + + + + + +