diff --git a/lib/i18n/da.i18n.json b/lib/i18n/da.i18n.json index bd7c2982..a1fa1b62 100644 --- a/lib/i18n/da.i18n.json +++ b/lib/i18n/da.i18n.json @@ -738,7 +738,8 @@ "unwatchedOnly": "Kun usete", "nextNUnwatched": "Næste ${count} usete", "customAmount": "Angiv antal...", - "howManyEpisodes": "Hvor mange episoder?" + "howManyEpisodes": "Hvor mange episoder?", + "itemsQueued": "${count} elementer sat i kø til download" }, "shaders": { "title": "Shadere", diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index 1add250f..fd53bb38 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -635,7 +635,8 @@ "unwatchedOnly": "Nur ungesehene", "nextNUnwatched": "Nächste ${count} ungesehene", "customAmount": "Eigene Anzahl...", - "howManyEpisodes": "Wie viele Episoden?" + "howManyEpisodes": "Wie viele Episoden?", + "itemsQueued": "${count} Elemente zum Download eingereiht" }, "playlists": { "title": "Wiedergabelisten", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index b84915da..d9ebaed0 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -738,7 +738,8 @@ "unwatchedOnly": "Unwatched only", "nextNUnwatched": "Next ${count} unwatched", "customAmount": "Custom amount...", - "howManyEpisodes": "How many episodes?" + "howManyEpisodes": "How many episodes?", + "itemsQueued": "${count} items queued for download" }, "shaders": { "title": "Shaders", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index a58b8bad..aa379631 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -738,7 +738,8 @@ "unwatchedOnly": "Solo no vistos", "nextNUnwatched": "Próximos ${count} no vistos", "customAmount": "Cantidad personalizada...", - "howManyEpisodes": "¿Cuántos episodios?" + "howManyEpisodes": "¿Cuántos episodios?", + "itemsQueued": "${count} elementos en cola de descarga" }, "shaders": { "title": "Shaders", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index ea5cba42..f2b53852 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -738,7 +738,8 @@ "unwatchedOnly": "Non vus uniquement", "nextNUnwatched": "${count} prochains non vus", "customAmount": "Quantité personnalisée...", - "howManyEpisodes": "Combien d'épisodes ?" + "howManyEpisodes": "Combien d'épisodes ?", + "itemsQueued": "${count} éléments mis en file d'attente" }, "shaders": { "title": "Shaders", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 5e0754a8..25286630 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -635,7 +635,8 @@ "unwatchedOnly": "Solo non visti", "nextNUnwatched": "Prossimi ${count} non visti", "customAmount": "Quantità personalizzata...", - "howManyEpisodes": "Quanti episodi?" + "howManyEpisodes": "Quanti episodi?", + "itemsQueued": "${count} elementi in coda per il download" }, "playlists": { "title": "Playlist", diff --git a/lib/i18n/ja.i18n.json b/lib/i18n/ja.i18n.json index 247e8d2d..9447eee2 100644 --- a/lib/i18n/ja.i18n.json +++ b/lib/i18n/ja.i18n.json @@ -738,7 +738,8 @@ "unwatchedOnly": "未視聴のみ", "nextNUnwatched": "次の${count}件の未視聴", "customAmount": "数を指定...", - "howManyEpisodes": "何エピソード?" + "howManyEpisodes": "何エピソード?", + "itemsQueued": "${count}件をダウンロードキューに追加" }, "shaders": { "title": "シェーダー", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index 148812a9..945d6564 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -738,7 +738,8 @@ "unwatchedOnly": "시청하지 않은 것만", "nextNUnwatched": "다음 ${count}개 미시청", "customAmount": "직접 입력...", - "howManyEpisodes": "몇 개의 에피소드?" + "howManyEpisodes": "몇 개의 에피소드?", + "itemsQueued": "${count}개 항목이 다운로드 대기열에 추가됨" }, "shaders": { "title": "셰이더", diff --git a/lib/i18n/nb.i18n.json b/lib/i18n/nb.i18n.json index d2af43e1..b88e1f89 100644 --- a/lib/i18n/nb.i18n.json +++ b/lib/i18n/nb.i18n.json @@ -738,7 +738,8 @@ "unwatchedOnly": "Kun usette", "nextNUnwatched": "Neste ${count} usette", "customAmount": "Egendefinert antall...", - "howManyEpisodes": "Hvor mange episoder?" + "howManyEpisodes": "Hvor mange episoder?", + "itemsQueued": "${count} elementer i nedlastingskø" }, "shaders": { "title": "Shadere", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 0935bbd7..a519fd92 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -635,7 +635,8 @@ "unwatchedOnly": "Alleen onbekeken", "nextNUnwatched": "Volgende ${count} onbekeken", "customAmount": "Aangepast aantal...", - "howManyEpisodes": "Hoeveel afleveringen?" + "howManyEpisodes": "Hoeveel afleveringen?", + "itemsQueued": "${count} items in downloadwachtrij" }, "playlists": { "title": "Afspeellijsten", diff --git a/lib/i18n/pl.i18n.json b/lib/i18n/pl.i18n.json index 5f0cbfeb..552c1e1d 100644 --- a/lib/i18n/pl.i18n.json +++ b/lib/i18n/pl.i18n.json @@ -738,7 +738,8 @@ "unwatchedOnly": "Tylko nieobejrzane", "nextNUnwatched": "Następne ${count} nieobejrzanych", "customAmount": "Własna ilość...", - "howManyEpisodes": "Ile odcinków?" + "howManyEpisodes": "Ile odcinków?", + "itemsQueued": "${count} elementów dodanych do kolejki pobierania" }, "shaders": { "title": "Shadery", diff --git a/lib/i18n/pt.i18n.json b/lib/i18n/pt.i18n.json index c1a0f447..1bcac131 100644 --- a/lib/i18n/pt.i18n.json +++ b/lib/i18n/pt.i18n.json @@ -738,7 +738,8 @@ "unwatchedOnly": "Apenas não assistidos", "nextNUnwatched": "Próximos ${count} não assistidos", "customAmount": "Quantidade personalizada...", - "howManyEpisodes": "Quantos episódios?" + "howManyEpisodes": "Quantos episódios?", + "itemsQueued": "${count} itens na fila de download" }, "shaders": { "title": "Shaders", diff --git a/lib/i18n/ru.i18n.json b/lib/i18n/ru.i18n.json index c412d7f5..bf78618c 100644 --- a/lib/i18n/ru.i18n.json +++ b/lib/i18n/ru.i18n.json @@ -738,7 +738,8 @@ "unwatchedOnly": "Только непросмотренные", "nextNUnwatched": "Следующие ${count} непросмотренных", "customAmount": "Указать количество...", - "howManyEpisodes": "Сколько эпизодов?" + "howManyEpisodes": "Сколько эпизодов?", + "itemsQueued": "${count} элементов добавлено в очередь загрузки" }, "shaders": { "title": "Шейдеры", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 48f0bb6b..7800c3bd 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 15 -/// Strings: 12825 (855 per locale) +/// Strings: 12840 (856 per locale) /// -/// Built on 2026-04-07 at 18:27 UTC +/// Built on 2026-04-08 at 10:00 UTC // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_da.g.dart b/lib/i18n/strings_da.g.dart index ddf2aa0f..adc0b318 100644 --- a/lib/i18n/strings_da.g.dart +++ b/lib/i18n/strings_da.g.dart @@ -999,6 +999,7 @@ class _TranslationsDownloadsDa implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => 'Næste ${count} usete'; @override String get customAmount => 'Angiv antal...'; @override String get howManyEpisodes => 'Hvor mange episoder?'; + @override String itemsQueued({required Object count}) => '${count} elementer sat i kø til download'; } // Path: shaders @@ -2023,6 +2024,7 @@ extension on TranslationsDa { 'downloads.nextNUnwatched' => ({required Object count}) => 'Næste ${count} usete', 'downloads.customAmount' => 'Angiv antal...', 'downloads.howManyEpisodes' => 'Hvor mange episoder?', + 'downloads.itemsQueued' => ({required Object count}) => '${count} elementer sat i kø til download', 'shaders.title' => 'Shadere', 'shaders.noShaderDescription' => 'Ingen videoforbedring', 'shaders.nvscalerDescription' => 'NVIDIA-billedskalering for skarpere video', diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index cc832305..4bc35e08 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -875,6 +875,7 @@ class _TranslationsDownloadsDe implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => 'Nächste ${count} ungesehene'; @override String get customAmount => 'Eigene Anzahl...'; @override String get howManyEpisodes => 'Wie viele Episoden?'; + @override String itemsQueued({required Object count}) => '${count} Elemente zum Download eingereiht'; } // Path: playlists @@ -1926,6 +1927,7 @@ extension on TranslationsDe { 'downloads.nextNUnwatched' => ({required Object count}) => 'Nächste ${count} ungesehene', 'downloads.customAmount' => 'Eigene Anzahl...', 'downloads.howManyEpisodes' => 'Wie viele Episoden?', + 'downloads.itemsQueued' => ({required Object count}) => '${count} Elemente zum Download eingereiht', 'playlists.title' => 'Wiedergabelisten', 'playlists.noPlaylists' => 'Keine Wiedergabelisten gefunden', 'playlists.create' => 'Wiedergabeliste erstellen', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index b423aa30..87f2ee6c 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -2269,6 +2269,9 @@ class TranslationsDownloadsEn { /// en: 'How many episodes?' String get howManyEpisodes => 'How many episodes?'; + + /// en: '${count} items queued for download' + String itemsQueued({required Object count}) => '${count} items queued for download'; } // Path: shaders @@ -3740,6 +3743,7 @@ extension on Translations { 'downloads.nextNUnwatched' => ({required Object count}) => 'Next ${count} unwatched', 'downloads.customAmount' => 'Custom amount...', 'downloads.howManyEpisodes' => 'How many episodes?', + 'downloads.itemsQueued' => ({required Object count}) => '${count} items queued for download', 'shaders.title' => 'Shaders', 'shaders.noShaderDescription' => 'No video enhancement', 'shaders.nvscalerDescription' => 'NVIDIA image scaling for sharper video', diff --git a/lib/i18n/strings_es.g.dart b/lib/i18n/strings_es.g.dart index f63fe6eb..a4e0ed28 100644 --- a/lib/i18n/strings_es.g.dart +++ b/lib/i18n/strings_es.g.dart @@ -999,6 +999,7 @@ class _TranslationsDownloadsEs implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => 'Próximos ${count} no vistos'; @override String get customAmount => 'Cantidad personalizada...'; @override String get howManyEpisodes => '¿Cuántos episodios?'; + @override String itemsQueued({required Object count}) => '${count} elementos en cola de descarga'; } // Path: shaders @@ -2023,6 +2024,7 @@ extension on TranslationsEs { 'downloads.nextNUnwatched' => ({required Object count}) => 'Próximos ${count} no vistos', 'downloads.customAmount' => 'Cantidad personalizada...', 'downloads.howManyEpisodes' => '¿Cuántos episodios?', + 'downloads.itemsQueued' => ({required Object count}) => '${count} elementos en cola de descarga', 'shaders.title' => 'Shaders', 'shaders.noShaderDescription' => 'Sin mejora de video', 'shaders.nvscalerDescription' => 'Escalado de imagen NVIDIA para un video más nítido', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index 75b93f94..4a2de9fd 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -999,6 +999,7 @@ class _TranslationsDownloadsFr implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => '${count} prochains non vus'; @override String get customAmount => 'Quantité personnalisée...'; @override String get howManyEpisodes => 'Combien d\'épisodes ?'; + @override String itemsQueued({required Object count}) => '${count} éléments mis en file d\'attente'; } // Path: shaders @@ -2023,6 +2024,7 @@ extension on TranslationsFr { 'downloads.nextNUnwatched' => ({required Object count}) => '${count} prochains non vus', 'downloads.customAmount' => 'Quantité personnalisée...', 'downloads.howManyEpisodes' => 'Combien d\'épisodes ?', + 'downloads.itemsQueued' => ({required Object count}) => '${count} éléments mis en file d\'attente', 'shaders.title' => 'Shaders', 'shaders.noShaderDescription' => 'Aucune amélioration vidéo', 'shaders.nvscalerDescription' => 'Mise à l\'échelle NVIDIA pour une vidéo plus nette', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index 60b7924e..d8caee2c 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -875,6 +875,7 @@ class _TranslationsDownloadsIt implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => 'Prossimi ${count} non visti'; @override String get customAmount => 'Quantità personalizzata...'; @override String get howManyEpisodes => 'Quanti episodi?'; + @override String itemsQueued({required Object count}) => '${count} elementi in coda per il download'; } // Path: playlists @@ -1926,6 +1927,7 @@ extension on TranslationsIt { 'downloads.nextNUnwatched' => ({required Object count}) => 'Prossimi ${count} non visti', 'downloads.customAmount' => 'Quantità personalizzata...', 'downloads.howManyEpisodes' => 'Quanti episodi?', + 'downloads.itemsQueued' => ({required Object count}) => '${count} elementi in coda per il download', 'playlists.title' => 'Playlist', 'playlists.noPlaylists' => 'Nessuna playlist trovata', 'playlists.create' => 'Crea playlist', diff --git a/lib/i18n/strings_ja.g.dart b/lib/i18n/strings_ja.g.dart index 84981f11..4a9ed062 100644 --- a/lib/i18n/strings_ja.g.dart +++ b/lib/i18n/strings_ja.g.dart @@ -999,6 +999,7 @@ class _TranslationsDownloadsJa implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => '次の${count}件の未視聴'; @override String get customAmount => '数を指定...'; @override String get howManyEpisodes => '何エピソード?'; + @override String itemsQueued({required Object count}) => '${count}件をダウンロードキューに追加'; } // Path: shaders @@ -2023,6 +2024,7 @@ extension on TranslationsJa { 'downloads.nextNUnwatched' => ({required Object count}) => '次の${count}件の未視聴', 'downloads.customAmount' => '数を指定...', 'downloads.howManyEpisodes' => '何エピソード?', + 'downloads.itemsQueued' => ({required Object count}) => '${count}件をダウンロードキューに追加', 'shaders.title' => 'シェーダー', 'shaders.noShaderDescription' => '映像補正なし', 'shaders.nvscalerDescription' => 'よりシャープな映像のためのNVIDIA画像スケーリング', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index e0a90301..4595a249 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -999,6 +999,7 @@ class _TranslationsDownloadsKo implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => '다음 ${count}개 미시청'; @override String get customAmount => '직접 입력...'; @override String get howManyEpisodes => '몇 개의 에피소드?'; + @override String itemsQueued({required Object count}) => '${count}개 항목이 다운로드 대기열에 추가됨'; } // Path: shaders @@ -2023,6 +2024,7 @@ extension on TranslationsKo { 'downloads.nextNUnwatched' => ({required Object count}) => '다음 ${count}개 미시청', 'downloads.customAmount' => '직접 입력...', 'downloads.howManyEpisodes' => '몇 개의 에피소드?', + 'downloads.itemsQueued' => ({required Object count}) => '${count}개 항목이 다운로드 대기열에 추가됨', 'shaders.title' => '셰이더', 'shaders.noShaderDescription' => '비디오 향상 없음', 'shaders.nvscalerDescription' => '더 선명한 비디오를 위한 NVIDIA 이미지 스케일링', diff --git a/lib/i18n/strings_nb.g.dart b/lib/i18n/strings_nb.g.dart index 510e5326..841818ff 100644 --- a/lib/i18n/strings_nb.g.dart +++ b/lib/i18n/strings_nb.g.dart @@ -999,6 +999,7 @@ class _TranslationsDownloadsNb implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => 'Neste ${count} usette'; @override String get customAmount => 'Egendefinert antall...'; @override String get howManyEpisodes => 'Hvor mange episoder?'; + @override String itemsQueued({required Object count}) => '${count} elementer i nedlastingskø'; } // Path: shaders @@ -2023,6 +2024,7 @@ extension on TranslationsNb { 'downloads.nextNUnwatched' => ({required Object count}) => 'Neste ${count} usette', 'downloads.customAmount' => 'Egendefinert antall...', 'downloads.howManyEpisodes' => 'Hvor mange episoder?', + 'downloads.itemsQueued' => ({required Object count}) => '${count} elementer i nedlastingskø', 'shaders.title' => 'Shadere', 'shaders.noShaderDescription' => 'Ingen videoforbedring', 'shaders.nvscalerDescription' => 'NVIDIA bildeskalering for skarpere video', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index 4ddc24d5..748a68b9 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -875,6 +875,7 @@ class _TranslationsDownloadsNl implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => 'Volgende ${count} onbekeken'; @override String get customAmount => 'Aangepast aantal...'; @override String get howManyEpisodes => 'Hoeveel afleveringen?'; + @override String itemsQueued({required Object count}) => '${count} items in downloadwachtrij'; } // Path: playlists @@ -1926,6 +1927,7 @@ extension on TranslationsNl { 'downloads.nextNUnwatched' => ({required Object count}) => 'Volgende ${count} onbekeken', 'downloads.customAmount' => 'Aangepast aantal...', 'downloads.howManyEpisodes' => 'Hoeveel afleveringen?', + 'downloads.itemsQueued' => ({required Object count}) => '${count} items in downloadwachtrij', 'playlists.title' => 'Afspeellijsten', 'playlists.noPlaylists' => 'Geen afspeellijsten gevonden', 'playlists.create' => 'Afspeellijst maken', diff --git a/lib/i18n/strings_pl.g.dart b/lib/i18n/strings_pl.g.dart index cade86a0..6ef0cfa6 100644 --- a/lib/i18n/strings_pl.g.dart +++ b/lib/i18n/strings_pl.g.dart @@ -999,6 +999,7 @@ class _TranslationsDownloadsPl implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => 'Następne ${count} nieobejrzanych'; @override String get customAmount => 'Własna ilość...'; @override String get howManyEpisodes => 'Ile odcinków?'; + @override String itemsQueued({required Object count}) => '${count} elementów dodanych do kolejki pobierania'; } // Path: shaders @@ -2023,6 +2024,7 @@ extension on TranslationsPl { 'downloads.nextNUnwatched' => ({required Object count}) => 'Następne ${count} nieobejrzanych', 'downloads.customAmount' => 'Własna ilość...', 'downloads.howManyEpisodes' => 'Ile odcinków?', + 'downloads.itemsQueued' => ({required Object count}) => '${count} elementów dodanych do kolejki pobierania', 'shaders.title' => 'Shadery', 'shaders.noShaderDescription' => 'Bez ulepszenia wideo', 'shaders.nvscalerDescription' => 'Skalowanie obrazu NVIDIA dla ostrzejszego wideo', diff --git a/lib/i18n/strings_pt.g.dart b/lib/i18n/strings_pt.g.dart index c4f0f9b6..4731717a 100644 --- a/lib/i18n/strings_pt.g.dart +++ b/lib/i18n/strings_pt.g.dart @@ -999,6 +999,7 @@ class _TranslationsDownloadsPt implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => 'Próximos ${count} não assistidos'; @override String get customAmount => 'Quantidade personalizada...'; @override String get howManyEpisodes => 'Quantos episódios?'; + @override String itemsQueued({required Object count}) => '${count} itens na fila de download'; } // Path: shaders @@ -2023,6 +2024,7 @@ extension on TranslationsPt { 'downloads.nextNUnwatched' => ({required Object count}) => 'Próximos ${count} não assistidos', 'downloads.customAmount' => 'Quantidade personalizada...', 'downloads.howManyEpisodes' => 'Quantos episódios?', + 'downloads.itemsQueued' => ({required Object count}) => '${count} itens na fila de download', 'shaders.title' => 'Shaders', 'shaders.noShaderDescription' => 'Sem aprimoramento de vídeo', 'shaders.nvscalerDescription' => 'Escalonamento de imagem NVIDIA para vídeo mais nítido', diff --git a/lib/i18n/strings_ru.g.dart b/lib/i18n/strings_ru.g.dart index de4f70e6..236a3e89 100644 --- a/lib/i18n/strings_ru.g.dart +++ b/lib/i18n/strings_ru.g.dart @@ -999,6 +999,7 @@ class _TranslationsDownloadsRu implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => 'Следующие ${count} непросмотренных'; @override String get customAmount => 'Указать количество...'; @override String get howManyEpisodes => 'Сколько эпизодов?'; + @override String itemsQueued({required Object count}) => '${count} элементов добавлено в очередь загрузки'; } // Path: shaders @@ -2023,6 +2024,7 @@ extension on TranslationsRu { 'downloads.nextNUnwatched' => ({required Object count}) => 'Следующие ${count} непросмотренных', 'downloads.customAmount' => 'Указать количество...', 'downloads.howManyEpisodes' => 'Сколько эпизодов?', + 'downloads.itemsQueued' => ({required Object count}) => '${count} элементов добавлено в очередь загрузки', 'shaders.title' => 'Шейдеры', 'shaders.noShaderDescription' => 'Без улучшения видео', 'shaders.nvscalerDescription' => 'Масштабирование NVIDIA для более чёткого видео', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index f8b8e211..87a25416 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -875,6 +875,7 @@ class _TranslationsDownloadsSv implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => 'Nästa ${count} osedda'; @override String get customAmount => 'Ange antal...'; @override String get howManyEpisodes => 'Hur många avsnitt?'; + @override String itemsQueued({required Object count}) => '${count} objekt köade för nedladdning'; } // Path: playlists @@ -1926,6 +1927,7 @@ extension on TranslationsSv { 'downloads.nextNUnwatched' => ({required Object count}) => 'Nästa ${count} osedda', 'downloads.customAmount' => 'Ange antal...', 'downloads.howManyEpisodes' => 'Hur många avsnitt?', + 'downloads.itemsQueued' => ({required Object count}) => '${count} objekt köade för nedladdning', 'playlists.title' => 'Spellistor', 'playlists.noPlaylists' => 'Inga spellistor hittades', 'playlists.create' => 'Skapa spellista', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index 740d151f..a7a0ff4d 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -875,6 +875,7 @@ class _TranslationsDownloadsZh implements TranslationsDownloadsEn { @override String nextNUnwatched({required Object count}) => '接下来 ${count} 集未观看'; @override String get customAmount => '自定义数量...'; @override String get howManyEpisodes => '下载几集?'; + @override String itemsQueued({required Object count}) => '${count} 个项目已加入下载队列'; } // Path: playlists @@ -1926,6 +1927,7 @@ extension on TranslationsZh { 'downloads.nextNUnwatched' => ({required Object count}) => '接下来 ${count} 集未观看', 'downloads.customAmount' => '自定义数量...', 'downloads.howManyEpisodes' => '下载几集?', + 'downloads.itemsQueued' => ({required Object count}) => '${count} 个项目已加入下载队列', 'playlists.title' => '播放列表', 'playlists.noPlaylists' => '未找到播放列表', 'playlists.create' => '创建播放列表', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index e8822954..addbaa6f 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -635,7 +635,8 @@ "unwatchedOnly": "Endast osedda", "nextNUnwatched": "Nästa ${count} osedda", "customAmount": "Ange antal...", - "howManyEpisodes": "Hur många avsnitt?" + "howManyEpisodes": "Hur många avsnitt?", + "itemsQueued": "${count} objekt köade för nedladdning" }, "playlists": { "title": "Spellistor", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 18cff86a..e393b975 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -635,7 +635,8 @@ "unwatchedOnly": "仅未观看", "nextNUnwatched": "接下来 ${count} 集未观看", "customAmount": "自定义数量...", - "howManyEpisodes": "下载几集?" + "howManyEpisodes": "下载几集?", + "itemsQueued": "${count} 个项目已加入下载队列" }, "playlists": { "title": "播放列表", diff --git a/lib/providers/download_provider.dart b/lib/providers/download_provider.dart index 7f9ea009..34b463d8 100644 --- a/lib/providers/download_provider.dart +++ b/lib/providers/download_provider.dart @@ -639,6 +639,29 @@ class DownloadProvider extends ChangeNotifier { } } + /// Queue all video items from a playlist for download. + /// Returns the number of items queued. + Future queuePlaylistDownload( + List items, + PlexClient client, { + DownloadFilter filter = DownloadFilter.all, + }) async { + if (await DownloadManagerService.shouldBlockDownloadOnCellular()) { + throw CellularDownloadBlockedException(); + } + + int count = 0; + for (final item in items) { + final mt = item.mediaType; + if (mt != PlexMediaType.movie && mt != PlexMediaType.episode) continue; + if (filter == DownloadFilter.unwatched && item.isWatched && !item.hasActiveProgress) continue; + + final queued = await _queueSingleDownload(item, client); + if (queued) count++; + } + return count; + } + /// Queue a single movie or episode for download. /// Returns true if the item was actually queued, false if skipped. Future _queueSingleDownload( diff --git a/lib/screens/playlist/playlist_detail_screen.dart b/lib/screens/playlist/playlist_detail_screen.dart index eefe6329..d6651b60 100644 --- a/lib/screens/playlist/playlist_detail_screen.dart +++ b/lib/screens/playlist/playlist_detail_screen.dart @@ -13,9 +13,12 @@ import '../../widgets/desktop_app_bar.dart'; import '../../focus/dpad_navigator.dart'; import '../../focus/input_mode_tracker.dart'; import '../../focus/key_event_utils.dart'; +import 'package:provider/provider.dart'; import 'playlist_item_card.dart'; import '../../i18n/strings.g.dart'; +import '../../providers/download_provider.dart'; import '../../utils/dialogs.dart'; +import '../../utils/download_utils.dart'; import '../../utils/snackbar_helper.dart'; import '../base_media_list_detail_screen.dart'; import '../focusable_detail_screen_mixin.dart'; @@ -58,6 +61,8 @@ class _PlaylistDetailScreenState extends BaseMediaListDetailScreen _downloadPlaylist() async { + final downloadProvider = Provider.of(context, listen: false); + + try { + final count = await showPlaylistDownloadOptionsAndQueue( + context, + items: items, + client: client, + downloadProvider: downloadProvider, + ); + if (count == null || !mounted) return; + + final message = count > 1 ? t.downloads.itemsQueued(count: count) : t.downloads.downloadQueued; + showSuccessSnackBar(context, message); + } on CellularDownloadBlockedException { + if (mounted) { + showErrorSnackBar(context, t.settings.cellularDownloadBlocked); + } + } catch (e) { + if (mounted) { + showErrorSnackBar(context, t.messages.errorLoading(error: e.toString())); + } + } + } + Future _deletePlaylist() async { final confirmed = await showDeleteConfirmation( context, diff --git a/lib/utils/download_utils.dart b/lib/utils/download_utils.dart index 87a1463c..f19608c9 100644 --- a/lib/utils/download_utils.dart +++ b/lib/utils/download_utils.dart @@ -75,6 +75,32 @@ Future showDownloadOptionsAndQueue( ); } +/// Shows download options dialog for playlists, then queues the download. +/// Returns the number of items queued, or null if cancelled. +Future showPlaylistDownloadOptionsAndQueue( + BuildContext context, { + required List items, + required PlexClient client, + required DownloadProvider downloadProvider, +}) async { + final selected = await showOptionPickerDialog( + context, + title: t.downloads.downloadNow, + options: [ + (icon: Symbols.download_rounded, label: t.downloads.allEpisodes, value: DownloadFilter.all), + (icon: Symbols.visibility_off_rounded, label: t.downloads.unwatchedOnly, value: DownloadFilter.unwatched), + ], + ); + + if (selected == null || !context.mounted) return null; + + return await downloadProvider.queuePlaylistDownload( + items, + client, + filter: selected, + ); +} + Future _showEpisodeCountDialog(BuildContext context) { final autoFocus = InputModeTracker.isKeyboardMode(context); return showDialog( diff --git a/lib/widgets/media_context_menu.dart b/lib/widgets/media_context_menu.dart index a080bba6..cc483a35 100644 --- a/lib/widgets/media_context_menu.dart +++ b/lib/widgets/media_context_menu.dart @@ -158,6 +158,11 @@ class MediaContextMenuState extends State { // Shuffle menuActions.add(_MenuAction(value: 'shuffle', icon: Symbols.shuffle_rounded, label: t.mediaMenu.shufflePlay)); + // Download (video playlists only) + if (isPlaylist && (widget.item as PlexPlaylist).playlistType == 'video') { + menuActions.add(_MenuAction(value: 'download_playlist', icon: Symbols.download_rounded, label: t.downloads.downloadNow)); + } + // Delete menuActions.add(_MenuAction(value: 'delete', icon: Symbols.delete_rounded, label: t.common.delete)); @@ -534,6 +539,10 @@ class MediaContextMenuState extends State { await _handlePlayExternal(context); break; + case 'download_playlist': + await _handleDownloadPlaylist(context); + break; + case 'download': await _handleDownload(context); break; @@ -1109,6 +1118,38 @@ class MediaContextMenuState extends State { await ExternalPlayerService.launch(context: context, metadata: metadata, client: client); } + /// Handle download playlist action + Future _handleDownloadPlaylist(BuildContext context) async { + final playlist = widget.item as PlexPlaylist; + final downloadProvider = Provider.of(context, listen: false); + final client = _getClientForItem(); + + try { + final items = await client.getPlaylist(playlist.ratingKey); + if (!context.mounted) return; + + final count = await showPlaylistDownloadOptionsAndQueue( + context, + items: items, + client: client, + downloadProvider: downloadProvider, + ); + if (count == null || !context.mounted) return; + + final message = count > 1 ? t.downloads.itemsQueued(count: count) : t.downloads.downloadQueued; + showSuccessSnackBar(context, message); + } on CellularDownloadBlockedException { + if (context.mounted) { + showErrorSnackBar(context, t.settings.cellularDownloadBlocked); + } + } catch (e) { + appLogger.e('Failed to queue playlist download', error: e); + if (context.mounted) { + showErrorSnackBar(context, t.messages.errorLoading(error: e.toString())); + } + } + } + /// Handle download action Future _handleDownload(BuildContext context) async { final downloadProvider = Provider.of(context, listen: false);