diff --git a/.cursorignore b/.cursorignore
deleted file mode 100644
index 6f9f00ff..00000000
--- a/.cursorignore
+++ /dev/null
@@ -1 +0,0 @@
-# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
diff --git a/.gitignore b/.gitignore
index a4b6e829..30778c75 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,8 @@ Thumbs.db
.idea/
.vscode/
*.iml
+.cursorignore
+.cursorrules
# Kiro specs (development only)
.kiro/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71e18c34..57781320 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,56 @@
# Changelog
-## [3.1.0] - 2026-01-19
+## [3.1.1] - 2026-01-17
+
+### Added
+
+- **Lyrics Caching**: Lyrics are now cached for 24 hours to reduce API calls and improve performance
+ - Thread-safe cache with automatic expiration
+ - Cache key based on artist, track, and duration
+ - Log indicator shows "(cached)" when lyrics are served from cache
+
+- **Lyrics Duration Matching**: Improved lyrics accuracy with duration-based matching
+ - Compares track duration with lrclib.net results
+ - 10-second tolerance to handle version differences (radio edit, remaster, etc.)
+ - Prioritizes synced lyrics over plain text when duration matches
+ - Falls back gracefully if no duration match found
+
+- **Deezer Cover Art Upgrade**: Cover art from Deezer CDN now automatically upgraded to maximum quality
+ - Detects Deezer CDN URLs (`cdn-images.dzcdn.net`)
+ - Upgrades cover resolution to 1800x1800 (max available)
+ - Works alongside existing cover upgrade
+
+- **Live Search for Extensions**: Search-as-you-type functionality for extension search
+ - 800ms debounce delay to prevent excessive API calls
+ - Minimum 3 characters required before searching
+ - Concurrency control to prevent race conditions in extension runtime
+ - Queues pending searches if a search is already in progress
+
+- **Russian Language Support**: Added Russian (Русский) translation - 99% complete
+ - Translated via Crowdin community contributions
+ - Covers all UI elements, settings, and error messages
+
+### Fixed
+
+- **ISRC Index Race Condition**: Fixed repeated index rebuilding during parallel downloads
+ - Added per-directory build lock using `sync.Map` and `sync.Mutex`
+ - Double-check locking pattern ensures index is built only once
+ - Significantly improves performance during CSV import with many tracks
+
+- **Queue Tab Scroll Exception**: Fixed Flutter rendering exception with NestedScrollView
+ - Disabled Material 3 stretch overscroll indicator that caused `_StretchController` assertion
+ - Wrapped NestedScrollView with ScrollConfiguration to prevent `setState during build` errors
+ - Issue was especially noticeable during rapid queue updates (CSV import)
+
+- **CSV Import**: Fixed CSV export not being parsed correctly
+ - Added support for `Artist Name(s)` header (with parentheses)
+ - Added support for `Track URI` header for track IDs
+ - Added `artists` and `track_id` as alternative header names
+ - Now correctly parses "Liked Songs" and playlist exports
+
+---
+
+## [3.1.0] - 2026-01-16
### Added
@@ -105,17 +155,17 @@
- YT Music extension `getArtist()` now returns `top_tracks` array with up to 10 popular songs
- Go backend `GetArtistWithExtensionJSON` now forwards `top_tracks`, `header_image`, and `listeners` to Flutter
- `ExtensionArtistScreen` now parses and passes top tracks to `ArtistScreen`
- - `ArtistScreen` with `extensionId` skips Spotify/Deezer fetch, uses extension data only (fixes "Rate Limited" errors)
+ - `ArtistScreen` with `extensionId` skips metadata fetch, uses extension data only (fixes "Rate Limited" errors)
- **Search Bar Unfocus**: Fixed search bar not unfocusing when tapping outside - now properly dismisses keyboard and unfocus when tapping anywhere outside the search field
- **Keyboard Appearing on Settings Navigation**: Fixed keyboard randomly appearing when returning from Settings sub-pages (e.g., Appearance) - now uses `FocusManager.instance.primaryFocus?.unfocus()` for more aggressive unfocus
-- **Recent Access Artist Navigation**: Fixed opening artist from recent access using wrong screen - now correctly uses `ExtensionArtistScreen` for extension artists (YT Music, Spotify Web) instead of trying to fetch from Spotify API
+- **Recent Access Artist Navigation**: Fixed opening artist from recent access using wrong screen - now correctly uses `ExtensionArtistScreen` for extension artists (YT Music, etc.) instead of trying to fetch from API
### Extensions
- **YouTube Music Extension**: Updated to v1.5.0
- `getArtist()` now returns `top_tracks` array with popular songs
- Added `header_image` and `listeners` to artist response
-- **Spotify Web Extension**: Updated to v1.6.0
+- **Web Extension**: Updated to v1.6.0
### Localization
@@ -148,12 +198,12 @@ SpotiFLAC 3.0 introduces a powerful extension system that allows third-party int
- One-tap install, update, and uninstall
- Offline cache for browsing without internet
-#### Spotify Web Extension
+#### Web Extension
- Available in Extension Store - install and enable in Settings > Extensions
-- Metadata provider using Spotify's internal web player API
+- Metadata provider using web player API
- Download tracks from Daily Mix, Discover Weekly, and other personalized playlists
-- Useful when official Spotify API is rate-limited or unavailable
+- Useful when official API is rate-limited or unavailable
#### Extension Capabilities
@@ -188,7 +238,7 @@ SpotiFLAC 3.0 introduces a powerful extension system that allows third-party int
- **Separate Singles Folder**: Organize downloads into Albums/ and Singles/ folders
- - Based on `album_type` from Spotify/Deezer metadata
+ - Based on `album_type` from metadata
- Toggle in Settings > Download > Separate Singles Folder
- **Year in Album Folder Name**: New album folder structure options with release year
@@ -226,7 +276,7 @@ SpotiFLAC 3.0 introduces a powerful extension system that allows third-party int
- **Fixed Keyboard Appearing on Tab Switch**: Keyboard now auto-dismisses when swiping between tabs
-- **Removed Search Source Badges**: Removed "Free" and "API Key" labels from Deezer/Spotify selector in Options
+- **Removed Search Source Badges**: Removed "Free" and "API Key" labels from provider selector in Options
- **Back Gesture Freeze on Android 13+**: Fixed app freeze when using back gesture in settings
@@ -261,7 +311,7 @@ SpotiFLAC 3.0 introduces a powerful extension system that allows third-party int
- **Duplicate History Entries**: Fixed duplicate entries when re-downloading same track
- - Detects existing entries by Spotify ID, Deezer ID, or ISRC
+ - Detects existing entries by track ID, Deezer ID, or ISRC
- **Permission Error Message**: Fixed download showing "Song not found" when actually permission error
@@ -330,7 +380,7 @@ SpotiFLAC 3.0 introduces a powerful extension system that allows third-party int
- **Extension Disabled Search Fallback**: Fixed error when extension is disabled but still called
- `_performSearch` now checks if extension is still enabled before calling custom search
- - Automatically falls back to Deezer/Spotify search if extension was disabled
+ - Automatically falls back to Deezer search if extension was disabled
- Clears `searchProvider` setting if extension no longer available
- **Store Tab Unmount Crash**: Fixed "Using ref when widget is unmounted" error
@@ -450,7 +500,7 @@ SpotiFLAC 3.0 introduces a powerful extension system that allows third-party int
### Extensions
-- **Spotify Web Extension** (example): New extension for Spotify metadata via web API
+- **Web Extension** (example): New extension for metadata via web API
- Supports personalized playlists (Daily Mix, Discover Weekly, Release Radar, etc.)
- Search, album, playlist, track, and artist fetching
- Available in Extension Store (3.0.0-alpha.4)
@@ -462,7 +512,7 @@ SpotiFLAC 3.0 introduces a powerful extension system that allows third-party int
### Added
- **Separate Singles Folder**: Option to organize downloads into Albums/ and Singles/ folders
- - Based on `album_type` from Spotify/Deezer metadata
+ - Based on `album_type` from metadata
- Toggle in Settings > Download > Separate Singles Folder
- Singles saved to `{output}/Singles/`, albums to `{output}/Albums/`
- **Browser-like Polyfills**: New global APIs for easier library porting
@@ -482,7 +532,7 @@ SpotiFLAC 3.0 introduces a powerful extension system that allows third-party int
### Fixed
- **Duplicate History Entries**: Fixed duplicate entries when re-downloading same track
- - Detects existing entries by Spotify ID, Deezer ID, or ISRC
+ - Detects existing entries by track ID, Deezer ID, or ISRC
- Replaces existing entry and moves to top of list
- Auto-deduplicates existing history on app load
- **Extension Search Fallback**: Fixed error when extension is disabled but still called for search
diff --git a/README.md b/README.md
index 29bd4b2d..149d6b58 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
-Get Spotify tracks in true FLAC from Tidal, Qobuz & Amazon Music — no account required.
+Download music in true lossless FLAC from Tidal, Qobuz & Amazon Music — no account required.


@@ -26,12 +26,12 @@ Get Spotify tracks in true FLAC from Tidal, Qobuz & Amazon Music — no account
## Search Source
-SpotiFLAC supports two search sources:
+SpotiFLAC supports multiple search sources for finding music metadata:
| Source | Setup |
|--------|-------|
| **Deezer** (Default) | No setup required |
-| **Spotify** | Install **Spotify Web** extension from the Store, or use your own [Spotify Developer](https://developer.spotify.com) Client ID & Secret in Settings |
+| **Extensions** | Install additional search providers from the Store |
## Extensions
@@ -50,7 +50,7 @@ Want to create your own extension? Check out the [Extension Development Guide](h
## Other project
### [SpotiFLAC (Desktop)](https://github.com/afkarxyz/SpotiFLAC)
-Get Spotify tracks in true FLAC from Tidal, Qobuz & Amazon Music for Windows, macOS & Linux
+Download music in true lossless FLAC from Tidal, Qobuz & Amazon Music for Windows, macOS & Linux
## FAQ
@@ -60,15 +60,12 @@ A: The track may not be available on Tidal, Qobuz, or Amazon Music. Try enabling
**Q: Why are some tracks downloading in lower quality?**
A: Quality depends on what's available from the streaming service. Tidal offers up to 24-bit/192kHz, Qobuz up to 24-bit/192kHz, and Amazon up to 24-bit/48kHz. The app automatically selects the best available quality.
-**Q: Can I download my Spotify playlists?**
-A: Yes! Just paste the Spotify playlist URL in the search bar. The app will fetch all tracks and queue them for download.
+**Q: Can I download playlists?**
+A: Yes! Just paste the playlist URL in the search bar. The app will fetch all tracks and queue them for download.
**Q: Why do I need to grant storage permission?**
A: 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.
-**Q: How do I download Daily Mix or Discover Weekly?**
-A: Install the **Spotify Web** extension from the Store. This extension can access personalized playlists that aren't available through the public API.
-
**Q: Why is the mobile app so large (~50MB) compared to the PC version (~3MB)?**
A: The mobile app includes FFmpeg libraries for audio processing and format conversion, which adds significant size. The PC version relies on system-installed FFmpeg, keeping the download smaller. We bundle FFmpeg to ensure compatibility across all Android devices without requiring users to install additional software.
@@ -81,7 +78,9 @@ A: Yes, the app is open source and you can verify the code yourself. Each releas
This project is for **educational and private use only**. The developer does not condone or encourage copyright infringement.
-**SpotiFLAC** is a third-party tool and is not affiliated with, endorsed by, or connected to Spotify, Tidal, Qobuz, Amazon Music, or any other streaming service.
+**SpotiFLAC** is a third-party tool and is not affiliated with, endorsed by, or connected to Tidal, Qobuz, Amazon Music, Deezer, or any other streaming service.
+
+The application is purely a user interface that facilitates communication between your device and existing third-party services.
You are solely responsible for:
1. Ensuring your use of this software complies with your local laws.
diff --git a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt
index 07374cad..fb5ec321 100644
--- a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt
+++ b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt
@@ -158,8 +158,9 @@ class MainActivity: FlutterActivity() {
val spotifyId = call.argument("spotify_id") ?: ""
val trackName = call.argument("track_name") ?: ""
val artistName = call.argument("artist_name") ?: ""
+ val durationMs = call.argument("duration_ms")?.toLong() ?: 0L
val response = withContext(Dispatchers.IO) {
- Gobackend.fetchLyrics(spotifyId, trackName, artistName)
+ Gobackend.fetchLyrics(spotifyId, trackName, artistName, durationMs)
}
result.success(response)
}
@@ -168,8 +169,9 @@ class MainActivity: FlutterActivity() {
val trackName = call.argument("track_name") ?: ""
val artistName = call.argument("artist_name") ?: ""
val filePath = call.argument("file_path") ?: ""
+ val durationMs = call.argument("duration_ms")?.toLong() ?: 0L
val response = withContext(Dispatchers.IO) {
- Gobackend.getLyricsLRC(spotifyId, trackName, artistName, filePath)
+ Gobackend.getLyricsLRC(spotifyId, trackName, artistName, filePath, durationMs)
}
result.success(response)
}
diff --git a/crowdin.yml b/crowdin.yml
index 0c089bad..d19e6faf 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -1,3 +1,19 @@
files:
- source: /lib/l10n/arb/app_en.arb
- translation: /lib/l10n/arb/app_%locale_with_underscore%.arb
+ translation: /lib/l10n/arb/app_%locale%.arb
+ languages_mapping:
+ locale:
+ # Short codes for single-variant languages
+ de: de
+ es: es
+ fr: fr
+ hi: hi
+ id: id
+ ja: ja
+ ko: ko
+ nl: nl
+ pt: pt
+ ru: ru
+ # Full codes for Chinese variants
+ zh-CN: zh_CN
+ zh-TW: zh_TW
diff --git a/go_backend/amazon.go b/go_backend/amazon.go
index dc2a07c0..b6bb4c40 100644
--- a/go_backend/amazon.go
+++ b/go_backend/amazon.go
@@ -1,8 +1,8 @@
package gobackend
import (
- "context"
"bufio"
+ "context"
"encoding/base64"
"encoding/json"
"errors"
@@ -512,6 +512,7 @@ func downloadFromAmazon(req DownloadRequest) (AmazonDownloadResult, error) {
req.TrackName,
req.ArtistName,
req.EmbedLyrics,
+ int64(req.DurationMS),
)
}()
diff --git a/go_backend/cover.go b/go_backend/cover.go
index 46ca89fd..88d4d29b 100644
--- a/go_backend/cover.go
+++ b/go_backend/cover.go
@@ -4,6 +4,7 @@ import (
"fmt"
"io"
"net/http"
+ "regexp"
"strings"
)
@@ -14,6 +15,9 @@ const (
spotifySizeMax = "ab67616d000082c1" // Max resolution (~2000x2000)
)
+// Deezer CDN supports these sizes: 56, 250, 500, 1000, 1400, 1800
+var deezerSizeRegex = regexp.MustCompile(`/(\d+)x(\d+)-\d+-\d+-\d+-\d+\.jpg$`)
+
// convertSmallToMedium upgrades 300x300 cover URL to 640x640
// Same logic as PC version for consistency
func convertSmallToMedium(imageURL string) string {
@@ -41,9 +45,10 @@ func downloadCoverToMemory(coverURL string, maxQuality bool) ([]byte, error) {
maxURL := upgradeToMaxQuality(downloadURL)
if maxURL != downloadURL {
downloadURL = maxURL
- GoLog("[Cover] Upgraded to max resolution (~2000x2000)")
- } else {
- GoLog("[Cover] Max resolution not available, using 640x640")
+ // Log already printed by upgradeToMaxQuality for Deezer
+ if strings.Contains(coverURL, "scdn.co") || strings.Contains(coverURL, "spotifycdn") {
+ GoLog("[Cover] Spotify: upgraded to max resolution (~2000x2000)")
+ }
}
}
@@ -85,18 +90,38 @@ func downloadCoverToMemory(coverURL string, maxQuality bool) ([]byte, error) {
return data, nil
}
-// upgradeToMaxQuality upgrades Spotify cover URL to maximum quality
-// Same logic as PC version - directly replaces 640x640 size code with max resolution
-// No HEAD verification needed - Spotify CDN always serves max resolution if available
+// upgradeToMaxQuality upgrades cover URL to maximum quality
+// Supports both Spotify and Deezer CDNs
func upgradeToMaxQuality(coverURL string) string {
-
+ // Spotify CDN upgrade
if strings.Contains(coverURL, spotifySize640) {
return strings.Replace(coverURL, spotifySize640, spotifySizeMax, 1)
}
+ // Deezer CDN upgrade
+ if strings.Contains(coverURL, "cdn-images.dzcdn.net") {
+ return upgradeDeezerCover(coverURL)
+ }
+
return coverURL
}
+// upgradeDeezerCover upgrades Deezer cover URL to maximum quality (1800x1800)
+// Deezer CDN format: https://cdn-images.dzcdn.net/images/cover/{hash}/{size}x{size}-000000-80-0-0.jpg
+// Available sizes: 56, 250, 500, 1000, 1400, 1800
+func upgradeDeezerCover(coverURL string) string {
+ if !strings.Contains(coverURL, "cdn-images.dzcdn.net") {
+ return coverURL
+ }
+
+ // Replace any size pattern with 1800x1800
+ upgraded := deezerSizeRegex.ReplaceAllString(coverURL, "/1800x1800-000000-80-0-0.jpg")
+ if upgraded != coverURL {
+ GoLog("[Cover] Deezer: upgraded to 1800x1800")
+ }
+ return upgraded
+}
+
// GetCoverFromSpotify gets cover URL from Spotify metadata
func GetCoverFromSpotify(imageURL string, maxQuality bool) string {
if imageURL == "" {
diff --git a/go_backend/duplicate.go b/go_backend/duplicate.go
index c169a4b6..6d31705e 100644
--- a/go_backend/duplicate.go
+++ b/go_backend/duplicate.go
@@ -21,11 +21,14 @@ type ISRCIndex struct {
var (
isrcIndexCache = make(map[string]*ISRCIndex)
isrcIndexCacheMu sync.RWMutex
+ isrcBuildingMu sync.Map // Per-directory build lock to prevent concurrent builds
isrcIndexTTL = 5 * time.Minute
)
// GetISRCIndex returns or builds an ISRC index for the given directory
+// Uses per-directory mutex to prevent concurrent builds (race condition fix)
func GetISRCIndex(outputDir string) *ISRCIndex {
+ // Fast path: check cache first
isrcIndexCacheMu.RLock()
idx, exists := isrcIndexCache[outputDir]
isrcIndexCacheMu.RUnlock()
@@ -34,6 +37,22 @@ func GetISRCIndex(outputDir string) *ISRCIndex {
return idx
}
+ // Slow path: need to build index
+ // Use per-directory mutex to prevent multiple goroutines from building simultaneously
+ buildLock, _ := isrcBuildingMu.LoadOrStore(outputDir, &sync.Mutex{})
+ mu := buildLock.(*sync.Mutex)
+ mu.Lock()
+ defer mu.Unlock()
+
+ // Double-check cache after acquiring lock (another goroutine may have built it)
+ isrcIndexCacheMu.RLock()
+ idx, exists = isrcIndexCache[outputDir]
+ isrcIndexCacheMu.RUnlock()
+
+ if exists && time.Since(idx.buildTime) < isrcIndexTTL {
+ return idx
+ }
+
return buildISRCIndex(outputDir)
}
diff --git a/go_backend/exports.go b/go_backend/exports.go
index aced12de..3206b494 100644
--- a/go_backend/exports.go
+++ b/go_backend/exports.go
@@ -649,9 +649,11 @@ func SanitizeFilename(filename string) string {
// FetchLyrics fetches lyrics for a track from LRCLIB
// Returns JSON with lyrics data
-func FetchLyrics(spotifyID, trackName, artistName string) (string, error) {
+// durationMs: track duration in milliseconds for matching, use 0 to skip duration matching
+func FetchLyrics(spotifyID, trackName, artistName string, durationMs int64) (string, error) {
client := NewLyricsClient()
- lyrics, err := client.FetchLyricsAllSources(spotifyID, trackName, artistName)
+ durationSec := float64(durationMs) / 1000.0
+ lyrics, err := client.FetchLyricsAllSources(spotifyID, trackName, artistName, durationSec)
if err != nil {
return "", err
}
@@ -673,7 +675,8 @@ func FetchLyrics(spotifyID, trackName, artistName string) (string, error) {
// GetLyricsLRC fetches lyrics and converts to LRC format string with metadata headers
// First tries to extract from file, then falls back to fetching from internet
-func GetLyricsLRC(spotifyID, trackName, artistName string, filePath string) (string, error) {
+// durationMs: track duration in milliseconds for matching, use 0 to skip duration matching
+func GetLyricsLRC(spotifyID, trackName, artistName string, filePath string, durationMs int64) (string, error) {
if filePath != "" {
lyrics, err := ExtractLyrics(filePath)
if err == nil && lyrics != "" {
@@ -682,7 +685,8 @@ func GetLyricsLRC(spotifyID, trackName, artistName string, filePath string) (str
}
client := NewLyricsClient()
- lyricsData, err := client.FetchLyricsAllSources(spotifyID, trackName, artistName)
+ durationSec := float64(durationMs) / 1000.0
+ lyricsData, err := client.FetchLyricsAllSources(spotifyID, trackName, artistName, durationSec)
if err != nil {
return "", err
}
diff --git a/go_backend/lyrics.go b/go_backend/lyrics.go
index feef2c23..97254ff7 100644
--- a/go_backend/lyrics.go
+++ b/go_backend/lyrics.go
@@ -3,14 +3,100 @@ package gobackend
import (
"encoding/json"
"fmt"
+ "math"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
+ "sync"
"time"
)
+// ========================================
+// Lyrics Cache with TTL
+// ========================================
+
+const (
+ lyricsCacheTTL = 24 * time.Hour // Cache lyrics for 24 hours
+ durationToleranceSec = 10.0 // Duration matching tolerance in seconds
+)
+
+type lyricsCacheEntry struct {
+ response *LyricsResponse
+ expiresAt time.Time
+}
+
+type lyricsCache struct {
+ mu sync.RWMutex
+ cache map[string]*lyricsCacheEntry
+}
+
+var globalLyricsCache = &lyricsCache{
+ cache: make(map[string]*lyricsCacheEntry),
+}
+
+func (c *lyricsCache) generateKey(artist, track string, durationSec float64) string {
+ // Normalize key: lowercase, trim spaces
+ normalizedArtist := strings.ToLower(strings.TrimSpace(artist))
+ normalizedTrack := strings.ToLower(strings.TrimSpace(track))
+ // Round duration to nearest 10 seconds for cache key
+ roundedDuration := math.Round(durationSec/10) * 10
+ return fmt.Sprintf("%s|%s|%.0f", normalizedArtist, normalizedTrack, roundedDuration)
+}
+
+func (c *lyricsCache) Get(artist, track string, durationSec float64) (*LyricsResponse, bool) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ key := c.generateKey(artist, track, durationSec)
+ entry, exists := c.cache[key]
+ if !exists {
+ return nil, false
+ }
+
+ // Check if expired
+ if time.Now().After(entry.expiresAt) {
+ return nil, false
+ }
+
+ return entry.response, true
+}
+
+func (c *lyricsCache) Set(artist, track string, durationSec float64, response *LyricsResponse) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ key := c.generateKey(artist, track, durationSec)
+ c.cache[key] = &lyricsCacheEntry{
+ response: response,
+ expiresAt: time.Now().Add(lyricsCacheTTL),
+ }
+}
+
+// CleanExpired removes expired entries from cache
+func (c *lyricsCache) CleanExpired() int {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ now := time.Now()
+ cleaned := 0
+ for key, entry := range c.cache {
+ if now.After(entry.expiresAt) {
+ delete(c.cache, key)
+ cleaned++
+ }
+ }
+ return cleaned
+}
+
+// Size returns current cache size
+func (c *lyricsCache) Size() int {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ return len(c.cache)
+}
+
type LRCLibResponse struct {
ID int `json:"id"`
Name string `json:"name"`
@@ -86,7 +172,9 @@ func (c *LyricsClient) FetchLyricsWithMetadata(artist, track string) (*LyricsRes
return c.parseLRCLibResponse(&lrcResp), nil
}
-func (c *LyricsClient) FetchLyricsFromLRCLibSearch(query string) (*LyricsResponse, error) {
+// FetchLyricsFromLRCLibSearch searches lyrics with optional duration matching
+// durationSec: track duration in seconds, use 0 to skip duration matching
+func (c *LyricsClient) FetchLyricsFromLRCLibSearch(query string, durationSec float64) (*LyricsResponse, error) {
baseURL := "https://lrclib.net/api/search"
params := url.Values{}
params.Set("q", query)
@@ -118,6 +206,13 @@ func (c *LyricsClient) FetchLyricsFromLRCLibSearch(query string) (*LyricsRespons
return nil, fmt.Errorf("no lyrics found")
}
+ // Filter and score results based on duration matching and synced lyrics
+ bestMatch := c.findBestMatch(results, durationSec)
+ if bestMatch != nil {
+ return c.parseLRCLibResponse(bestMatch), nil
+ }
+
+ // Fallback: return first result with synced lyrics
for _, result := range results {
if result.SyncedLyrics != "" {
return c.parseLRCLibResponse(&result), nil
@@ -127,34 +222,89 @@ func (c *LyricsClient) FetchLyricsFromLRCLibSearch(query string) (*LyricsRespons
return c.parseLRCLibResponse(&results[0]), nil
}
-func (c *LyricsClient) FetchLyricsAllSources(spotifyID, trackName, artistName string) (*LyricsResponse, error) {
- lyrics, err := c.FetchLyricsWithMetadata(artistName, trackName)
+// findBestMatch finds the best matching lyrics based on duration and sync status
+func (c *LyricsClient) findBestMatch(results []LRCLibResponse, targetDurationSec float64) *LRCLibResponse {
+ var bestSynced *LRCLibResponse
+ var bestPlain *LRCLibResponse
+
+ for i := range results {
+ result := &results[i]
+
+ // Check duration match if target duration is provided
+ durationMatches := targetDurationSec == 0 || c.durationMatches(result.Duration, targetDurationSec)
+
+ if durationMatches {
+ // Prefer synced lyrics over plain
+ if result.SyncedLyrics != "" && bestSynced == nil {
+ bestSynced = result
+ } else if result.PlainLyrics != "" && bestPlain == nil {
+ bestPlain = result
+ }
+ }
+ }
+
+ // Return synced first, then plain
+ if bestSynced != nil {
+ return bestSynced
+ }
+ return bestPlain
+}
+
+// durationMatches checks if two durations are within tolerance
+func (c *LyricsClient) durationMatches(lrcDuration, targetDuration float64) bool {
+ diff := math.Abs(lrcDuration - targetDuration)
+ return diff <= durationToleranceSec
+}
+
+// FetchLyricsAllSources fetches lyrics from multiple sources with caching and duration matching
+// durationSec: track duration in seconds for matching, use 0 to skip duration matching
+func (c *LyricsClient) FetchLyricsAllSources(spotifyID, trackName, artistName string, durationSec float64) (*LyricsResponse, error) {
+ // Check cache first
+ if cached, found := globalLyricsCache.Get(artistName, trackName, durationSec); found {
+ fmt.Printf("[Lyrics] Cache hit for: %s - %s\n", artistName, trackName)
+ cachedCopy := *cached
+ cachedCopy.Source = cached.Source + " (cached)"
+ return &cachedCopy, nil
+ }
+
+ var lyrics *LyricsResponse
+ var err error
+
+ // Try exact match first
+ lyrics, err = c.FetchLyricsWithMetadata(artistName, trackName)
if err == nil && lyrics != nil && len(lyrics.Lines) > 0 {
lyrics.Source = "LRCLIB"
+ globalLyricsCache.Set(artistName, trackName, durationSec, lyrics)
return lyrics, nil
}
+ // Try with simplified track name
simplifiedTrack := simplifyTrackName(trackName)
if simplifiedTrack != trackName {
lyrics, err = c.FetchLyricsWithMetadata(artistName, simplifiedTrack)
if err == nil && lyrics != nil && len(lyrics.Lines) > 0 {
lyrics.Source = "LRCLIB (simplified)"
+ globalLyricsCache.Set(artistName, trackName, durationSec, lyrics)
return lyrics, nil
}
}
+ // Search with duration matching
query := artistName + " " + trackName
- lyrics, err = c.FetchLyricsFromLRCLibSearch(query)
+ lyrics, err = c.FetchLyricsFromLRCLibSearch(query, durationSec)
if err == nil && lyrics != nil && len(lyrics.Lines) > 0 {
lyrics.Source = "LRCLIB Search"
+ globalLyricsCache.Set(artistName, trackName, durationSec, lyrics)
return lyrics, nil
}
+ // Search with simplified name and duration matching
if simplifiedTrack != trackName {
query = artistName + " " + simplifiedTrack
- lyrics, err = c.FetchLyricsFromLRCLibSearch(query)
+ lyrics, err = c.FetchLyricsFromLRCLibSearch(query, durationSec)
if err == nil && lyrics != nil && len(lyrics.Lines) > 0 {
lyrics.Source = "LRCLIB Search (simplified)"
+ globalLyricsCache.Set(artistName, trackName, durationSec, lyrics)
return lyrics, nil
}
}
diff --git a/go_backend/parallel.go b/go_backend/parallel.go
index 37484714..88eee90a 100644
--- a/go_backend/parallel.go
+++ b/go_backend/parallel.go
@@ -124,6 +124,7 @@ type ParallelDownloadResult struct {
// FetchCoverAndLyricsParallel downloads cover and fetches lyrics in parallel
// This runs while the main audio download is happening
+// durationMs: track duration in milliseconds for lyrics matching
func FetchCoverAndLyricsParallel(
coverURL string,
maxQualityCover bool,
@@ -131,6 +132,7 @@ func FetchCoverAndLyricsParallel(
trackName string,
artistName string,
embedLyrics bool,
+ durationMs int64,
) *ParallelDownloadResult {
result := &ParallelDownloadResult{}
var wg sync.WaitGroup
@@ -158,7 +160,8 @@ func FetchCoverAndLyricsParallel(
defer wg.Done()
fmt.Println("[Parallel] Starting lyrics fetch...")
client := NewLyricsClient()
- lyrics, err := client.FetchLyricsAllSources(spotifyID, trackName, artistName)
+ durationSec := float64(durationMs) / 1000.0
+ lyrics, err := client.FetchLyricsAllSources(spotifyID, trackName, artistName, durationSec)
if err != nil {
result.LyricsErr = err
fmt.Printf("[Parallel] Lyrics fetch failed: %v\n", err)
diff --git a/go_backend/qobuz.go b/go_backend/qobuz.go
index 5e3311f8..2719b909 100644
--- a/go_backend/qobuz.go
+++ b/go_backend/qobuz.go
@@ -1,8 +1,8 @@
package gobackend
import (
- "context"
"bufio"
+ "context"
"encoding/base64"
"encoding/json"
"errors"
@@ -1085,6 +1085,7 @@ func downloadFromQobuz(req DownloadRequest) (QobuzDownloadResult, error) {
req.TrackName,
req.ArtistName,
req.EmbedLyrics,
+ int64(req.DurationMS),
)
}()
diff --git a/go_backend/tidal.go b/go_backend/tidal.go
index 29898552..d537a954 100644
--- a/go_backend/tidal.go
+++ b/go_backend/tidal.go
@@ -1,8 +1,8 @@
package gobackend
import (
- "context"
"bufio"
+ "context"
"encoding/base64"
"encoding/json"
"encoding/xml"
@@ -1666,6 +1666,7 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) {
req.TrackName,
req.ArtistName,
req.EmbedLyrics,
+ int64(req.DurationMS),
)
}()
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
index 43ed4e34..789a950d 100644
--- a/ios/Runner/AppDelegate.swift
+++ b/ios/Runner/AppDelegate.swift
@@ -161,7 +161,8 @@ import Gobackend // Import Go framework
let spotifyId = args["spotify_id"] as! String
let trackName = args["track_name"] as! String
let artistName = args["artist_name"] as! String
- let response = GobackendFetchLyrics(spotifyId, trackName, artistName, &error)
+ let durationMs = args["duration_ms"] as? Int64 ?? 0
+ let response = GobackendFetchLyrics(spotifyId, trackName, artistName, durationMs, &error)
if let error = error { throw error }
return response
@@ -171,7 +172,8 @@ import Gobackend // Import Go framework
let trackName = args["track_name"] as! String
let artistName = args["artist_name"] as! String
let filePath = args["file_path"] as? String ?? ""
- let response = GobackendGetLyricsLRC(spotifyId, trackName, artistName, filePath, &error)
+ let durationMs = args["duration_ms"] as? Int64 ?? 0
+ let response = GobackendGetLyricsLRC(spotifyId, trackName, artistName, filePath, durationMs, &error)
if let error = error { throw error }
return response
diff --git a/lib/constants/app_info.dart b/lib/constants/app_info.dart
index 1f0a11fd..187318d6 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.1.0';
- static const String buildNumber = '59';
+ static const String version = '3.1.1';
+ static const String buildNumber = '60';
static const String fullVersion = '$version+$buildNumber';
diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb
index fc875778..c5d32df4 100644
--- a/lib/l10n/arb/app_de.arb
+++ b/lib/l10n/arb/app_de.arb
@@ -1,23 +1,23 @@
{
"@@locale": "de",
- "@@last_modified": "2026-01-16",
+ "@@last_modified": "2026-01-17",
"appName": "SpotiFLAC",
"@appName": {
"description": "App name - DO NOT TRANSLATE"
},
- "appDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.",
+ "appDescription": "Laden Sie Spotify-Titel in verlustfreier Qualität von Tidal, Qobuz und Amazon Music herunter.",
"@appDescription": {
"description": "App description shown in about page"
},
- "navHome": "Home",
+ "navHome": "Startseite",
"@navHome": {
"description": "Bottom navigation - Home tab"
},
- "navHistory": "History",
+ "navHistory": "Verlauf",
"@navHistory": {
"description": "Bottom navigation - History tab"
},
- "navSettings": "Settings",
+ "navSettings": "Einstellungen",
"@navSettings": {
"description": "Bottom navigation - Settings tab"
},
@@ -25,15 +25,15 @@
"@navStore": {
"description": "Bottom navigation - Extension store tab"
},
- "homeTitle": "Home",
+ "homeTitle": "Startseite",
"@homeTitle": {
"description": "Home screen title"
},
- "homeSearchHint": "Paste Spotify URL or search...",
+ "homeSearchHint": "Spotify-URL einfügen oder suchen...",
"@homeSearchHint": {
"description": "Placeholder text in search box"
},
- "homeSearchHintExtension": "Search with {extensionName}...",
+ "homeSearchHintExtension": "Mit {extensionName} suchen...",
"@homeSearchHintExtension": {
"description": "Placeholder when extension search is active",
"placeholders": {
@@ -43,23 +43,23 @@
}
}
},
- "homeSubtitle": "Paste a Spotify link or search by name",
+ "homeSubtitle": "Spotify-Link einfügen oder nach Namen suchen",
"@homeSubtitle": {
"description": "Subtitle shown below search box"
},
- "homeSupports": "Supports: Track, Album, Playlist, Artist URLs",
+ "homeSupports": "Unterstützt: Titel, Album, Playlist, Künstler-URLs",
"@homeSupports": {
"description": "Info text about supported URL types"
},
- "homeRecent": "Recent",
+ "homeRecent": "Zuletzt",
"@homeRecent": {
"description": "Section header for recent searches"
},
- "historyTitle": "History",
+ "historyTitle": "Verlauf",
"@historyTitle": {
"description": "History screen title"
},
- "historyDownloading": "Downloading ({count})",
+ "historyDownloading": "Wird heruntergeladen ({count})",
"@historyDownloading": {
"description": "Tab showing active downloads count",
"placeholders": {
@@ -69,15 +69,15 @@
}
}
},
- "historyDownloaded": "Downloaded",
+ "historyDownloaded": "Heruntergeladen",
"@historyDownloaded": {
"description": "Tab showing completed downloads"
},
- "historyFilterAll": "All",
+ "historyFilterAll": "Alle",
"@historyFilterAll": {
"description": "Filter chip - show all items"
},
- "historyFilterAlbums": "Albums",
+ "historyFilterAlbums": "Alben",
"@historyFilterAlbums": {
"description": "Filter chip - show albums only"
},
@@ -85,7 +85,7 @@
"@historyFilterSingles": {
"description": "Filter chip - show singles only"
},
- "historyTracksCount": "{count, plural, =1{1 track} other{{count} tracks}}",
+ "historyTracksCount": "{count, plural, =1{1 Titel} other{{count} Titel}}",
"@historyTracksCount": {
"description": "Track count with plural form",
"placeholders": {
@@ -94,7 +94,7 @@
}
}
},
- "historyAlbumsCount": "{count, plural, =1{1 album} other{{count} albums}}",
+ "historyAlbumsCount": "{count, plural, =1{1 Album} other{{count} Alben}}",
"@historyAlbumsCount": {
"description": "Album count with plural form",
"placeholders": {
@@ -103,31 +103,31 @@
}
}
},
- "historyNoDownloads": "No download history",
+ "historyNoDownloads": "Kein Download-Verlauf",
"@historyNoDownloads": {
"description": "Empty state title"
},
- "historyNoDownloadsSubtitle": "Downloaded tracks will appear here",
+ "historyNoDownloadsSubtitle": "Heruntergeladene Titel werden hier angezeigt",
"@historyNoDownloadsSubtitle": {
"description": "Empty state subtitle"
},
- "historyNoAlbums": "No album downloads",
+ "historyNoAlbums": "Keine Album-Downloads",
"@historyNoAlbums": {
"description": "Empty state when filtering albums"
},
- "historyNoAlbumsSubtitle": "Download multiple tracks from an album to see them here",
+ "historyNoAlbumsSubtitle": "Laden Sie mehrere Titel eines Albums herunter, um sie hier zu sehen",
"@historyNoAlbumsSubtitle": {
"description": "Empty state subtitle for albums filter"
},
- "historyNoSingles": "No single downloads",
+ "historyNoSingles": "Keine Einzel-Downloads",
"@historyNoSingles": {
"description": "Empty state when filtering singles"
},
- "historyNoSinglesSubtitle": "Single track downloads will appear here",
+ "historyNoSinglesSubtitle": "Einzelne Titel-Downloads werden hier angezeigt",
"@historyNoSinglesSubtitle": {
"description": "Empty state subtitle for singles filter"
},
- "settingsTitle": "Settings",
+ "settingsTitle": "Einstellungen",
"@settingsTitle": {
"description": "Settings screen title"
},
@@ -135,19 +135,19 @@
"@settingsDownload": {
"description": "Settings section - download options"
},
- "settingsAppearance": "Appearance",
+ "settingsAppearance": "Erscheinungsbild",
"@settingsAppearance": {
"description": "Settings section - visual customization"
},
- "settingsOptions": "Options",
+ "settingsOptions": "Optionen",
"@settingsOptions": {
"description": "Settings section - app options"
},
- "settingsExtensions": "Extensions",
+ "settingsExtensions": "Erweiterungen",
"@settingsExtensions": {
"description": "Settings section - extension management"
},
- "settingsAbout": "About",
+ "settingsAbout": "Über",
"@settingsAbout": {
"description": "Settings section - app info"
},
@@ -155,55 +155,55 @@
"@downloadTitle": {
"description": "Download settings page title"
},
- "downloadLocation": "Download Location",
+ "downloadLocation": "Download-Speicherort",
"@downloadLocation": {
"description": "Setting for download folder"
},
- "downloadLocationSubtitle": "Choose where to save files",
+ "downloadLocationSubtitle": "Wählen Sie den Speicherort für Dateien",
"@downloadLocationSubtitle": {
"description": "Subtitle for download location"
},
- "downloadLocationDefault": "Default location",
+ "downloadLocationDefault": "Standard-Speicherort",
"@downloadLocationDefault": {
"description": "Shown when using default folder"
},
- "downloadDefaultService": "Default Service",
+ "downloadDefaultService": "Standard-Dienst",
"@downloadDefaultService": {
"description": "Setting for preferred download service (Tidal/Qobuz/Amazon)"
},
- "downloadDefaultServiceSubtitle": "Service used for downloads",
+ "downloadDefaultServiceSubtitle": "Dienst für Downloads",
"@downloadDefaultServiceSubtitle": {
"description": "Subtitle for default service"
},
- "downloadDefaultQuality": "Default Quality",
+ "downloadDefaultQuality": "Standard-Qualität",
"@downloadDefaultQuality": {
"description": "Setting for audio quality"
},
- "downloadAskQuality": "Ask Quality Before Download",
+ "downloadAskQuality": "Qualität vor Download abfragen",
"@downloadAskQuality": {
"description": "Toggle to show quality picker"
},
- "downloadAskQualitySubtitle": "Show quality picker for each download",
+ "downloadAskQualitySubtitle": "Qualitätsauswahl für jeden Download anzeigen",
"@downloadAskQualitySubtitle": {
"description": "Subtitle for ask quality toggle"
},
- "downloadFilenameFormat": "Filename Format",
+ "downloadFilenameFormat": "Dateinamenformat",
"@downloadFilenameFormat": {
"description": "Setting for output filename pattern"
},
- "downloadFolderOrganization": "Folder Organization",
+ "downloadFolderOrganization": "Ordnerstruktur",
"@downloadFolderOrganization": {
"description": "Setting for folder structure"
},
- "downloadSeparateSingles": "Separate Singles",
+ "downloadSeparateSingles": "Singles trennen",
"@downloadSeparateSingles": {
"description": "Toggle to separate single tracks"
},
- "downloadSeparateSinglesSubtitle": "Put single tracks in a separate folder",
+ "downloadSeparateSinglesSubtitle": "Einzelne Titel in separatem Ordner speichern",
"@downloadSeparateSinglesSubtitle": {
"description": "Subtitle for separate singles toggle"
},
- "qualityBest": "Best Available",
+ "qualityBest": "Beste Qualität",
"@qualityBest": {
"description": "Audio quality option - highest available"
},
@@ -219,11 +219,11 @@
"@quality128": {
"description": "Audio quality option - 128kbps MP3"
},
- "appearanceTitle": "Appearance",
+ "appearanceTitle": "Erscheinungsbild",
"@appearanceTitle": {
"description": "Appearance settings page title"
},
- "appearanceTheme": "Theme",
+ "appearanceTheme": "Design",
"@appearanceTheme": {
"description": "Theme mode setting"
},
@@ -231,55 +231,55 @@
"@appearanceThemeSystem": {
"description": "Follow system theme"
},
- "appearanceThemeLight": "Light",
+ "appearanceThemeLight": "Hell",
"@appearanceThemeLight": {
"description": "Light theme"
},
- "appearanceThemeDark": "Dark",
+ "appearanceThemeDark": "Dunkel",
"@appearanceThemeDark": {
"description": "Dark theme"
},
- "appearanceDynamicColor": "Dynamic Color",
+ "appearanceDynamicColor": "Dynamische Farben",
"@appearanceDynamicColor": {
"description": "Material You dynamic colors"
},
- "appearanceDynamicColorSubtitle": "Use colors from your wallpaper",
+ "appearanceDynamicColorSubtitle": "Farben von Ihrem Hintergrundbild verwenden",
"@appearanceDynamicColorSubtitle": {
"description": "Subtitle for dynamic color"
},
- "appearanceAccentColor": "Accent Color",
+ "appearanceAccentColor": "Akzentfarbe",
"@appearanceAccentColor": {
"description": "Custom accent color picker"
},
- "appearanceHistoryView": "History View",
+ "appearanceHistoryView": "Verlaufsansicht",
"@appearanceHistoryView": {
"description": "Layout style for history"
},
- "appearanceHistoryViewList": "List",
+ "appearanceHistoryViewList": "Liste",
"@appearanceHistoryViewList": {
"description": "List layout option"
},
- "appearanceHistoryViewGrid": "Grid",
+ "appearanceHistoryViewGrid": "Raster",
"@appearanceHistoryViewGrid": {
"description": "Grid layout option"
},
- "optionsTitle": "Options",
+ "optionsTitle": "Optionen",
"@optionsTitle": {
"description": "Options settings page title"
},
- "optionsSearchSource": "Search Source",
+ "optionsSearchSource": "Suchquelle",
"@optionsSearchSource": {
"description": "Section for search provider settings"
},
- "optionsPrimaryProvider": "Primary Provider",
+ "optionsPrimaryProvider": "Primärer Anbieter",
"@optionsPrimaryProvider": {
"description": "Main search provider setting"
},
- "optionsPrimaryProviderSubtitle": "Service used when searching by track name.",
+ "optionsPrimaryProviderSubtitle": "Dienst für die Suche nach Titelnamen.",
"@optionsPrimaryProviderSubtitle": {
"description": "Subtitle for primary provider"
},
- "optionsUsingExtension": "Using extension: {extensionName}",
+ "optionsUsingExtension": "Erweiterung verwenden: {extensionName}",
"@optionsUsingExtension": {
"description": "Shows active extension name",
"placeholders": {
@@ -288,55 +288,55 @@
}
}
},
- "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension",
+ "optionsSwitchBack": "Tippen Sie auf Deezer oder Spotify, um von der Erweiterung zurückzuwechseln",
"@optionsSwitchBack": {
"description": "Hint to switch back to built-in providers"
},
- "optionsAutoFallback": "Auto Fallback",
+ "optionsAutoFallback": "Automatischer Fallback",
"@optionsAutoFallback": {
"description": "Auto-retry with other services"
},
- "optionsAutoFallbackSubtitle": "Try other services if download fails",
+ "optionsAutoFallbackSubtitle": "Andere Dienste versuchen, wenn Download fehlschlägt",
"@optionsAutoFallbackSubtitle": {
"description": "Subtitle for auto fallback"
},
- "optionsUseExtensionProviders": "Use Extension Providers",
+ "optionsUseExtensionProviders": "Erweiterungs-Anbieter verwenden",
"@optionsUseExtensionProviders": {
"description": "Enable extension download providers"
},
- "optionsUseExtensionProvidersOn": "Extensions will be tried first",
+ "optionsUseExtensionProvidersOn": "Erweiterungen werden zuerst versucht",
"@optionsUseExtensionProvidersOn": {
"description": "Status when extension providers enabled"
},
- "optionsUseExtensionProvidersOff": "Using built-in providers only",
+ "optionsUseExtensionProvidersOff": "Nur integrierte Anbieter verwenden",
"@optionsUseExtensionProvidersOff": {
"description": "Status when extension providers disabled"
},
- "optionsEmbedLyrics": "Embed Lyrics",
+ "optionsEmbedLyrics": "Liedtexte einbetten",
"@optionsEmbedLyrics": {
"description": "Embed lyrics in audio files"
},
- "optionsEmbedLyricsSubtitle": "Embed synced lyrics into FLAC files",
+ "optionsEmbedLyricsSubtitle": "Synchronisierte Liedtexte in FLAC-Dateien einbetten",
"@optionsEmbedLyricsSubtitle": {
"description": "Subtitle for embed lyrics"
},
- "optionsMaxQualityCover": "Max Quality Cover",
+ "optionsMaxQualityCover": "Maximale Cover-Qualität",
"@optionsMaxQualityCover": {
"description": "Download highest quality album art"
},
- "optionsMaxQualityCoverSubtitle": "Download highest resolution cover art",
+ "optionsMaxQualityCoverSubtitle": "Cover in höchster Auflösung herunterladen",
"@optionsMaxQualityCoverSubtitle": {
"description": "Subtitle for max quality cover"
},
- "optionsConcurrentDownloads": "Concurrent Downloads",
+ "optionsConcurrentDownloads": "Parallele Downloads",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
- "optionsConcurrentSequential": "Sequential (1 at a time)",
+ "optionsConcurrentSequential": "Sequentiell (1 gleichzeitig)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
- "optionsConcurrentParallel": "{count} parallel downloads",
+ "optionsConcurrentParallel": "{count} parallele Downloads",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
@@ -345,67 +345,67 @@
}
}
},
- "optionsConcurrentWarning": "Parallel downloads may trigger rate limiting",
+ "optionsConcurrentWarning": "Parallele Downloads können Ratenlimitierung auslösen",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
- "optionsExtensionStore": "Extension Store",
+ "optionsExtensionStore": "Erweiterungs-Store",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
},
- "optionsExtensionStoreSubtitle": "Show Store tab in navigation",
+ "optionsExtensionStoreSubtitle": "Store-Tab in Navigation anzeigen",
"@optionsExtensionStoreSubtitle": {
"description": "Subtitle for extension store toggle"
},
- "optionsCheckUpdates": "Check for Updates",
+ "optionsCheckUpdates": "Nach Updates suchen",
"@optionsCheckUpdates": {
"description": "Auto update check toggle"
},
- "optionsCheckUpdatesSubtitle": "Notify when new version is available",
+ "optionsCheckUpdatesSubtitle": "Benachrichtigen, wenn neue Version verfügbar",
"@optionsCheckUpdatesSubtitle": {
"description": "Subtitle for update check"
},
- "optionsUpdateChannel": "Update Channel",
+ "optionsUpdateChannel": "Update-Kanal",
"@optionsUpdateChannel": {
"description": "Stable vs preview releases"
},
- "optionsUpdateChannelStable": "Stable releases only",
+ "optionsUpdateChannelStable": "Nur stabile Versionen",
"@optionsUpdateChannelStable": {
"description": "Only stable updates"
},
- "optionsUpdateChannelPreview": "Get preview releases",
+ "optionsUpdateChannelPreview": "Vorschau-Versionen erhalten",
"@optionsUpdateChannelPreview": {
"description": "Include beta/preview updates"
},
- "optionsUpdateChannelWarning": "Preview may contain bugs or incomplete features",
+ "optionsUpdateChannelWarning": "Vorschau kann Fehler oder unvollständige Funktionen enthalten",
"@optionsUpdateChannelWarning": {
"description": "Warning about preview channel"
},
- "optionsClearHistory": "Clear Download History",
+ "optionsClearHistory": "Download-Verlauf löschen",
"@optionsClearHistory": {
"description": "Delete all download history"
},
- "optionsClearHistorySubtitle": "Remove all downloaded tracks from history",
+ "optionsClearHistorySubtitle": "Alle heruntergeladenen Titel aus dem Verlauf entfernen",
"@optionsClearHistorySubtitle": {
"description": "Subtitle for clear history"
},
- "optionsDetailedLogging": "Detailed Logging",
+ "optionsDetailedLogging": "Detaillierte Protokollierung",
"@optionsDetailedLogging": {
"description": "Enable verbose logs for debugging"
},
- "optionsDetailedLoggingOn": "Detailed logs are being recorded",
+ "optionsDetailedLoggingOn": "Detaillierte Protokolle werden aufgezeichnet",
"@optionsDetailedLoggingOn": {
"description": "Status when logging enabled"
},
- "optionsDetailedLoggingOff": "Enable for bug reports",
+ "optionsDetailedLoggingOff": "Für Fehlerberichte aktivieren",
"@optionsDetailedLoggingOff": {
"description": "Status when logging disabled"
},
- "optionsSpotifyCredentials": "Spotify Credentials",
+ "optionsSpotifyCredentials": "Spotify-Anmeldedaten",
"@optionsSpotifyCredentials": {
"description": "Spotify API credentials setting"
},
- "optionsSpotifyCredentialsConfigured": "Client ID: {clientId}...",
+ "optionsSpotifyCredentialsConfigured": "Client-ID: {clientId}...",
"@optionsSpotifyCredentialsConfigured": {
"description": "Shows configured client ID preview",
"placeholders": {
@@ -414,35 +414,35 @@
}
}
},
- "optionsSpotifyCredentialsRequired": "Required - tap to configure",
+ "optionsSpotifyCredentialsRequired": "Erforderlich - zum Konfigurieren tippen",
"@optionsSpotifyCredentialsRequired": {
"description": "Prompt to set up credentials"
},
- "optionsSpotifyWarning": "Spotify requires your own API credentials. Get them free from developer.spotify.com",
+ "optionsSpotifyWarning": "Spotify erfordert eigene API-Anmeldedaten. Kostenlos erhältlich auf developer.spotify.com",
"@optionsSpotifyWarning": {
"description": "Info about Spotify API requirement"
},
- "extensionsTitle": "Extensions",
+ "extensionsTitle": "Erweiterungen",
"@extensionsTitle": {
"description": "Extensions page title"
},
- "extensionsInstalled": "Installed Extensions",
+ "extensionsInstalled": "Installierte Erweiterungen",
"@extensionsInstalled": {
"description": "Section header for installed extensions"
},
- "extensionsNone": "No extensions installed",
+ "extensionsNone": "Keine Erweiterungen installiert",
"@extensionsNone": {
"description": "Empty state title"
},
- "extensionsNoneSubtitle": "Install extensions from the Store tab",
+ "extensionsNoneSubtitle": "Erweiterungen aus dem Store-Tab installieren",
"@extensionsNoneSubtitle": {
"description": "Empty state subtitle"
},
- "extensionsEnabled": "Enabled",
+ "extensionsEnabled": "Aktiviert",
"@extensionsEnabled": {
"description": "Extension status - active"
},
- "extensionsDisabled": "Disabled",
+ "extensionsDisabled": "Deaktiviert",
"@extensionsDisabled": {
"description": "Extension status - inactive"
},
@@ -455,7 +455,7 @@
}
}
},
- "extensionsAuthor": "by {author}",
+ "extensionsAuthor": "von {author}",
"@extensionsAuthor": {
"description": "Extension author credit",
"placeholders": {
@@ -464,47 +464,47 @@
}
}
},
- "extensionsUninstall": "Uninstall",
+ "extensionsUninstall": "Deinstallieren",
"@extensionsUninstall": {
"description": "Uninstall extension button"
},
- "extensionsSetAsSearch": "Set as Search Provider",
+ "extensionsSetAsSearch": "Als Suchanbieter festlegen",
"@extensionsSetAsSearch": {
"description": "Use extension for search"
},
- "storeTitle": "Extension Store",
+ "storeTitle": "Erweiterungs-Store",
"@storeTitle": {
"description": "Store screen title"
},
- "storeSearch": "Search extensions...",
+ "storeSearch": "Erweiterungen suchen...",
"@storeSearch": {
"description": "Store search placeholder"
},
- "storeInstall": "Install",
+ "storeInstall": "Installieren",
"@storeInstall": {
"description": "Install extension button"
},
- "storeInstalled": "Installed",
+ "storeInstalled": "Installiert",
"@storeInstalled": {
"description": "Already installed badge"
},
- "storeUpdate": "Update",
+ "storeUpdate": "Aktualisieren",
"@storeUpdate": {
"description": "Update available button"
},
- "aboutTitle": "About",
+ "aboutTitle": "Über",
"@aboutTitle": {
"description": "About page title"
},
- "aboutContributors": "Contributors",
+ "aboutContributors": "Mitwirkende",
"@aboutContributors": {
"description": "Section for contributors"
},
- "aboutMobileDeveloper": "Mobile version developer",
+ "aboutMobileDeveloper": "Mobile-Version Entwickler",
"@aboutMobileDeveloper": {
"description": "Role description for mobile dev"
},
- "aboutOriginalCreator": "Creator of the original SpotiFLAC",
+ "aboutOriginalCreator": "Schöpfer des ursprünglichen SpotiFLAC",
"@aboutOriginalCreator": {
"description": "Role description for original creator"
},
diff --git a/lib/l10n/arb/app_ja.arb b/lib/l10n/arb/app_ja.arb
index 9e85578e..1da85d29 100644
--- a/lib/l10n/arb/app_ja.arb
+++ b/lib/l10n/arb/app_ja.arb
@@ -9,23 +9,23 @@
"@appDescription": {
"description": "App description shown in about page"
},
- "navHome": "Home",
+ "navHome": "ホーム",
"@navHome": {
"description": "Bottom navigation - Home tab"
},
- "navHistory": "History",
+ "navHistory": "履歴",
"@navHistory": {
"description": "Bottom navigation - History tab"
},
- "navSettings": "Settings",
+ "navSettings": "設定",
"@navSettings": {
"description": "Bottom navigation - Settings tab"
},
- "navStore": "Store",
+ "navStore": "ストア",
"@navStore": {
"description": "Bottom navigation - Extension store tab"
},
- "homeTitle": "Home",
+ "homeTitle": "ホーム",
"@homeTitle": {
"description": "Home screen title"
},
@@ -59,7 +59,7 @@
"@historyTitle": {
"description": "History screen title"
},
- "historyDownloading": "Downloading ({count})",
+ "historyDownloading": "ダウンロード中 ({count})",
"@historyDownloading": {
"description": "Tab showing active downloads count",
"placeholders": {
@@ -69,19 +69,19 @@
}
}
},
- "historyDownloaded": "Downloaded",
+ "historyDownloaded": "ダウンロード済み",
"@historyDownloaded": {
"description": "Tab showing completed downloads"
},
- "historyFilterAll": "All",
+ "historyFilterAll": "すべて",
"@historyFilterAll": {
"description": "Filter chip - show all items"
},
- "historyFilterAlbums": "Albums",
+ "historyFilterAlbums": "アルバム",
"@historyFilterAlbums": {
"description": "Filter chip - show albums only"
},
- "historyFilterSingles": "Singles",
+ "historyFilterSingles": "シングル",
"@historyFilterSingles": {
"description": "Filter chip - show singles only"
},
@@ -127,31 +127,31 @@
"@historyNoSinglesSubtitle": {
"description": "Empty state subtitle for singles filter"
},
- "settingsTitle": "Settings",
+ "settingsTitle": "設定",
"@settingsTitle": {
"description": "Settings screen title"
},
- "settingsDownload": "Download",
+ "settingsDownload": "ダウンロード",
"@settingsDownload": {
"description": "Settings section - download options"
},
- "settingsAppearance": "Appearance",
+ "settingsAppearance": "外観",
"@settingsAppearance": {
"description": "Settings section - visual customization"
},
- "settingsOptions": "Options",
+ "settingsOptions": "オプション",
"@settingsOptions": {
"description": "Settings section - app options"
},
- "settingsExtensions": "Extensions",
+ "settingsExtensions": "拡張",
"@settingsExtensions": {
"description": "Settings section - extension management"
},
- "settingsAbout": "About",
+ "settingsAbout": "アプリについて",
"@settingsAbout": {
"description": "Settings section - app info"
},
- "downloadTitle": "Download",
+ "downloadTitle": "ダウンロード",
"@downloadTitle": {
"description": "Download settings page title"
},
@@ -163,19 +163,19 @@
"@downloadLocationSubtitle": {
"description": "Subtitle for download location"
},
- "downloadLocationDefault": "Default location",
+ "downloadLocationDefault": "デフォルトの場所",
"@downloadLocationDefault": {
"description": "Shown when using default folder"
},
- "downloadDefaultService": "Default Service",
+ "downloadDefaultService": "デフォルトのサービス",
"@downloadDefaultService": {
"description": "Setting for preferred download service (Tidal/Qobuz/Amazon)"
},
- "downloadDefaultServiceSubtitle": "Service used for downloads",
+ "downloadDefaultServiceSubtitle": "ダウンロードに使用したサービス",
"@downloadDefaultServiceSubtitle": {
"description": "Subtitle for default service"
},
- "downloadDefaultQuality": "Default Quality",
+ "downloadDefaultQuality": "デフォルトの品質",
"@downloadDefaultQuality": {
"description": "Setting for audio quality"
},
@@ -187,7 +187,7 @@
"@downloadAskQualitySubtitle": {
"description": "Subtitle for ask quality toggle"
},
- "downloadFilenameFormat": "Filename Format",
+ "downloadFilenameFormat": "ファイル名の形式",
"@downloadFilenameFormat": {
"description": "Setting for output filename pattern"
},
@@ -219,27 +219,27 @@
"@quality128": {
"description": "Audio quality option - 128kbps MP3"
},
- "appearanceTitle": "Appearance",
+ "appearanceTitle": "外観",
"@appearanceTitle": {
"description": "Appearance settings page title"
},
- "appearanceTheme": "Theme",
+ "appearanceTheme": "テーマ",
"@appearanceTheme": {
"description": "Theme mode setting"
},
- "appearanceThemeSystem": "System",
+ "appearanceThemeSystem": "システム",
"@appearanceThemeSystem": {
"description": "Follow system theme"
},
- "appearanceThemeLight": "Light",
+ "appearanceThemeLight": "ライト",
"@appearanceThemeLight": {
"description": "Light theme"
},
- "appearanceThemeDark": "Dark",
+ "appearanceThemeDark": "ダーク",
"@appearanceThemeDark": {
"description": "Dark theme"
},
- "appearanceDynamicColor": "Dynamic Color",
+ "appearanceDynamicColor": "ダイナミックカラー",
"@appearanceDynamicColor": {
"description": "Material You dynamic colors"
},
@@ -247,31 +247,31 @@
"@appearanceDynamicColorSubtitle": {
"description": "Subtitle for dynamic color"
},
- "appearanceAccentColor": "Accent Color",
+ "appearanceAccentColor": "アクセントカラー",
"@appearanceAccentColor": {
"description": "Custom accent color picker"
},
- "appearanceHistoryView": "History View",
+ "appearanceHistoryView": "履歴の表示",
"@appearanceHistoryView": {
"description": "Layout style for history"
},
- "appearanceHistoryViewList": "List",
+ "appearanceHistoryViewList": "リスト",
"@appearanceHistoryViewList": {
"description": "List layout option"
},
- "appearanceHistoryViewGrid": "Grid",
+ "appearanceHistoryViewGrid": "グリッド",
"@appearanceHistoryViewGrid": {
"description": "Grid layout option"
},
- "optionsTitle": "Options",
+ "optionsTitle": "オプション",
"@optionsTitle": {
"description": "Options settings page title"
},
- "optionsSearchSource": "Search Source",
+ "optionsSearchSource": "検索ソース",
"@optionsSearchSource": {
"description": "Section for search provider settings"
},
- "optionsPrimaryProvider": "Primary Provider",
+ "optionsPrimaryProvider": "プライマリーのプロバイダー",
"@optionsPrimaryProvider": {
"description": "Main search provider setting"
},
@@ -279,7 +279,7 @@
"@optionsPrimaryProviderSubtitle": {
"description": "Subtitle for primary provider"
},
- "optionsUsingExtension": "Using extension: {extensionName}",
+ "optionsUsingExtension": "拡張の使用: {extensionName}",
"@optionsUsingExtension": {
"description": "Shows active extension name",
"placeholders": {
@@ -300,7 +300,7 @@
"@optionsAutoFallbackSubtitle": {
"description": "Subtitle for auto fallback"
},
- "optionsUseExtensionProviders": "Use Extension Providers",
+ "optionsUseExtensionProviders": "拡張のプロバイダーを使用する",
"@optionsUseExtensionProviders": {
"description": "Enable extension download providers"
},
@@ -308,11 +308,11 @@
"@optionsUseExtensionProvidersOn": {
"description": "Status when extension providers enabled"
},
- "optionsUseExtensionProvidersOff": "Using built-in providers only",
+ "optionsUseExtensionProvidersOff": "内蔵のプロバイダーのみを使用する",
"@optionsUseExtensionProvidersOff": {
"description": "Status when extension providers disabled"
},
- "optionsEmbedLyrics": "Embed Lyrics",
+ "optionsEmbedLyrics": "歌詞を埋め込む",
"@optionsEmbedLyrics": {
"description": "Embed lyrics in audio files"
},
@@ -320,7 +320,7 @@
"@optionsEmbedLyricsSubtitle": {
"description": "Subtitle for embed lyrics"
},
- "optionsMaxQualityCover": "Max Quality Cover",
+ "optionsMaxQualityCover": "最大品質のカバー",
"@optionsMaxQualityCover": {
"description": "Download highest quality album art"
},
@@ -349,7 +349,7 @@
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
- "optionsExtensionStore": "Extension Store",
+ "optionsExtensionStore": "拡張ストア",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
},
@@ -357,7 +357,7 @@
"@optionsExtensionStoreSubtitle": {
"description": "Subtitle for extension store toggle"
},
- "optionsCheckUpdates": "Check for Updates",
+ "optionsCheckUpdates": "更新を確認",
"@optionsCheckUpdates": {
"description": "Auto update check toggle"
},
@@ -365,15 +365,15 @@
"@optionsCheckUpdatesSubtitle": {
"description": "Subtitle for update check"
},
- "optionsUpdateChannel": "Update Channel",
+ "optionsUpdateChannel": "更新チャンネル",
"@optionsUpdateChannel": {
"description": "Stable vs preview releases"
},
- "optionsUpdateChannelStable": "Stable releases only",
+ "optionsUpdateChannelStable": "安定版リリースのみ",
"@optionsUpdateChannelStable": {
"description": "Only stable updates"
},
- "optionsUpdateChannelPreview": "Get preview releases",
+ "optionsUpdateChannelPreview": "プレビューリリースを入手",
"@optionsUpdateChannelPreview": {
"description": "Include beta/preview updates"
},
@@ -401,11 +401,11 @@
"@optionsDetailedLoggingOff": {
"description": "Status when logging disabled"
},
- "optionsSpotifyCredentials": "Spotify Credentials",
+ "optionsSpotifyCredentials": "Spotify の認証情報",
"@optionsSpotifyCredentials": {
"description": "Spotify API credentials setting"
},
- "optionsSpotifyCredentialsConfigured": "Client ID: {clientId}...",
+ "optionsSpotifyCredentialsConfigured": "クライアント ID: {clientId}...",
"@optionsSpotifyCredentialsConfigured": {
"description": "Shows configured client ID preview",
"placeholders": {
@@ -422,23 +422,23 @@
"@optionsSpotifyWarning": {
"description": "Info about Spotify API requirement"
},
- "extensionsTitle": "Extensions",
+ "extensionsTitle": "拡張",
"@extensionsTitle": {
"description": "Extensions page title"
},
- "extensionsInstalled": "Installed Extensions",
+ "extensionsInstalled": "インストール済みの拡張",
"@extensionsInstalled": {
"description": "Section header for installed extensions"
},
- "extensionsNone": "No extensions installed",
+ "extensionsNone": "拡張はインストールされていません",
"@extensionsNone": {
"description": "Empty state title"
},
- "extensionsNoneSubtitle": "Install extensions from the Store tab",
+ "extensionsNoneSubtitle": "ストアタブから拡張をインストール",
"@extensionsNoneSubtitle": {
"description": "Empty state subtitle"
},
- "extensionsEnabled": "Enabled",
+ "extensionsEnabled": "有効",
"@extensionsEnabled": {
"description": "Extension status - active"
},
@@ -446,7 +446,7 @@
"@extensionsDisabled": {
"description": "Extension status - inactive"
},
- "extensionsVersion": "Version {version}",
+ "extensionsVersion": "バージョン {version}",
"@extensionsVersion": {
"description": "Extension version display",
"placeholders": {
@@ -455,7 +455,7 @@
}
}
},
- "extensionsAuthor": "by {author}",
+ "extensionsAuthor": "作者 {author}",
"@extensionsAuthor": {
"description": "Extension author credit",
"placeholders": {
@@ -464,43 +464,43 @@
}
}
},
- "extensionsUninstall": "Uninstall",
+ "extensionsUninstall": "アンインストール",
"@extensionsUninstall": {
"description": "Uninstall extension button"
},
- "extensionsSetAsSearch": "Set as Search Provider",
+ "extensionsSetAsSearch": "検索プロバイダーを設定",
"@extensionsSetAsSearch": {
"description": "Use extension for search"
},
- "storeTitle": "Extension Store",
+ "storeTitle": "拡張ストア",
"@storeTitle": {
"description": "Store screen title"
},
- "storeSearch": "Search extensions...",
+ "storeSearch": "拡張を検索...",
"@storeSearch": {
"description": "Store search placeholder"
},
- "storeInstall": "Install",
+ "storeInstall": "インストール",
"@storeInstall": {
"description": "Install extension button"
},
- "storeInstalled": "Installed",
+ "storeInstalled": "インストール済み",
"@storeInstalled": {
"description": "Already installed badge"
},
- "storeUpdate": "Update",
+ "storeUpdate": "更新",
"@storeUpdate": {
"description": "Update available button"
},
- "aboutTitle": "About",
+ "aboutTitle": "アプリについて",
"@aboutTitle": {
"description": "About page title"
},
- "aboutContributors": "Contributors",
+ "aboutContributors": "貢献者",
"@aboutContributors": {
"description": "Section for contributors"
},
- "aboutMobileDeveloper": "Mobile version developer",
+ "aboutMobileDeveloper": "モバイルバージョンの開発者",
"@aboutMobileDeveloper": {
"description": "Role description for mobile dev"
},
@@ -512,23 +512,23 @@
"@aboutLogoArtist": {
"description": "Role description for logo artist"
},
- "aboutSpecialThanks": "Special Thanks",
+ "aboutSpecialThanks": "スペシャルサンクス",
"@aboutSpecialThanks": {
"description": "Section for special thanks"
},
- "aboutLinks": "Links",
+ "aboutLinks": "リンク",
"@aboutLinks": {
"description": "Section for external links"
},
- "aboutMobileSource": "Mobile source code",
+ "aboutMobileSource": "モバイル版のソースコード",
"@aboutMobileSource": {
"description": "Link to mobile GitHub repo"
},
- "aboutPCSource": "PC source code",
+ "aboutPCSource": "PC 版のソースコード",
"@aboutPCSource": {
"description": "Link to PC GitHub repo"
},
- "aboutReportIssue": "Report an issue",
+ "aboutReportIssue": "Issue で報告する",
"@aboutReportIssue": {
"description": "Link to report bugs"
},
@@ -536,7 +536,7 @@
"@aboutReportIssueSubtitle": {
"description": "Subtitle for report issue"
},
- "aboutFeatureRequest": "Feature request",
+ "aboutFeatureRequest": "機能の要望",
"@aboutFeatureRequest": {
"description": "Link to suggest features"
},
@@ -548,19 +548,19 @@
"@aboutSupport": {
"description": "Section for support/donation links"
},
- "aboutBuyMeCoffee": "Buy me a coffee",
+ "aboutBuyMeCoffee": "コーヒーを買ってください",
"@aboutBuyMeCoffee": {
"description": "Donation link"
},
- "aboutBuyMeCoffeeSubtitle": "Support development on Ko-fi",
+ "aboutBuyMeCoffeeSubtitle": "Ko-fi で開発をサポートします",
"@aboutBuyMeCoffeeSubtitle": {
"description": "Subtitle for donation"
},
- "aboutApp": "App",
+ "aboutApp": "アプリ",
"@aboutApp": {
"description": "Section for app info"
},
- "aboutVersion": "Version",
+ "aboutVersion": "バージョン",
"@aboutVersion": {
"description": "Version info label"
},
@@ -625,11 +625,11 @@
"@artistAlbums": {
"description": "Section header for artist albums"
},
- "artistSingles": "Singles & EPs",
+ "artistSingles": "シングルと EP",
"@artistSingles": {
"description": "Section header for singles/EPs"
},
- "artistCompilations": "Compilations",
+ "artistCompilations": "コンピレーション",
"@artistCompilations": {
"description": "Section header for compilations"
},
@@ -642,6 +642,20 @@
}
}
},
+ "artistPopular": "Popular",
+ "@artistPopular": {
+ "description": "Section header for popular/top tracks"
+ },
+ "artistMonthlyListeners": "{count} monthly listeners",
+ "@artistMonthlyListeners": {
+ "description": "Monthly listener count display",
+ "placeholders": {
+ "count": {
+ "type": "String",
+ "description": "Formatted listener count"
+ }
+ }
+ },
"trackMetadataTitle": "Track Info",
"@trackMetadataTitle": {
"description": "Track metadata screen title"
@@ -730,15 +744,15 @@
"@setupChooseFolder": {
"description": "Button to pick folder"
},
- "setupContinue": "Continue",
+ "setupContinue": "続行",
"@setupContinue": {
"description": "Continue to next step button"
},
- "setupSkip": "Skip for now",
+ "setupSkip": "今はスキップ",
"@setupSkip": {
"description": "Skip current step button"
},
- "setupStorageAccessRequired": "Storage Access Required",
+ "setupStorageAccessRequired": "ストレージアクセスが必要です",
"@setupStorageAccessRequired": {
"description": "Title when storage access needed"
},
@@ -841,7 +855,7 @@
"@setupStepSpotify": {
"description": "Setup step indicator - Spotify API"
},
- "setupStepPermission": "Permission",
+ "setupStepPermission": "権限",
"@setupStepPermission": {
"description": "Setup step indicator - permission"
},
@@ -861,7 +875,7 @@
"@setupNotificationGranted": {
"description": "Success message for notification permission"
},
- "setupNotificationEnable": "Enable Notifications",
+ "setupNotificationEnable": "通知を有効化する",
"@setupNotificationEnable": {
"description": "Button to enable notifications"
},
@@ -869,7 +883,7 @@
"@setupNotificationDescription": {
"description": "Explanation for notifications"
},
- "setupFolderSelected": "Download Folder Selected!",
+ "setupFolderSelected": "ダウンロードフォルダが選択済みです!",
"@setupFolderSelected": {
"description": "Success message for folder selection"
},
@@ -889,7 +903,7 @@
"@setupSelectFolder": {
"description": "Button to select folder"
},
- "setupSpotifyApiOptional": "Spotify API (Optional)",
+ "setupSpotifyApiOptional": "Spotify API (任意)",
"@setupSpotifyApiOptional": {
"description": "Spotify API step title"
},
@@ -897,7 +911,7 @@
"@setupSpotifyApiDescription": {
"description": "Explanation for Spotify API"
},
- "setupUseSpotifyApi": "Use Spotify API",
+ "setupUseSpotifyApi": "Spotify API を使用する",
"@setupUseSpotifyApi": {
"description": "Toggle to enable Spotify API"
},
@@ -905,15 +919,15 @@
"@setupEnterCredentialsBelow": {
"description": "Prompt to enter credentials"
},
- "setupUsingDeezer": "Using Deezer (no account needed)",
+ "setupUsingDeezer": "Deezer を使用中 (アカウントは不要です)",
"@setupUsingDeezer": {
"description": "Status when using Deezer"
},
- "setupEnterClientId": "Enter Spotify Client ID",
+ "setupEnterClientId": "Spotify クライアント ID を入力",
"@setupEnterClientId": {
"description": "Placeholder for client ID field"
},
- "setupEnterClientSecret": "Enter Spotify Client Secret",
+ "setupEnterClientSecret": "Spotify クライアントシークレットを入力",
"@setupEnterClientSecret": {
"description": "Placeholder for client secret field"
},
@@ -937,15 +951,15 @@
"@setupNotificationBackgroundDescription": {
"description": "Detailed notification explanation"
},
- "setupSkipForNow": "Skip for now",
+ "setupSkipForNow": "今はスキップ",
"@setupSkipForNow": {
"description": "Skip button text"
},
- "setupBack": "Back",
+ "setupBack": "戻る",
"@setupBack": {
"description": "Back button text"
},
- "setupNext": "Next",
+ "setupNext": "次へ",
"@setupNext": {
"description": "Next button text"
},
@@ -953,7 +967,7 @@
"@setupGetStarted": {
"description": "Final setup button"
},
- "setupSkipAndStart": "Skip & Start",
+ "setupSkipAndStart": "スキップと開始",
"@setupSkipAndStart": {
"description": "Skip setup and start app"
},
@@ -1069,7 +1083,7 @@
"@dialogRemoveExtensionMessage": {
"description": "Dialog message - uninstall confirmation"
},
- "dialogUninstallExtension": "Uninstall Extension?",
+ "dialogUninstallExtension": "拡張をアンインストールしますか?",
"@dialogUninstallExtension": {
"description": "Dialog title - uninstall extension"
},
@@ -1103,7 +1117,7 @@
}
}
},
- "dialogImportPlaylistTitle": "Import Playlist",
+ "dialogImportPlaylistTitle": "プレイリストをインポート",
"@dialogImportPlaylistTitle": {
"description": "Dialog title - import CSV playlist"
},
@@ -1242,7 +1256,7 @@
"@snackbarFailedToUpdate": {
"description": "Snackbar - extension update error"
},
- "errorRateLimited": "Rate Limited",
+ "errorRateLimited": "レート制限",
"@errorRateLimited": {
"description": "Error title - too many requests"
},
@@ -1509,7 +1523,7 @@
}
}
},
- "updateDownload": "Download",
+ "updateDownload": "ダウンロード",
"@updateDownload": {
"description": "Update button - download update"
},
@@ -1537,7 +1551,7 @@
"@updateNewVersionReady": {
"description": "Update subtitle"
},
- "updateCurrent": "Current",
+ "updateCurrent": "現在",
"@updateCurrent": {
"description": "Label for current version"
},
@@ -1669,15 +1683,15 @@
"@logClearLogsMessage": {
"description": "Clear logs confirmation message"
},
- "logIspBlocking": "ISP BLOCKING DETECTED",
+ "logIspBlocking": "ISP のブロックを検出しました",
"@logIspBlocking": {
"description": "Error category - ISP blocking"
},
- "logRateLimited": "RATE LIMITED",
+ "logRateLimited": "レート制限",
"@logRateLimited": {
"description": "Error category - rate limiting"
},
- "logNetworkError": "NETWORK ERROR",
+ "logNetworkError": "ネットワークエラー",
"@logNetworkError": {
"description": "Error category - network issues"
},
@@ -1851,27 +1865,15 @@
},
"sectionLanguage": "Language",
"@sectionLanguage": {
- "description": "Settings section header for language selection"
+ "description": "Settings section header for language"
},
"appearanceLanguage": "App Language",
"@appearanceLanguage": {
- "description": "Setting title for language selection"
+ "description": "Language setting title"
},
"appearanceLanguageSubtitle": "Choose your preferred language",
"@appearanceLanguageSubtitle": {
- "description": "Subtitle for language setting"
- },
- "languageSystem": "System Default",
- "@languageSystem": {
- "description": "Use device system language"
- },
- "languageEnglish": "English",
- "@languageEnglish": {
- "description": "English language option"
- },
- "languageIndonesian": "Bahasa Indonesia",
- "@languageIndonesian": {
- "description": "Indonesian language option"
+ "description": "Language setting subtitle"
},
"settingsAppearanceSubtitle": "Theme, colors, display",
"@settingsAppearanceSubtitle": {
@@ -1939,27 +1941,27 @@
"@trackMetadata": {
"description": "Tab title - track metadata"
},
- "trackFileInfo": "File Info",
+ "trackFileInfo": "ファイル情報",
"@trackFileInfo": {
"description": "Tab title - file information"
},
- "trackLyrics": "Lyrics",
+ "trackLyrics": "歌詞",
"@trackLyrics": {
"description": "Tab title - lyrics"
},
- "trackFileNotFound": "File not found",
+ "trackFileNotFound": "ファイルがありません",
"@trackFileNotFound": {
"description": "Error - file doesn't exist"
},
- "trackOpenInDeezer": "Open in Deezer",
+ "trackOpenInDeezer": "Deezer で開く",
"@trackOpenInDeezer": {
"description": "Action - open track in Deezer app"
},
- "trackOpenInSpotify": "Open in Spotify",
+ "trackOpenInSpotify": "Spotify で開く",
"@trackOpenInSpotify": {
"description": "Action - open track in Spotify app"
},
- "trackTrackName": "Track name",
+ "trackTrackName": "トラック名",
"@trackTrackName": {
"description": "Metadata label - track title"
},
@@ -2131,11 +2133,11 @@
"@extensionDefaultProvider": {
"description": "Default search provider option"
},
- "extensionDefaultProviderSubtitle": "Use built-in search",
+ "extensionDefaultProviderSubtitle": "内蔵の検索を使用する",
"@extensionDefaultProviderSubtitle": {
"description": "Subtitle for default provider"
},
- "extensionAuthor": "Author",
+ "extensionAuthor": "作者",
"@extensionAuthor": {
"description": "Extension detail - author"
},
@@ -2143,7 +2145,7 @@
"@extensionId": {
"description": "Extension detail - unique ID"
},
- "extensionError": "Error",
+ "extensionError": "エラー",
"@extensionError": {
"description": "Extension detail - error message"
},
@@ -2183,19 +2185,19 @@
"@extensionSettings": {
"description": "Section header - extension settings"
},
- "extensionRemoveButton": "Remove Extension",
+ "extensionRemoveButton": "拡張を削除",
"@extensionRemoveButton": {
"description": "Button to uninstall extension"
},
- "extensionUpdated": "Updated",
+ "extensionUpdated": "更新済み",
"@extensionUpdated": {
"description": "Extension detail - last update"
},
- "extensionMinAppVersion": "Min App Version",
+ "extensionMinAppVersion": "最小のアプリバージョン",
"@extensionMinAppVersion": {
"description": "Extension detail - minimum app version"
},
- "extensionCustomTrackMatching": "Custom Track Matching",
+ "extensionCustomTrackMatching": "カスタムトラックマッチング",
"@extensionCustomTrackMatching": {
"description": "Capability - custom track matching algorithm"
},
@@ -2234,11 +2236,11 @@
"@extensionsProviderPrioritySection": {
"description": "Section header - provider priority"
},
- "extensionsInstalledSection": "Installed Extensions",
+ "extensionsInstalledSection": "インストール済みの拡張",
"@extensionsInstalledSection": {
"description": "Section header - installed extensions"
},
- "extensionsNoExtensions": "No extensions installed",
+ "extensionsNoExtensions": "拡張はインストールされていません",
"@extensionsNoExtensions": {
"description": "Empty state - no extensions"
},
@@ -2246,7 +2248,7 @@
"@extensionsNoExtensionsSubtitle": {
"description": "Empty state subtitle"
},
- "extensionsInstallButton": "Install Extension",
+ "extensionsInstallButton": "拡張をインストール",
"@extensionsInstallButton": {
"description": "Button to install extension from file"
},
@@ -2302,7 +2304,7 @@
"@extensionsErrorLoading": {
"description": "Error message when extension fails to load"
},
- "qualityFlacLossless": "FLAC Lossless",
+ "qualityFlacLossless": "FLAC ロスレス",
"@qualityFlacLossless": {
"description": "Quality option - CD quality FLAC"
},
@@ -2310,19 +2312,19 @@
"@qualityFlacLosslessSubtitle": {
"description": "Technical spec for lossless"
},
- "qualityHiResFlac": "Hi-Res FLAC",
+ "qualityHiResFlac": "ハイレゾ FLAC",
"@qualityHiResFlac": {
"description": "Quality option - high resolution FLAC"
},
- "qualityHiResFlacSubtitle": "24-bit / up to 96kHz",
+ "qualityHiResFlacSubtitle": "24-bit / 最大 96kHz",
"@qualityHiResFlacSubtitle": {
"description": "Technical spec for hi-res"
},
- "qualityHiResFlacMax": "Hi-Res FLAC Max",
+ "qualityHiResFlacMax": "ハイレゾ FLAC 最大",
"@qualityHiResFlacMax": {
"description": "Quality option - maximum resolution FLAC"
},
- "qualityHiResFlacMaxSubtitle": "24-bit / up to 192kHz",
+ "qualityHiResFlacMaxSubtitle": "24-bit / 最大 192kHz",
"@qualityHiResFlacMaxSubtitle": {
"description": "Technical spec for hi-res max"
},
@@ -2334,11 +2336,11 @@
"@downloadAskBeforeDownload": {
"description": "Setting - show quality picker"
},
- "downloadDirectory": "Download Directory",
+ "downloadDirectory": "ダウンロードディレクトリ",
"@downloadDirectory": {
"description": "Setting - download folder"
},
- "downloadSeparateSinglesFolder": "Separate Singles Folder",
+ "downloadSeparateSinglesFolder": "シングルのフォルダを分割",
"@downloadSeparateSinglesFolder": {
"description": "Setting - separate folder for singles"
},
@@ -2422,11 +2424,11 @@
"@serviceSpotify": {
"description": "Service name - DO NOT TRANSLATE"
},
- "appearanceAmoledDark": "AMOLED Dark",
+ "appearanceAmoledDark": "AMOLED ダーク",
"@appearanceAmoledDark": {
"description": "Theme option - pure black"
},
- "appearanceAmoledDarkSubtitle": "Pure black background",
+ "appearanceAmoledDarkSubtitle": "ピュアブラックの背景",
"@appearanceAmoledDarkSubtitle": {
"description": "Subtitle for AMOLED dark"
},
@@ -2434,15 +2436,15 @@
"@appearanceChooseAccentColor": {
"description": "Color picker dialog title"
},
- "appearanceChooseTheme": "Theme Mode",
+ "appearanceChooseTheme": "テーマモード",
"@appearanceChooseTheme": {
"description": "Theme picker dialog title"
},
- "queueTitle": "Download Queue",
+ "queueTitle": "ダウンロードキュー",
"@queueTitle": {
"description": "Queue screen title"
},
- "queueClearAll": "Clear All",
+ "queueClearAll": "すべて消去",
"@queueClearAll": {
"description": "Button - clear all queue items"
},
@@ -2573,5 +2575,41 @@
"utilityFunctions": "Utility Functions",
"@utilityFunctions": {
"description": "Extension capability - utility functions"
+ },
+ "recentTypeArtist": "Artist",
+ "@recentTypeArtist": {
+ "description": "Recent access item type - artist"
+ },
+ "recentTypeAlbum": "Album",
+ "@recentTypeAlbum": {
+ "description": "Recent access item type - album"
+ },
+ "recentTypeSong": "Song",
+ "@recentTypeSong": {
+ "description": "Recent access item type - song/track"
+ },
+ "recentTypePlaylist": "Playlist",
+ "@recentTypePlaylist": {
+ "description": "Recent access item type - playlist"
+ },
+ "recentPlaylistInfo": "Playlist: {name}",
+ "@recentPlaylistInfo": {
+ "description": "Snackbar message when tapping playlist in recent access",
+ "placeholders": {
+ "name": {
+ "type": "String",
+ "description": "Playlist name"
+ }
+ }
+ },
+ "errorGeneric": "Error: {message}",
+ "@errorGeneric": {
+ "description": "Generic error message format",
+ "placeholders": {
+ "message": {
+ "type": "String",
+ "description": "Error message"
+ }
+ }
}
}
\ No newline at end of file
diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb
index 08c0892d..bff3e1bf 100644
--- a/lib/l10n/arb/app_ru.arb
+++ b/lib/l10n/arb/app_ru.arb
@@ -5,35 +5,35 @@
"@appName": {
"description": "App name - DO NOT TRANSLATE"
},
- "appDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.",
+ "appDescription": "Скачайте треки Spotify в Lossless качестве из Tidal, Qobuz и Amazon Music.",
"@appDescription": {
"description": "App description shown in about page"
},
- "navHome": "Home",
+ "navHome": "Главная",
"@navHome": {
"description": "Bottom navigation - Home tab"
},
- "navHistory": "History",
+ "navHistory": "История",
"@navHistory": {
"description": "Bottom navigation - History tab"
},
- "navSettings": "Settings",
+ "navSettings": "Настройки",
"@navSettings": {
"description": "Bottom navigation - Settings tab"
},
- "navStore": "Store",
+ "navStore": "Магазин",
"@navStore": {
"description": "Bottom navigation - Extension store tab"
},
- "homeTitle": "Home",
+ "homeTitle": "Главная",
"@homeTitle": {
"description": "Home screen title"
},
- "homeSearchHint": "Paste Spotify URL or search...",
+ "homeSearchHint": "Вставьте URL Spotify или выполните поиск...",
"@homeSearchHint": {
"description": "Placeholder text in search box"
},
- "homeSearchHintExtension": "Search with {extensionName}...",
+ "homeSearchHintExtension": "Искать с помощью {extensionName}...",
"@homeSearchHintExtension": {
"description": "Placeholder when extension search is active",
"placeholders": {
@@ -43,23 +43,23 @@
}
}
},
- "homeSubtitle": "Paste a Spotify link or search by name",
+ "homeSubtitle": "Вставьте ссылку Spotify или ищите по названию",
"@homeSubtitle": {
"description": "Subtitle shown below search box"
},
- "homeSupports": "Supports: Track, Album, Playlist, Artist URLs",
+ "homeSupports": "Поддерживается: Трек, Альбом, Плейлист, URL исполнителя",
"@homeSupports": {
"description": "Info text about supported URL types"
},
- "homeRecent": "Recent",
+ "homeRecent": "Недавние",
"@homeRecent": {
"description": "Section header for recent searches"
},
- "historyTitle": "History",
+ "historyTitle": "История",
"@historyTitle": {
"description": "History screen title"
},
- "historyDownloading": "Downloading ({count})",
+ "historyDownloading": "Скачивание ({count})",
"@historyDownloading": {
"description": "Tab showing active downloads count",
"placeholders": {
@@ -69,23 +69,23 @@
}
}
},
- "historyDownloaded": "Downloaded",
+ "historyDownloaded": "Скачано",
"@historyDownloaded": {
"description": "Tab showing completed downloads"
},
- "historyFilterAll": "All",
+ "historyFilterAll": "Все",
"@historyFilterAll": {
"description": "Filter chip - show all items"
},
- "historyFilterAlbums": "Albums",
+ "historyFilterAlbums": "Альбомы",
"@historyFilterAlbums": {
"description": "Filter chip - show albums only"
},
- "historyFilterSingles": "Singles",
+ "historyFilterSingles": "Синглы",
"@historyFilterSingles": {
"description": "Filter chip - show singles only"
},
- "historyTracksCount": "{count, plural, =1{1 track} other{{count} tracks}}",
+ "historyTracksCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}",
"@historyTracksCount": {
"description": "Track count with plural form",
"placeholders": {
@@ -94,7 +94,7 @@
}
}
},
- "historyAlbumsCount": "{count, plural, =1{1 album} other{{count} albums}}",
+ "historyAlbumsCount": "{count, plural, one {{count} альбом} few {{count} альбома} many {{count} альбомов} =1 {1 альбом} other {{count} альбомов}}",
"@historyAlbumsCount": {
"description": "Album count with plural form",
"placeholders": {
@@ -103,107 +103,107 @@
}
}
},
- "historyNoDownloads": "No download history",
+ "historyNoDownloads": "Нет истории скачиваний",
"@historyNoDownloads": {
"description": "Empty state title"
},
- "historyNoDownloadsSubtitle": "Downloaded tracks will appear here",
+ "historyNoDownloadsSubtitle": "Скачанные треки появятся здесь",
"@historyNoDownloadsSubtitle": {
"description": "Empty state subtitle"
},
- "historyNoAlbums": "No album downloads",
+ "historyNoAlbums": "Нет скачанных альбомов",
"@historyNoAlbums": {
"description": "Empty state when filtering albums"
},
- "historyNoAlbumsSubtitle": "Download multiple tracks from an album to see them here",
+ "historyNoAlbumsSubtitle": "Скачайте несколько треков из альбома, чтобы увидеть их здесь",
"@historyNoAlbumsSubtitle": {
"description": "Empty state subtitle for albums filter"
},
- "historyNoSingles": "No single downloads",
+ "historyNoSingles": "Нет скачанных синглов",
"@historyNoSingles": {
"description": "Empty state when filtering singles"
},
- "historyNoSinglesSubtitle": "Single track downloads will appear here",
+ "historyNoSinglesSubtitle": "Здесь будут отображаться загрузки синглов",
"@historyNoSinglesSubtitle": {
"description": "Empty state subtitle for singles filter"
},
- "settingsTitle": "Settings",
+ "settingsTitle": "Настройки",
"@settingsTitle": {
"description": "Settings screen title"
},
- "settingsDownload": "Download",
+ "settingsDownload": "Скачивание",
"@settingsDownload": {
"description": "Settings section - download options"
},
- "settingsAppearance": "Appearance",
+ "settingsAppearance": "Внешний вид",
"@settingsAppearance": {
"description": "Settings section - visual customization"
},
- "settingsOptions": "Options",
+ "settingsOptions": "Опции",
"@settingsOptions": {
"description": "Settings section - app options"
},
- "settingsExtensions": "Extensions",
+ "settingsExtensions": "Расширения",
"@settingsExtensions": {
"description": "Settings section - extension management"
},
- "settingsAbout": "About",
+ "settingsAbout": "О программе",
"@settingsAbout": {
"description": "Settings section - app info"
},
- "downloadTitle": "Download",
+ "downloadTitle": "Скачивание",
"@downloadTitle": {
"description": "Download settings page title"
},
- "downloadLocation": "Download Location",
+ "downloadLocation": "Папка для скачивания",
"@downloadLocation": {
"description": "Setting for download folder"
},
- "downloadLocationSubtitle": "Choose where to save files",
+ "downloadLocationSubtitle": "Выберите, куда сохранить файлы",
"@downloadLocationSubtitle": {
"description": "Subtitle for download location"
},
- "downloadLocationDefault": "Default location",
+ "downloadLocationDefault": "Расположение по умолчанию",
"@downloadLocationDefault": {
"description": "Shown when using default folder"
},
- "downloadDefaultService": "Default Service",
+ "downloadDefaultService": "Сервис по умолчанию",
"@downloadDefaultService": {
"description": "Setting for preferred download service (Tidal/Qobuz/Amazon)"
},
- "downloadDefaultServiceSubtitle": "Service used for downloads",
+ "downloadDefaultServiceSubtitle": "Сервис, используемый для скачивания",
"@downloadDefaultServiceSubtitle": {
"description": "Subtitle for default service"
},
- "downloadDefaultQuality": "Default Quality",
+ "downloadDefaultQuality": "Качество по умолчанию",
"@downloadDefaultQuality": {
"description": "Setting for audio quality"
},
- "downloadAskQuality": "Ask Quality Before Download",
+ "downloadAskQuality": "Спрашивать качество перед скачиванием",
"@downloadAskQuality": {
"description": "Toggle to show quality picker"
},
- "downloadAskQualitySubtitle": "Show quality picker for each download",
+ "downloadAskQualitySubtitle": "Показывать выбор качества для каждого скачивания",
"@downloadAskQualitySubtitle": {
"description": "Subtitle for ask quality toggle"
},
- "downloadFilenameFormat": "Filename Format",
+ "downloadFilenameFormat": "Формат имени файла",
"@downloadFilenameFormat": {
"description": "Setting for output filename pattern"
},
- "downloadFolderOrganization": "Folder Organization",
+ "downloadFolderOrganization": "Организация папок",
"@downloadFolderOrganization": {
"description": "Setting for folder structure"
},
- "downloadSeparateSingles": "Separate Singles",
+ "downloadSeparateSingles": "Разделять синглы",
"@downloadSeparateSingles": {
"description": "Toggle to separate single tracks"
},
- "downloadSeparateSinglesSubtitle": "Put single tracks in a separate folder",
+ "downloadSeparateSinglesSubtitle": "Помещать синглы в отдельную папку",
"@downloadSeparateSinglesSubtitle": {
"description": "Subtitle for separate singles toggle"
},
- "qualityBest": "Best Available",
+ "qualityBest": "Лучшее из доступных",
"@qualityBest": {
"description": "Audio quality option - highest available"
},
@@ -211,75 +211,75 @@
"@qualityFlac": {
"description": "Audio quality option - FLAC lossless"
},
- "quality320": "320 kbps",
+ "quality320": "320 кбит/с",
"@quality320": {
"description": "Audio quality option - 320kbps MP3"
},
- "quality128": "128 kbps",
+ "quality128": "128 кбит/с",
"@quality128": {
"description": "Audio quality option - 128kbps MP3"
},
- "appearanceTitle": "Appearance",
+ "appearanceTitle": "Внешний вид",
"@appearanceTitle": {
"description": "Appearance settings page title"
},
- "appearanceTheme": "Theme",
+ "appearanceTheme": "Тема",
"@appearanceTheme": {
"description": "Theme mode setting"
},
- "appearanceThemeSystem": "System",
+ "appearanceThemeSystem": "Системная",
"@appearanceThemeSystem": {
"description": "Follow system theme"
},
- "appearanceThemeLight": "Light",
+ "appearanceThemeLight": "Светлая",
"@appearanceThemeLight": {
"description": "Light theme"
},
- "appearanceThemeDark": "Dark",
+ "appearanceThemeDark": "Тёмная",
"@appearanceThemeDark": {
"description": "Dark theme"
},
- "appearanceDynamicColor": "Dynamic Color",
+ "appearanceDynamicColor": "Динамический цвет",
"@appearanceDynamicColor": {
"description": "Material You dynamic colors"
},
- "appearanceDynamicColorSubtitle": "Use colors from your wallpaper",
+ "appearanceDynamicColorSubtitle": "Использовать цвета из ваших обоев",
"@appearanceDynamicColorSubtitle": {
"description": "Subtitle for dynamic color"
},
- "appearanceAccentColor": "Accent Color",
+ "appearanceAccentColor": "Акцентный цвет",
"@appearanceAccentColor": {
"description": "Custom accent color picker"
},
- "appearanceHistoryView": "History View",
+ "appearanceHistoryView": "Отображение истории",
"@appearanceHistoryView": {
"description": "Layout style for history"
},
- "appearanceHistoryViewList": "List",
+ "appearanceHistoryViewList": "Список",
"@appearanceHistoryViewList": {
"description": "List layout option"
},
- "appearanceHistoryViewGrid": "Grid",
+ "appearanceHistoryViewGrid": "Сетка",
"@appearanceHistoryViewGrid": {
"description": "Grid layout option"
},
- "optionsTitle": "Options",
+ "optionsTitle": "Опции",
"@optionsTitle": {
"description": "Options settings page title"
},
- "optionsSearchSource": "Search Source",
+ "optionsSearchSource": "Поиск источника",
"@optionsSearchSource": {
"description": "Section for search provider settings"
},
- "optionsPrimaryProvider": "Primary Provider",
+ "optionsPrimaryProvider": "Основной провайдер",
"@optionsPrimaryProvider": {
"description": "Main search provider setting"
},
- "optionsPrimaryProviderSubtitle": "Service used when searching by track name.",
+ "optionsPrimaryProviderSubtitle": "Сервис, используемый при поиске по названию трека.",
"@optionsPrimaryProviderSubtitle": {
"description": "Subtitle for primary provider"
},
- "optionsUsingExtension": "Using extension: {extensionName}",
+ "optionsUsingExtension": "Используется расширение: {extensionName}",
"@optionsUsingExtension": {
"description": "Shows active extension name",
"placeholders": {
@@ -288,55 +288,55 @@
}
}
},
- "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension",
+ "optionsSwitchBack": "Нажмите Deezer или Spotify для возврата с расширения",
"@optionsSwitchBack": {
"description": "Hint to switch back to built-in providers"
},
- "optionsAutoFallback": "Auto Fallback",
+ "optionsAutoFallback": "Автоматический переход",
"@optionsAutoFallback": {
"description": "Auto-retry with other services"
},
- "optionsAutoFallbackSubtitle": "Try other services if download fails",
+ "optionsAutoFallbackSubtitle": "Попробовать другие сервисы при сбое загрузки",
"@optionsAutoFallbackSubtitle": {
"description": "Subtitle for auto fallback"
},
- "optionsUseExtensionProviders": "Use Extension Providers",
+ "optionsUseExtensionProviders": "Использовать провайдера расширений",
"@optionsUseExtensionProviders": {
"description": "Enable extension download providers"
},
- "optionsUseExtensionProvidersOn": "Extensions will be tried first",
+ "optionsUseExtensionProvidersOn": "Сначала будут опробованы расширения",
"@optionsUseExtensionProvidersOn": {
"description": "Status when extension providers enabled"
},
- "optionsUseExtensionProvidersOff": "Using built-in providers only",
+ "optionsUseExtensionProvidersOff": "Использование только встроенных провайдеров",
"@optionsUseExtensionProvidersOff": {
"description": "Status when extension providers disabled"
},
- "optionsEmbedLyrics": "Embed Lyrics",
+ "optionsEmbedLyrics": "Вставить текст песни",
"@optionsEmbedLyrics": {
"description": "Embed lyrics in audio files"
},
- "optionsEmbedLyricsSubtitle": "Embed synced lyrics into FLAC files",
+ "optionsEmbedLyricsSubtitle": "Вставить синхронизированные тексты в FLAC файлы",
"@optionsEmbedLyricsSubtitle": {
"description": "Subtitle for embed lyrics"
},
- "optionsMaxQualityCover": "Max Quality Cover",
+ "optionsMaxQualityCover": "Максимальное качество обложки",
"@optionsMaxQualityCover": {
"description": "Download highest quality album art"
},
- "optionsMaxQualityCoverSubtitle": "Download highest resolution cover art",
+ "optionsMaxQualityCoverSubtitle": "Скачивать обложку в макс. разрешении",
"@optionsMaxQualityCoverSubtitle": {
"description": "Subtitle for max quality cover"
},
- "optionsConcurrentDownloads": "Concurrent Downloads",
+ "optionsConcurrentDownloads": "Одновременные загрузки",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
- "optionsConcurrentSequential": "Sequential (1 at a time)",
+ "optionsConcurrentSequential": "Последовательно (1 за раз)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
- "optionsConcurrentParallel": "{count} parallel downloads",
+ "optionsConcurrentParallel": "{count} параллельных загрузок",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
@@ -345,63 +345,63 @@
}
}
},
- "optionsConcurrentWarning": "Parallel downloads may trigger rate limiting",
+ "optionsConcurrentWarning": "Параллельные загрузки могут вызвать ограничение скорости",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
- "optionsExtensionStore": "Extension Store",
+ "optionsExtensionStore": "Магазин расширений",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
},
- "optionsExtensionStoreSubtitle": "Show Store tab in navigation",
+ "optionsExtensionStoreSubtitle": "Показывать вкладку Магазин в гл. меню",
"@optionsExtensionStoreSubtitle": {
"description": "Subtitle for extension store toggle"
},
- "optionsCheckUpdates": "Check for Updates",
+ "optionsCheckUpdates": "Проверить обновления",
"@optionsCheckUpdates": {
"description": "Auto update check toggle"
},
- "optionsCheckUpdatesSubtitle": "Notify when new version is available",
+ "optionsCheckUpdatesSubtitle": "Уведомлять о наличии новой версии",
"@optionsCheckUpdatesSubtitle": {
"description": "Subtitle for update check"
},
- "optionsUpdateChannel": "Update Channel",
+ "optionsUpdateChannel": "Канал обновлений",
"@optionsUpdateChannel": {
"description": "Stable vs preview releases"
},
- "optionsUpdateChannelStable": "Stable releases only",
+ "optionsUpdateChannelStable": "Только стабильные релизы",
"@optionsUpdateChannelStable": {
"description": "Only stable updates"
},
- "optionsUpdateChannelPreview": "Get preview releases",
+ "optionsUpdateChannelPreview": "Предварительные версии",
"@optionsUpdateChannelPreview": {
"description": "Include beta/preview updates"
},
- "optionsUpdateChannelWarning": "Preview may contain bugs or incomplete features",
+ "optionsUpdateChannelWarning": "Предварительная версия может содержать ошибки или неполные функции",
"@optionsUpdateChannelWarning": {
"description": "Warning about preview channel"
},
- "optionsClearHistory": "Clear Download History",
+ "optionsClearHistory": "Очистить историю загрузок",
"@optionsClearHistory": {
"description": "Delete all download history"
},
- "optionsClearHistorySubtitle": "Remove all downloaded tracks from history",
+ "optionsClearHistorySubtitle": "Удалить все скачанные треки из истории",
"@optionsClearHistorySubtitle": {
"description": "Subtitle for clear history"
},
- "optionsDetailedLogging": "Detailed Logging",
+ "optionsDetailedLogging": "Подробный лог",
"@optionsDetailedLogging": {
"description": "Enable verbose logs for debugging"
},
- "optionsDetailedLoggingOn": "Detailed logs are being recorded",
+ "optionsDetailedLoggingOn": "Ведутся подробные логи",
"@optionsDetailedLoggingOn": {
"description": "Status when logging enabled"
},
- "optionsDetailedLoggingOff": "Enable for bug reports",
+ "optionsDetailedLoggingOff": "Включить для отчётов об ошибках",
"@optionsDetailedLoggingOff": {
"description": "Status when logging disabled"
},
- "optionsSpotifyCredentials": "Spotify Credentials",
+ "optionsSpotifyCredentials": "Учётные данные Spotify",
"@optionsSpotifyCredentials": {
"description": "Spotify API credentials setting"
},
@@ -414,39 +414,39 @@
}
}
},
- "optionsSpotifyCredentialsRequired": "Required - tap to configure",
+ "optionsSpotifyCredentialsRequired": "Необходимо - нажмите для настройки",
"@optionsSpotifyCredentialsRequired": {
"description": "Prompt to set up credentials"
},
- "optionsSpotifyWarning": "Spotify requires your own API credentials. Get them free from developer.spotify.com",
+ "optionsSpotifyWarning": "Spotify требует ваши собственные учетные данные API. Получите их бесплатно на сайте developer.spotify.com",
"@optionsSpotifyWarning": {
"description": "Info about Spotify API requirement"
},
- "extensionsTitle": "Extensions",
+ "extensionsTitle": "Расширения",
"@extensionsTitle": {
"description": "Extensions page title"
},
- "extensionsInstalled": "Installed Extensions",
+ "extensionsInstalled": "Установленные расширения",
"@extensionsInstalled": {
"description": "Section header for installed extensions"
},
- "extensionsNone": "No extensions installed",
+ "extensionsNone": "Нет установленных расширений",
"@extensionsNone": {
"description": "Empty state title"
},
- "extensionsNoneSubtitle": "Install extensions from the Store tab",
+ "extensionsNoneSubtitle": "Установка расширений из вкладки Магазин",
"@extensionsNoneSubtitle": {
"description": "Empty state subtitle"
},
- "extensionsEnabled": "Enabled",
+ "extensionsEnabled": "Включено",
"@extensionsEnabled": {
"description": "Extension status - active"
},
- "extensionsDisabled": "Disabled",
+ "extensionsDisabled": "Выключено",
"@extensionsDisabled": {
"description": "Extension status - inactive"
},
- "extensionsVersion": "Version {version}",
+ "extensionsVersion": "Версия {version}",
"@extensionsVersion": {
"description": "Extension version display",
"placeholders": {
@@ -455,7 +455,7 @@
}
}
},
- "extensionsAuthor": "by {author}",
+ "extensionsAuthor": "от {author}",
"@extensionsAuthor": {
"description": "Extension author credit",
"placeholders": {
@@ -464,111 +464,111 @@
}
}
},
- "extensionsUninstall": "Uninstall",
+ "extensionsUninstall": "Удалить",
"@extensionsUninstall": {
"description": "Uninstall extension button"
},
- "extensionsSetAsSearch": "Set as Search Provider",
+ "extensionsSetAsSearch": "Установить в качестве поисковой системы",
"@extensionsSetAsSearch": {
"description": "Use extension for search"
},
- "storeTitle": "Extension Store",
+ "storeTitle": "Магазин расширений",
"@storeTitle": {
"description": "Store screen title"
},
- "storeSearch": "Search extensions...",
+ "storeSearch": "Поиск расширений...",
"@storeSearch": {
"description": "Store search placeholder"
},
- "storeInstall": "Install",
+ "storeInstall": "Установить",
"@storeInstall": {
"description": "Install extension button"
},
- "storeInstalled": "Installed",
+ "storeInstalled": "Установлено",
"@storeInstalled": {
"description": "Already installed badge"
},
- "storeUpdate": "Update",
+ "storeUpdate": "Обновить",
"@storeUpdate": {
"description": "Update available button"
},
- "aboutTitle": "About",
+ "aboutTitle": "О программе",
"@aboutTitle": {
"description": "About page title"
},
- "aboutContributors": "Contributors",
+ "aboutContributors": "Участники",
"@aboutContributors": {
"description": "Section for contributors"
},
- "aboutMobileDeveloper": "Mobile version developer",
+ "aboutMobileDeveloper": "Разработчик мобильной версии",
"@aboutMobileDeveloper": {
"description": "Role description for mobile dev"
},
- "aboutOriginalCreator": "Creator of the original SpotiFLAC",
+ "aboutOriginalCreator": "Создатель оригинального SpotiFLAC",
"@aboutOriginalCreator": {
"description": "Role description for original creator"
},
- "aboutLogoArtist": "The talented artist who created our beautiful app logo!",
+ "aboutLogoArtist": "Талантливый художник, который создал наш красивый логотип приложения!",
"@aboutLogoArtist": {
"description": "Role description for logo artist"
},
- "aboutSpecialThanks": "Special Thanks",
+ "aboutSpecialThanks": "Особая благодарность",
"@aboutSpecialThanks": {
"description": "Section for special thanks"
},
- "aboutLinks": "Links",
+ "aboutLinks": "Ссылки",
"@aboutLinks": {
"description": "Section for external links"
},
- "aboutMobileSource": "Mobile source code",
+ "aboutMobileSource": "Исходный код мобильной версии",
"@aboutMobileSource": {
"description": "Link to mobile GitHub repo"
},
- "aboutPCSource": "PC source code",
+ "aboutPCSource": "Исходный код ПК версии",
"@aboutPCSource": {
"description": "Link to PC GitHub repo"
},
- "aboutReportIssue": "Report an issue",
+ "aboutReportIssue": "Сообщить о проблеме",
"@aboutReportIssue": {
"description": "Link to report bugs"
},
- "aboutReportIssueSubtitle": "Report any problems you encounter",
+ "aboutReportIssueSubtitle": "Сообщите о возникших проблемах",
"@aboutReportIssueSubtitle": {
"description": "Subtitle for report issue"
},
- "aboutFeatureRequest": "Feature request",
+ "aboutFeatureRequest": "Предложить новую функцию",
"@aboutFeatureRequest": {
"description": "Link to suggest features"
},
- "aboutFeatureRequestSubtitle": "Suggest new features for the app",
+ "aboutFeatureRequestSubtitle": "Предложить новые функции для приложения",
"@aboutFeatureRequestSubtitle": {
"description": "Subtitle for feature request"
},
- "aboutSupport": "Support",
+ "aboutSupport": "Поддержка",
"@aboutSupport": {
"description": "Section for support/donation links"
},
- "aboutBuyMeCoffee": "Buy me a coffee",
+ "aboutBuyMeCoffee": "Купить мне кофе",
"@aboutBuyMeCoffee": {
"description": "Donation link"
},
- "aboutBuyMeCoffeeSubtitle": "Support development on Ko-fi",
+ "aboutBuyMeCoffeeSubtitle": "Поддержать разработку на Ko-fi",
"@aboutBuyMeCoffeeSubtitle": {
"description": "Subtitle for donation"
},
- "aboutApp": "App",
+ "aboutApp": "Приложение",
"@aboutApp": {
"description": "Section for app info"
},
- "aboutVersion": "Version",
+ "aboutVersion": "Версия",
"@aboutVersion": {
"description": "Version info label"
},
- "aboutBinimumDesc": "The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn't exist!",
+ "aboutBinimumDesc": "Создатель QQDL & HiFi API. Без этого API загрузки Tidal не существовали бы!",
"@aboutBinimumDesc": {
"description": "Credit description for binimum"
},
- "aboutSachinsenalDesc": "The original HiFi project creator. The foundation of Tidal integration!",
+ "aboutSachinsenalDesc": "Оригинальный создатель проекта HiFi. Основатель Tidal интеграции!",
"@aboutSachinsenalDesc": {
"description": "Credit description for sachinsenal0x64"
},
@@ -576,7 +576,7 @@
"@aboutDoubleDouble": {
"description": "Name of Amazon API service - DO NOT TRANSLATE"
},
- "aboutDoubleDoubleDesc": "Amazing API for Amazon Music downloads. Thank you for making it free!",
+ "aboutDoubleDoubleDesc": "Удивительный API для загрузок Amazon Music. Спасибо за то, что сделали это бесплатно!",
"@aboutDoubleDoubleDesc": {
"description": "Credit for DoubleDouble API"
},
@@ -584,19 +584,19 @@
"@aboutDabMusic": {
"description": "Name of Qobuz API service - DO NOT TRANSLATE"
},
- "aboutDabMusicDesc": "The best Qobuz streaming API. Hi-Res downloads wouldn't be possible without this!",
+ "aboutDabMusicDesc": "Лучший API для стриминга Qobuz. Без него загрузка файлов в высоком разрешении была бы невозможна!",
"@aboutDabMusicDesc": {
"description": "Credit for DAB Music API"
},
- "aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.",
+ "aboutAppDescription": "Скачайте треки Spotify в Lossless качестве из Tidal, Qobuz и Amazon Music.",
"@aboutAppDescription": {
"description": "App description in header card"
},
- "albumTitle": "Album",
+ "albumTitle": "Альбом",
"@albumTitle": {
"description": "Album screen title"
},
- "albumTracks": "{count, plural, =1{1 track} other{{count} tracks}}",
+ "albumTracks": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}",
"@albumTracks": {
"description": "Album track count",
"placeholders": {
@@ -605,35 +605,35 @@
}
}
},
- "albumDownloadAll": "Download All",
+ "albumDownloadAll": "Скачать всё",
"@albumDownloadAll": {
"description": "Button to download all tracks"
},
- "albumDownloadRemaining": "Download Remaining",
+ "albumDownloadRemaining": "Скачать оставшиеся",
"@albumDownloadRemaining": {
"description": "Button to download remaining tracks"
},
- "playlistTitle": "Playlist",
+ "playlistTitle": "Плейлист",
"@playlistTitle": {
"description": "Playlist screen title"
},
- "artistTitle": "Artist",
+ "artistTitle": "Исполнитель",
"@artistTitle": {
"description": "Artist screen title"
},
- "artistAlbums": "Albums",
+ "artistAlbums": "Альбомы",
"@artistAlbums": {
"description": "Section header for artist albums"
},
- "artistSingles": "Singles & EPs",
+ "artistSingles": "Синглы и EP",
"@artistSingles": {
"description": "Section header for singles/EPs"
},
- "artistCompilations": "Compilations",
+ "artistCompilations": "Сборники",
"@artistCompilations": {
"description": "Section header for compilations"
},
- "artistReleases": "{count, plural, =1{1 release} other{{count} releases}}",
+ "artistReleases": "{count, plural, one {{count} релиз} few {{count} релиза} many {{count} релизов} =1 {1 релиз} other {{count} релизов}}",
"@artistReleases": {
"description": "Artist release count",
"placeholders": {
@@ -642,123 +642,137 @@
}
}
},
- "trackMetadataTitle": "Track Info",
+ "artistPopular": "Популярное",
+ "@artistPopular": {
+ "description": "Section header for popular/top tracks"
+ },
+ "artistMonthlyListeners": "{count} слушателей в месяц",
+ "@artistMonthlyListeners": {
+ "description": "Monthly listener count display",
+ "placeholders": {
+ "count": {
+ "type": "String",
+ "description": "Formatted listener count"
+ }
+ }
+ },
+ "trackMetadataTitle": "Информация о треке",
"@trackMetadataTitle": {
"description": "Track metadata screen title"
},
- "trackMetadataArtist": "Artist",
+ "trackMetadataArtist": "Исполнитель",
"@trackMetadataArtist": {
"description": "Metadata field - artist name"
},
- "trackMetadataAlbum": "Album",
+ "trackMetadataAlbum": "Альбом",
"@trackMetadataAlbum": {
"description": "Metadata field - album name"
},
- "trackMetadataDuration": "Duration",
+ "trackMetadataDuration": "Продолжительность",
"@trackMetadataDuration": {
"description": "Metadata field - track length"
},
- "trackMetadataQuality": "Quality",
+ "trackMetadataQuality": "Качество",
"@trackMetadataQuality": {
"description": "Metadata field - audio quality"
},
- "trackMetadataPath": "File Path",
+ "trackMetadataPath": "Путь к файлу",
"@trackMetadataPath": {
"description": "Metadata field - file location"
},
- "trackMetadataDownloadedAt": "Downloaded",
+ "trackMetadataDownloadedAt": "Скачано",
"@trackMetadataDownloadedAt": {
"description": "Metadata field - download date"
},
- "trackMetadataService": "Service",
+ "trackMetadataService": "Сервис",
"@trackMetadataService": {
"description": "Metadata field - download service used"
},
- "trackMetadataPlay": "Play",
+ "trackMetadataPlay": "Воспроизвести",
"@trackMetadataPlay": {
"description": "Action button - play track"
},
- "trackMetadataShare": "Share",
+ "trackMetadataShare": "Поделиться",
"@trackMetadataShare": {
"description": "Action button - share track"
},
- "trackMetadataDelete": "Delete",
+ "trackMetadataDelete": "Удалить",
"@trackMetadataDelete": {
"description": "Action button - delete track"
},
- "trackMetadataRedownload": "Re-download",
+ "trackMetadataRedownload": "Скачать снова",
"@trackMetadataRedownload": {
"description": "Action button - download again"
},
- "trackMetadataOpenFolder": "Open Folder",
+ "trackMetadataOpenFolder": "Открыть папку",
"@trackMetadataOpenFolder": {
"description": "Action button - open containing folder"
},
- "setupTitle": "Welcome to SpotiFLAC",
+ "setupTitle": "Добро пожаловать в SpotiFLAC",
"@setupTitle": {
"description": "Setup wizard title"
},
- "setupSubtitle": "Let's get you started",
+ "setupSubtitle": "Давайте начнем",
"@setupSubtitle": {
"description": "Setup wizard subtitle"
},
- "setupStoragePermission": "Storage Permission",
+ "setupStoragePermission": "Доступ к хранилищу",
"@setupStoragePermission": {
"description": "Storage permission step title"
},
- "setupStoragePermissionSubtitle": "Required to save downloaded files",
+ "setupStoragePermissionSubtitle": "Необходимо для сохранения загруженных файлов",
"@setupStoragePermissionSubtitle": {
"description": "Explanation for storage permission"
},
- "setupStoragePermissionGranted": "Permission granted",
+ "setupStoragePermissionGranted": "Разрешение предоставлено",
"@setupStoragePermissionGranted": {
"description": "Status when permission granted"
},
- "setupStoragePermissionDenied": "Permission denied",
+ "setupStoragePermissionDenied": "Разрешение не предоставлено",
"@setupStoragePermissionDenied": {
"description": "Status when permission denied"
},
- "setupGrantPermission": "Grant Permission",
+ "setupGrantPermission": "Предоставить разрешение",
"@setupGrantPermission": {
"description": "Button to request permission"
},
- "setupDownloadLocation": "Download Location",
+ "setupDownloadLocation": "Папка для скачивания",
"@setupDownloadLocation": {
"description": "Download folder step title"
},
- "setupChooseFolder": "Choose Folder",
+ "setupChooseFolder": "Выбрать папку",
"@setupChooseFolder": {
"description": "Button to pick folder"
},
- "setupContinue": "Continue",
+ "setupContinue": "Продолжить",
"@setupContinue": {
"description": "Continue to next step button"
},
- "setupSkip": "Skip for now",
+ "setupSkip": "Пропустить",
"@setupSkip": {
"description": "Skip current step button"
},
- "setupStorageAccessRequired": "Storage Access Required",
+ "setupStorageAccessRequired": "Требуется доступ к хранилищу",
"@setupStorageAccessRequired": {
"description": "Title when storage access needed"
},
- "setupStorageAccessMessage": "SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.",
+ "setupStorageAccessMessage": "SpotiFLAC требуется разрешение \"Доступ ко всем файлам\" для сохранения музыкальных файлов в выбранную папку.",
"@setupStorageAccessMessage": {
"description": "Explanation for storage access"
},
- "setupStorageAccessMessageAndroid11": "Android 11+ requires \"All files access\" permission to save files to your chosen download folder.",
+ "setupStorageAccessMessageAndroid11": "Для Android 11+ требуется разрешение \"Доступ ко всем файлам\" для сохранения файлов в выбранную вами папку загрузки.",
"@setupStorageAccessMessageAndroid11": {
"description": "Android 11+ specific explanation"
},
- "setupOpenSettings": "Open Settings",
+ "setupOpenSettings": "Открыть настройки",
"@setupOpenSettings": {
"description": "Button to open system settings"
},
- "setupPermissionDeniedMessage": "Permission denied. Please grant all permissions to continue.",
+ "setupPermissionDeniedMessage": "В разрешении отказано. Пожалуйста, предоставьте все разрешения для продолжения.",
"@setupPermissionDeniedMessage": {
"description": "Error when permission denied"
},
- "setupPermissionRequired": "{permissionType} Permission Required",
+ "setupPermissionRequired": "Требуется разрешение {permissionType}",
"@setupPermissionRequired": {
"description": "Generic permission required title",
"placeholders": {
@@ -768,7 +782,7 @@
}
}
},
- "setupPermissionRequiredMessage": "{permissionType} permission is required for the best experience. You can change this later in Settings.",
+ "setupPermissionRequiredMessage": "Для оптимальной работы требуется разрешение {permissionType}. Вы можете изменить это позже в настройках.",
"@setupPermissionRequiredMessage": {
"description": "Generic permission required message",
"placeholders": {
@@ -777,63 +791,63 @@
}
}
},
- "setupSelectDownloadFolder": "Select Download Folder",
+ "setupSelectDownloadFolder": "Выбрать папку для скачивания",
"@setupSelectDownloadFolder": {
"description": "Folder selection step title"
},
- "setupUseDefaultFolder": "Use Default Folder?",
+ "setupUseDefaultFolder": "Использовать папку по умолчанию?",
"@setupUseDefaultFolder": {
"description": "Dialog title for default folder"
},
- "setupNoFolderSelected": "No folder selected. Would you like to use the default Music folder?",
+ "setupNoFolderSelected": "Папка не выбрана. Хотите использовать папку Музыка по умолчанию?",
"@setupNoFolderSelected": {
"description": "Prompt when no folder selected"
},
- "setupUseDefault": "Use Default",
+ "setupUseDefault": "По умолчанию",
"@setupUseDefault": {
"description": "Button to use default folder"
},
- "setupDownloadLocationTitle": "Download Location",
+ "setupDownloadLocationTitle": "Папка для скачивания",
"@setupDownloadLocationTitle": {
"description": "Download location dialog title"
},
- "setupDownloadLocationIosMessage": "On iOS, downloads are saved to the app's Documents folder. You can access them via the Files app.",
+ "setupDownloadLocationIosMessage": "В iOS загрузки сохраняются в папке Документы приложения. Вы можете получить к ним доступ через приложение Файлы.",
"@setupDownloadLocationIosMessage": {
"description": "iOS-specific folder info"
},
- "setupAppDocumentsFolder": "App Documents Folder",
+ "setupAppDocumentsFolder": "Папка Документы приложения",
"@setupAppDocumentsFolder": {
"description": "iOS documents folder option"
},
- "setupAppDocumentsFolderSubtitle": "Recommended - accessible via Files app",
+ "setupAppDocumentsFolderSubtitle": "Рекомендуется - доступ через Файлы",
"@setupAppDocumentsFolderSubtitle": {
"description": "Subtitle for documents folder"
},
- "setupChooseFromFiles": "Choose from Files",
+ "setupChooseFromFiles": "Выбрать из файлов",
"@setupChooseFromFiles": {
"description": "iOS file picker option"
},
- "setupChooseFromFilesSubtitle": "Select iCloud or other location",
+ "setupChooseFromFilesSubtitle": "Выберите iCloud или другое местоположение",
"@setupChooseFromFilesSubtitle": {
"description": "Subtitle for file picker"
},
- "setupIosEmptyFolderWarning": "iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.",
+ "setupIosEmptyFolderWarning": "Ограничение iOS: пустые папки не могут быть выбраны. Выберите папку, содержащую хотя бы один файл.",
"@setupIosEmptyFolderWarning": {
"description": "iOS folder selection warning"
},
- "setupDownloadInFlac": "Download Spotify tracks in FLAC",
+ "setupDownloadInFlac": "Скачать Spotify треки во FLAC",
"@setupDownloadInFlac": {
"description": "App tagline in setup"
},
- "setupStepStorage": "Storage",
+ "setupStepStorage": "Хранилище",
"@setupStepStorage": {
"description": "Setup step indicator - storage"
},
- "setupStepNotification": "Notification",
+ "setupStepNotification": "Уведомления",
"@setupStepNotification": {
"description": "Setup step indicator - notification"
},
- "setupStepFolder": "Folder",
+ "setupStepFolder": "Папка",
"@setupStepFolder": {
"description": "Setup step indicator - folder"
},
@@ -841,239 +855,239 @@
"@setupStepSpotify": {
"description": "Setup step indicator - Spotify API"
},
- "setupStepPermission": "Permission",
+ "setupStepPermission": "Разрешение",
"@setupStepPermission": {
"description": "Setup step indicator - permission"
},
- "setupStorageGranted": "Storage Permission Granted!",
+ "setupStorageGranted": "Доступ к хранилищу предоставлен!",
"@setupStorageGranted": {
"description": "Success message for storage permission"
},
- "setupStorageRequired": "Storage Permission Required",
+ "setupStorageRequired": "Требуется доступ к хранилищу",
"@setupStorageRequired": {
"description": "Title when storage permission needed"
},
- "setupStorageDescription": "SpotiFLAC needs storage permission to save your downloaded music files.",
+ "setupStorageDescription": "SpotiFLAC требуется разрешение на хранение для сохранения скачанных файлов.",
"@setupStorageDescription": {
"description": "Explanation for storage permission"
},
- "setupNotificationGranted": "Notification Permission Granted!",
+ "setupNotificationGranted": "Разрешение на уведомление предоставлено!",
"@setupNotificationGranted": {
"description": "Success message for notification permission"
},
- "setupNotificationEnable": "Enable Notifications",
+ "setupNotificationEnable": "Включить уведомления",
"@setupNotificationEnable": {
"description": "Button to enable notifications"
},
- "setupNotificationDescription": "Get notified when downloads complete or require attention.",
+ "setupNotificationDescription": "Получайте уведомления о завершении загрузки или о необходимости привлечения внимания.",
"@setupNotificationDescription": {
"description": "Explanation for notifications"
},
- "setupFolderSelected": "Download Folder Selected!",
+ "setupFolderSelected": "Папка для загрузки выбрана!",
"@setupFolderSelected": {
"description": "Success message for folder selection"
},
- "setupFolderChoose": "Choose Download Folder",
+ "setupFolderChoose": "Выбрать папку для скачивания",
"@setupFolderChoose": {
"description": "Button to choose folder"
},
- "setupFolderDescription": "Select a folder where your downloaded music will be saved.",
+ "setupFolderDescription": "Выберите папку, в которой будет сохраняться скачанная музыка.",
"@setupFolderDescription": {
"description": "Explanation for folder selection"
},
- "setupChangeFolder": "Change Folder",
+ "setupChangeFolder": "Сменить папку",
"@setupChangeFolder": {
"description": "Button to change selected folder"
},
- "setupSelectFolder": "Select Folder",
+ "setupSelectFolder": "Выбрать папку",
"@setupSelectFolder": {
"description": "Button to select folder"
},
- "setupSpotifyApiOptional": "Spotify API (Optional)",
+ "setupSpotifyApiOptional": "Spotify API (необязательно)",
"@setupSpotifyApiOptional": {
"description": "Spotify API step title"
},
- "setupSpotifyApiDescription": "Add your Spotify API credentials for better search results and access to Spotify-exclusive content.",
+ "setupSpotifyApiDescription": "Добавьте свои учётные данные Spotify для улучшения результатов поиска и доступа к эксклюзивному контенту Spotify.",
"@setupSpotifyApiDescription": {
"description": "Explanation for Spotify API"
},
- "setupUseSpotifyApi": "Use Spotify API",
+ "setupUseSpotifyApi": "Использовать Spotify API",
"@setupUseSpotifyApi": {
"description": "Toggle to enable Spotify API"
},
- "setupEnterCredentialsBelow": "Enter your credentials below",
+ "setupEnterCredentialsBelow": "Введите ваши учётные данные ниже",
"@setupEnterCredentialsBelow": {
"description": "Prompt to enter credentials"
},
- "setupUsingDeezer": "Using Deezer (no account needed)",
+ "setupUsingDeezer": "Использование Deezer (аккаунт не требуется)",
"@setupUsingDeezer": {
"description": "Status when using Deezer"
},
- "setupEnterClientId": "Enter Spotify Client ID",
+ "setupEnterClientId": "Введите Client ID Spotify",
"@setupEnterClientId": {
"description": "Placeholder for client ID field"
},
- "setupEnterClientSecret": "Enter Spotify Client Secret",
+ "setupEnterClientSecret": "Введите Spotify Client Secret",
"@setupEnterClientSecret": {
"description": "Placeholder for client secret field"
},
- "setupGetFreeCredentials": "Get your free API credentials from the Spotify Developer Dashboard.",
+ "setupGetFreeCredentials": "Получите бесплатный API учётной записи на панели разработчика Spotify.",
"@setupGetFreeCredentials": {
"description": "Info about getting Spotify credentials"
},
- "setupEnableNotifications": "Enable Notifications",
+ "setupEnableNotifications": "Включить уведомления",
"@setupEnableNotifications": {
"description": "Button to enable notifications"
},
- "setupProceedToNextStep": "You can now proceed to the next step.",
+ "setupProceedToNextStep": "Теперь вы можете перейти к следующему шагу.",
"@setupProceedToNextStep": {
"description": "Message after completing a step"
},
- "setupNotificationProgressDescription": "You will receive download progress notifications.",
+ "setupNotificationProgressDescription": "Вы будете получать уведомления о ходе загрузки.",
"@setupNotificationProgressDescription": {
"description": "Info about notification usage"
},
- "setupNotificationBackgroundDescription": "Get notified about download progress and completion. This helps you track downloads when the app is in background.",
+ "setupNotificationBackgroundDescription": "Получайте уведомления о ходе и завершении загрузки. Это поможет вам отслеживать загрузки, когда приложение находится в фоновом режиме.",
"@setupNotificationBackgroundDescription": {
"description": "Detailed notification explanation"
},
- "setupSkipForNow": "Skip for now",
+ "setupSkipForNow": "Пропустить",
"@setupSkipForNow": {
"description": "Skip button text"
},
- "setupBack": "Back",
+ "setupBack": "Назад",
"@setupBack": {
"description": "Back button text"
},
- "setupNext": "Next",
+ "setupNext": "Далее",
"@setupNext": {
"description": "Next button text"
},
- "setupGetStarted": "Get Started",
+ "setupGetStarted": "Приступить к работе",
"@setupGetStarted": {
"description": "Final setup button"
},
- "setupSkipAndStart": "Skip & Start",
+ "setupSkipAndStart": "Пропустить и начать",
"@setupSkipAndStart": {
"description": "Skip setup and start app"
},
- "setupAllowAccessToManageFiles": "Please enable \"Allow access to manage all files\" in the next screen.",
+ "setupAllowAccessToManageFiles": "Пожалуйста, включите \"Разрешить доступ для управления всеми файлами\" на следующем экране.",
"@setupAllowAccessToManageFiles": {
"description": "Instruction for file access permission"
},
- "setupGetCredentialsFromSpotify": "Get credentials from developer.spotify.com",
+ "setupGetCredentialsFromSpotify": "Получить учётные данные с developer.spotify.com",
"@setupGetCredentialsFromSpotify": {
"description": "Link text for Spotify developer portal"
},
- "dialogCancel": "Cancel",
+ "dialogCancel": "Отмена",
"@dialogCancel": {
"description": "Dialog button - cancel action"
},
- "dialogOk": "OK",
+ "dialogOk": "ОК",
"@dialogOk": {
"description": "Dialog button - confirm/acknowledge"
},
- "dialogSave": "Save",
+ "dialogSave": "Сохранить",
"@dialogSave": {
"description": "Dialog button - save changes"
},
- "dialogDelete": "Delete",
+ "dialogDelete": "Удалить",
"@dialogDelete": {
"description": "Dialog button - delete item"
},
- "dialogRetry": "Retry",
+ "dialogRetry": "Повторить",
"@dialogRetry": {
"description": "Dialog button - retry action"
},
- "dialogClose": "Close",
+ "dialogClose": "Закрыть",
"@dialogClose": {
"description": "Dialog button - close dialog"
},
- "dialogYes": "Yes",
+ "dialogYes": "Да",
"@dialogYes": {
"description": "Dialog button - confirm yes"
},
- "dialogNo": "No",
+ "dialogNo": "Нет",
"@dialogNo": {
"description": "Dialog button - confirm no"
},
- "dialogClear": "Clear",
+ "dialogClear": "Очистить",
"@dialogClear": {
"description": "Dialog button - clear items"
},
- "dialogConfirm": "Confirm",
+ "dialogConfirm": "Подтвердить",
"@dialogConfirm": {
"description": "Dialog button - confirm action"
},
- "dialogDone": "Done",
+ "dialogDone": "Готово",
"@dialogDone": {
"description": "Dialog button - action completed"
},
- "dialogImport": "Import",
+ "dialogImport": "Импорт",
"@dialogImport": {
"description": "Dialog button - import data"
},
- "dialogDiscard": "Discard",
+ "dialogDiscard": "Отменить",
"@dialogDiscard": {
"description": "Dialog button - discard changes"
},
- "dialogRemove": "Remove",
+ "dialogRemove": "Убрать",
"@dialogRemove": {
"description": "Dialog button - remove item"
},
- "dialogUninstall": "Uninstall",
+ "dialogUninstall": "Удалить",
"@dialogUninstall": {
"description": "Dialog button - uninstall extension"
},
- "dialogDiscardChanges": "Discard Changes?",
+ "dialogDiscardChanges": "Отменить изменения?",
"@dialogDiscardChanges": {
"description": "Dialog title - unsaved changes warning"
},
- "dialogUnsavedChanges": "You have unsaved changes. Do you want to discard them?",
+ "dialogUnsavedChanges": "Есть несохраненные изменения. Отменить их?",
"@dialogUnsavedChanges": {
"description": "Dialog message - unsaved changes"
},
- "dialogDownloadFailed": "Download Failed",
+ "dialogDownloadFailed": "Ошибка скачивания",
"@dialogDownloadFailed": {
"description": "Dialog title - download error"
},
- "dialogTrackLabel": "Track:",
+ "dialogTrackLabel": "Трек:",
"@dialogTrackLabel": {
"description": "Label for track name in error dialog"
},
- "dialogArtistLabel": "Artist:",
+ "dialogArtistLabel": "Исполнитель:",
"@dialogArtistLabel": {
"description": "Label for artist name in error dialog"
},
- "dialogErrorLabel": "Error:",
+ "dialogErrorLabel": "Ошибка:",
"@dialogErrorLabel": {
"description": "Label for error message"
},
- "dialogClearAll": "Clear All",
+ "dialogClearAll": "Очистить всё",
"@dialogClearAll": {
"description": "Dialog title - clear all items"
},
- "dialogClearAllDownloads": "Are you sure you want to clear all downloads?",
+ "dialogClearAllDownloads": "Вы уверены, что хотите очистить все загрузки?",
"@dialogClearAllDownloads": {
"description": "Dialog message - clear downloads confirmation"
},
- "dialogRemoveFromDevice": "Remove from device?",
+ "dialogRemoveFromDevice": "Удалить с устройства?",
"@dialogRemoveFromDevice": {
"description": "Dialog title - delete file confirmation"
},
- "dialogRemoveExtension": "Remove Extension",
+ "dialogRemoveExtension": "Удалить расширение",
"@dialogRemoveExtension": {
"description": "Dialog title - uninstall extension"
},
- "dialogRemoveExtensionMessage": "Are you sure you want to remove this extension? This cannot be undone.",
+ "dialogRemoveExtensionMessage": "Вы уверены, что хотите удалить это расширение? Это действие не может быть отменено.",
"@dialogRemoveExtensionMessage": {
"description": "Dialog message - uninstall confirmation"
},
- "dialogUninstallExtension": "Uninstall Extension?",
+ "dialogUninstallExtension": "Удалить расширение?",
"@dialogUninstallExtension": {
"description": "Dialog title - uninstall extension"
},
- "dialogUninstallExtensionMessage": "Are you sure you want to remove {extensionName}?",
+ "dialogUninstallExtensionMessage": "Вы уверены, что хотите удалить {extensionName}?",
"@dialogUninstallExtensionMessage": {
"description": "Dialog message - uninstall specific extension",
"placeholders": {
@@ -1082,19 +1096,19 @@
}
}
},
- "dialogClearHistoryTitle": "Clear History",
+ "dialogClearHistoryTitle": "Очистить историю",
"@dialogClearHistoryTitle": {
"description": "Dialog title - clear download history"
},
- "dialogClearHistoryMessage": "Are you sure you want to clear all download history? This cannot be undone.",
+ "dialogClearHistoryMessage": "Вы уверены, что хотите удалить всю историю загрузок? Это действие необратимо.",
"@dialogClearHistoryMessage": {
"description": "Dialog message - clear history confirmation"
},
- "dialogDeleteSelectedTitle": "Delete Selected",
+ "dialogDeleteSelectedTitle": "Удалить выбранные",
"@dialogDeleteSelectedTitle": {
"description": "Dialog title - delete selected items"
},
- "dialogDeleteSelectedMessage": "Delete {count} {count, plural, =1{track} other{tracks}} from history?\n\nThis will also delete the files from storage.",
+ "dialogDeleteSelectedMessage": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}} из истории?\n\nЭто также удалит файлы из хранилища.",
"@dialogDeleteSelectedMessage": {
"description": "Dialog message - delete selected tracks",
"placeholders": {
@@ -1103,11 +1117,11 @@
}
}
},
- "dialogImportPlaylistTitle": "Import Playlist",
+ "dialogImportPlaylistTitle": "Импорт плейлиста",
"@dialogImportPlaylistTitle": {
"description": "Dialog title - import CSV playlist"
},
- "dialogImportPlaylistMessage": "Found {count} tracks in CSV. Add them to download queue?",
+ "dialogImportPlaylistMessage": "Найдено {count} треков в CSV. Добавить их в очередь загрузки?",
"@dialogImportPlaylistMessage": {
"description": "Dialog message - import playlist confirmation",
"placeholders": {
@@ -1116,7 +1130,7 @@
}
}
},
- "snackbarAddedToQueue": "Added \"{trackName}\" to queue",
+ "snackbarAddedToQueue": "\"{trackName}\" добавлен в очередь",
"@snackbarAddedToQueue": {
"description": "Snackbar - track added to download queue",
"placeholders": {
@@ -1125,7 +1139,7 @@
}
}
},
- "snackbarAddedTracksToQueue": "Added {count} tracks to queue",
+ "snackbarAddedTracksToQueue": "Добавлено {count} треков в очередь",
"@snackbarAddedTracksToQueue": {
"description": "Snackbar - multiple tracks added to queue",
"placeholders": {
@@ -1134,7 +1148,7 @@
}
}
},
- "snackbarAlreadyDownloaded": "\"{trackName}\" already downloaded",
+ "snackbarAlreadyDownloaded": "\"{trackName}\" уже скачан",
"@snackbarAlreadyDownloaded": {
"description": "Snackbar - track already exists",
"placeholders": {
@@ -1143,19 +1157,19 @@
}
}
},
- "snackbarHistoryCleared": "History cleared",
+ "snackbarHistoryCleared": "История очищена",
"@snackbarHistoryCleared": {
"description": "Snackbar - history deleted"
},
- "snackbarCredentialsSaved": "Credentials saved",
+ "snackbarCredentialsSaved": "Учётные данные сохранены",
"@snackbarCredentialsSaved": {
"description": "Snackbar - Spotify credentials saved"
},
- "snackbarCredentialsCleared": "Credentials cleared",
+ "snackbarCredentialsCleared": "Учётные данные очищены",
"@snackbarCredentialsCleared": {
"description": "Snackbar - Spotify credentials removed"
},
- "snackbarDeletedTracks": "Deleted {count} {count, plural, =1{track} other{tracks}}",
+ "snackbarDeletedTracks": "Удалено {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}",
"@snackbarDeletedTracks": {
"description": "Snackbar - tracks deleted",
"placeholders": {
@@ -1164,7 +1178,7 @@
}
}
},
- "snackbarCannotOpenFile": "Cannot open file: {error}",
+ "snackbarCannotOpenFile": "Невозможно открыть файл: {error}",
"@snackbarCannotOpenFile": {
"description": "Snackbar - file open error",
"placeholders": {
@@ -1173,15 +1187,15 @@
}
}
},
- "snackbarFillAllFields": "Please fill all fields",
+ "snackbarFillAllFields": "Пожалуйста, заполните все поля",
"@snackbarFillAllFields": {
"description": "Snackbar - validation error"
},
- "snackbarViewQueue": "View Queue",
+ "snackbarViewQueue": "Просмотр очереди",
"@snackbarViewQueue": {
"description": "Snackbar action - view download queue"
},
- "snackbarFailedToLoad": "Failed to load: {error}",
+ "snackbarFailedToLoad": "Ошибка загрузки: {error}",
"@snackbarFailedToLoad": {
"description": "Snackbar - loading error",
"placeholders": {
@@ -1190,7 +1204,7 @@
}
}
},
- "snackbarUrlCopied": "{platform} URL copied to clipboard",
+ "snackbarUrlCopied": "{platform} ссылка скопирована в буфер обмена",
"@snackbarUrlCopied": {
"description": "Snackbar - URL copied",
"placeholders": {
@@ -1200,23 +1214,23 @@
}
}
},
- "snackbarFileNotFound": "File not found",
+ "snackbarFileNotFound": "Файл не найден",
"@snackbarFileNotFound": {
"description": "Snackbar - file doesn't exist"
},
- "snackbarSelectExtFile": "Please select a .spotiflac-ext file",
+ "snackbarSelectExtFile": "Пожалуйста, выберите .spotiflac-ext-файл",
"@snackbarSelectExtFile": {
"description": "Snackbar - wrong file type selected"
},
- "snackbarProviderPrioritySaved": "Provider priority saved",
+ "snackbarProviderPrioritySaved": "Приоритет провайдера сохранён",
"@snackbarProviderPrioritySaved": {
"description": "Snackbar - provider order saved"
},
- "snackbarMetadataProviderSaved": "Metadata provider priority saved",
+ "snackbarMetadataProviderSaved": "Приоритет провайдера метаданных сохранён",
"@snackbarMetadataProviderSaved": {
"description": "Snackbar - metadata provider order saved"
},
- "snackbarExtensionInstalled": "{extensionName} installed.",
+ "snackbarExtensionInstalled": "{extensionName} установлено.",
"@snackbarExtensionInstalled": {
"description": "Snackbar - extension installed successfully",
"placeholders": {
@@ -1225,7 +1239,7 @@
}
}
},
- "snackbarExtensionUpdated": "{extensionName} updated.",
+ "snackbarExtensionUpdated": "{extensionName} Обновлено.",
"@snackbarExtensionUpdated": {
"description": "Snackbar - extension updated successfully",
"placeholders": {
@@ -1234,23 +1248,23 @@
}
}
},
- "snackbarFailedToInstall": "Failed to install extension",
+ "snackbarFailedToInstall": "Не удалось установить расширение",
"@snackbarFailedToInstall": {
"description": "Snackbar - extension install error"
},
- "snackbarFailedToUpdate": "Failed to update extension",
+ "snackbarFailedToUpdate": "Не удалось обновить расширение",
"@snackbarFailedToUpdate": {
"description": "Snackbar - extension update error"
},
- "errorRateLimited": "Rate Limited",
+ "errorRateLimited": "Слишком много запросов",
"@errorRateLimited": {
"description": "Error title - too many requests"
},
- "errorRateLimitedMessage": "Too many requests. Please wait a moment before searching again.",
+ "errorRateLimitedMessage": "Слишком много запросов. Пожалуйста, подождите минуту перед повторным поиском.",
"@errorRateLimitedMessage": {
"description": "Error message - rate limit explanation"
},
- "errorFailedToLoad": "Failed to load {item}",
+ "errorFailedToLoad": "Ошибка загрузки {item}",
"@errorFailedToLoad": {
"description": "Error message - loading failed",
"placeholders": {
@@ -1260,11 +1274,11 @@
}
}
},
- "errorNoTracksFound": "No tracks found",
+ "errorNoTracksFound": "Треки не найдены",
"@errorNoTracksFound": {
"description": "Error - search returned no results"
},
- "errorMissingExtensionSource": "Cannot load {item}: missing extension source",
+ "errorMissingExtensionSource": "Невозможно загрузить {item}: отсутствует источник расширения",
"@errorMissingExtensionSource": {
"description": "Error - extension source not available",
"placeholders": {
@@ -1273,79 +1287,79 @@
}
}
},
- "statusQueued": "Queued",
+ "statusQueued": "В очереди",
"@statusQueued": {
"description": "Download status - waiting in queue"
},
- "statusDownloading": "Downloading",
+ "statusDownloading": "Скачивание",
"@statusDownloading": {
"description": "Download status - in progress"
},
- "statusFinalizing": "Finalizing",
+ "statusFinalizing": "Завершение",
"@statusFinalizing": {
"description": "Download status - writing metadata"
},
- "statusCompleted": "Completed",
+ "statusCompleted": "Завершено",
"@statusCompleted": {
"description": "Download status - finished"
},
- "statusFailed": "Failed",
+ "statusFailed": "Неудачно",
"@statusFailed": {
"description": "Download status - error occurred"
},
- "statusSkipped": "Skipped",
+ "statusSkipped": "Пропущено",
"@statusSkipped": {
"description": "Download status - already exists"
},
- "statusPaused": "Paused",
+ "statusPaused": "Приостановлено",
"@statusPaused": {
"description": "Download status - paused"
},
- "actionPause": "Pause",
+ "actionPause": "Пауза",
"@actionPause": {
"description": "Action button - pause download"
},
- "actionResume": "Resume",
+ "actionResume": "Возобновить",
"@actionResume": {
"description": "Action button - resume download"
},
- "actionCancel": "Cancel",
+ "actionCancel": "Отмена",
"@actionCancel": {
"description": "Action button - cancel operation"
},
- "actionStop": "Stop",
+ "actionStop": "Стоп",
"@actionStop": {
"description": "Action button - stop operation"
},
- "actionSelect": "Select",
+ "actionSelect": "Выбрать",
"@actionSelect": {
"description": "Action button - enter selection mode"
},
- "actionSelectAll": "Select All",
+ "actionSelectAll": "Выбрать все",
"@actionSelectAll": {
"description": "Action button - select all items"
},
- "actionDeselect": "Deselect",
+ "actionDeselect": "Снять выделение",
"@actionDeselect": {
"description": "Action button - deselect all"
},
- "actionPaste": "Paste",
+ "actionPaste": "Вставить",
"@actionPaste": {
"description": "Action button - paste from clipboard"
},
- "actionImportCsv": "Import CSV",
+ "actionImportCsv": "Импорт CSV",
"@actionImportCsv": {
"description": "Action button - import CSV file"
},
- "actionRemoveCredentials": "Remove Credentials",
+ "actionRemoveCredentials": "Удалить учётные данные",
"@actionRemoveCredentials": {
"description": "Action button - delete Spotify credentials"
},
- "actionSaveCredentials": "Save Credentials",
+ "actionSaveCredentials": "Сохранить учётные данные",
"@actionSaveCredentials": {
"description": "Action button - save Spotify credentials"
},
- "selectionSelected": "{count} selected",
+ "selectionSelected": "{count} выбрано",
"@selectionSelected": {
"description": "Selection count indicator",
"placeholders": {
@@ -1354,15 +1368,15 @@
}
}
},
- "selectionAllSelected": "All tracks selected",
+ "selectionAllSelected": "Все треки выбраны",
"@selectionAllSelected": {
"description": "Status - all items selected"
},
- "selectionTapToSelect": "Tap tracks to select",
+ "selectionTapToSelect": "Нажмите на треки для выбора",
"@selectionTapToSelect": {
"description": "Hint - how to select items"
},
- "selectionDeleteTracks": "Delete {count} {count, plural, =1{track} other{tracks}}",
+ "selectionDeleteTracks": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}",
"@selectionDeleteTracks": {
"description": "Delete button with count",
"placeholders": {
@@ -1371,11 +1385,11 @@
}
}
},
- "selectionSelectToDelete": "Select tracks to delete",
+ "selectionSelectToDelete": "Выберите треки для удаления",
"@selectionSelectToDelete": {
"description": "Placeholder when nothing selected"
},
- "progressFetchingMetadata": "Fetching metadata... {current}/{total}",
+ "progressFetchingMetadata": "Получение метаданных... {current}/{total}",
"@progressFetchingMetadata": {
"description": "Progress indicator - loading track info",
"placeholders": {
@@ -1387,59 +1401,59 @@
}
}
},
- "progressReadingCsv": "Reading CSV...",
+ "progressReadingCsv": "Чтение CSV...",
"@progressReadingCsv": {
"description": "Progress indicator - parsing CSV file"
},
- "searchSongs": "Songs",
+ "searchSongs": "Песни",
"@searchSongs": {
"description": "Search result category - songs"
},
- "searchArtists": "Artists",
+ "searchArtists": "Исполнители",
"@searchArtists": {
"description": "Search result category - artists"
},
- "searchAlbums": "Albums",
+ "searchAlbums": "Альбомы",
"@searchAlbums": {
"description": "Search result category - albums"
},
- "searchPlaylists": "Playlists",
+ "searchPlaylists": "Плейлисты",
"@searchPlaylists": {
"description": "Search result category - playlists"
},
- "tooltipPlay": "Play",
+ "tooltipPlay": "Воспроизвести",
"@tooltipPlay": {
"description": "Tooltip - play button"
},
- "tooltipCancel": "Cancel",
+ "tooltipCancel": "Отмена",
"@tooltipCancel": {
"description": "Tooltip - cancel button"
},
- "tooltipStop": "Stop",
+ "tooltipStop": "Стоп",
"@tooltipStop": {
"description": "Tooltip - stop button"
},
- "tooltipRetry": "Retry",
+ "tooltipRetry": "Повторить",
"@tooltipRetry": {
"description": "Tooltip - retry button"
},
- "tooltipRemove": "Remove",
+ "tooltipRemove": "Убрать",
"@tooltipRemove": {
"description": "Tooltip - remove button"
},
- "tooltipClear": "Clear",
+ "tooltipClear": "Очистить",
"@tooltipClear": {
"description": "Tooltip - clear button"
},
- "tooltipPaste": "Paste",
+ "tooltipPaste": "Вставить",
"@tooltipPaste": {
"description": "Tooltip - paste button"
},
- "filenameFormat": "Filename Format",
+ "filenameFormat": "Формат имени файла",
"@filenameFormat": {
"description": "Setting title - filename pattern"
},
- "filenameFormatPreview": "Preview: {preview}",
+ "filenameFormatPreview": "Предпросмотр: {preview}",
"@filenameFormatPreview": {
"description": "Preview of filename pattern",
"placeholders": {
@@ -1448,7 +1462,7 @@
}
}
},
- "filenameAvailablePlaceholders": "Available placeholders:",
+ "filenameAvailablePlaceholders": "Доступные заполнители:",
"@filenameAvailablePlaceholders": {
"description": "Label for placeholder list"
},
@@ -1456,51 +1470,51 @@
"@filenameHint": {
"description": "Default filename format hint"
},
- "folderOrganization": "Folder Organization",
+ "folderOrganization": "Организация папок",
"@folderOrganization": {
"description": "Setting title - folder structure"
},
- "folderOrganizationNone": "No organization",
+ "folderOrganizationNone": "Без организации",
"@folderOrganizationNone": {
"description": "Folder option - flat structure"
},
- "folderOrganizationByArtist": "By Artist",
+ "folderOrganizationByArtist": "По исполнителю",
"@folderOrganizationByArtist": {
"description": "Folder option - artist folders"
},
- "folderOrganizationByAlbum": "By Album",
+ "folderOrganizationByAlbum": "По альбому",
"@folderOrganizationByAlbum": {
"description": "Folder option - album folders"
},
- "folderOrganizationByArtistAlbum": "Artist/Album",
+ "folderOrganizationByArtistAlbum": "Исполнитель/Альбом",
"@folderOrganizationByArtistAlbum": {
"description": "Folder option - nested folders"
},
- "folderOrganizationDescription": "Organize downloaded files into folders",
+ "folderOrganizationDescription": "Сортировать скачанные файлы по папкам",
"@folderOrganizationDescription": {
"description": "Folder organization sheet description"
},
- "folderOrganizationNoneSubtitle": "All files in download folder",
+ "folderOrganizationNoneSubtitle": "Все файлы в папке загрузок",
"@folderOrganizationNoneSubtitle": {
"description": "Subtitle for no organization option"
},
- "folderOrganizationByArtistSubtitle": "Separate folder for each artist",
+ "folderOrganizationByArtistSubtitle": "Отдельная папка для каждого исполнителя",
"@folderOrganizationByArtistSubtitle": {
"description": "Subtitle for artist folder option"
},
- "folderOrganizationByAlbumSubtitle": "Separate folder for each album",
+ "folderOrganizationByAlbumSubtitle": "Отдельная папка для каждого альбома",
"@folderOrganizationByAlbumSubtitle": {
"description": "Subtitle for album folder option"
},
- "folderOrganizationByArtistAlbumSubtitle": "Nested folders for artist and album",
+ "folderOrganizationByArtistAlbumSubtitle": "Вложенные папки для исполнителей и альбомов",
"@folderOrganizationByArtistAlbumSubtitle": {
"description": "Subtitle for nested folder option"
},
- "updateAvailable": "Update Available",
+ "updateAvailable": "Доступно обновление",
"@updateAvailable": {
"description": "Update dialog title"
},
- "updateNewVersion": "Version {version} is available",
+ "updateNewVersion": "Версия {version} доступна",
"@updateNewVersion": {
"description": "Update available message",
"placeholders": {
@@ -1509,231 +1523,231 @@
}
}
},
- "updateDownload": "Download",
+ "updateDownload": "Скачать",
"@updateDownload": {
"description": "Update button - download update"
},
- "updateLater": "Later",
+ "updateLater": "Позже",
"@updateLater": {
"description": "Update button - dismiss"
},
- "updateChangelog": "Changelog",
+ "updateChangelog": "Список изменений",
"@updateChangelog": {
"description": "Link to changelog"
},
- "updateStartingDownload": "Starting download...",
+ "updateStartingDownload": "Загрузка началась...",
"@updateStartingDownload": {
"description": "Update status - initializing"
},
- "updateDownloadFailed": "Download failed",
+ "updateDownloadFailed": "Не удалось скачать",
"@updateDownloadFailed": {
"description": "Update error title"
},
- "updateFailedMessage": "Failed to download update",
+ "updateFailedMessage": "Сбой загрузки обновления",
"@updateFailedMessage": {
"description": "Update error message"
},
- "updateNewVersionReady": "A new version is ready",
+ "updateNewVersionReady": "Доступна новая версия",
"@updateNewVersionReady": {
"description": "Update subtitle"
},
- "updateCurrent": "Current",
+ "updateCurrent": "Текущая",
"@updateCurrent": {
"description": "Label for current version"
},
- "updateNew": "New",
+ "updateNew": "Новая",
"@updateNew": {
"description": "Label for new version"
},
- "updateDownloading": "Downloading...",
+ "updateDownloading": "Скачивание...",
"@updateDownloading": {
"description": "Update status - downloading"
},
- "updateWhatsNew": "What's New",
+ "updateWhatsNew": "Что нового",
"@updateWhatsNew": {
"description": "Changelog section title"
},
- "updateDownloadInstall": "Download & Install",
+ "updateDownloadInstall": "Скачать и установить",
"@updateDownloadInstall": {
"description": "Update button - download and install"
},
- "updateDontRemind": "Don't remind",
+ "updateDontRemind": "Не напоминать",
"@updateDontRemind": {
"description": "Update button - skip this version"
},
- "providerPriority": "Provider Priority",
+ "providerPriority": "Приоритет провайдера",
"@providerPriority": {
"description": "Setting title - download provider order"
},
- "providerPrioritySubtitle": "Drag to reorder download providers",
+ "providerPrioritySubtitle": "Перетащите для изменения порядка",
"@providerPrioritySubtitle": {
"description": "Subtitle for provider priority"
},
- "providerPriorityTitle": "Provider Priority",
+ "providerPriorityTitle": "Приоритет провайдера",
"@providerPriorityTitle": {
"description": "Provider priority page title"
},
- "providerPriorityDescription": "Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.",
+ "providerPriorityDescription": "Перетаскивайте, чтобы изменить порядок провайдеров загрузки. Приложение будет пробовать провайдеров сверху вниз при загрузке треков.",
"@providerPriorityDescription": {
"description": "Provider priority page description"
},
- "providerPriorityInfo": "If a track is not available on the first provider, the app will automatically try the next one.",
+ "providerPriorityInfo": "Если трек не доступен у первого провайдера, приложение автоматически попробует следующий.",
"@providerPriorityInfo": {
"description": "Info tip about fallback behavior"
},
- "providerBuiltIn": "Built-in",
+ "providerBuiltIn": "Встроенные",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
},
- "providerExtension": "Extension",
+ "providerExtension": "Расширение",
"@providerExtension": {
"description": "Label for extension-provided providers"
},
- "metadataProviderPriority": "Metadata Provider Priority",
+ "metadataProviderPriority": "Приоритет провайдера метаданных",
"@metadataProviderPriority": {
"description": "Setting title - metadata provider order"
},
- "metadataProviderPrioritySubtitle": "Order used when fetching track metadata",
+ "metadataProviderPrioritySubtitle": "Порядок, используемый при получении метаданных",
"@metadataProviderPrioritySubtitle": {
"description": "Subtitle for metadata priority"
},
- "metadataProviderPriorityTitle": "Metadata Priority",
+ "metadataProviderPriorityTitle": "Приоритет метаданных",
"@metadataProviderPriorityTitle": {
"description": "Metadata priority page title"
},
- "metadataProviderPriorityDescription": "Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.",
+ "metadataProviderPriorityDescription": "Перетаскивайте, чтобы изменить порядок провайдеров метаданных. Приложение будет пробовать провайдеров сверху вниз при поиске треков и извлечении метаданных.",
"@metadataProviderPriorityDescription": {
"description": "Metadata priority page description"
},
- "metadataProviderPriorityInfo": "Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.",
+ "metadataProviderPriorityInfo": "Deezer не имеет ограничений по скорости и рекомендуется в качестве основного. Spotify может ограничивать скорость после большого количества запросов.",
"@metadataProviderPriorityInfo": {
"description": "Info tip about rate limits"
},
- "metadataNoRateLimits": "No rate limits",
+ "metadataNoRateLimits": "Без ограничений по скорости",
"@metadataNoRateLimits": {
"description": "Deezer provider description"
},
- "metadataMayRateLimit": "May rate limit",
+ "metadataMayRateLimit": "Есть ограничения по скорости",
"@metadataMayRateLimit": {
"description": "Spotify provider description"
},
- "logTitle": "Logs",
+ "logTitle": "Логи",
"@logTitle": {
"description": "Logs screen title"
},
- "logCopy": "Copy Logs",
+ "logCopy": "Скопировать логи",
"@logCopy": {
"description": "Action - copy logs to clipboard"
},
- "logClear": "Clear Logs",
+ "logClear": "Очистить логи",
"@logClear": {
"description": "Action - delete all logs"
},
- "logShare": "Share Logs",
+ "logShare": "Поделиться логами",
"@logShare": {
"description": "Action - share logs file"
},
- "logEmpty": "No logs yet",
+ "logEmpty": "Логов нет",
"@logEmpty": {
"description": "Empty state title"
},
- "logCopied": "Logs copied to clipboard",
+ "logCopied": "Логи скопированы в буфер обмена",
"@logCopied": {
"description": "Snackbar - logs copied"
},
- "logSearchHint": "Search logs...",
+ "logSearchHint": "Поиск логов...",
"@logSearchHint": {
"description": "Log search placeholder"
},
- "logFilterLevel": "Level",
+ "logFilterLevel": "Уровень",
"@logFilterLevel": {
"description": "Filter by log level"
},
- "logFilterSection": "Filter",
+ "logFilterSection": "Фильтр",
"@logFilterSection": {
"description": "Filter section title"
},
- "logShareLogs": "Share logs",
+ "logShareLogs": "Поделиться логами",
"@logShareLogs": {
"description": "Share button tooltip"
},
- "logClearLogs": "Clear logs",
+ "logClearLogs": "Очистить логи",
"@logClearLogs": {
"description": "Clear button tooltip"
},
- "logClearLogsTitle": "Clear Logs",
+ "logClearLogsTitle": "Очистить логи",
"@logClearLogsTitle": {
"description": "Clear logs dialog title"
},
- "logClearLogsMessage": "Are you sure you want to clear all logs?",
+ "logClearLogsMessage": "Вы уверены, что хотите очистить все логи?",
"@logClearLogsMessage": {
"description": "Clear logs confirmation message"
},
- "logIspBlocking": "ISP BLOCKING DETECTED",
+ "logIspBlocking": "ОБНАРУЖЕНА БЛОКИРОВКА ИНТЕРНЕТ ПРОВАЙДЕРОМ",
"@logIspBlocking": {
"description": "Error category - ISP blocking"
},
- "logRateLimited": "RATE LIMITED",
+ "logRateLimited": "ОГРАНИЧЕННАЯ СКОРОСТЬ",
"@logRateLimited": {
"description": "Error category - rate limiting"
},
- "logNetworkError": "NETWORK ERROR",
+ "logNetworkError": "ОШИБКА СЕТИ",
"@logNetworkError": {
"description": "Error category - network issues"
},
- "logTrackNotFound": "TRACK NOT FOUND",
+ "logTrackNotFound": "ТРЕК НЕ НАЙДЕН",
"@logTrackNotFound": {
"description": "Error category - missing tracks"
},
- "logFilterBySeverity": "Filter logs by severity",
+ "logFilterBySeverity": "Фильтровать логи по серьезности",
"@logFilterBySeverity": {
"description": "Filter dialog title"
},
- "logNoLogsYet": "No logs yet",
+ "logNoLogsYet": "Логов нет",
"@logNoLogsYet": {
"description": "Empty state title"
},
- "logNoLogsYetSubtitle": "Logs will appear here as you use the app",
+ "logNoLogsYetSubtitle": "Логи появятся здесь по мере использования приложения",
"@logNoLogsYetSubtitle": {
"description": "Empty state subtitle"
},
- "logIssueSummary": "Issue Summary",
+ "logIssueSummary": "Краткое описание проблемы",
"@logIssueSummary": {
"description": "Section header for error summary"
},
- "logIspBlockingDescription": "Your ISP may be blocking access to download services",
+ "logIspBlockingDescription": "Ваш провайдер может блокировать доступ к сервисам скачивания",
"@logIspBlockingDescription": {
"description": "ISP blocking explanation"
},
- "logIspBlockingSuggestion": "Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8",
+ "logIspBlockingSuggestion": "Попробуйте использовать VPN или измените DNS на 1.1.1.1 или 8.8.8.8",
"@logIspBlockingSuggestion": {
"description": "ISP blocking fix suggestion"
},
- "logRateLimitedDescription": "Too many requests to the service",
+ "logRateLimitedDescription": "Слишком много запросов к сервису",
"@logRateLimitedDescription": {
"description": "Rate limit explanation"
},
- "logRateLimitedSuggestion": "Wait a few minutes before trying again",
+ "logRateLimitedSuggestion": "Подождите несколько минут, прежде чем повторить попытку",
"@logRateLimitedSuggestion": {
"description": "Rate limit fix suggestion"
},
- "logNetworkErrorDescription": "Connection issues detected",
+ "logNetworkErrorDescription": "Обнаружены проблемы с подключением",
"@logNetworkErrorDescription": {
"description": "Network error explanation"
},
- "logNetworkErrorSuggestion": "Check your internet connection",
+ "logNetworkErrorSuggestion": "Проверьте подключение к Интернету",
"@logNetworkErrorSuggestion": {
"description": "Network error fix suggestion"
},
- "logTrackNotFoundDescription": "Some tracks could not be found on download services",
+ "logTrackNotFoundDescription": "Некоторые треки не найдены в сервисах загрузки",
"@logTrackNotFoundDescription": {
"description": "Track not found explanation"
},
- "logTrackNotFoundSuggestion": "The track may not be available in lossless quality",
+ "logTrackNotFoundSuggestion": "Трек может быть недоступен в lossless формате",
"@logTrackNotFoundSuggestion": {
"description": "Track not found explanation"
},
- "logTotalErrors": "Total errors: {count}",
+ "logTotalErrors": "Всего ошибок: {count}",
"@logTotalErrors": {
"description": "Error count display",
"placeholders": {
@@ -1742,7 +1756,7 @@
}
}
},
- "logAffected": "Affected: {domains}",
+ "logAffected": "Затронуто: {domains}",
"@logAffected": {
"description": "Affected domains display",
"placeholders": {
@@ -1751,7 +1765,7 @@
}
}
},
- "logEntriesFiltered": "Entries ({count} filtered)",
+ "logEntriesFiltered": "Записи ({count} фильтровано)",
"@logEntriesFiltered": {
"description": "Log count with filter active",
"placeholders": {
@@ -1760,7 +1774,7 @@
}
}
},
- "logEntries": "Entries ({count})",
+ "logEntries": "Записи ({count})",
"@logEntries": {
"description": "Total log count",
"placeholders": {
@@ -1769,11 +1783,11 @@
}
}
},
- "credentialsTitle": "Spotify Credentials",
+ "credentialsTitle": "Учётные данные Spotify",
"@credentialsTitle": {
"description": "Credentials dialog title"
},
- "credentialsDescription": "Enter your Client ID and Secret to use your own Spotify application quota.",
+ "credentialsDescription": "Введите свой Client ID и Secret, чтобы использовать собственные квоты в Spotify.",
"@credentialsDescription": {
"description": "Credentials dialog explanation"
},
@@ -1781,7 +1795,7 @@
"@credentialsClientId": {
"description": "Client ID field label - DO NOT TRANSLATE"
},
- "credentialsClientIdHint": "Paste Client ID",
+ "credentialsClientIdHint": "Вставьте Client ID",
"@credentialsClientIdHint": {
"description": "Client ID placeholder"
},
@@ -1789,123 +1803,111 @@
"@credentialsClientSecret": {
"description": "Client Secret field label - DO NOT TRANSLATE"
},
- "credentialsClientSecretHint": "Paste Client Secret",
+ "credentialsClientSecretHint": "Вставьте Client Secret",
"@credentialsClientSecretHint": {
"description": "Client Secret placeholder"
},
- "channelStable": "Stable",
+ "channelStable": "Стабильный",
"@channelStable": {
"description": "Update channel - stable releases"
},
- "channelPreview": "Preview",
+ "channelPreview": "Предварительный",
"@channelPreview": {
"description": "Update channel - beta/preview releases"
},
- "sectionSearchSource": "Search Source",
+ "sectionSearchSource": "Поиск источника",
"@sectionSearchSource": {
"description": "Settings section header"
},
- "sectionDownload": "Download",
+ "sectionDownload": "Скачивание",
"@sectionDownload": {
"description": "Settings section header"
},
- "sectionPerformance": "Performance",
+ "sectionPerformance": "Производительность",
"@sectionPerformance": {
"description": "Settings section header"
},
- "sectionApp": "App",
+ "sectionApp": "Приложение",
"@sectionApp": {
"description": "Settings section header"
},
- "sectionData": "Data",
+ "sectionData": "Данные",
"@sectionData": {
"description": "Settings section header"
},
- "sectionDebug": "Debug",
+ "sectionDebug": "Отладка",
"@sectionDebug": {
"description": "Settings section header"
},
- "sectionService": "Service",
+ "sectionService": "Сервис",
"@sectionService": {
"description": "Settings section header"
},
- "sectionAudioQuality": "Audio Quality",
+ "sectionAudioQuality": "Качество аудио",
"@sectionAudioQuality": {
"description": "Settings section header"
},
- "sectionFileSettings": "File Settings",
+ "sectionFileSettings": "Настройки файла",
"@sectionFileSettings": {
"description": "Settings section header"
},
- "sectionColor": "Color",
+ "sectionColor": "Цвет",
"@sectionColor": {
"description": "Settings section header"
},
- "sectionTheme": "Theme",
+ "sectionTheme": "Тема",
"@sectionTheme": {
"description": "Settings section header"
},
- "sectionLayout": "Layout",
+ "sectionLayout": "Разметка",
"@sectionLayout": {
"description": "Settings section header"
},
- "sectionLanguage": "Language",
+ "sectionLanguage": "Язык",
"@sectionLanguage": {
- "description": "Settings section header for language selection"
+ "description": "Settings section header for language"
},
- "appearanceLanguage": "App Language",
+ "appearanceLanguage": "Язык приложения",
"@appearanceLanguage": {
- "description": "Setting title for language selection"
+ "description": "Language setting title"
},
- "appearanceLanguageSubtitle": "Choose your preferred language",
+ "appearanceLanguageSubtitle": "Выберите предпочитаемый язык",
"@appearanceLanguageSubtitle": {
- "description": "Subtitle for language setting"
+ "description": "Language setting subtitle"
},
- "languageSystem": "System Default",
- "@languageSystem": {
- "description": "Use device system language"
- },
- "languageEnglish": "English",
- "@languageEnglish": {
- "description": "English language option"
- },
- "languageIndonesian": "Bahasa Indonesia",
- "@languageIndonesian": {
- "description": "Indonesian language option"
- },
- "settingsAppearanceSubtitle": "Theme, colors, display",
+ "settingsAppearanceSubtitle": "Тема, цвета, дисплей",
"@settingsAppearanceSubtitle": {
"description": "Appearance settings description"
},
- "settingsDownloadSubtitle": "Service, quality, filename format",
+ "settingsDownloadSubtitle": "Сервисы, качество, формат имени файла",
"@settingsDownloadSubtitle": {
"description": "Download settings description"
},
- "settingsOptionsSubtitle": "Fallback, lyrics, cover art, updates",
+ "settingsOptionsSubtitle": "Резерв. сервер, тексты песен, обложки, обновления",
"@settingsOptionsSubtitle": {
"description": "Options settings description"
},
- "settingsExtensionsSubtitle": "Manage download providers",
+ "settingsExtensionsSubtitle": "Управление провайдерами скачивания",
"@settingsExtensionsSubtitle": {
"description": "Extensions settings description"
},
- "settingsLogsSubtitle": "View app logs for debugging",
+ "settingsLogsSubtitle": "Просмотреть логи для отладки",
"@settingsLogsSubtitle": {
"description": "Logs settings description"
},
- "loadingSharedLink": "Loading shared link...",
+ "loadingSharedLink": "Загрузка общедоступной ссылки...",
"@loadingSharedLink": {
"description": "Status when opening shared URL"
},
- "pressBackAgainToExit": "Press back again to exit",
+ "pressBackAgainToExit": "Нажмите «Назад» ещё раз, чтобы выйти",
"@pressBackAgainToExit": {
"description": "Exit confirmation message"
},
- "tracksHeader": "Tracks",
+ "tracksHeader": "Треки",
"@tracksHeader": {
"description": "Section header for track list"
},
- "downloadAllCount": "Download All ({count})",
+ "downloadAllCount": "Скачать все ({count})",
"@downloadAllCount": {
"description": "Download all button with count",
"placeholders": {
@@ -1914,7 +1916,7 @@
}
}
},
- "tracksCount": "{count, plural, =1{1 track} other{{count} tracks}}",
+ "tracksCount": "{count, plural, one {{count} трек} few {{count} трека} many {{count} треков} =1 {1 трек} other {{count} треков}}",
"@tracksCount": {
"description": "Track count display",
"placeholders": {
@@ -1923,111 +1925,111 @@
}
}
},
- "trackCopyFilePath": "Copy file path",
+ "trackCopyFilePath": "Скопировать путь к файлу",
"@trackCopyFilePath": {
"description": "Action - copy file path"
},
- "trackRemoveFromDevice": "Remove from device",
+ "trackRemoveFromDevice": "Удалить с устройства",
"@trackRemoveFromDevice": {
"description": "Action - delete downloaded file"
},
- "trackLoadLyrics": "Load Lyrics",
+ "trackLoadLyrics": "Загрузить текст песни",
"@trackLoadLyrics": {
"description": "Action - fetch lyrics"
},
- "trackMetadata": "Metadata",
+ "trackMetadata": "Метаданные",
"@trackMetadata": {
"description": "Tab title - track metadata"
},
- "trackFileInfo": "File Info",
+ "trackFileInfo": "Информация о файле",
"@trackFileInfo": {
"description": "Tab title - file information"
},
- "trackLyrics": "Lyrics",
+ "trackLyrics": "Тексты песен",
"@trackLyrics": {
"description": "Tab title - lyrics"
},
- "trackFileNotFound": "File not found",
+ "trackFileNotFound": "Файл не найден",
"@trackFileNotFound": {
"description": "Error - file doesn't exist"
},
- "trackOpenInDeezer": "Open in Deezer",
+ "trackOpenInDeezer": "Открыть в Deezer",
"@trackOpenInDeezer": {
"description": "Action - open track in Deezer app"
},
- "trackOpenInSpotify": "Open in Spotify",
+ "trackOpenInSpotify": "Открыть в Spotify",
"@trackOpenInSpotify": {
"description": "Action - open track in Spotify app"
},
- "trackTrackName": "Track name",
+ "trackTrackName": "Название трека",
"@trackTrackName": {
"description": "Metadata label - track title"
},
- "trackArtist": "Artist",
+ "trackArtist": "Исполнитель",
"@trackArtist": {
"description": "Metadata label - artist name"
},
- "trackAlbumArtist": "Album artist",
+ "trackAlbumArtist": "Исполнитель альбома",
"@trackAlbumArtist": {
"description": "Metadata label - album artist"
},
- "trackAlbum": "Album",
+ "trackAlbum": "Альбом",
"@trackAlbum": {
"description": "Metadata label - album name"
},
- "trackTrackNumber": "Track number",
+ "trackTrackNumber": "Номер трека",
"@trackTrackNumber": {
"description": "Metadata label - track number"
},
- "trackDiscNumber": "Disc number",
+ "trackDiscNumber": "Номер диска",
"@trackDiscNumber": {
"description": "Metadata label - disc number"
},
- "trackDuration": "Duration",
+ "trackDuration": "Продолжительность",
"@trackDuration": {
"description": "Metadata label - track length"
},
- "trackAudioQuality": "Audio quality",
+ "trackAudioQuality": "Качество записи",
"@trackAudioQuality": {
"description": "Metadata label - audio quality"
},
- "trackReleaseDate": "Release date",
+ "trackReleaseDate": "Дата выхода",
"@trackReleaseDate": {
"description": "Metadata label - release date"
},
- "trackDownloaded": "Downloaded",
+ "trackDownloaded": "Скачано",
"@trackDownloaded": {
"description": "Metadata label - download date"
},
- "trackCopyLyrics": "Copy lyrics",
+ "trackCopyLyrics": "Копировать текст",
"@trackCopyLyrics": {
"description": "Action - copy lyrics to clipboard"
},
- "trackLyricsNotAvailable": "Lyrics not available for this track",
+ "trackLyricsNotAvailable": "Текст песни недоступен для этого трека",
"@trackLyricsNotAvailable": {
"description": "Message when lyrics not found"
},
- "trackLyricsTimeout": "Request timed out. Try again later.",
+ "trackLyricsTimeout": "Время ожидания запроса истекло. Повторите попытку позже.",
"@trackLyricsTimeout": {
"description": "Message when lyrics request times out"
},
- "trackLyricsLoadFailed": "Failed to load lyrics",
+ "trackLyricsLoadFailed": "Не удалось загрузить текст песни",
"@trackLyricsLoadFailed": {
"description": "Message when lyrics loading fails"
},
- "trackCopiedToClipboard": "Copied to clipboard",
+ "trackCopiedToClipboard": "Скопировано в буфер обмена",
"@trackCopiedToClipboard": {
"description": "Snackbar - content copied"
},
- "trackDeleteConfirmTitle": "Remove from device?",
+ "trackDeleteConfirmTitle": "Удалить с устройства?",
"@trackDeleteConfirmTitle": {
"description": "Delete confirmation title"
},
- "trackDeleteConfirmMessage": "This will permanently delete the downloaded file and remove it from your history.",
+ "trackDeleteConfirmMessage": "Это приведет к окончательному удалению загруженного файла и его удалению из истории.",
"@trackDeleteConfirmMessage": {
"description": "Delete confirmation message"
},
- "trackCannotOpen": "Cannot open: {message}",
+ "trackCannotOpen": "Невозможно открыть: {message}",
"@trackCannotOpen": {
"description": "Error opening file",
"placeholders": {
@@ -2036,15 +2038,15 @@
}
}
},
- "dateToday": "Today",
+ "dateToday": "Сегодня",
"@dateToday": {
"description": "Relative date - today"
},
- "dateYesterday": "Yesterday",
+ "dateYesterday": "Вчера",
"@dateYesterday": {
"description": "Relative date - yesterday"
},
- "dateDaysAgo": "{count} days ago",
+ "dateDaysAgo": "{count} дней назад",
"@dateDaysAgo": {
"description": "Relative date - days ago",
"placeholders": {
@@ -2053,7 +2055,7 @@
}
}
},
- "dateWeeksAgo": "{count} weeks ago",
+ "dateWeeksAgo": "{count} недель назад",
"@dateWeeksAgo": {
"description": "Relative date - weeks ago",
"placeholders": {
@@ -2062,7 +2064,7 @@
}
}
},
- "dateMonthsAgo": "{count} months ago",
+ "dateMonthsAgo": "{count} месяцев назад",
"@dateMonthsAgo": {
"description": "Relative date - months ago",
"placeholders": {
@@ -2071,71 +2073,71 @@
}
}
},
- "concurrentSequential": "Sequential",
+ "concurrentSequential": "Последовательно",
"@concurrentSequential": {
"description": "Download mode - one at a time"
},
- "concurrentParallel2": "2 Parallel",
+ "concurrentParallel2": "2 параллельно",
"@concurrentParallel2": {
"description": "Download mode - 2 simultaneous"
},
- "concurrentParallel3": "3 Parallel",
+ "concurrentParallel3": "3 параллельно",
"@concurrentParallel3": {
"description": "Download mode - 3 simultaneous"
},
- "tapToSeeError": "Tap to see error details",
+ "tapToSeeError": "Нажмите, чтобы увидеть подробности ошибки",
"@tapToSeeError": {
"description": "Tooltip for failed download"
},
- "storeFilterAll": "All",
+ "storeFilterAll": "Все",
"@storeFilterAll": {
"description": "Store filter - all extensions"
},
- "storeFilterMetadata": "Metadata",
+ "storeFilterMetadata": "Метаданные",
"@storeFilterMetadata": {
"description": "Store filter - metadata providers"
},
- "storeFilterDownload": "Download",
+ "storeFilterDownload": "Скачивание",
"@storeFilterDownload": {
"description": "Store filter - download providers"
},
- "storeFilterUtility": "Utility",
+ "storeFilterUtility": "Утилиты",
"@storeFilterUtility": {
"description": "Store filter - utility extensions"
},
- "storeFilterLyrics": "Lyrics",
+ "storeFilterLyrics": "Тексты песен",
"@storeFilterLyrics": {
"description": "Store filter - lyrics providers"
},
- "storeFilterIntegration": "Integration",
+ "storeFilterIntegration": "Интеграция",
"@storeFilterIntegration": {
"description": "Store filter - integrations"
},
- "storeClearFilters": "Clear filters",
+ "storeClearFilters": "Очистить фильтры",
"@storeClearFilters": {
"description": "Button to clear all filters"
},
- "storeNoResults": "No extensions found",
+ "storeNoResults": "Расширения не найдены",
"@storeNoResults": {
"description": "Empty state when no extensions match filters"
},
- "extensionProviderPriority": "Provider Priority",
+ "extensionProviderPriority": "Приоритет провайдера",
"@extensionProviderPriority": {
"description": "Extension capability - provider priority"
},
- "extensionInstallButton": "Install Extension",
+ "extensionInstallButton": "Установить расширение",
"@extensionInstallButton": {
"description": "Button to install extension"
},
- "extensionDefaultProvider": "Default (Deezer/Spotify)",
+ "extensionDefaultProvider": "По умолчанию (Deezer/Spotify)",
"@extensionDefaultProvider": {
"description": "Default search provider option"
},
- "extensionDefaultProviderSubtitle": "Use built-in search",
+ "extensionDefaultProviderSubtitle": "Использовать встроенный поиск",
"@extensionDefaultProviderSubtitle": {
"description": "Subtitle for default provider"
},
- "extensionAuthor": "Author",
+ "extensionAuthor": "Автор",
"@extensionAuthor": {
"description": "Extension detail - author"
},
@@ -2143,67 +2145,67 @@
"@extensionId": {
"description": "Extension detail - unique ID"
},
- "extensionError": "Error",
+ "extensionError": "Ошибка",
"@extensionError": {
"description": "Extension detail - error message"
},
- "extensionCapabilities": "Capabilities",
+ "extensionCapabilities": "Возможности",
"@extensionCapabilities": {
"description": "Section header - extension features"
},
- "extensionMetadataProvider": "Metadata Provider",
+ "extensionMetadataProvider": "Провайдер метаданных",
"@extensionMetadataProvider": {
"description": "Capability - provides metadata"
},
- "extensionDownloadProvider": "Download Provider",
+ "extensionDownloadProvider": "Провайдер скачивания",
"@extensionDownloadProvider": {
"description": "Capability - provides downloads"
},
- "extensionLyricsProvider": "Lyrics Provider",
+ "extensionLyricsProvider": "Провайдер текстов",
"@extensionLyricsProvider": {
"description": "Capability - provides lyrics"
},
- "extensionUrlHandler": "URL Handler",
+ "extensionUrlHandler": "URL-обработчик",
"@extensionUrlHandler": {
"description": "Capability - handles URLs"
},
- "extensionQualityOptions": "Quality Options",
+ "extensionQualityOptions": "Параметры качества",
"@extensionQualityOptions": {
"description": "Capability - quality selection"
},
- "extensionPostProcessingHooks": "Post-Processing Hooks",
+ "extensionPostProcessingHooks": "Хуки постобработки",
"@extensionPostProcessingHooks": {
"description": "Capability - post-processing"
},
- "extensionPermissions": "Permissions",
+ "extensionPermissions": "Разрешения",
"@extensionPermissions": {
"description": "Section header - required permissions"
},
- "extensionSettings": "Settings",
+ "extensionSettings": "Настройки",
"@extensionSettings": {
"description": "Section header - extension settings"
},
- "extensionRemoveButton": "Remove Extension",
+ "extensionRemoveButton": "Удалить расширение",
"@extensionRemoveButton": {
"description": "Button to uninstall extension"
},
- "extensionUpdated": "Updated",
+ "extensionUpdated": "Обновлено",
"@extensionUpdated": {
"description": "Extension detail - last update"
},
- "extensionMinAppVersion": "Min App Version",
+ "extensionMinAppVersion": "Мин. версия приложения",
"@extensionMinAppVersion": {
"description": "Extension detail - minimum app version"
},
- "extensionCustomTrackMatching": "Custom Track Matching",
+ "extensionCustomTrackMatching": "Соответствие пользовательских треков",
"@extensionCustomTrackMatching": {
"description": "Capability - custom track matching algorithm"
},
- "extensionPostProcessing": "Post-Processing",
+ "extensionPostProcessing": "Постобработка",
"@extensionPostProcessing": {
"description": "Capability - post-download processing"
},
- "extensionHooksAvailable": "{count} hook(s) available",
+ "extensionHooksAvailable": "Доступно {count} хуков(ов)",
"@extensionHooksAvailable": {
"description": "Post-processing hooks count",
"placeholders": {
@@ -2212,7 +2214,7 @@
}
}
},
- "extensionPatternsCount": "{count} pattern(s)",
+ "extensionPatternsCount": "{count} шаблон(ов)",
"@extensionPatternsCount": {
"description": "URL patterns count",
"placeholders": {
@@ -2221,7 +2223,7 @@
}
}
},
- "extensionStrategy": "Strategy: {strategy}",
+ "extensionStrategy": "Стратегия: {strategy}",
"@extensionStrategy": {
"description": "Track matching strategy name",
"placeholders": {
@@ -2230,75 +2232,75 @@
}
}
},
- "extensionsProviderPrioritySection": "Provider Priority",
+ "extensionsProviderPrioritySection": "Приоритет провайдера",
"@extensionsProviderPrioritySection": {
"description": "Section header - provider priority"
},
- "extensionsInstalledSection": "Installed Extensions",
+ "extensionsInstalledSection": "Установленные расширения",
"@extensionsInstalledSection": {
"description": "Section header - installed extensions"
},
- "extensionsNoExtensions": "No extensions installed",
+ "extensionsNoExtensions": "Нет установленных расширений",
"@extensionsNoExtensions": {
"description": "Empty state - no extensions"
},
- "extensionsNoExtensionsSubtitle": "Install .spotiflac-ext files to add new providers",
+ "extensionsNoExtensionsSubtitle": "Установите .spotiflac-ext файлы для добавления новых провайдеров",
"@extensionsNoExtensionsSubtitle": {
"description": "Empty state subtitle"
},
- "extensionsInstallButton": "Install Extension",
+ "extensionsInstallButton": "Установить расширение",
"@extensionsInstallButton": {
"description": "Button to install extension from file"
},
- "extensionsInfoTip": "Extensions can add new metadata and download providers. Only install extensions from trusted sources.",
+ "extensionsInfoTip": "Расширения могут добавлять новые метаданные и провайдеров загрузки. Устанавливайте только расширения из надежных источников.",
"@extensionsInfoTip": {
"description": "Security warning about extensions"
},
- "extensionsInstalledSuccess": "Extension installed successfully",
+ "extensionsInstalledSuccess": "Расширение успешно установлено",
"@extensionsInstalledSuccess": {
"description": "Success message after install"
},
- "extensionsDownloadPriority": "Download Priority",
+ "extensionsDownloadPriority": "Приоритет скачивания",
"@extensionsDownloadPriority": {
"description": "Setting - download provider order"
},
- "extensionsDownloadPrioritySubtitle": "Set download service order",
+ "extensionsDownloadPrioritySubtitle": "Установка порядок сервисов скачивания",
"@extensionsDownloadPrioritySubtitle": {
"description": "Subtitle for download priority"
},
- "extensionsNoDownloadProvider": "No extensions with download provider",
+ "extensionsNoDownloadProvider": "Нет расширений с провайдером загрузки",
"@extensionsNoDownloadProvider": {
"description": "Empty state - no download providers"
},
- "extensionsMetadataPriority": "Metadata Priority",
+ "extensionsMetadataPriority": "Приоритет метаданных",
"@extensionsMetadataPriority": {
"description": "Setting - metadata provider order"
},
- "extensionsMetadataPrioritySubtitle": "Set search & metadata source order",
+ "extensionsMetadataPrioritySubtitle": "Установка порядка поиска и источника метаданных",
"@extensionsMetadataPrioritySubtitle": {
"description": "Subtitle for metadata priority"
},
- "extensionsNoMetadataProvider": "No extensions with metadata provider",
+ "extensionsNoMetadataProvider": "Нет расширений с провайдером метаданных",
"@extensionsNoMetadataProvider": {
"description": "Empty state - no metadata providers"
},
- "extensionsSearchProvider": "Search Provider",
+ "extensionsSearchProvider": "Провайдер поиска",
"@extensionsSearchProvider": {
"description": "Setting - search provider selection"
},
- "extensionsNoCustomSearch": "No extensions with custom search",
+ "extensionsNoCustomSearch": "Нет расширений с пользовательским поиском",
"@extensionsNoCustomSearch": {
"description": "Empty state - no search providers"
},
- "extensionsSearchProviderDescription": "Choose which service to use for searching tracks",
+ "extensionsSearchProviderDescription": "Выберите, какой сервис использовать для поиска треков",
"@extensionsSearchProviderDescription": {
"description": "Search provider setting description"
},
- "extensionsCustomSearch": "Custom search",
+ "extensionsCustomSearch": "Пользовательский поиск",
"@extensionsCustomSearch": {
"description": "Label for custom search provider"
},
- "extensionsErrorLoading": "Error loading extension",
+ "extensionsErrorLoading": "Ошибка загрузки расширения",
"@extensionsErrorLoading": {
"description": "Error message when extension fails to load"
},
@@ -2306,7 +2308,7 @@
"@qualityFlacLossless": {
"description": "Quality option - CD quality FLAC"
},
- "qualityFlacLosslessSubtitle": "16-bit / 44.1kHz",
+ "qualityFlacLosslessSubtitle": "16-бит / 44.1 кГц",
"@qualityFlacLosslessSubtitle": {
"description": "Technical spec for lossless"
},
@@ -2314,91 +2316,91 @@
"@qualityHiResFlac": {
"description": "Quality option - high resolution FLAC"
},
- "qualityHiResFlacSubtitle": "24-bit / up to 96kHz",
+ "qualityHiResFlacSubtitle": "24-бит / до 96кГц",
"@qualityHiResFlacSubtitle": {
"description": "Technical spec for hi-res"
},
- "qualityHiResFlacMax": "Hi-Res FLAC Max",
+ "qualityHiResFlacMax": "Hi-Res FLAC Макс.",
"@qualityHiResFlacMax": {
"description": "Quality option - maximum resolution FLAC"
},
- "qualityHiResFlacMaxSubtitle": "24-bit / up to 192kHz",
+ "qualityHiResFlacMaxSubtitle": "24-бит / до 192кГц",
"@qualityHiResFlacMaxSubtitle": {
"description": "Technical spec for hi-res max"
},
- "qualityNote": "Actual quality depends on track availability from the service",
+ "qualityNote": "Фактическое качество зависит от доступности треков в сервисе",
"@qualityNote": {
"description": "Note about quality availability"
},
- "downloadAskBeforeDownload": "Ask Before Download",
+ "downloadAskBeforeDownload": "Спрашивать перед скачиванием",
"@downloadAskBeforeDownload": {
"description": "Setting - show quality picker"
},
- "downloadDirectory": "Download Directory",
+ "downloadDirectory": "Папка для скачивания",
"@downloadDirectory": {
"description": "Setting - download folder"
},
- "downloadSeparateSinglesFolder": "Separate Singles Folder",
+ "downloadSeparateSinglesFolder": "Отдельная папка для синглов",
"@downloadSeparateSinglesFolder": {
"description": "Setting - separate folder for singles"
},
- "downloadAlbumFolderStructure": "Album Folder Structure",
+ "downloadAlbumFolderStructure": "Структура папок альбома",
"@downloadAlbumFolderStructure": {
"description": "Setting - album folder organization"
},
- "downloadSaveFormat": "Save Format",
+ "downloadSaveFormat": "Формат сохранения",
"@downloadSaveFormat": {
"description": "Setting - output file format"
},
- "downloadSelectService": "Select Service",
+ "downloadSelectService": "Выбор сервиса",
"@downloadSelectService": {
"description": "Dialog title - choose download service"
},
- "downloadSelectQuality": "Select Quality",
+ "downloadSelectQuality": "Выбор качества",
"@downloadSelectQuality": {
"description": "Dialog title - choose audio quality"
},
- "downloadFrom": "Download From",
+ "downloadFrom": "Скачивать из",
"@downloadFrom": {
"description": "Label - download source"
},
- "downloadDefaultQualityLabel": "Default Quality",
+ "downloadDefaultQualityLabel": "Качество по умолчанию",
"@downloadDefaultQualityLabel": {
"description": "Label - default quality setting"
},
- "downloadBestAvailable": "Best available",
+ "downloadBestAvailable": "Лучшее из доступных",
"@downloadBestAvailable": {
"description": "Quality option - highest available"
},
- "folderNone": "None",
+ "folderNone": "Отсутствует",
"@folderNone": {
"description": "Folder option - no organization"
},
- "folderNoneSubtitle": "Save all files directly to download folder",
+ "folderNoneSubtitle": "Сохранить все файлы непосредственно в папку загрузки",
"@folderNoneSubtitle": {
"description": "Subtitle for no folder organization"
},
- "folderArtist": "Artist",
+ "folderArtist": "Исполнитель",
"@folderArtist": {
"description": "Folder option - by artist"
},
- "folderArtistSubtitle": "Artist Name/filename",
+ "folderArtistSubtitle": "Исполнитель/имя файла",
"@folderArtistSubtitle": {
"description": "Folder structure example"
},
- "folderAlbum": "Album",
+ "folderAlbum": "Альбом",
"@folderAlbum": {
"description": "Folder option - by album"
},
- "folderAlbumSubtitle": "Album Name/filename",
+ "folderAlbumSubtitle": "Альбом/имя файла",
"@folderAlbumSubtitle": {
"description": "Folder structure example"
},
- "folderArtistAlbum": "Artist/Album",
+ "folderArtistAlbum": "Исполнитель/Альбом",
"@folderArtistAlbum": {
"description": "Folder option - nested"
},
- "folderArtistAlbumSubtitle": "Artist Name/Album Name/filename",
+ "folderArtistAlbumSubtitle": "Исполнитель/ Альбом/имя файла",
"@folderArtistAlbumSubtitle": {
"description": "Folder structure example"
},
@@ -2422,103 +2424,103 @@
"@serviceSpotify": {
"description": "Service name - DO NOT TRANSLATE"
},
- "appearanceAmoledDark": "AMOLED Dark",
+ "appearanceAmoledDark": "AMOLED",
"@appearanceAmoledDark": {
"description": "Theme option - pure black"
},
- "appearanceAmoledDarkSubtitle": "Pure black background",
+ "appearanceAmoledDarkSubtitle": "Глубокий чёрный фон",
"@appearanceAmoledDarkSubtitle": {
"description": "Subtitle for AMOLED dark"
},
- "appearanceChooseAccentColor": "Choose Accent Color",
+ "appearanceChooseAccentColor": "Выберите акцентный цвет",
"@appearanceChooseAccentColor": {
"description": "Color picker dialog title"
},
- "appearanceChooseTheme": "Theme Mode",
+ "appearanceChooseTheme": "Режим темы",
"@appearanceChooseTheme": {
"description": "Theme picker dialog title"
},
- "queueTitle": "Download Queue",
+ "queueTitle": "Очередь скачиваний",
"@queueTitle": {
"description": "Queue screen title"
},
- "queueClearAll": "Clear All",
+ "queueClearAll": "Очистить всё",
"@queueClearAll": {
"description": "Button - clear all queue items"
},
- "queueClearAllMessage": "Are you sure you want to clear all downloads?",
+ "queueClearAllMessage": "Вы уверены, что хотите очистить все загрузки?",
"@queueClearAllMessage": {
"description": "Clear queue confirmation"
},
- "queueEmpty": "No downloads in queue",
+ "queueEmpty": "Нет загрузок в очереди",
"@queueEmpty": {
"description": "Empty queue state title"
},
- "queueEmptySubtitle": "Add tracks from the home screen",
+ "queueEmptySubtitle": "Добавить треки с главного экрана",
"@queueEmptySubtitle": {
"description": "Empty queue state subtitle"
},
- "queueClearCompleted": "Clear completed",
+ "queueClearCompleted": "Очистка завершена",
"@queueClearCompleted": {
"description": "Button - clear finished downloads"
},
- "queueDownloadFailed": "Download Failed",
+ "queueDownloadFailed": "Ошибка скачивания",
"@queueDownloadFailed": {
"description": "Error dialog title"
},
- "queueTrackLabel": "Track:",
+ "queueTrackLabel": "Трек:",
"@queueTrackLabel": {
"description": "Label in error dialog"
},
- "queueArtistLabel": "Artist:",
+ "queueArtistLabel": "Исполнитель:",
"@queueArtistLabel": {
"description": "Label in error dialog"
},
- "queueErrorLabel": "Error:",
+ "queueErrorLabel": "Ошибка:",
"@queueErrorLabel": {
"description": "Label in error dialog"
},
- "queueUnknownError": "Unknown error",
+ "queueUnknownError": "Неизвестная ошибка",
"@queueUnknownError": {
"description": "Fallback error message"
},
- "albumFolderArtistAlbum": "Artist / Album",
+ "albumFolderArtistAlbum": "Исполнитель / Альбом",
"@albumFolderArtistAlbum": {
"description": "Album folder option"
},
- "albumFolderArtistAlbumSubtitle": "Albums/Artist Name/Album Name/",
+ "albumFolderArtistAlbumSubtitle": "Альбомы/Исполнитель/Название Альбома/",
"@albumFolderArtistAlbumSubtitle": {
"description": "Folder structure example"
},
- "albumFolderArtistYearAlbum": "Artist / [Year] Album",
+ "albumFolderArtistYearAlbum": "Исполнитель / [Год] Альбом",
"@albumFolderArtistYearAlbum": {
"description": "Album folder option with year"
},
- "albumFolderArtistYearAlbumSubtitle": "Albums/Artist Name/[2005] Album Name/",
+ "albumFolderArtistYearAlbumSubtitle": "Альбомы/Исполнитель/[2005] Название Альбома/",
"@albumFolderArtistYearAlbumSubtitle": {
"description": "Folder structure example"
},
- "albumFolderAlbumOnly": "Album Only",
+ "albumFolderAlbumOnly": "Только альбом",
"@albumFolderAlbumOnly": {
"description": "Album folder option"
},
- "albumFolderAlbumOnlySubtitle": "Albums/Album Name/",
+ "albumFolderAlbumOnlySubtitle": "Альбомы/Название Альбома/",
"@albumFolderAlbumOnlySubtitle": {
"description": "Folder structure example"
},
- "albumFolderYearAlbum": "[Year] Album",
+ "albumFolderYearAlbum": "[Год] Альбом",
"@albumFolderYearAlbum": {
"description": "Album folder option with year"
},
- "albumFolderYearAlbumSubtitle": "Albums/[2005] Album Name/",
+ "albumFolderYearAlbumSubtitle": "Альбомы/[2005] Название Альбома /",
"@albumFolderYearAlbumSubtitle": {
"description": "Folder structure example"
},
- "downloadedAlbumDeleteSelected": "Delete Selected",
+ "downloadedAlbumDeleteSelected": "Удалить выбранные",
"@downloadedAlbumDeleteSelected": {
"description": "Button - delete selected tracks"
},
- "downloadedAlbumDeleteMessage": "Delete {count} {count, plural, =1{track} other{tracks}} from this album?\n\nThis will also delete the files from storage.",
+ "downloadedAlbumDeleteMessage": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}} из этого альбома?\n\nЭто также удалит файлы из хранилища.",
"@downloadedAlbumDeleteMessage": {
"description": "Delete confirmation with count",
"placeholders": {
@@ -2527,11 +2529,11 @@
}
}
},
- "downloadedAlbumTracksHeader": "Tracks",
+ "downloadedAlbumTracksHeader": "Треки",
"@downloadedAlbumTracksHeader": {
"description": "Section header for tracks"
},
- "downloadedAlbumDownloadedCount": "{count} downloaded",
+ "downloadedAlbumDownloadedCount": "{count} скачано",
"@downloadedAlbumDownloadedCount": {
"description": "Downloaded tracks count badge",
"placeholders": {
@@ -2540,7 +2542,7 @@
}
}
},
- "downloadedAlbumSelectedCount": "{count} selected",
+ "downloadedAlbumSelectedCount": "{count} выбрано",
"@downloadedAlbumSelectedCount": {
"description": "Selection count indicator",
"placeholders": {
@@ -2549,15 +2551,15 @@
}
}
},
- "downloadedAlbumAllSelected": "All tracks selected",
+ "downloadedAlbumAllSelected": "Все треки выбраны",
"@downloadedAlbumAllSelected": {
"description": "Status - all items selected"
},
- "downloadedAlbumTapToSelect": "Tap tracks to select",
+ "downloadedAlbumTapToSelect": "Нажмите на треки для выбора",
"@downloadedAlbumTapToSelect": {
"description": "Selection hint"
},
- "downloadedAlbumDeleteCount": "Delete {count} {count, plural, =1{track} other{tracks}}",
+ "downloadedAlbumDeleteCount": "Удалить {count} {count, plural, one {трек} few {трека} many {треков} =1{трек} other{треков}}",
"@downloadedAlbumDeleteCount": {
"description": "Delete button text with count",
"placeholders": {
@@ -2566,12 +2568,48 @@
}
}
},
- "downloadedAlbumSelectToDelete": "Select tracks to delete",
+ "downloadedAlbumSelectToDelete": "Выберите треки для удаления",
"@downloadedAlbumSelectToDelete": {
"description": "Placeholder when nothing selected"
},
- "utilityFunctions": "Utility Functions",
+ "utilityFunctions": "Функции утилиты",
"@utilityFunctions": {
"description": "Extension capability - utility functions"
+ },
+ "recentTypeArtist": "Исполнитель",
+ "@recentTypeArtist": {
+ "description": "Recent access item type - artist"
+ },
+ "recentTypeAlbum": "Альбом",
+ "@recentTypeAlbum": {
+ "description": "Recent access item type - album"
+ },
+ "recentTypeSong": "Песня",
+ "@recentTypeSong": {
+ "description": "Recent access item type - song/track"
+ },
+ "recentTypePlaylist": "Плейлист",
+ "@recentTypePlaylist": {
+ "description": "Recent access item type - playlist"
+ },
+ "recentPlaylistInfo": "Плейлист: {name}",
+ "@recentPlaylistInfo": {
+ "description": "Snackbar message when tapping playlist in recent access",
+ "placeholders": {
+ "name": {
+ "type": "String",
+ "description": "Playlist name"
+ }
+ }
+ },
+ "errorGeneric": "Ошибка: {message}",
+ "@errorGeneric": {
+ "description": "Generic error message format",
+ "placeholders": {
+ "message": {
+ "type": "String",
+ "description": "Error message"
+ }
+ }
}
}
\ No newline at end of file
diff --git a/lib/l10n/arb/app_zh_CN.arb b/lib/l10n/arb/app_zh_CN.arb
index 07634e27..7e545a0b 100644
--- a/lib/l10n/arb/app_zh_CN.arb
+++ b/lib/l10n/arb/app_zh_CN.arb
@@ -1,5 +1,5 @@
{
- "@@locale": "zh_CN",
+ "@@locale": "zh-CN",
"@@last_modified": "2026-01-16",
"appName": "SpotiFLAC",
"@appName": {
@@ -642,6 +642,20 @@
}
}
},
+ "artistPopular": "Popular",
+ "@artistPopular": {
+ "description": "Section header for popular/top tracks"
+ },
+ "artistMonthlyListeners": "{count} monthly listeners",
+ "@artistMonthlyListeners": {
+ "description": "Monthly listener count display",
+ "placeholders": {
+ "count": {
+ "type": "String",
+ "description": "Formatted listener count"
+ }
+ }
+ },
"trackMetadataTitle": "Track Info",
"@trackMetadataTitle": {
"description": "Track metadata screen title"
@@ -1851,27 +1865,15 @@
},
"sectionLanguage": "Language",
"@sectionLanguage": {
- "description": "Settings section header for language selection"
+ "description": "Settings section header for language"
},
"appearanceLanguage": "App Language",
"@appearanceLanguage": {
- "description": "Setting title for language selection"
+ "description": "Language setting title"
},
"appearanceLanguageSubtitle": "Choose your preferred language",
"@appearanceLanguageSubtitle": {
- "description": "Subtitle for language setting"
- },
- "languageSystem": "System Default",
- "@languageSystem": {
- "description": "Use device system language"
- },
- "languageEnglish": "English",
- "@languageEnglish": {
- "description": "English language option"
- },
- "languageIndonesian": "Bahasa Indonesia",
- "@languageIndonesian": {
- "description": "Indonesian language option"
+ "description": "Language setting subtitle"
},
"settingsAppearanceSubtitle": "Theme, colors, display",
"@settingsAppearanceSubtitle": {
@@ -2573,5 +2575,41 @@
"utilityFunctions": "Utility Functions",
"@utilityFunctions": {
"description": "Extension capability - utility functions"
+ },
+ "recentTypeArtist": "Artist",
+ "@recentTypeArtist": {
+ "description": "Recent access item type - artist"
+ },
+ "recentTypeAlbum": "Album",
+ "@recentTypeAlbum": {
+ "description": "Recent access item type - album"
+ },
+ "recentTypeSong": "Song",
+ "@recentTypeSong": {
+ "description": "Recent access item type - song/track"
+ },
+ "recentTypePlaylist": "Playlist",
+ "@recentTypePlaylist": {
+ "description": "Recent access item type - playlist"
+ },
+ "recentPlaylistInfo": "Playlist: {name}",
+ "@recentPlaylistInfo": {
+ "description": "Snackbar message when tapping playlist in recent access",
+ "placeholders": {
+ "name": {
+ "type": "String",
+ "description": "Playlist name"
+ }
+ }
+ },
+ "errorGeneric": "Error: {message}",
+ "@errorGeneric": {
+ "description": "Generic error message format",
+ "placeholders": {
+ "message": {
+ "type": "String",
+ "description": "Error message"
+ }
+ }
}
}
\ No newline at end of file
diff --git a/lib/l10n/arb/app_zh_TW.arb b/lib/l10n/arb/app_zh_TW.arb
index d22c0ab4..8526e88f 100644
--- a/lib/l10n/arb/app_zh_TW.arb
+++ b/lib/l10n/arb/app_zh_TW.arb
@@ -1,5 +1,5 @@
{
- "@@locale": "zh_TW",
+ "@@locale": "zh-TW",
"@@last_modified": "2026-01-16",
"appName": "SpotiFLAC",
"@appName": {
@@ -642,6 +642,20 @@
}
}
},
+ "artistPopular": "Popular",
+ "@artistPopular": {
+ "description": "Section header for popular/top tracks"
+ },
+ "artistMonthlyListeners": "{count} monthly listeners",
+ "@artistMonthlyListeners": {
+ "description": "Monthly listener count display",
+ "placeholders": {
+ "count": {
+ "type": "String",
+ "description": "Formatted listener count"
+ }
+ }
+ },
"trackMetadataTitle": "Track Info",
"@trackMetadataTitle": {
"description": "Track metadata screen title"
@@ -1849,6 +1863,18 @@
"@sectionLayout": {
"description": "Settings section header"
},
+ "sectionLanguage": "Language",
+ "@sectionLanguage": {
+ "description": "Settings section header for language"
+ },
+ "appearanceLanguage": "App Language",
+ "@appearanceLanguage": {
+ "description": "Language setting title"
+ },
+ "appearanceLanguageSubtitle": "Choose your preferred language",
+ "@appearanceLanguageSubtitle": {
+ "description": "Language setting subtitle"
+ },
"settingsAppearanceSubtitle": "Theme, colors, display",
"@settingsAppearanceSubtitle": {
"description": "Appearance settings description"
@@ -2549,5 +2575,41 @@
"utilityFunctions": "Utility Functions",
"@utilityFunctions": {
"description": "Extension capability - utility functions"
+ },
+ "recentTypeArtist": "Artist",
+ "@recentTypeArtist": {
+ "description": "Recent access item type - artist"
+ },
+ "recentTypeAlbum": "Album",
+ "@recentTypeAlbum": {
+ "description": "Recent access item type - album"
+ },
+ "recentTypeSong": "Song",
+ "@recentTypeSong": {
+ "description": "Recent access item type - song/track"
+ },
+ "recentTypePlaylist": "Playlist",
+ "@recentTypePlaylist": {
+ "description": "Recent access item type - playlist"
+ },
+ "recentPlaylistInfo": "Playlist: {name}",
+ "@recentPlaylistInfo": {
+ "description": "Snackbar message when tapping playlist in recent access",
+ "placeholders": {
+ "name": {
+ "type": "String",
+ "description": "Playlist name"
+ }
+ }
+ },
+ "errorGeneric": "Error: {message}",
+ "@errorGeneric": {
+ "description": "Generic error message format",
+ "placeholders": {
+ "message": {
+ "type": "String",
+ "description": "Error message"
+ }
+ }
}
}
\ No newline at end of file
diff --git a/lib/l10n/supported_locales.dart b/lib/l10n/supported_locales.dart
index 65a59b24..e170ed11 100644
--- a/lib/l10n/supported_locales.dart
+++ b/lib/l10n/supported_locales.dart
@@ -1,24 +1,48 @@
// GENERATED FILE - DO NOT EDIT
-// Generated by: dart run tool/check_translations.dart 70
-// Only languages with >= 70% translation completion are included.
+// Generated by: dart run tool/check_translations.dart 0
+// Only languages with >= 0% translation completion are included.
// Translation is measured by comparing VALUES (not just key existence).
//
-// To regenerate, run: dart run tool/check_translations.dart 70
+// To regenerate, run: dart run tool/check_translations.dart 0
import 'package:flutter/widgets.dart';
/// Minimum translation completion threshold used to filter languages.
-const int translationThreshold = 70;
+const int translationThreshold = 0;
/// List of locales that meet the translation threshold.
/// Only these languages will be available in the app.
const List filteredSupportedLocales = [
Locale('en'),
+ Locale('ru'),
Locale('id'),
+ Locale('ja'),
+ Locale('de'),
+ Locale('es'),
+ Locale('fr'),
+ Locale('hi'),
+ Locale('ko'),
+ Locale('nl'),
+ Locale('pt'),
+ Locale('zh'),
+ Locale('zh', 'CN'),
+ Locale('zh', 'TW'),
];
/// Set of locale codes for quick lookup.
const Set filteredLocaleCodes = {
'en',
+ 'ru',
'id',
+ 'ja',
+ 'de',
+ 'es',
+ 'fr',
+ 'hi',
+ 'ko',
+ 'nl',
+ 'pt',
+ 'zh',
+ 'zh_CN',
+ 'zh_TW',
};
diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart
index 6da7e81e..2712251d 100644
--- a/lib/providers/download_queue_provider.dart
+++ b/lib/providers/download_queue_provider.dart
@@ -1084,11 +1084,15 @@ class DownloadQueueNotifier extends Notifier {
_log.d('Metadata map content: $metadata');
try {
+ // Convert duration from seconds to milliseconds for better lyrics matching
+ final durationMs = track.duration * 1000;
+
final lrcContent = await PlatformBridge.getLyricsLRC(
track.id, // spotifyID
track.name,
track.artistName,
filePath: '', // No local file path yet (processed in memory)
+ durationMs: durationMs,
);
if (lrcContent.isNotEmpty) {
diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart
index d79c5c47..bdabe145 100644
--- a/lib/screens/home_tab.dart
+++ b/lib/screens/home_tab.dart
@@ -32,6 +32,21 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient
final FocusNode _searchFocusNode = FocusNode();
String? _lastSearchQuery;
+ /// Debounce timer for live search (extension-only feature)
+ Timer? _liveSearchDebounce;
+
+ /// Flag to prevent concurrent live search calls (prevents race conditions in extensions)
+ bool _isLiveSearchInProgress = false;
+
+ /// Pending query to execute after current search completes
+ String? _pendingLiveSearchQuery;
+
+ /// Minimum characters required to trigger live search
+ static const int _minLiveSearchChars = 3;
+
+ /// Debounce duration for live search
+ static const Duration _liveSearchDelay = Duration(milliseconds: 800);
+
@override
bool get wantKeepAlive => true;
@@ -44,6 +59,7 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient
@override
void dispose() {
+ _liveSearchDebounce?.cancel();
_urlController.removeListener(_onSearchChanged);
_searchFocusNode.removeListener(_onSearchFocusChanged);
_urlController.dispose();
@@ -68,7 +84,22 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient
_urlController.clear();
setState(() => _isTyping = false);
}
- } void _onSearchChanged() {
+ }
+
+ /// Check if live search is available (extension is set as search provider)
+ bool _isLiveSearchEnabled() {
+ final settings = ref.read(settingsProvider);
+ final extState = ref.read(extensionProvider);
+ final searchProvider = settings.searchProvider;
+
+ if (searchProvider == null || searchProvider.isEmpty) return false;
+
+ // Check if the extension is enabled and has search capability
+ final extension = extState.extensions.where((e) => e.id == searchProvider && e.enabled).firstOrNull;
+ return extension != null;
+ }
+
+ void _onSearchChanged() {
final text = _urlController.text.trim();
ref.read(trackProvider.notifier).setSearchText(text.isNotEmpty);
@@ -77,10 +108,60 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient
setState(() => _isTyping = true);
} else if (text.isEmpty && _isTyping) {
setState(() => _isTyping = false);
+ _liveSearchDebounce?.cancel();
// Don't clear provider here - it causes focus issues
// Provider will be cleared when user explicitly clears or navigates away
return;
}
+
+ // Live search - only for extensions
+ if (_isLiveSearchEnabled() && text.length >= _minLiveSearchChars) {
+ // Skip if it's a URL (let user press enter for URLs)
+ if (text.startsWith('http') || text.startsWith('spotify:')) return;
+
+ _liveSearchDebounce?.cancel();
+ _liveSearchDebounce = Timer(_liveSearchDelay, () {
+ if (mounted && _urlController.text.trim() == text) {
+ _executeLiveSearch(text);
+ }
+ });
+ }
+ }
+
+ /// Execute live search with concurrency protection
+ /// Prevents race conditions in extensions by ensuring only one search runs at a time
+ Future _executeLiveSearch(String query) async {
+ // If a search is already in progress, queue this one
+ if (_isLiveSearchInProgress) {
+ _pendingLiveSearchQuery = query;
+ return;
+ }
+
+ _isLiveSearchInProgress = true;
+ _pendingLiveSearchQuery = null;
+
+ try {
+ await _performSearch(query);
+ } finally {
+ _isLiveSearchInProgress = false;
+
+ // Check if there's a pending query that was queued while we were searching
+ final pending = _pendingLiveSearchQuery;
+ _pendingLiveSearchQuery = null;
+
+ // Execute pending query if it's different from what we just searched
+ // and still matches current text field content
+ if (pending != null &&
+ pending != query &&
+ mounted &&
+ _urlController.text.trim() == pending) {
+ // Small delay to let extension's state settle
+ await Future.delayed(const Duration(milliseconds: 100));
+ if (mounted && _urlController.text.trim() == pending) {
+ _executeLiveSearch(pending);
+ }
+ }
+ }
}
Future _performSearch(String query) async {
@@ -119,6 +200,8 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient
}
Future _clearAndRefresh() async {
+ _liveSearchDebounce?.cancel();
+ _pendingLiveSearchQuery = null;
_urlController.clear();
_searchFocusNode.unfocus();
_lastSearchQuery = null;
@@ -1260,6 +1343,10 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient
/// Handle Enter key press - search or fetch URL
void _onSearchSubmitted() {
+ // Cancel any pending live search since user explicitly pressed enter
+ _liveSearchDebounce?.cancel();
+ _pendingLiveSearchQuery = null;
+
final text = _urlController.text.trim();
if (text.isEmpty) return;
diff --git a/lib/screens/queue_tab.dart b/lib/screens/queue_tab.dart
index 8c345d6d..2bf54f5f 100644
--- a/lib/screens/queue_tab.dart
+++ b/lib/screens/queue_tab.dart
@@ -452,8 +452,14 @@ class _QueueTabState extends ConsumerState {
},
child: Stack(
children: [
- NestedScrollView(
- headerSliverBuilder: (context, innerBoxIsScrolled) => [
+ // ScrollConfiguration disables stretch overscroll to fix _StretchController exception
+ // This is a known Flutter issue with NestedScrollView + Material 3 stretch indicator
+ ScrollConfiguration(
+ behavior: ScrollConfiguration.of(context).copyWith(
+ overscroll: false,
+ ),
+ child: NestedScrollView(
+ headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverAppBar(
expandedHeight: 120 + topPadding,
collapsedHeight: kToolbarHeight,
@@ -696,6 +702,7 @@ class _QueueTabState extends ConsumerState {
),
),
),
+ ), // ScrollConfiguration
AnimatedPositioned(
duration: const Duration(milliseconds: 250),
diff --git a/lib/screens/track_metadata_screen.dart b/lib/screens/track_metadata_screen.dart
index 39adcfec..67693d2a 100644
--- a/lib/screens/track_metadata_screen.dart
+++ b/lib/screens/track_metadata_screen.dart
@@ -766,12 +766,16 @@ class _TrackMetadataScreenState extends ConsumerState {
});
try {
+ // Convert duration from seconds to milliseconds
+ final durationMs = (item.duration ?? 0) * 1000;
+
// Add timeout to prevent infinite loading
final result = await PlatformBridge.getLyricsLRC(
item.spotifyId ?? '',
item.trackName,
item.artistName,
filePath: _fileExists ? cleanFilePath : null, // Try embedded lyrics first
+ durationMs: durationMs,
).timeout(
const Duration(seconds: 20),
onTimeout: () => '', // Return empty string on timeout
diff --git a/lib/services/csv_import_service.dart b/lib/services/csv_import_service.dart
index e8090b4e..b2ef0a1c 100644
--- a/lib/services/csv_import_service.dart
+++ b/lib/services/csv_import_service.dart
@@ -156,10 +156,10 @@ class CsvImportService {
}
String? trackName = getVal(['track name', 'track', 'name', 'title']);
- String? artistName = getVal(['artist name', 'artist']);
+ String? artistName = getVal(['artist name(s)', 'artist name', 'artist', 'artists']);
String? albumName = getVal(['album name', 'album']);
String? isrc = getVal(['isrc']);
- String? spotifyId = getVal(['spotify - id', 'spotify id', 'id', 'uri']);
+ String? spotifyId = getVal(['track uri', 'spotify - id', 'spotify id', 'spotify_id', 'id', 'uri']);
if (spotifyId != null && spotifyId.startsWith('spotify:track:')) {
spotifyId = spotifyId.replaceAll('spotify:track:', '');
diff --git a/lib/services/platform_bridge.dart b/lib/services/platform_bridge.dart
index 2c7bc6cb..3df93bfc 100644
--- a/lib/services/platform_bridge.dart
+++ b/lib/services/platform_bridge.dart
@@ -236,32 +236,38 @@ class PlatformBridge {
}
/// Fetch lyrics for a track
+ /// [durationMs] is the track duration in milliseconds for better matching
static Future