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 b0b47717..327bc820 100644 --- a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt +++ b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt @@ -3152,6 +3152,13 @@ class MainActivity: FlutterFragmentActivity() { } result.success(null) } + "setMetadataLanguage" -> { + val tag = call.argument("tag") ?: "" + withContext(Dispatchers.IO) { + Gobackend.setMetadataLanguage(tag) + } + result.success(null) + } "getLogCount" -> { val count = withContext(Dispatchers.IO) { Gobackend.getLogCount() diff --git a/go_backend/deezer.go b/go_backend/deezer.go index 8d78730e..55742ea3 100644 --- a/go_backend/deezer.go +++ b/go_backend/deezer.go @@ -1321,6 +1321,10 @@ func (c *DeezerClient) doGetJSON(ctx context.Context, endpoint string, dst any) } req.Header.Set("Accept", "application/json") + // Without an explicit language Deezer localizes artist/genre names by + // the caller's IP geolocation (issue #480: Arabic metadata on an + // English device). Follow the app's display language instead. + req.Header.Set("Accept-Language", metadataAcceptLanguage()) resp, err := c.httpClient.Do(req) if err != nil { diff --git a/go_backend/exports.go b/go_backend/exports.go index 12b13832..1a519c18 100644 --- a/go_backend/exports.go +++ b/go_backend/exports.go @@ -4,8 +4,33 @@ import ( "encoding/json" "runtime/debug" "strings" + "sync" ) +var ( + metadataLanguageMu sync.RWMutex + metadataLanguageTag string +) + +// SetMetadataLanguage sets the app's display language (BCP 47 tag, e.g. +// "en-US" or "id"), used as Accept-Language on metadata API requests so +// providers localize names by the app language instead of IP geolocation. +func SetMetadataLanguage(tag string) { + metadataLanguageMu.Lock() + metadataLanguageTag = strings.TrimSpace(tag) + metadataLanguageMu.Unlock() +} + +func metadataAcceptLanguage() string { + metadataLanguageMu.RLock() + tag := metadataLanguageTag + metadataLanguageMu.RUnlock() + if tag == "" || strings.HasPrefix(strings.ToLower(tag), "en") { + return "en-US,en;q=0.9" + } + return tag + ",en;q=0.8" +} + // ReleaseMemory drops idle pooled extension runtimes, forces a GC, and // returns freed heap to the OS. Called from the app on OS memory pressure and // when backgrounded, so the Go side's RSS doesn't sit at its high-water mark diff --git a/lib/screens/main_shell.dart b/lib/screens/main_shell.dart index bfbb09b2..4e8e2330 100644 --- a/lib/screens/main_shell.dart +++ b/lib/screens/main_shell.dart @@ -75,6 +75,13 @@ class _MainShellState extends ConsumerState setPlaybackNormalizationEnabled( ref.read(settingsProvider).playbackNormalization, ); + // Deezer & co. localize artist/genre names by IP unless told the app's + // language (issue #480). + unawaited( + PlatformBridge.setMetadataLanguage( + Localizations.localeOf(context).toLanguageTag(), + ), + ); } @override diff --git a/lib/services/platform_bridge.dart b/lib/services/platform_bridge.dart index 09a11432..110d72ac 100644 --- a/lib/services/platform_bridge.dart +++ b/lib/services/platform_bridge.dart @@ -1210,6 +1210,14 @@ class PlatformBridge { } catch (_) {} } + /// Tells the backend the app's display language so metadata providers + /// localize by it instead of IP geolocation. Best-effort. + static Future setMetadataLanguage(String tag) async { + try { + await _channel.invokeMethod('setMetadataLanguage', {'tag': tag}); + } catch (_) {} + } + static Future getGoLogCount() async { final result = await _channel.invokeMethod('getLogCount'); return result as int;