From 95b013e155352aebbcd4251bce10ca91c84fceb1 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sun, 2 Aug 2026 15:35:42 +0200 Subject: [PATCH] fix(seerr): show worldwide popular titles in Explore again Overseerr and Jellyseerr bind the `language` query parameter of `/discover/movies` and `/discover/tv` to `originalLanguage`, which becomes TMDB's `with_original_language`. Sending the app locale there collapsed both shelves to titles originally made in that language, so a Portuguese UI saw only Portuguese films. Those two routes take their display language from the instance/user locale, which already wins over the query value, so the parameter was pure filtering with no localization to show for it. Drop it from the two paged discover routes. Trending, both upcoming rows, search, details and recommendations keep it: Seerr treats it as the display language everywhere else. close #1763 --- lib/services/seerr/seerr_client.dart | 28 +++++++++++++++++---- test/services/seerr/seerr_client_test.dart | 29 ++++++++++++++++++---- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/lib/services/seerr/seerr_client.dart b/lib/services/seerr/seerr_client.dart index cbf8def9..c3c2a0f4 100644 --- a/lib/services/seerr/seerr_client.dart +++ b/lib/services/seerr/seerr_client.dart @@ -89,10 +89,20 @@ class SeerrClient { // ---------- Discover / search ---------- /// `/discover/movies` — popular movies. - Future> getPopularMovies({int page = 1}) => _mediaPage('/discover/movies', page, 'movie'); + /// + /// Deliberately unlocalized. Overseerr and Jellyseerr bind this route's + /// `language` query parameter to `originalLanguage`, i.e. TMDB's + /// `with_original_language`, so sending the app locale narrows the shelf to + /// titles *originally made* in that language (#1763). The display language + /// here comes from the instance/user locale, which already wins over the + /// query value, so omitting it costs nothing. + Future> getPopularMovies({int page = 1}) => + _mediaPage('/discover/movies', page, 'movie', localized: false); - /// `/discover/tv` — popular series. - Future> getPopularTv({int page = 1}) => _mediaPage('/discover/tv', page, 'tv'); + /// `/discover/tv` — popular series. Unlocalized for the same reason as + /// [getPopularMovies]. + Future> getPopularTv({int page = 1}) => + _mediaPage('/discover/tv', page, 'tv', localized: false); Future> getUpcomingMovies({int page = 1}) => _mediaPage('/discover/movies/upcoming', page, 'movie'); @@ -118,8 +128,16 @@ class SeerrClient { Future> getTvRecommendations(int tmdbId, {int page = 1}) => _mediaPage('/tv/$tmdbId/recommendations', page, 'tv'); - Future> _mediaPage(String path, int page, String? coerceMediaType) async { - final data = await _request('GET', path, query: {'page': page, 'language': _language}); + /// [localized] adds the app locale as `language`. Only the two paged + /// discover routes opt out; everywhere else Seerr treats it as the display + /// language, which is what we want. + Future> _mediaPage( + String path, + int page, + String? coerceMediaType, { + bool localized = true, + }) async { + final data = await _request('GET', path, query: {'page': page, if (localized) 'language': _language}); return _parseMediaPage(data, coerceMediaType); } diff --git a/test/services/seerr/seerr_client_test.dart b/test/services/seerr/seerr_client_test.dart index f3f3c8c0..88f7091c 100644 --- a/test/services/seerr/seerr_client_test.dart +++ b/test/services/seerr/seerr_client_test.dart @@ -297,7 +297,7 @@ void main() { expect(page.totalResults, 37); }); - test('adds the current Plezy locale to every catalog GET', () async { + test('adds the current Plezy locale to the catalog GETs Seerr localizes', () async { final urls = []; final client = clientWith( MockClient((request) async { @@ -309,8 +309,6 @@ void main() { }), ); - await client.getPopularMovies(); - await client.getPopularTv(); await client.getUpcomingMovies(); await client.getUpcomingTv(); await client.getTrending(); @@ -321,8 +319,6 @@ void main() { await client.getTv(4); expect(urls.map((url) => url.path).toSet(), { - '/api/v1/discover/movies', - '/api/v1/discover/tv', '/api/v1/discover/movies/upcoming', '/api/v1/discover/tv/upcoming', '/api/v1/discover/trending', @@ -338,6 +334,29 @@ void main() { } }); + test('popular rows omit language so Seerr cannot filter them by original language', () async { + // Overseerr and Jellyseerr pass `/discover/movies` and `/discover/tv`'s + // `language` straight into `originalLanguage`, i.e. TMDB's + // `with_original_language`. Sending the app locale collapsed both shelves + // to titles originally made in that language (#1763). + final urls = []; + final client = clientWith( + MockClient((request) async { + urls.add(request.url); + return _json({'page': 1, 'totalPages': 1, 'results': []}); + }), + ); + + await client.getPopularMovies(page: 2); + await client.getPopularTv(); + + expect(urls.map((url) => url.path).toList(), ['/api/v1/discover/movies', '/api/v1/discover/tv']); + for (final url in urls) { + expect(url.queryParameters.containsKey('language'), isFalse, reason: url.path); + } + expect(urls.first.queryParameters['page'], '2', reason: 'paging must survive the locale opt-out'); + }); + test('createRequest posts the movie payload without seasons', () async { late Map body; final client = clientWith(