diff --git a/Flixy/Resources/Localizable.xcstrings b/Flixy/Resources/Localizable.xcstrings index df25801..f355e58 100644 --- a/Flixy/Resources/Localizable.xcstrings +++ b/Flixy/Resources/Localizable.xcstrings @@ -1,7575 +1,7629 @@ { - "sourceLanguage" : "en", - "strings" : { - " · " : { - "comment" : "A separator between different parts of the episode metadata.", - "isCommentAutoGenerated" : true + "sourceLanguage": "en", + "strings": { + " · ": { + "comment": "A separator between different parts of the episode metadata.", + "isCommentAutoGenerated": true }, - "%@" : { - "comment" : "A number in a list.", - "isCommentAutoGenerated" : true + "%@": { + "comment": "A number in a list.", + "isCommentAutoGenerated": true }, - "%@ → %@" : { - "comment" : "A line in a diagnostic trace that shows a network request path and its result. The first argument is the network request path. The second argument is the network response result.", - "isCommentAutoGenerated" : true, - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@ → %2$@" + "%@ GB": { + "comment": "A picker option in the settings view that lets the user select the maximum amount of storage space that can be used for downloaded content. The value is given in gigabytes.", + "isCommentAutoGenerated": true + }, + "%@ → %@": { + "comment": "A line in a diagnostic trace that shows a network request path and its result. The first argument is the network request path. The second argument is the network response result.", + "isCommentAutoGenerated": true, + "localizations": { + "en": { + "stringUnit": { + "state": "new", + "value": "%1$@ → %2$@" } } } }, - "%@ GB" : { - "comment" : "A picker option in the settings view that lets the user select the maximum amount of storage space that can be used for downloaded content. The value is given in gigabytes.", - "isCommentAutoGenerated" : true + "4K": { + "comment": "A label for a 4K video.", + "isCommentAutoGenerated": true + }, + "Full HD": { + "comment": "A label for a media resolution.", + "isCommentAutoGenerated": true + }, + "HD": { + "comment": "A label for a media resolution.", + "isCommentAutoGenerated": true }, - "4K" : { - "comment" : "A label for a 4K video.", - "isCommentAutoGenerated" : true + "SD": { + "comment": "A label for a 720p movie.", + "isCommentAutoGenerated": true + }, + "account.hint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Öffnet Einstellungen und Abmelden" + } + }, + "en": { + "stringUnit": { + "state": "translated", + "value": "Opens settings and sign out" + } + } + } }, - "account.hint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Öffnet Einstellungen und Abmelden" + "account.settings": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Einstellungen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Opens settings and sign out" + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings" } } } }, - "account.settings" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Einstellungen" + "action.addFavourite": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zu Favoriten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Settings" + "en": { + "stringUnit": { + "state": "translated", + "value": "Add to favourites" } } } }, - "action.addFavourite" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zu Favoriten" + "action.apply": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Übernehmen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Add to favourites" + "en": { + "stringUnit": { + "state": "translated", + "value": "Apply" } } } }, - "action.apply" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Übernehmen" + "action.cancel": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abbrechen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Apply" + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" } } } }, - "action.cancel" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abbrechen" + "action.clearRating": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bewertung entfernen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cancel" + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear rating" } } } }, - "action.clearRating" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bewertung entfernen" + "action.close": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Schließen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Clear rating" + "en": { + "stringUnit": { + "state": "translated", + "value": "Close" } } } }, - "action.close" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schließen" + "action.delete": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Löschen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Close" + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete" } } } }, - "action.delete" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Löschen" + "action.done": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Fertig" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Delete" + "en": { + "stringUnit": { + "state": "translated", + "value": "Done" } } } }, - "action.done" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fertig" + "action.markUnwatched": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Als ungesehen markieren" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Done" + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark as unwatched" } } } }, - "action.markUnwatched" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Als ungesehen markieren" + "action.markWatched": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Als gesehen markieren" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mark as unwatched" + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark as watched" } } } }, - "action.markWatched" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Als gesehen markieren" + "action.rate": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bewerten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mark as watched" + "en": { + "stringUnit": { + "state": "translated", + "value": "Rate" } } } }, - "action.rate" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bewerten" + "action.removeFavourite": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aus Favoriten entfernen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rate" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove from favourites" } } } }, - "action.removeFavourite" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aus Favoriten entfernen" + "action.removeFromContinue": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aus „Weiterschauen“ entfernen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remove from favourites" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove from Continue Watching" } } } }, - "action.removeFromContinue" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aus „Weiterschauen“ entfernen" + "action.retry": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Erneut versuchen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remove from Continue Watching" + "en": { + "stringUnit": { + "state": "translated", + "value": "Try again" } } } }, - "action.retry" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Erneut versuchen" + "action.signOut": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abmelden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Try again" + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign out" } } } }, - "action.signOut" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abmelden" + "appearance.accent": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Akzentfarbe" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign out" + "en": { + "stringUnit": { + "state": "translated", + "value": "Accent color" } } } }, - "appearance.accent" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Akzentfarbe" + "appearance.accentExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Farbe erscheint auf Knöpfen, Fortschrittsbalken und der ausgewählten Zeile in der Seitenleiste." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Accent color" + "en": { + "stringUnit": { + "state": "translated", + "value": "The color appears on buttons, progress bars and the selected row in the sidebar." } } } }, - "appearance.accentExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Farbe erscheint auf Knöpfen, Fortschrittsbalken und der ausgewählten Zeile in der Seitenleiste." + "appearance.adjusted %@": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Farbe wurde auf %@ aufgehellt, damit sie auf dem dunklen Hintergrund lesbar bleibt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The color appears on buttons, progress bars and the selected row in the sidebar." + "en": { + "stringUnit": { + "state": "translated", + "value": "Lightened to %@ so it stays readable on the dark background." } } } }, - "appearance.adjusted %@" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Farbe wurde auf %@ aufgehellt, damit sie auf dem dunklen Hintergrund lesbar bleibt." + "appearance.cardScale": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Kachelgröße" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lightened to %@ so it stays readable on the dark background." + "en": { + "stringUnit": { + "state": "translated", + "value": "Tile size" } } } }, - "appearance.cardScale" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kachelgröße" + "appearance.cardScaleExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wie groß Plakate und Vorschaubilder erscheinen — auf der Startseite und in den Mediatheken. Die Einstellung gilt nur auf diesem Gerät." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tile size" + "en": { + "stringUnit": { + "state": "translated", + "value": "How large posters and thumbnails appear — on the home screen and in libraries. This setting applies to this device only." } } } }, - "appearance.cardScaleExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wie groß Plakate und Vorschaubilder erscheinen — auf der Startseite und in den Mediatheken. Die Einstellung gilt nur auf diesem Gerät." + "appearance.customColor": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Eigene Farbe" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "How large posters and thumbnails appear — on the home screen and in libraries. This setting applies to this device only." + "en": { + "stringUnit": { + "state": "translated", + "value": "Custom color" } } } }, - "appearance.customColor" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Eigene Farbe" + "appearance.hexPlaceholder": { + "comment": "Beispiel für eine Hex-Farbe", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "#1DB954" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Custom color" + "en": { + "stringUnit": { + "state": "translated", + "value": "#1DB954" } } } }, - "appearance.hexPlaceholder" : { - "comment" : "Beispiel für eine Hex-Farbe", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "#1DB954" + "appearance.invalidHex": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Das ist keine gültige Farbe. Erwartet wird etwas wie #1DB954." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "#1DB954" + "en": { + "stringUnit": { + "state": "translated", + "value": "That's not a valid color. Try something like #1DB954." } } } }, - "appearance.invalidHex" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das ist keine gültige Farbe. Erwartet wird etwas wie #1DB954." + "appearance.navigation": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Navigation" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "That's not a valid color. Try something like #1DB954." + "en": { + "stringUnit": { + "state": "translated", + "value": "Navigation" } } } }, - "appearance.navigation" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Navigation" + "appearance.navigationExplanation": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitlich fährt die Leiste über den Inhalt und gibt ihn danach wieder frei. Oben stehen alle Einträge gleichzeitig da und nehmen dauerhaft eine Zeile ein." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Navigation" + "en": { + "stringUnit": { + "state": "translated", + "value": "At the side, the bar slides over the content and gets out of the way again. At the top, every entry is visible at once and takes up a row permanently." } } } }, - "appearance.navigationExplanation" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seitlich fährt die Leiste über den Inhalt und gibt ihn danach wieder frei. Oben stehen alle Einträge gleichzeitig da und nehmen dauerhaft eine Zeile ein." + "appearance.navigationSide": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitlich" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "At the side, the bar slides over the content and gets out of the way again. At the top, every entry is visible at once and takes up a row permanently." + "en": { + "stringUnit": { + "state": "translated", + "value": "At the side" } } } }, - "appearance.navigationSide" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seitlich" + "appearance.navigationTop": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Oben" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "At the side" + "en": { + "stringUnit": { + "state": "translated", + "value": "At the top" } } } }, - "appearance.navigationTop" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Oben" + "appearance.preview": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorschau" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "At the top" + "en": { + "stringUnit": { + "state": "translated", + "value": "Preview" } } } }, - "appearance.preview" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vorschau" + "appearance.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Erscheinungsbild" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Preview" + "en": { + "stringUnit": { + "state": "translated", + "value": "Appearance" } } } }, - "appearance.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Erscheinungsbild" + "connecting.searching": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Server werden gesucht …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Appearance" + "en": { + "stringUnit": { + "state": "translated", + "value": "Looking for servers…" } } } }, - "connecting.searching" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server werden gesucht …" + "connecting.to %@": { + "comment": "Platzhalter: Servername", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verbinde mit %@ …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Looking for servers…" + "en": { + "stringUnit": { + "state": "translated", + "value": "Connecting to %@…" } } } }, - "connecting.to %@" : { - "comment" : "Platzhalter: Servername", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verbinde mit %@ …" + "detail.cast": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Besetzung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connecting to %@…" + "en": { + "stringUnit": { + "state": "translated", + "value": "Cast" } } } }, - "detail.cast" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Besetzung" + "detail.episodeCount %lld": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Folgen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cast" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld episodes" } } } }, - "detail.episodeCount %lld" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Folgen" + "detail.episodes": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Folgen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld episodes" + "en": { + "stringUnit": { + "state": "translated", + "value": "Episodes" } } } }, - "detail.episodes" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Folgen" + "detail.playFromStart": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Von vorn abspielen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Episodes" + "en": { + "stringUnit": { + "state": "translated", + "value": "Play from start" } } } }, - "detail.playFromStart" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Von vorn abspielen" + "detail.seasons": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Staffeln" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Play from start" + "en": { + "stringUnit": { + "state": "translated", + "value": "Seasons" } } } }, - "detail.seasons" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Staffeln" + "detail.toSeason": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zur Staffel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seasons" + "en": { + "stringUnit": { + "state": "translated", + "value": "Go to season" } } } }, - "detail.toSeason" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zur Staffel" + "detail.toSeries": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zur Serie" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Go to season" + "en": { + "stringUnit": { + "state": "translated", + "value": "Go to series" } } } }, - "detail.toSeries" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zur Serie" + "detail.unwatchedCount %lld": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld ungesehen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Go to series" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld unwatched" } } } }, - "detail.unwatchedCount %lld" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld ungesehen" + "detail.versions": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Fassung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld unwatched" + "en": { + "stringUnit": { + "state": "translated", + "value": "Version" } } } }, - "detail.versions" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fassung" + "diagnostics.connection": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verbindung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Version" + "en": { + "stringUnit": { + "state": "translated", + "value": "Connection" } } } }, - "diagnostics.connection" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verbindung" + "diagnostics.connectionUsed": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verbindung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connection" + "en": { + "stringUnit": { + "state": "translated", + "value": "Connection" } } } }, - "diagnostics.connectionUsed" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verbindung" + "diagnostics.copy": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bericht kopieren" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connection" + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy report" } } } }, - "diagnostics.copy" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bericht kopieren" + "diagnostics.copyHint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Enthält keinen Token. Kannst du unbesorgt weitergeben." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Copy report" + "en": { + "stringUnit": { + "state": "translated", + "value": "Contains no token. Safe to share." } } } }, - "diagnostics.copyHint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Enthält keinen Token. Kannst du unbesorgt weitergeben." + "diagnostics.lastAttempt": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Letzter Abspielversuch" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Contains no token. Safe to share." + "en": { + "stringUnit": { + "state": "translated", + "value": "Last playback attempt" } } } }, - "diagnostics.lastAttempt" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Letzter Abspielversuch" + "diagnostics.liveTV": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Live-TV-Antwort ansehen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Last playback attempt" + "en": { + "stringUnit": { + "state": "translated", + "value": "Inspect Live TV response" } } } }, - "diagnostics.liveTV" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Live-TV-Antwort ansehen" + "diagnostics.liveTVHint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt den Aufbau der Serverantwort — nur Schlüssel, Anzahl und Sendernamen, keine Zugangsdaten. Hilft dabei zu finden, wo die Sender stehen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Inspect Live TV response" + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows the structure of the server's response — keys, counts and channel names only, no credentials. Helps locate where the channels are." } } } }, - "diagnostics.liveTVHint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zeigt den Aufbau der Serverantwort — nur Schlüssel, Anzahl und Sendernamen, keine Zugangsdaten. Hilft dabei zu finden, wo die Sender stehen." + "diagnostics.method": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Weg" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Shows the structure of the server's response — keys, counts and channel names only, no credentials. Helps locate where the channels are." + "en": { + "stringUnit": { + "state": "translated", + "value": "Method" } } } }, - "diagnostics.method" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Weg" + "diagnostics.noServer": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Serververbindung." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Method" + "en": { + "stringUnit": { + "state": "translated", + "value": "No server connection." } } } }, - "diagnostics.noServer" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Serververbindung." + "diagnostics.nothingYet": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Noch nichts abgespielt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No server connection." + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing played yet." } } } }, - "diagnostics.nothingYet" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Noch nichts abgespielt." + "diagnostics.relayWarning": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Über die Plex-Vermittlung. Stark gedrosselt — große Dateien laufen darüber kaum an." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing played yet." + "en": { + "stringUnit": { + "state": "translated", + "value": "Via the Plex relay. Heavily throttled — large files barely start over it." } } } }, - "diagnostics.relayWarning" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Über die Plex-Vermittlung. Stark gedrosselt — große Dateien laufen darüber kaum an." + "diagnostics.source": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Quelle (ohne Token)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Via the Plex relay. Heavily throttled — large files barely start over it." + "en": { + "stringUnit": { + "state": "translated", + "value": "Source (token removed)" } } } }, - "diagnostics.source" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Quelle (ohne Token)" + "diagnostics.steps": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ablauf" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Source (token removed)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Timeline" } } } }, - "diagnostics.steps" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ablauf" + "diagnostics.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Diagnose" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Timeline" + "en": { + "stringUnit": { + "state": "translated", + "value": "Diagnostics" } } } }, - "diagnostics.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Diagnose" + "diagnostics.title.label": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Titel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Diagnostics" + "en": { + "stringUnit": { + "state": "translated", + "value": "Title" } } } }, - "diagnostics.title.label" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titel" + "downloads.active": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Läuft gerade" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Title" + "en": { + "stringUnit": { + "state": "translated", + "value": "In progress" } } } }, - "downloads.active" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Läuft gerade" + "downloads.allEpisodes": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle Folgen laden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "In progress" + "en": { + "stringUnit": { + "state": "translated", + "value": "Download all episodes" } } } }, - "downloads.allEpisodes" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alle Folgen laden" + "downloads.available": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Offline verfügbar" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download all episodes" + "en": { + "stringUnit": { + "state": "translated", + "value": "Available offline" } } } }, - "downloads.available" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Offline verfügbar" + "downloads.batch.queued %lld": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Folgen in die Warteschlange gelegt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Available offline" + "en": { + "stringUnit": { + "state": "translated", + "value": "Queued %lld episodes." } } } }, - "downloads.batch.queued %lld" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Folgen in die Warteschlange gelegt." + "downloads.batch.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Downloads" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Queued %lld episodes." + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloads" } } } }, - "downloads.batch.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Downloads" + "downloads.batch.unavailable %lld": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Folgen haben keine abspielbare Datei auf dem Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Downloads" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld episodes have no playable file on the server." } } } }, - "downloads.batch.unavailable %lld" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Folgen haben keine abspielbare Datei auf dem Server." + "downloads.cancel": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Download abbrechen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld episodes have no playable file on the server." + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel download" } } } }, - "downloads.cancel" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download abbrechen" + "downloads.download": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Herunterladen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cancel download" + "en": { + "stringUnit": { + "state": "translated", + "value": "Download" } } } }, - "downloads.download" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Herunterladen" + "downloads.empty.description": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Lade Filme und Folgen herunter, um sie ohne Verbindung anzusehen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download" + "en": { + "stringUnit": { + "state": "translated", + "value": "Download movies and episodes to watch them without a connection." } } } }, - "downloads.empty.description" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lade Filme und Folgen herunter, um sie ohne Verbindung anzusehen." + "downloads.empty.title": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nichts offline" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download movies and episodes to watch them without a connection." + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing offline" } } } }, - "downloads.empty.title" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nichts offline" + "downloads.failed": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Fehlgeschlagen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing offline" + "en": { + "stringUnit": { + "state": "translated", + "value": "Failed" } } } }, - "downloads.failed" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fehlgeschlagen" + "downloads.hold.offline": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Verbindung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Failed" + "en": { + "stringUnit": { + "state": "translated", + "value": "No connection" } } } }, - "downloads.hold.offline" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Verbindung" + "downloads.hold.offline.description": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ohne Verbindung geht nichts weiter. Angefangene Downloads laufen weiter, sobald das Gerät wieder am Netz ist." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No connection" + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing transfers without a connection. Started downloads continue once the device is back online." } } } }, - "downloads.hold.offline.description" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ohne Verbindung geht nichts weiter. Angefangene Downloads laufen weiter, sobald das Gerät wieder am Netz ist." + "downloads.hold.wifi": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wartet auf WLAN" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing transfers without a connection. Started downloads continue once the device is back online." + "en": { + "stringUnit": { + "state": "translated", + "value": "Waiting for Wi-Fi" } } } }, - "downloads.hold.wifi" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wartet auf WLAN" + "downloads.hold.wifi.description": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "„Nur über WLAN“ ist eingeschaltet und das Gerät hängt gerade am Mobilfunk. Sobald WLAN da ist, geht es von selbst weiter." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Waiting for Wi-Fi" + "en": { + "stringUnit": { + "state": "translated", + "value": "“Wi-Fi only” is on and the device is on cellular. Transfers resume by themselves once Wi-Fi is back." } } } }, - "downloads.hold.wifi.description" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "„Nur über WLAN“ ist eingeschaltet und das Gerät hängt gerade am Mobilfunk. Sobald WLAN da ist, geht es von selbst weiter." + "downloads.movies": { + "comment": "Überschrift für heruntergeladene Filme", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Filme" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "“Wi-Fi only” is on and the device is on cellular. Transfers resume by themselves once Wi-Fi is back." + "en": { + "stringUnit": { + "state": "translated", + "value": "Movies" } } } }, - "downloads.movies" : { - "comment" : "Überschrift für heruntergeladene Filme", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filme" + "downloads.pause": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Anhalten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Movies" + "en": { + "stringUnit": { + "state": "translated", + "value": "Pause" } } } }, - "downloads.pause" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Anhalten" + "downloads.paused": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Angehalten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pause" + "en": { + "stringUnit": { + "state": "translated", + "value": "Paused" } } } }, - "downloads.paused" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Angehalten" + "downloads.perSecond %@": { + "comment": "Tempo einer laufenden Übertragung", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%@/s" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Paused" + "en": { + "stringUnit": { + "state": "translated", + "value": "%@/s" } } } }, - "downloads.quality.approx %@ %@" : { - "comment" : "Platzhalter: Stufenname, geschätzte Größe", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ · etwa %@" + "downloads.quality.approx %@ %@": { + "comment": "Platzhalter: Stufenname, geschätzte Größe", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ · etwa %@" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ · about %@" + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ · about %@" } } } }, - "downloads.quality.hint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Niedrigere Stufen rechnet der Server unterwegs herunter. Das dauert länger, spart aber deutlich Platz." + "downloads.quality.hint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Niedrigere Stufen rechnet der Server unterwegs herunter. Das dauert länger, spart aber deutlich Platz." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lower levels are transcoded by the server. That takes longer but saves a lot of space." + "en": { + "stringUnit": { + "state": "translated", + "value": "Lower levels are transcoded by the server. That takes longer but saves a lot of space." } } } }, - "downloads.quality.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "In welcher Qualität?" + "downloads.quality.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "In welcher Qualität?" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Which quality?" + "en": { + "stringUnit": { + "state": "translated", + "value": "Which quality?" } } } }, - "downloads.queued" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wartet" + "downloads.queued": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wartet" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Queued" + "en": { + "stringUnit": { + "state": "translated", + "value": "Queued" } } } }, - "downloads.refused.exists" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dieser Titel steht bereits in der Warteschlange." + "downloads.refused.exists": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Dieser Titel steht bereits in der Warteschlange." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This title is already queued." + "en": { + "stringUnit": { + "state": "translated", + "value": "This title is already queued." } } } }, - "downloads.refused.limit %@ %@" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das Speicherlimit ist erreicht: %@ von %@ belegt." + "downloads.refused.limit %@ %@": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Speicherlimit ist erreicht: %@ von %@ belegt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Storage limit reached: %@ of %@ used." + "en": { + "stringUnit": { + "state": "translated", + "value": "Storage limit reached: %@ of %@ used." } } } }, - "downloads.refused.noMedia" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zu diesem Titel liegt keine Datei auf dem Server." + "downloads.refused.noMedia": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zu diesem Titel liegt keine Datei auf dem Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "There's no file for this title on the server." + "en": { + "stringUnit": { + "state": "translated", + "value": "There's no file for this title on the server." } } } }, - "downloads.refused.noPart" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Datei auf dem Server hat keinen abrufbaren Teil. Möglicherweise wurde sie noch nicht analysiert." + "downloads.refused.noPart": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Datei auf dem Server hat keinen abrufbaren Teil. Möglicherweise wurde sie noch nicht analysiert." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The file on the server has no retrievable part. It may not have been analyzed yet." + "en": { + "stringUnit": { + "state": "translated", + "value": "The file on the server has no retrievable part. It may not have been analyzed yet." } } } }, - "downloads.refused.noServer" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Verbindung zum Server." + "downloads.refused.noServer": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Verbindung zum Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No connection to the server." + "en": { + "stringUnit": { + "state": "translated", + "value": "No connection to the server." } } } }, - "downloads.refused.noURL" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Download-Adresse ließ sich nicht bilden." + "downloads.refused.noURL": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Download-Adresse ließ sich nicht bilden." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The download address couldn't be built." + "en": { + "stringUnit": { + "state": "translated", + "value": "The download address couldn't be built." } } } }, - "downloads.refused.space %@ %@" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zu wenig Platz auf dem Gerät: %@ nötig, %@ frei." + "downloads.refused.space %@ %@": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zu wenig Platz auf dem Gerät: %@ nötig, %@ frei." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Not enough space on this device: %@ needed, %@ free." + "en": { + "stringUnit": { + "state": "translated", + "value": "Not enough space on this device: %@ needed, %@ free." } } } }, - "downloads.refused.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download nicht möglich" + "downloads.refused.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Download nicht möglich" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Can't download" + "en": { + "stringUnit": { + "state": "translated", + "value": "Can't download" } } } }, - "downloads.removeConfirm" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download entfernen?" + "downloads.remaining %@": { + "comment": "Geschätzte Restzeit eines Downloads", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "noch %@" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remove download?" + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ left" } } } }, - "downloads.resume" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fortsetzen" + "downloads.remaining.underMinute": { + "comment": "Restzeit unter einer Minute — eine Zahl wäre hier genauer, als die Schätzung ist", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "gleich fertig" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resume" + "en": { + "stringUnit": { + "state": "translated", + "value": "almost done" } } } }, - "downloads.season" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Staffel laden" + "downloads.removeConfirm": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Download entfernen?" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download season" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove download?" } } } }, - "downloads.used" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Belegt" + "downloads.resume": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Fortsetzen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Used" + "en": { + "stringUnit": { + "state": "translated", + "value": "Resume" } } } }, - "downloads.wifiOnly" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nur über WLAN laden" + "downloads.season": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Staffel laden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download over Wi-Fi only" + "en": { + "stringUnit": { + "state": "translated", + "value": "Download season" } } } }, - "episode.long %lld %lld" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Staffel %lld · Episode %lld" + "downloads.used": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Belegt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Season %lld · Episode %lld" + "en": { + "stringUnit": { + "state": "translated", + "value": "Used" } } } }, - "episode.number %lld" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Folge %lld" + "downloads.wifiOnly": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nur über WLAN laden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Episode %lld" + "en": { + "stringUnit": { + "state": "translated", + "value": "Download over Wi-Fi only" } } } }, - "episode.short %lld %lld" : { - "comment" : "Nur auf Kacheln, wo eine Zeile breit ist. Wo Platz ist — Player, Aufmacher am Fernseher — steht episode.long.", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "S%lld E%lld" + "episode.long %lld %lld": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Staffel %lld · Episode %lld" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "S%lld E%lld" + "en": { + "stringUnit": { + "state": "translated", + "value": "Season %lld · Episode %lld" } } } }, - "error.noServers" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "An deinem Plex-Konto hängt kein Server." + "episode.number %lld": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Folge %lld" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "There's no server on your Plex account." + "en": { + "stringUnit": { + "state": "translated", + "value": "Episode %lld" } } } }, - "error.notFound" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dieser Titel ist auf dem Server nicht mehr vorhanden." + "episode.short %lld %lld": { + "comment": "Nur auf Kacheln, wo eine Zeile breit ist. Wo Platz ist — Player, Aufmacher am Fernseher — steht episode.long.", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "S%lld E%lld" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This title is no longer on the server." + "en": { + "stringUnit": { + "state": "translated", + "value": "S%lld E%lld" } } } }, - "error.server %lld" : { - "comment" : "Platzhalter: HTTP-Statuscode", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Server hat mit Fehler %lld geantwortet." + "error.noServers": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "An deinem Plex-Konto hängt kein Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server responded with error %lld." + "en": { + "stringUnit": { + "state": "translated", + "value": "There's no server on your Plex account." } } } }, - "error.serverUnreachable" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Server ist nicht erreichbar. Prüfe, ob er läuft und ob du im selben Netzwerk bist." + "error.notFound": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Dieser Titel ist auf dem Server nicht mehr vorhanden." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server can't be reached. Check that it's running and that you're on the same network." + "en": { + "stringUnit": { + "state": "translated", + "value": "This title is no longer on the server." } } } }, - "error.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das hat nicht geklappt" + "error.server %lld": { + "comment": "Platzhalter: HTTP-Statuscode", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Server hat mit Fehler %lld geantwortet." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Something went wrong" + "en": { + "stringUnit": { + "state": "translated", + "value": "The server responded with error %lld." } } } }, - "error.unauthorized" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Anmeldung ist abgelaufen. Melde dich erneut an." + "error.serverUnreachable": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Server ist nicht erreichbar. Prüfe, ob er läuft und ob du im selben Netzwerk bist." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Your sign-in has expired. Please sign in again." + "en": { + "stringUnit": { + "state": "translated", + "value": "The server can't be reached. Check that it's running and that you're on the same network." } } } }, - "error.unexpectedResponse" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Antwort des Servers war unerwartet. Möglicherweise ist eine neuere Plex-Version installiert." + "error.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Das hat nicht geklappt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server's response was unexpected. It may be running a newer version of Plex." + "en": { + "stringUnit": { + "state": "translated", + "value": "Something went wrong" } } } }, - "filter.any" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alle" + "error.unauthorized": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Anmeldung ist abgelaufen. Melde dich erneut an." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Any" + "en": { + "stringUnit": { + "state": "translated", + "value": "Your sign-in has expired. Please sign in again." } } } }, - "filter.filters" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filter" + "error.unexpectedResponse": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Antwort des Servers war unerwartet. Möglicherweise ist eine neuere Plex-Version installiert." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filters" + "en": { + "stringUnit": { + "state": "translated", + "value": "The server's response was unexpected. It may be running a newer version of Plex." } } } }, - "filter.genre" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Genre" + "filter.any": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Genre" + "en": { + "stringUnit": { + "state": "translated", + "value": "Any" } } } }, - "filter.librarySettings" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Diese Mediathek" + "filter.filters": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Filter" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This library" + "en": { + "stringUnit": { + "state": "translated", + "value": "Filters" } } } }, - "filter.reset" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zurücksetzen" + "filter.genre": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Genre" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Reset" + "en": { + "stringUnit": { + "state": "translated", + "value": "Genre" } } } }, - "filter.resolution" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auflösung" + "filter.librarySettings": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Diese Mediathek" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resolution" + "en": { + "stringUnit": { + "state": "translated", + "value": "This library" } } } }, - "filter.sort" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sortierung" + "filter.reset": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zurücksetzen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sort by" + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset" } } } }, - "filter.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filter" + "filter.resolution": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auflösung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filters" + "en": { + "stringUnit": { + "state": "translated", + "value": "Resolution" } } } }, - "filter.unwatchedOnly" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nur Ungesehene" + "filter.sort": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sortierung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unwatched only" + "en": { + "stringUnit": { + "state": "translated", + "value": "Sort by" } } } }, - "Full HD" : { - "comment" : "A label for a media resolution.", - "isCommentAutoGenerated" : true + "filter.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Filter" + } + }, + "en": { + "stringUnit": { + "state": "translated", + "value": "Filters" + } + } + } }, - "HD" : { - "comment" : "A label for a media resolution.", - "isCommentAutoGenerated" : true + "filter.unwatchedOnly": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nur Ungesehene" + } + }, + "en": { + "stringUnit": { + "state": "translated", + "value": "Unwatched only" + } + } + } }, - "hero.details" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Details" + "hero.details": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Details" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Details" + "en": { + "stringUnit": { + "state": "translated", + "value": "Details" } } } }, - "hero.next" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nächster Vorschlag" + "hero.next": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nächster Vorschlag" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Next suggestion" + "en": { + "stringUnit": { + "state": "translated", + "value": "Next suggestion" } } } }, - "hero.play" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abspielen" + "hero.play": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abspielen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Play" + "en": { + "stringUnit": { + "state": "translated", + "value": "Play" } } } }, - "hero.previous" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Voriger Vorschlag" + "hero.previous": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Voriger Vorschlag" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Previous suggestion" + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous suggestion" } } } }, - "hero.resume" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Weiterschauen" + "hero.resume": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Weiterschauen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resume" + "en": { + "stringUnit": { + "state": "translated", + "value": "Resume" } } } }, - "home.combineContinue" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Weiterschauen und Als Nächstes zusammenlegen" + "home.combineContinue": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Weiterschauen und Als Nächstes zusammenlegen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Merge Continue and Next Up" + "en": { + "stringUnit": { + "state": "translated", + "value": "Merge Continue and Next Up" } } } }, - "home.empty.description" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sobald dein Server Mediatheken enthält, erscheinen sie hier." + "home.empty.description": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sobald dein Server Mediatheken enthält, erscheinen sie hier." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Once your server has libraries, they'll show up here." + "en": { + "stringUnit": { + "state": "translated", + "value": "Once your server has libraries, they'll show up here." } } } }, - "home.empty.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Noch nichts da" + "home.empty.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Noch nichts da" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing here yet" + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing here yet" } } } }, - "home.hero" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aufmacher" + "home.hero": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aufmacher" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Featured" + "en": { + "stringUnit": { + "state": "translated", + "value": "Featured" } } } }, - "home.heroCount" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titel im Wechsel" + "home.heroCount": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Titel im Wechsel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titles in rotation" + "en": { + "stringUnit": { + "state": "translated", + "value": "Titles in rotation" } } } }, - "home.heroExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Aufmacher wandert alle sieben Sekunden weiter und hört damit auf, sobald du ihn berührst." + "home.heroExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Aufmacher wandert alle sieben Sekunden weiter und hört damit auf, sobald du ihn berührst." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The featured area moves on every seven seconds and stops as soon as you touch it." + "en": { + "stringUnit": { + "state": "translated", + "value": "The featured area moves on every seven seconds and stops as soon as you touch it." } } } }, - "home.heroHeight" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Höhe des Aufmachers" + "home.heroHeight": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Höhe des Aufmachers" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Featured height" + "en": { + "stringUnit": { + "state": "translated", + "value": "Featured height" } } } }, - "home.heroLibraries" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mediatheken für den Aufmacher" + "home.heroLibraries": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mediatheken für den Aufmacher" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Libraries for the hero" + "en": { + "stringUnit": { + "state": "translated", + "value": "Libraries for the hero" } } } }, - "home.heroLibrariesExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ohne Auswahl kommt alles infrage. Ist etwas gewählt, speist sich der Aufmacher nur noch aus diesen Mediatheken — Weiterschauen und Favoriten bleiben dann draussen, weil der Server nicht sagt, aus welcher Mediathek ein angefangener Titel stammt." + "home.heroLibrariesExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ohne Auswahl kommt alles infrage. Ist etwas gewählt, speist sich der Aufmacher nur noch aus diesen Mediatheken — Weiterschauen und Favoriten bleiben dann draussen, weil der Server nicht sagt, aus welcher Mediathek ein angefangener Titel stammt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "With nothing selected, everything is eligible. Once you select libraries, the hero is drawn only from them — Continue Watching and Favourites stay out, because the server does not say which library a part-watched title came from." + "en": { + "stringUnit": { + "state": "translated", + "value": "With nothing selected, everything is eligible. Once you select libraries, the hero is drawn only from them — Continue Watching and Favourites stay out, because the server does not say which library a part-watched title came from." } } } }, - "home.heroLibrariesNoMatch" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Für die gewählten Mediatheken gibt es derzeit keine eigene Reihe auf der Startseite. Die Auswahl greift deshalb nicht — der Aufmacher zeigt alles." + "home.heroLibrariesNoMatch": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Für die gewählten Mediatheken gibt es derzeit keine eigene Reihe auf der Startseite. Die Auswahl greift deshalb nicht — der Aufmacher zeigt alles." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The selected libraries currently have no row of their own on the home screen, so the selection has no effect — the hero shows everything." + "en": { + "stringUnit": { + "state": "translated", + "value": "The selected libraries currently have no row of their own on the home screen, so the selection has no effect — the hero shows everything." } } } }, - "home.heroSource" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Inhalt" + "home.heroSource": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Inhalt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Content" + "en": { + "stringUnit": { + "state": "translated", + "value": "Content" } } } }, - "home.heroSource.continueFirst" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Angefangenes zuerst" + "home.heroSource.continueFirst": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Angefangenes zuerst" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Started titles first" + "en": { + "stringUnit": { + "state": "translated", + "value": "Started titles first" } } } }, - "home.heroSource.latestOnly" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nur Neuzugänge" + "home.heroSource.latestOnly": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nur Neuzugänge" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "New arrivals only" + "en": { + "stringUnit": { + "state": "translated", + "value": "New arrivals only" } } } }, - "home.hideWatched" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gesehenes aus Neuzugängen ausblenden" + "home.hideWatched": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gesehenes aus Neuzugängen ausblenden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hide watched titles from new arrivals" + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide watched titles from new arrivals" } } } }, - "home.rowsHeader" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Reihen" + "home.rowsHeader": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Reihen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rows" + "en": { + "stringUnit": { + "state": "translated", + "value": "Rows" } } } }, - "homeLayout.empty" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Server liefert derzeit keine Reihen." + "homeLayout.empty": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Server liefert derzeit keine Reihen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server isn't providing any rows right now." + "en": { + "stringUnit": { + "state": "translated", + "value": "The server isn't providing any rows right now." } } } }, - "homeLayout.explanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schalte Reihen aus, die du nicht brauchst, und sortiere sie über „Bearbeiten“ um. Neue Reihen vom Server erscheinen am Ende." + "homeLayout.explanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Schalte Reihen aus, die du nicht brauchst, und sortiere sie über „Bearbeiten“ um. Neue Reihen vom Server erscheinen am Ende." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Turn off rows you don't need and reorder them via Edit. New rows from the server appear at the end." + "en": { + "stringUnit": { + "state": "translated", + "value": "Turn off rows you don't need and reorder them via Edit. New rows from the server appear at the end." } } } }, - "homeLayout.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Startseite" + "homeLayout.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Startseite" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Home screen" + "en": { + "stringUnit": { + "state": "translated", + "value": "Home screen" } } } }, - "item.watched" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gesehen" + "item.watched": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gesehen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Watched" + "en": { + "stringUnit": { + "state": "translated", + "value": "Watched" } } } }, - "languages.audio" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tonspur" + "languages.audio": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Tonspur" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio track" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio track" } } } }, - "languages.audioExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gibt es beim Abspielen eine Tonspur in dieser Sprache, wird sie von selbst gewählt. Sonst bleibt es bei der Spur, die der Server voranstellt." + "languages.audioExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gibt es beim Abspielen eine Tonspur in dieser Sprache, wird sie von selbst gewählt. Sonst bleibt es bei der Spur, die der Server voranstellt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "If a track in this language exists, it is selected automatically. Otherwise the server's own first track is used." + "en": { + "stringUnit": { + "state": "translated", + "value": "If a track in this language exists, it is selected automatically. Otherwise the server's own first track is used." } } } }, - "languages.audioHeader" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ton" + "languages.audioHeader": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ton" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio" } } } }, - "languages.avoidAudioDescription" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audiodeskription meiden" + "languages.avoidAudioDescription": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Audiodeskription meiden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Avoid audio description" + "en": { + "stringUnit": { + "state": "translated", + "value": "Avoid audio description" } } } }, - "languages.avoidAudioDescriptionExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Spuren, die eine Szenenbeschreibung für Sehbehinderte enthalten, werden nur genommen, wenn es keine andere in dieser Sprache gibt. Erkannt wird das am Namen der Spur — der Server kennzeichnet es nicht." + "languages.avoidAudioDescriptionExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Spuren, die eine Szenenbeschreibung für Sehbehinderte enthalten, werden nur genommen, wenn es keine andere in dieser Sprache gibt. Erkannt wird das am Namen der Spur — der Server kennzeichnet es nicht." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tracks that describe the scene for blind viewers are only used when there is no other track in that language. They are recognised by name — the server doesn't flag them." + "en": { + "stringUnit": { + "state": "translated", + "value": "Tracks that describe the scene for blind viewers are only used when there is no other track in that language. They are recognised by name — the server doesn't flag them." } } } }, - "languages.fallbackAdd" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sprache hinzufügen" + "languages.fallbackAdd": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sprache hinzufügen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Add language" + "en": { + "stringUnit": { + "state": "translated", + "value": "Add language" } } } }, - "languages.fallbackEmpty" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Ausweichsprachen" + "languages.fallbackEmpty": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Ausweichsprachen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No fallback languages" + "en": { + "stringUnit": { + "state": "translated", + "value": "No fallback languages" } } } }, - "languages.fallbackExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gilt nur, wenn ein Titel die gewünschte Tonsprache gar nicht hat. Dann wird von oben nach unten gesucht." + "languages.fallbackExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gilt nur, wenn ein Titel die gewünschte Tonsprache gar nicht hat. Dann wird von oben nach unten gesucht." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Applies only when a title doesn't have your preferred audio language at all. Then it works down the list." + "en": { + "stringUnit": { + "state": "translated", + "value": "Applies only when a title doesn't have your preferred audio language at all. Then it works down the list." } } } }, - "languages.fallbackHeader" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rangliste" + "languages.fallbackHeader": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Rangliste" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fallback order" + "en": { + "stringUnit": { + "state": "translated", + "value": "Fallback order" } } } }, - "languages.forThisLibrary" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tonsprache für diese Mediathek" + "languages.forThisLibrary": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Tonsprache für diese Mediathek" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio language for this library" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio language for this library" } } } }, - "languages.forThisLibraryExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gilt für alle Titel dieser Mediathek. Eine Serie kann es überstimmen, und was du im Player von Hand wählst, überstimmt beides." + "languages.forThisLibraryExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gilt für alle Titel dieser Mediathek. Eine Serie kann es überstimmen, und was du im Player von Hand wählst, überstimmt beides." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Applies to every title in this library. A series can override it, and what you pick by hand in the player overrides both." + "en": { + "stringUnit": { + "state": "translated", + "value": "Applies to every title in this library. A series can override it, and what you pick by hand in the player overrides both." } } } }, - "languages.forThisSeries" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tonsprache für diese Serie" + "languages.forThisSeries": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Tonsprache für diese Serie" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio language for this series" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio language for this series" } } } }, - "languages.headerExplanation" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Welche Tonspur und welche Untertitel beim Abspielen von selbst gewählt werden." + "languages.headerExplanation": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Welche Tonspur und welche Untertitel beim Abspielen von selbst gewählt werden." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Which audio track and which subtitles are chosen automatically during playback." + "en": { + "stringUnit": { + "state": "translated", + "value": "Which audio track and which subtitles are chosen automatically during playback." } } } }, - "languages.inherit" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wie allgemein" + "languages.inherit": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wie allgemein" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "As set generally" + "en": { + "stringUnit": { + "state": "translated", + "value": "As set generally" } } } }, - "languages.serverDefault" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wie der Server vorgibt" + "languages.serverDefault": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wie der Server vorgibt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "As the server prefers" + "en": { + "stringUnit": { + "state": "translated", + "value": "As the server prefers" } } } }, - "languages.subtitles" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel" + "languages.subtitles": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles" + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles" } } } }, - "languages.subtitlesExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel werden nur eingeschaltet, wenn es sie in dieser Sprache gibt. „Aus“ heißt: gar keine, bis du sie im Player selbst einschaltest." + "languages.subtitlesExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel werden nur eingeschaltet, wenn es sie in dieser Sprache gibt. „Aus“ heißt: gar keine, bis du sie im Player selbst einschaltest." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles are only switched on when they exist in this language. “Off” means none until you turn them on in the player." + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles are only switched on when they exist in this language. “Off” means none until you turn them on in the player." } } } }, - "languages.subtitlesHeader" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel" + "languages.subtitlesHeader": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles" + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles" } } } }, - "languages.subtitlesOff" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aus" + "languages.subtitlesOff": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aus" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Off" + "en": { + "stringUnit": { + "state": "translated", + "value": "Off" } } } }, - "languages.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ton & Untertitel" + "languages.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ton & Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio & subtitles" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio & subtitles" } } } }, - "library.allLoaded %@" : { - "comment" : "A footer in the library view that shows the number of items in the library.", - "isCommentAutoGenerated" : true + "library.allLoaded %@": { + "comment": "A footer in the library view that shows the number of items in the library.", + "isCommentAutoGenerated": true }, - "library.allLoaded %lld" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alle %lld Titel geladen" + "library.allLoaded %lld": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle %lld Titel geladen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "All %lld titles loaded" + "en": { + "stringUnit": { + "state": "translated", + "value": "All %lld titles loaded" } } } }, - "library.clearFilters" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filter zurücksetzen" + "library.clearFilters": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Filter zurücksetzen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Clear filters" + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear filters" } } } }, - "library.empty.description" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mit den aktuellen Filtern bleibt nichts übrig." + "library.empty.description": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mit den aktuellen Filtern bleibt nichts übrig." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing matches the current filters." + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing matches the current filters." } } } }, - "library.empty.title" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Titel" + "library.empty.title": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Titel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No titles" + "en": { + "stringUnit": { + "state": "translated", + "value": "No titles" } } } }, - "library.filters" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filter" + "library.filters": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Filter" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filters" + "en": { + "stringUnit": { + "state": "translated", + "value": "Filters" } } } }, - "library.kind.liveTV" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Live-TV" + "library.kind.liveTV": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Live-TV" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Live TV" + "en": { + "stringUnit": { + "state": "translated", + "value": "Live TV" } } } }, - "library.kind.movie" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filme" + "library.kind.movie": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Filme" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Movies" + "en": { + "stringUnit": { + "state": "translated", + "value": "Movies" } } } }, - "library.kind.music" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Musik" + "library.kind.music": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Musik" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Music" + "en": { + "stringUnit": { + "state": "translated", + "value": "Music" } } } }, - "library.kind.other" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sonstiges" + "library.kind.other": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sonstiges" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Other" + "en": { + "stringUnit": { + "state": "translated", + "value": "Other" } } } }, - "library.kind.photo" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fotos" + "library.kind.photo": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Fotos" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Photos" + "en": { + "stringUnit": { + "state": "translated", + "value": "Photos" } } } }, - "library.kind.show" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Serien" + "library.kind.show": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Serien" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "TV shows" + "en": { + "stringUnit": { + "state": "translated", + "value": "TV shows" } } } }, - "library.loadMore" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Weitere laden" + "library.loadMore": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Weitere laden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Load more" + "en": { + "stringUnit": { + "state": "translated", + "value": "Load more" } } } }, - "library.loadMoreFailed" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das Nachladen ist hängen geblieben — es fehlen noch Titel." + "library.loadMoreFailed": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Nachladen ist hängen geblieben — es fehlen noch Titel." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Loading more stalled — some titles are still missing." + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading more stalled — some titles are still missing." } } } }, - "library.reload" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Neu laden" + "library.reload": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Neu laden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Reload" + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload" } } } }, - "library.scan" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mediathek durchsuchen" + "library.scan": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mediathek durchsuchen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Scan library" + "en": { + "stringUnit": { + "state": "translated", + "value": "Scan library" } } } }, - "library.search" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Suchen" + "library.search": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Suchen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Search" + "en": { + "stringUnit": { + "state": "translated", + "value": "Search" } } } }, - "library.title" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mediatheken" + "library.title": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mediatheken" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Libraries" + "en": { + "stringUnit": { + "state": "translated", + "value": "Libraries" } } } }, - "library.toggleLayout" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ansicht wechseln" + "library.toggleLayout": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ansicht wechseln" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Switch layout" + "en": { + "stringUnit": { + "state": "translated", + "value": "Switch layout" } } } }, - "libraryHub.empty" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auf diesem Server ist keine Mediathek zu sehen." + "libraryHub.empty": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auf diesem Server ist keine Mediathek zu sehen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No library is visible on this server." + "en": { + "stringUnit": { + "state": "translated", + "value": "No library is visible on this server." } } } }, - "liveTV.alreadyScheduled" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zur Aufnahme vorgemerkt" + "liveTV.alreadyScheduled": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zur Aufnahme vorgemerkt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Scheduled to record" + "en": { + "stringUnit": { + "state": "translated", + "value": "Scheduled to record" } } } }, - "liveTV.anyChannel" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auf allen Sendern" + "liveTV.anyChannel": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auf allen Sendern" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "On any channel" + "en": { + "stringUnit": { + "state": "translated", + "value": "On any channel" } } } }, - "liveTV.cancelTimer" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Absagen" + "liveTV.cancelTimer": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Absagen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cancel" + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" } } } }, - "liveTV.channelCount %@" : { - "comment" : "A label displaying the number of channels. The argument is the number of channels.", - "isCommentAutoGenerated" : true + "liveTV.channelCount %@": { + "comment": "A label displaying the number of channels. The argument is the number of channels.", + "isCommentAutoGenerated": true }, - "liveTV.channelCount %lld" : { - "comment" : "Platzhalter: Anzahl Sender", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Sender" + "liveTV.channelCount %lld": { + "comment": "Platzhalter: Anzahl Sender", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Sender" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld channels" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld channels" } } } }, - "liveTV.deleteRecording.confirm" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aufnahme löschen?" + "liveTV.deleteRecording.confirm": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aufnahme löschen?" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Delete recording?" + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete recording?" } } } }, - "liveTV.gap" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Angaben" + "liveTV.gap": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Angaben" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No data" + "en": { + "stringUnit": { + "state": "translated", + "value": "No data" } } } }, - "liveTV.mode" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ansicht" + "liveTV.mode": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ansicht" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "View" + "en": { + "stringUnit": { + "state": "translated", + "value": "View" } } } }, - "liveTV.mode.guide" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Programm" + "liveTV.mode.guide": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Programm" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Guide" + "en": { + "stringUnit": { + "state": "translated", + "value": "Guide" } } } }, - "liveTV.mode.now" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Jetzt" + "liveTV.mode.now": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Jetzt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Now" + "en": { + "stringUnit": { + "state": "translated", + "value": "Now" } } } }, - "liveTV.next %@ %@" : { - "comment" : "Platzhalter: Uhrzeit, Sendungstitel", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Danach %@ · %@" + "liveTV.next %@ %@": { + "comment": "Platzhalter: Uhrzeit, Sendungstitel", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Danach %@ · %@" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Next %@ · %@" + "en": { + "stringUnit": { + "state": "translated", + "value": "Next %@ · %@" } } } }, - "liveTV.noChannels.description" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Empfänger ist eingerichtet, liefert aber keine Sender." + "liveTV.noChannels.description": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Empfänger ist eingerichtet, liefert aber keine Sender." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The tuner is set up but isn't providing any channels." + "en": { + "stringUnit": { + "state": "translated", + "value": "The tuner is set up but isn't providing any channels." } } } }, - "liveTV.noChannels.title" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Sender" + "liveTV.noChannels.title": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Sender" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No channels" + "en": { + "stringUnit": { + "state": "translated", + "value": "No channels" } } } }, - "liveTV.noProgram" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kein Programm verfügbar" + "liveTV.noProgram": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Kein Programm verfügbar" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No guide data" + "en": { + "stringUnit": { + "state": "translated", + "value": "No guide data" } } } }, - "liveTV.noStream" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Server hat keine Adresse für diesen Sender geliefert." + "liveTV.noStream": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Server hat keine Adresse für diesen Sender geliefert." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server did not return a stream for this channel." + "en": { + "stringUnit": { + "state": "translated", + "value": "The server did not return a stream for this channel." } } } }, - "liveTV.notConfigured.description" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Live-TV braucht eine Fernsehkarte oder einen Netzwerk-Tuner am Plex-Server. Richte ihn in den Servereinstellungen ein, dann erscheinen die Sender hier." + "liveTV.notConfigured.description": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Live-TV braucht eine Fernsehkarte oder einen Netzwerk-Tuner am Plex-Server. Richte ihn in den Servereinstellungen ein, dann erscheinen die Sender hier." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Live TV needs a tuner card or network tuner on your Plex server. Set one up in the server settings and channels will appear here." + "en": { + "stringUnit": { + "state": "translated", + "value": "Live TV needs a tuner card or network tuner on your Plex server. Set one up in the server settings and channels will appear here." } } } }, - "liveTV.notConfigured.title" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kein Empfänger eingerichtet" + "liveTV.notConfigured.title": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Kein Empfänger eingerichtet" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No tuner set up" + "en": { + "stringUnit": { + "state": "translated", + "value": "No tuner set up" } } } }, - "liveTV.now" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Jetzt" + "liveTV.now": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Jetzt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Now" + "en": { + "stringUnit": { + "state": "translated", + "value": "Now" } } } }, - "liveTV.record" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aufnehmen" + "liveTV.record": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aufnehmen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Record" + "en": { + "stringUnit": { + "state": "translated", + "value": "Record" } } } }, - "liveTV.recording.inProgress" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nimmt auf" + "liveTV.recordSeries": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Serie aufnehmen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Recording" + "en": { + "stringUnit": { + "state": "translated", + "value": "Record series" } } } }, - "liveTV.recordings" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aufnahmen" + "liveTV.recording.inProgress": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nimmt auf" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Recordings" + "en": { + "stringUnit": { + "state": "translated", + "value": "Recording" } } } }, - "liveTV.recordings.empty.description" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Im Programm eine Sendung antippen und „Aufnehmen“ wählen — sie steht dann hier." + "liveTV.recordings": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aufnahmen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tap a programme in the guide and choose “Record” — it will show up here." + "en": { + "stringUnit": { + "state": "translated", + "value": "Recordings" } } } }, - "liveTV.recordings.empty.title" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Noch nichts aufgenommen" + "liveTV.recordings.empty.description": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Im Programm eine Sendung antippen und „Aufnehmen“ wählen — sie steht dann hier." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing recorded yet" + "en": { + "stringUnit": { + "state": "translated", + "value": "Tap a programme in the guide and choose “Record” — it will show up here." } } } }, - "liveTV.recordSeries" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Serie aufnehmen" + "liveTV.recordings.empty.title": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Noch nichts aufgenommen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Record series" + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing recorded yet" } } } }, - "liveTV.runningNow" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Läuft gerade" + "liveTV.runningNow": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Läuft gerade" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "On now" + "en": { + "stringUnit": { + "state": "translated", + "value": "On now" } } } }, - "liveTV.scheduled" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Geplant" + "liveTV.scheduled": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Geplant" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Scheduled" + "en": { + "stringUnit": { + "state": "translated", + "value": "Scheduled" } } } }, - "liveTV.selection" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sender auswählen" + "liveTV.selection": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sender auswählen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Choose channels" + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose channels" } } } }, - "liveTV.selection.all" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alle" + "liveTV.selection.all": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "All" + "en": { + "stringUnit": { + "state": "translated", + "value": "All" } } } }, - "liveTV.selection.hint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ohne Auswahl werden alle Sender gezeigt." + "liveTV.selection.hint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ohne Auswahl werden alle Sender gezeigt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "With nothing selected, all channels are shown." + "en": { + "stringUnit": { + "state": "translated", + "value": "With nothing selected, all channels are shown." } } } }, - "liveTV.series" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ganze Reihe" + "liveTV.series": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ganze Reihe" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Whole series" + "en": { + "stringUnit": { + "state": "translated", + "value": "Whole series" } } } }, - "liveTV.timeRange %@ %@" : { - "comment" : "Platzhalter: Anfangs- und Endzeit", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ – %@" + "liveTV.timeRange %@ %@": { + "comment": "Platzhalter: Anfangs- und Endzeit", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ – %@" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ – %@" + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ – %@" } } } }, - "liveTV.tune" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Einschalten" + "liveTV.tune": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Einschalten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Watch" + "en": { + "stringUnit": { + "state": "translated", + "value": "Watch" } } } }, - "liveTV.tuneFailed" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sender lässt sich nicht einschalten" + "liveTV.tuneFailed": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sender lässt sich nicht einschalten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cannot tune channel" + "en": { + "stringUnit": { + "state": "translated", + "value": "Cannot tune channel" } } } }, - "miniPlayer.close" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wiedergabe beenden" + "miniPlayer.close": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wiedergabe beenden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stop playback" + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop playback" } } } }, - "miniPlayer.expand" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auf Vollbild vergrößern" + "miniPlayer.expand": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auf Vollbild vergrößern" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Expand to full screen" + "en": { + "stringUnit": { + "state": "translated", + "value": "Expand to full screen" } } } }, - "miniPlayer.resize" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Größe ändern" + "miniPlayer.resize": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Größe ändern" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resize" + "en": { + "stringUnit": { + "state": "translated", + "value": "Resize" } } } }, - "placeholder.description" : { - "comment" : "Platzhalter-Bildschirme", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dieser Bereich entsteht in einer späteren Phase." + "placeholder.description": { + "comment": "Platzhalter-Bildschirme", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Dieser Bereich entsteht in einer späteren Phase." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This area is coming in a later phase." + "en": { + "stringUnit": { + "state": "translated", + "value": "This area is coming in a later phase." } } } }, - "player.airPlay" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auf Gerät übertragen" + "player.airPlay": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auf Gerät übertragen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Play on another device" + "en": { + "stringUnit": { + "state": "translated", + "value": "Play on another device" } } } }, - "player.audioTracks" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tonspur" + "player.audioTracks": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Tonspur" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio" } } } }, - "player.back10" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "10 Sekunden zurück" + "player.back10": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "10 Sekunden zurück" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Back 10 seconds" + "en": { + "stringUnit": { + "state": "translated", + "value": "Back 10 seconds" } } } }, - "player.buffering" : { - "comment" : "Sprachausgabe für den Abspielknopf, solange auf Daten gewartet wird", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lädt …" + "player.buffering": { + "comment": "Sprachausgabe für den Abspielknopf, solange auf Daten gewartet wird", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Lädt …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Loading…" + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading…" } } } }, - "player.close" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Player schließen" + "player.close": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Player schließen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Close player" + "en": { + "stringUnit": { + "state": "translated", + "value": "Close player" } } } }, - "player.exitFullScreen" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vollbild beenden" + "player.exitFullScreen": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Vollbild beenden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Exit full screen" + "en": { + "stringUnit": { + "state": "translated", + "value": "Exit full screen" } } } }, - "player.fillVideo" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bild füllen" + "player.fillVideo": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bild füllen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fill screen" + "en": { + "stringUnit": { + "state": "translated", + "value": "Fill screen" } } } }, - "player.fitVideo" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bild einpassen" + "player.fitVideo": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bild einpassen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fit to screen" + "en": { + "stringUnit": { + "state": "translated", + "value": "Fit to screen" } } } }, - "player.forgetRememberedTrack" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nicht mehr merken" + "player.forgetRememberedTrack": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nicht mehr merken" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stop remembering" + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop remembering" } } } }, - "player.forward10" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "10 Sekunden vor" + "player.forward10": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "10 Sekunden vor" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Forward 10 seconds" + "en": { + "stringUnit": { + "state": "translated", + "value": "Forward 10 seconds" } } } }, - "player.fullScreen" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vollbild" + "player.fullScreen": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Vollbild" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Enter full screen" + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter full screen" } } } }, - "player.jellyfinPending" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Wiedergabe von Jellyfin ist noch nicht fertig — sie ist der nächste Schritt." + "player.jellyfinPending": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Wiedergabe von Jellyfin ist noch nicht fertig — sie ist der nächste Schritt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Playback from Jellyfin isn't finished yet — it's the next step." + "en": { + "stringUnit": { + "state": "translated", + "value": "Playback from Jellyfin isn't finished yet — it's the next step." } } } }, - "player.loadFailed" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Datei ließ sich nicht öffnen." + "player.loadFailed": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Datei ließ sich nicht öffnen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The file couldn't be opened." + "en": { + "stringUnit": { + "state": "translated", + "value": "The file couldn't be opened." } } } }, - "player.minimize" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verkleinern" + "player.minimize": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verkleinern" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Minimize" + "en": { + "stringUnit": { + "state": "translated", + "value": "Minimize" } } } }, - "player.nextEpisode" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nächste Folge" + "player.nextEpisode": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nächste Folge" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Next episode" + "en": { + "stringUnit": { + "state": "translated", + "value": "Next episode" } } } }, - "player.noPlayableFile" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zu diesem Titel gibt es keine abspielbare Datei auf dem Server." + "player.noPlayableFile": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zu diesem Titel gibt es keine abspielbare Datei auf dem Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "There's no playable file for this title on the server." + "en": { + "stringUnit": { + "state": "translated", + "value": "There's no playable file for this title on the server." } } } }, - "player.pause" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pause" + "player.pause": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Pause" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pause" + "en": { + "stringUnit": { + "state": "translated", + "value": "Pause" } } } }, - "player.play" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abspielen" + "player.play": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abspielen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Play" + "en": { + "stringUnit": { + "state": "translated", + "value": "Play" } } } }, - "player.playbackFailed" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Wiedergabe wurde unterbrochen." + "player.playbackFailed": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Wiedergabe wurde unterbrochen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Playback was interrupted." + "en": { + "stringUnit": { + "state": "translated", + "value": "Playback was interrupted." } } } }, - "player.position" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Position" + "player.position": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Position" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Position" + "en": { + "stringUnit": { + "state": "translated", + "value": "Position" } } } }, - "player.preparing" : { - "comment" : "Hinweis, während der Player die Datei öffnet", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wird vorbereitet …" + "player.preparing": { + "comment": "Hinweis, während der Player die Datei öffnet", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wird vorbereitet …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Preparing…" + "en": { + "stringUnit": { + "state": "translated", + "value": "Preparing…" } } } }, - "player.quality" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Qualität" + "player.quality": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Qualität" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Quality" + "en": { + "stringUnit": { + "state": "translated", + "value": "Quality" } } } }, - "player.rebuilding" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stream wird neu aufgebaut …" + "player.rebuilding": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stream wird neu aufgebaut …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rebuilding stream …" + "en": { + "stringUnit": { + "state": "translated", + "value": "Rebuilding stream …" } } } }, - "player.rebuilds" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stream wird neu aufgebaut" + "player.rebuilds": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stream wird neu aufgebaut" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stream will be rebuilt" + "en": { + "stringUnit": { + "state": "translated", + "value": "Stream will be rebuilt" } } } }, - "player.relayWarning" : { - "comment" : "Warnung im Player, wenn die Verbindung über die Plex-Vermittlung läuft", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Verbindung läuft über die Vermittlung von Plex und ist stark gedrosselt. Große Dateien laufen darüber kaum an." + "player.relayWarning": { + "comment": "Warnung im Player, wenn die Verbindung über die Plex-Vermittlung läuft", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Verbindung läuft über die Vermittlung von Plex und ist stark gedrosselt. Große Dateien laufen darüber kaum an." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "You're connected through the Plex relay, which is heavily throttled. Large files barely start over it." + "en": { + "stringUnit": { + "state": "translated", + "value": "You're connected through the Plex relay, which is heavily throttled. Large files barely start over it." } } } }, - "player.rememberedTrack" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Für diese Serie gemerkt" + "player.rememberedTrack": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Für diese Serie gemerkt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remembered for this series" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remembered for this series" } } } }, - "player.rememberedTrackExplanation %@" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seit du „%@“ gewählt hast, wird diese Spur in jeder Folge genommen." + "player.rememberedTrackExplanation %@": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Seit du „%@“ gewählt hast, wird diese Spur in jeder Folge genommen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Since you picked “%@”, that track is used for every episode." + "en": { + "stringUnit": { + "state": "translated", + "value": "Since you picked “%@”, that track is used for every episode." } } } }, - "player.restoring" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stelle wird wiederhergestellt" + "player.restoring": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stelle wird wiederhergestellt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Returning to your position" + "en": { + "stringUnit": { + "state": "translated", + "value": "Returning to your position" } } } }, - "player.rotate" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Drehen" + "player.rotate": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Drehen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rotate" + "en": { + "stringUnit": { + "state": "translated", + "value": "Rotate" } } } }, - "player.skipCredits" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abspann überspringen" + "player.skipCredits": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abspann überspringen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Skip credits" + "en": { + "stringUnit": { + "state": "translated", + "value": "Skip credits" } } } }, - "player.skipIntro" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Intro überspringen" + "player.skipIntro": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Intro überspringen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Skip intro" + "en": { + "stringUnit": { + "state": "translated", + "value": "Skip intro" } } } }, - "player.stalled" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Es kommen keine Daten mehr. Der Stream ist abgerissen." + "player.stalled": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Es kommen keine Daten mehr. Der Stream ist abgerissen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No more data is arriving. The stream stopped." + "en": { + "stringUnit": { + "state": "translated", + "value": "No more data is arriving. The stream stopped." } } } }, - "player.stalledUnlimited" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Es kommen keine Daten mehr. Die Qualität steht auf „Original\", der Server überträgt also die Datei unverändert — dafür reicht diese Verbindung nicht. Unter Einstellungen → Wiedergabe eine kleinere Stufe wählen." + "player.stalledUnlimited": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Es kommen keine Daten mehr. Die Qualität steht auf „Original\", der Server überträgt also die Datei unverändert — dafür reicht diese Verbindung nicht. Unter Einstellungen → Wiedergabe eine kleinere Stufe wählen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No more data is arriving. Quality is set to “Original”, so the server sends the file unchanged — this connection cannot carry that. Pick a smaller step under Settings → Playback." + "en": { + "stringUnit": { + "state": "translated", + "value": "No more data is arriving. Quality is set to “Original”, so the server sends the file unchanged — this connection cannot carry that. Pick a smaller step under Settings → Playback." } } } }, - "player.stop" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Beenden" + "player.stop": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Beenden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stop" + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop" } } } }, - "player.subtitlesOff" : { - "comment" : "Eintrag, der die Untertitel abschaltet", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aus" + "player.subtitleTracks": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Off" + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles" } } } }, - "player.subtitleTracks" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel" + "player.subtitlesOff": { + "comment": "Eintrag, der die Untertitel abschaltet", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aus" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles" + "en": { + "stringUnit": { + "state": "translated", + "value": "Off" } } } }, - "player.tracks" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ton und Untertitel" + "player.tracks": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ton und Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio and subtitles" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio and subtitles" } } } }, - "player.transcodeRefused" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Server hat die Wiedergabe abgelehnt." + "player.transcodeRefused": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Server hat die Wiedergabe abgelehnt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server refused playback." + "en": { + "stringUnit": { + "state": "translated", + "value": "The server refused playback." } } } }, - "quality.audioOnly" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nur Ton" + "quality.audioOnly": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nur Ton" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio only" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio only" } } } }, - "quality.audioOnly.hint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Plex kennt für Filme keinen reinen Tonstrom. Diese Stufe überträgt das Bild in der kleinsten Auflösung und blendet es aus." + "quality.audioOnly.hint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Plex kennt für Filme keinen reinen Tonstrom. Diese Stufe überträgt das Bild in der kleinsten Auflösung und blendet es aus." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Plex has no audio-only stream for video. This setting sends the picture at the smallest size and hides it." + "en": { + "stringUnit": { + "state": "translated", + "value": "Plex has no audio-only stream for video. This setting sends the picture at the smallest size and hides it." } } } }, - "quality.audioShort" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ton" + "quality.audioShort": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ton" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio" } } } }, - "quality.automatic" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Automatisch" + "quality.autoShort": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auto" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Automatic" + "en": { + "stringUnit": { + "state": "translated", + "value": "Auto" } } } }, - "quality.autoShort" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auto" + "quality.automatic": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Automatisch" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auto" + "en": { + "stringUnit": { + "state": "translated", + "value": "Automatic" } } } }, - "quality.current" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Läuft gerade" + "quality.current": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Läuft gerade" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Currently playing" + "en": { + "stringUnit": { + "state": "translated", + "value": "Currently playing" } } } }, - "quality.current.bitrate" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bitrate" + "quality.current.bitrate": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bitrate" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bitrate" + "en": { + "stringUnit": { + "state": "translated", + "value": "Bitrate" } } } }, - "quality.current.buffer" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Puffer" + "quality.current.buffer": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Puffer" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Buffer" + "en": { + "stringUnit": { + "state": "translated", + "value": "Buffer" } } } }, - "quality.current.dropped" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verworfene Bilder" + "quality.current.dropped": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verworfene Bilder" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dropped frames" + "en": { + "stringUnit": { + "state": "translated", + "value": "Dropped frames" } } } }, - "quality.current.hint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gemessen, nicht gewünscht: die Stufe sagt, was erlaubt ist — hier steht, was ankommt." + "quality.current.hint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gemessen, nicht gewünscht: die Stufe sagt, was erlaubt ist — hier steht, was ankommt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Measured, not requested: the setting caps the rate, this is what arrives." + "en": { + "stringUnit": { + "state": "translated", + "value": "Measured, not requested: the setting caps the rate, this is what arrives." } } } }, - "quality.current.noLadder" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dieser Stream hat nur eine Stufe — es gibt nichts, worauf der Player ausweichen könnte. Bei Rücklern hilft hier nur eine kleinere Qualität von Hand." + "quality.current.noLadder": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Dieser Stream hat nur eine Stufe — es gibt nichts, worauf der Player ausweichen könnte. Bei Rücklern hilft hier nur eine kleinere Qualität von Hand." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This stream has a single variant — there is nothing for the player to fall back to. If it stutters, only a manually chosen lower quality helps." + "en": { + "stringUnit": { + "state": "translated", + "value": "This stream has a single variant — there is nothing for the player to fall back to. If it stutters, only a manually chosen lower quality helps." } } } }, - "quality.current.prefetch" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Eigener Vorrat" + "quality.current.prefetch": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Eigener Vorrat" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Own prefetch" + "en": { + "stringUnit": { + "state": "translated", + "value": "Own prefetch" } } } }, - "quality.current.resolution" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auflösung" + "quality.current.resolution": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auflösung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resolution" + "en": { + "stringUnit": { + "state": "translated", + "value": "Resolution" } } } }, - "quality.current.stalls" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rückler" + "quality.current.stalls": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Rückler" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stalls" + "en": { + "stringUnit": { + "state": "translated", + "value": "Stalls" } } } }, - "quality.current.variants" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stufen in der Playlist" + "quality.current.variants": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stufen in der Playlist" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Variants in playlist" + "en": { + "stringUnit": { + "state": "translated", + "value": "Variants in playlist" } } } }, - "quality.everyRung" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alle Stufen" + "quality.everyRung": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle Stufen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "All steps" + "en": { + "stringUnit": { + "state": "translated", + "value": "All steps" } } } }, - "quality.fullHD" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "1080p" + "quality.fullHD": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "1080p" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "1080p" + "en": { + "stringUnit": { + "state": "translated", + "value": "1080p" } } } }, - "quality.hd" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "720p" + "quality.hd": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "720p" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "720p" + "en": { + "stringUnit": { + "state": "translated", + "value": "720p" } } } }, - "quality.high" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hoch (12 Mbit/s)" + "quality.high": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Hoch (12 Mbit/s)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "High (12 Mbps)" + "en": { + "stringUnit": { + "state": "translated", + "value": "High (12 Mbps)" } } } }, - "quality.kilobits %lld" : { - "comment" : "Platzhalter: Kilobit je Sekunde", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld kbps" + "quality.kilobits %lld": { + "comment": "Platzhalter: Kilobit je Sekunde", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld kbps" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld kbps" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld kbps" } } } }, - "quality.low" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Niedrig (4 Mbit/s)" + "quality.low": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Niedrig (4 Mbit/s)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Low (4 Mbps)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Low (4 Mbps)" } } } }, - "quality.lowRes" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "480p" + "quality.lowRes": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "480p" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "480p" + "en": { + "stringUnit": { + "state": "translated", + "value": "480p" } } } }, - "quality.medium" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mittel (8 Mbit/s)" + "quality.medium": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mittel (8 Mbit/s)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Medium (8 Mbps)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Medium (8 Mbps)" } } } }, - "quality.megabits %@" : { - "comment" : "Platzhalter: Megabit je Sekunde", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ Mbps" + "quality.megabits %@": { + "comment": "Platzhalter: Megabit je Sekunde", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ Mbps" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ Mbps" + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ Mbps" } } } }, - "quality.minimal" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sparsam (2 Mbit/s)" + "quality.minimal": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sparsam (2 Mbit/s)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Data saver (2 Mbps)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Data saver (2 Mbps)" } } } }, - "quality.original" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Original" + "quality.original": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Original" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Original" + "en": { + "stringUnit": { + "state": "translated", + "value": "Original" } } } }, - "quality.originalShort" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Max" + "quality.originalShort": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Max" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Max" + "en": { + "stringUnit": { + "state": "translated", + "value": "Max" } } } }, - "quality.prefetch.ahead %lld" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld s voraus" + "quality.prefetch.ahead %lld": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld s voraus" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld s ahead" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld s ahead" } } } }, - "quality.prefetch.off" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "aus" + "quality.prefetch.off": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "aus" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "off" + "en": { + "stringUnit": { + "state": "translated", + "value": "off" } } } }, - "quality.prefetch.starting" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "läuft an" + "quality.prefetch.starting": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "läuft an" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "starting" + "en": { + "stringUnit": { + "state": "translated", + "value": "starting" } } } }, - "quality.seconds %@" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ s" + "quality.seconds %@": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ s" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ s" + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ s" } } } }, - "quality.stalls %lld %@" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld · %@ gewartet" + "quality.stalls %lld %@": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld · %@ gewartet" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld · %@ waiting" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld · %@ waiting" } } } }, - "quickPlay.failed" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abspielen nicht möglich" + "quickPlay.failed": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abspielen nicht möglich" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Can’t play this" + "en": { + "stringUnit": { + "state": "translated", + "value": "Can’t play this" } } } }, - "quickPlay.noServer" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Verbindung zum Server." + "quickPlay.noServer": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Verbindung zum Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Not connected to the server." + "en": { + "stringUnit": { + "state": "translated", + "value": "Not connected to the server." } } } }, - "quickPlay.nothingToPlay" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hier ist keine Folge zu finden, die sich abspielen lässt." + "quickPlay.nothingToPlay": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Hier ist keine Folge zu finden, die sich abspielen lässt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "There’s no episode here that could be played." + "en": { + "stringUnit": { + "state": "translated", + "value": "There’s no episode here that could be played." } } } }, - "runtime.hoursMinutes %lld %lld" : { - "comment" : "Laufzeit mit Stunden", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Std %lld Min" + "runtime.hoursMinutes %lld %lld": { + "comment": "Laufzeit mit Stunden", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Std %lld Min" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldh %lldm" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lldh %lldm" } } } }, - "runtime.minutes %lld" : { - "comment" : "Laufzeit unter einer Stunde", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Min" + "runtime.minutes %lld": { + "comment": "Laufzeit unter einer Stunde", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Min" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldm" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lldm" } } } }, - "SD" : { - "comment" : "A label for a 720p movie.", - "isCommentAutoGenerated" : true - }, - "search.forgetRecent" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verlauf löschen" + "search.forgetRecent": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verlauf löschen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Clear history" + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear history" } } } }, - "search.hint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Suche nach Filmen, Serien, Folgen und Personen." + "search.hint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Suche nach Filmen, Serien, Folgen und Personen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Search for movies, shows, episodes and people." + "en": { + "stringUnit": { + "state": "translated", + "value": "Search for movies, shows, episodes and people." } } } }, - "search.noResults" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nichts gefunden." + "search.noResults": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nichts gefunden." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing found." + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing found." } } } }, - "search.recent" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zuletzt gesucht" + "search.recent": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zuletzt gesucht" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Recent searches" + "en": { + "stringUnit": { + "state": "translated", + "value": "Recent searches" } } } }, - "search.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Suche" + "search.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Suche" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Search" + "en": { + "stringUnit": { + "state": "translated", + "value": "Search" } } } }, - "season.number %lld" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Staffel %lld" + "season.number %lld": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Staffel %lld" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Season %lld" + "en": { + "stringUnit": { + "state": "translated", + "value": "Season %lld" } } } }, - "season.short %lld" : { - "comment" : "Platzhalter: Staffelnummer", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Staffel %lld" + "season.short %lld": { + "comment": "Platzhalter: Staffelnummer", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Staffel %lld" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Season %lld" + "en": { + "stringUnit": { + "state": "translated", + "value": "Season %lld" } } } }, - "serverPicker.owned" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Eigener Server" + "serverPicker.owned": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Eigener Server" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Your server" + "en": { + "stringUnit": { + "state": "translated", + "value": "Your server" } } } }, - "serverPicker.shared" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Geteilt" + "serverPicker.shared": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Geteilt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Shared" + "en": { + "stringUnit": { + "state": "translated", + "value": "Shared" } } } }, - "serverPicker.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server wählen" + "serverPicker.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Server wählen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Choose a server" + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose a server" } } } }, - "settings.account" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Konto" + "settings.account": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Konto" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Account" + "en": { + "stringUnit": { + "state": "translated", + "value": "Account" } } } }, - "settings.address" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Adresse" + "settings.address": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Adresse" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Address" + "en": { + "stringUnit": { + "state": "translated", + "value": "Address" } } } }, - "settings.app" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "App" + "settings.app": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "App" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "App" + "en": { + "stringUnit": { + "state": "translated", + "value": "App" } } } }, - "settings.autoPlayNext" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nächste Folge automatisch" + "settings.autoPlayNext": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nächste Folge automatisch" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Play next episode automatically" + "en": { + "stringUnit": { + "state": "translated", + "value": "Play next episode automatically" } } } }, - "settings.autoSkipCredits" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abspann automatisch überspringen" + "settings.autoSkipCredits": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abspann automatisch überspringen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Skip credits automatically" + "en": { + "stringUnit": { + "state": "translated", + "value": "Skip credits automatically" } } } }, - "settings.autoSkipIntro" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Intro automatisch überspringen" + "settings.autoSkipIntro": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Intro automatisch überspringen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Skip intros automatically" + "en": { + "stringUnit": { + "state": "translated", + "value": "Skip intros automatically" } } } }, - "settings.build" : { - "comment" : "Zeitpunkt, zu dem der laufende Build entstanden ist", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Build" + "settings.build": { + "comment": "Zeitpunkt, zu dem der laufende Build entstanden ist", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Build" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Build" + "en": { + "stringUnit": { + "state": "translated", + "value": "Build" } } } }, - "settings.concurrent" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Höchstens gleichzeitig" + "settings.concurrent": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Höchstens gleichzeitig" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "At most at once" + "en": { + "stringUnit": { + "state": "translated", + "value": "At most at once" } } } }, - "settings.concurrentExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Eine ganze Staffel auf einmal zu laden teilt die Leitung so auf, dass keine Folge fertig wird. Unterwegs ist eine fertige Folge mehr wert als zehn halbe." + "settings.concurrentExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Eine ganze Staffel auf einmal zu laden teilt die Leitung so auf, dass keine Folge fertig wird. Unterwegs ist eine fertige Folge mehr wert als zehn halbe." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Loading a whole season at once splits the line so that no episode finishes. On the move, one finished episode beats ten half ones." + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading a whole season at once splits the line so that no episode finishes. On the move, one finished episode beats ten half ones." } } } }, - "settings.concurrentHeader" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gleichzeitige Downloads" + "settings.concurrentHeader": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gleichzeitige Downloads" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Concurrent downloads" + "en": { + "stringUnit": { + "state": "translated", + "value": "Concurrent downloads" } } } }, - "settings.connection" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verbindung" + "settings.connection": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verbindung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connection" + "en": { + "stringUnit": { + "state": "translated", + "value": "Connection" } } } }, - "settings.connectionLocal" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lokal" + "settings.connectionLocal": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Lokal" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Local" + "en": { + "stringUnit": { + "state": "translated", + "value": "Local" } } } }, - "settings.connectionRelay" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Über Plex-Vermittlung (gedrosselt)" + "settings.connectionRelay": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Über Plex-Vermittlung (gedrosselt)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Via Plex relay (throttled)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Via Plex relay (throttled)" } } } }, - "settings.connectionRemote" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Direkt von außen" + "settings.connectionRemote": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Direkt von außen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Direct remote" + "en": { + "stringUnit": { + "state": "translated", + "value": "Direct remote" } } } }, - "settings.controls" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Steuerung" + "settings.controls": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Steuerung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Controls" + "en": { + "stringUnit": { + "state": "translated", + "value": "Controls" } } } }, - "settings.controlsExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zurück und vor lassen sich getrennt einstellen: zurück springt man, weil man etwas nicht verstanden hat — vor, weil man etwas überspringen will." + "settings.controlsExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zurück und vor lassen sich getrennt einstellen: zurück springt man, weil man etwas nicht verstanden hat — vor, weil man etwas überspringen will." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Back and forward are set separately: you jump back because you missed something, and forward because you want to skip something." + "en": { + "stringUnit": { + "state": "translated", + "value": "Back and forward are set separately: you jump back because you missed something, and forward because you want to skip something." } } } }, - "settings.defaultRate" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Startgeschwindigkeit" + "settings.defaultRate": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Startgeschwindigkeit" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Starting speed" + "en": { + "stringUnit": { + "state": "translated", + "value": "Starting speed" } } } }, - "settings.deleteAfterDay" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nach einem Tag" + "settings.deleteAfterDay": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach einem Tag" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "After a day" + "en": { + "stringUnit": { + "state": "translated", + "value": "After a day" } } } }, - "settings.deleteAfterWeek" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nach einer Woche" + "settings.deleteAfterWeek": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach einer Woche" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "After a week" + "en": { + "stringUnit": { + "state": "translated", + "value": "After a week" } } } }, - "settings.deleteImmediately" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sofort" + "settings.deleteImmediately": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sofort" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Immediately" + "en": { + "stringUnit": { + "state": "translated", + "value": "Immediately" } } } }, - "settings.deleteNever" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nie" + "settings.deleteNever": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nie" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Never" + "en": { + "stringUnit": { + "state": "translated", + "value": "Never" } } } }, - "settings.deleteWatched" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gesehenes löschen" + "settings.deleteWatched": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gesehenes löschen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Delete watched" + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete watched" } } } }, - "settings.downloadsExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fehlende Folgen werden nachgeladen, sobald du eine gesehen hast. Gesehenes wird nach der eingestellten Frist entfernt." + "settings.downloadsExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Fehlende Folgen werden nachgeladen, sobald du eine gesehen hast. Gesehenes wird nach der eingestellten Frist entfernt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Missing episodes are downloaded once you finish one. Watched titles are removed after the chosen delay." + "en": { + "stringUnit": { + "state": "translated", + "value": "Missing episodes are downloaded once you finish one. Watched titles are removed after the chosen delay." } } } }, - "settings.downloadsHeaderExplanation" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wie viel Platz Offline-Titel belegen dürfen, wie viele gleichzeitig laden und wann sie wieder verschwinden." + "settings.downloadsHeaderExplanation": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wie viel Platz Offline-Titel belegen dürfen, wie viele gleichzeitig laden und wann sie wieder verschwinden." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "How much space offline titles may take, how many load at once, and when they disappear again." + "en": { + "stringUnit": { + "state": "translated", + "value": "How much space offline titles may take, how many load at once, and when they disappear again." } } } }, - "settings.episodes" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Folgen" + "settings.episodes": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Folgen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Episodes" + "en": { + "stringUnit": { + "state": "translated", + "value": "Episodes" } } } }, - "settings.forward" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vorspulen" + "settings.forward": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorspulen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fast-forward" + "en": { + "stringUnit": { + "state": "translated", + "value": "Fast-forward" } } } }, - "settings.keepCount %@" : { - "comment" : "A label that lets the user choose how many episodes of a series to keep after they've finished watching them all. The argument is the number of episodes to keep.", - "isCommentAutoGenerated" : true + "settings.keepCount %@": { + "comment": "A label that lets the user choose how many episodes of a series to keep after they've finished watching them all. The argument is the number of episodes to keep.", + "isCommentAutoGenerated": true }, - "settings.keepCount %lld" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld ungesehene" + "settings.keepCount %lld": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld ungesehene" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld unwatched" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld unwatched" } } } }, - "settings.keepOff" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aus" + "settings.keepOff": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aus" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Off" + "en": { + "stringUnit": { + "state": "translated", + "value": "Off" } } } }, - "settings.keepUnwatched" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Folgen vorhalten" + "settings.keepUnwatched": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Folgen vorhalten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keep episodes ready" + "en": { + "stringUnit": { + "state": "translated", + "value": "Keep episodes ready" } } } }, - "settings.libraries" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mediatheken" + "settings.libraries": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mediatheken" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Libraries" + "en": { + "stringUnit": { + "state": "translated", + "value": "Libraries" } } } }, - "settings.lockLandscape" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Im Querformat sperren" + "settings.lockLandscape": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Im Querformat sperren" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lock to landscape" + "en": { + "stringUnit": { + "state": "translated", + "value": "Lock to landscape" } } } }, - "settings.lockLandscapeExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Player dreht beim Öffnen ins Querformat und bleibt dort. Abgeschaltet folgt er dem Gerät — für den, der im Liegen schaut." + "settings.lockLandscapeExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Player dreht beim Öffnen ins Querformat und bleibt dort. Abgeschaltet folgt er dem Gerät — für den, der im Liegen schaut." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The player rotates to landscape when it opens and stays there. Switched off it follows the device — for watching while lying down." + "en": { + "stringUnit": { + "state": "translated", + "value": "The player rotates to landscape when it opens and stays there. Switched off it follows the device — for watching while lying down." } } } }, - "settings.playback" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wiedergabe" + "settings.playback": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wiedergabe" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Playback" + "en": { + "stringUnit": { + "state": "translated", + "value": "Playback" } } } }, - "settings.playbackExplanation" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wie gut das Bild sein soll, wie weit der Doppeltipp springt und was am Ende einer Folge passiert." + "settings.playbackExplanation": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wie gut das Bild sein soll, wie weit der Doppeltipp springt und was am Ende einer Folge passiert." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "How good the picture should be, how far a double tap jumps, and what happens at the end of an episode." + "en": { + "stringUnit": { + "state": "translated", + "value": "How good the picture should be, how far a double tap jumps, and what happens at the end of an episode." } } } }, - "settings.quality" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Qualität" + "settings.quality": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Qualität" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Quality" + "en": { + "stringUnit": { + "state": "translated", + "value": "Quality" } } } }, - "settings.qualityCellular" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Über Mobilfunk" + "settings.qualityCellular": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Über Mobilfunk" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "On cellular" + "en": { + "stringUnit": { + "state": "translated", + "value": "On cellular" } } } }, - "settings.qualityExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Obergrenze, nicht das Ziel. Im eigenen Netz überträgt der Server das Original; unterwegs beginnt Flixy kleiner und tastet sich hinauf, solange die Verbindung es trägt — höchstens bis zu dieser Stufe. Auflösung und Bitrate gehen dabei zusammen." + "settings.qualityExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Obergrenze, nicht das Ziel. Im eigenen Netz überträgt der Server das Original; unterwegs beginnt Flixy kleiner und tastet sich hinauf, solange die Verbindung es trägt — höchstens bis zu dieser Stufe. Auflösung und Bitrate gehen dabei zusammen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "An upper limit, not a target. On your own network the server sends the original; away from home Flixy starts lower and works its way up as long as the connection holds — never above this step. Resolution and bitrate move together." + "en": { + "stringUnit": { + "state": "translated", + "value": "An upper limit, not a target. On your own network the server sends the original; away from home Flixy starts lower and works its way up as long as the connection holds — never above this step. Resolution and bitrate move together." } } } }, - "settings.qualityUnlimitedHint" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zuletzt hat die Wiedergabe gehakt. Ohne Grenze überträgt der Server das Original — gibt die Leitung das nicht her, kann nichts ausweichen. Eine Stufe darunter läuft dann ruhiger." + "settings.qualityUnlimitedHint": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zuletzt hat die Wiedergabe gehakt. Ohne Grenze überträgt der Server das Original — gibt die Leitung das nicht her, kann nichts ausweichen. Eine Stufe darunter läuft dann ruhiger." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Playback stalled recently. Without a limit the server sends the original — if the connection cannot carry it, there is nothing to fall back to. One step down plays more smoothly." + "en": { + "stringUnit": { + "state": "translated", + "value": "Playback stalled recently. Without a limit the server sends the original — if the connection cannot carry it, there is nothing to fall back to. One step down plays more smoothly." } } } }, - "settings.qualityWifi" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Im WLAN" + "settings.qualityWifi": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Im WLAN" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "On Wi-Fi" + "en": { + "stringUnit": { + "state": "translated", + "value": "On Wi-Fi" } } } }, - "settings.rateNormal" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Normal" + "settings.rateNormal": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Normal" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Normal" + "en": { + "stringUnit": { + "state": "translated", + "value": "Normal" } } } }, - "settings.rememberQuality" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gewählte Qualität merken" + "settings.rememberQuality": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gewählte Qualität merken" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remember the chosen quality" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remember the chosen quality" } } } }, - "settings.rememberQualityExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die im Player gewählte Stufe gilt dann auch für den nächsten Titel. Gibt es sie dort nicht, wird die nächstgelegene genommen." + "settings.rememberQualityExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die im Player gewählte Stufe gilt dann auch für den nächsten Titel. Gibt es sie dort nicht, wird die nächstgelegene genommen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The step you pick in the player then applies to the next title as well. If it isn't available there, the closest one is used." + "en": { + "stringUnit": { + "state": "translated", + "value": "The step you pick in the player then applies to the next title as well. If it isn't available there, the closest one is used." } } } }, - "settings.rewind" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zurückspulen" + "settings.rewind": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zurückspulen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rewind" + "en": { + "stringUnit": { + "state": "translated", + "value": "Rewind" } } } }, - "settings.scanExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Suchlauf durchsucht die Ordner auf dem Server nach neuen Dateien. Das kann einige Minuten dauern." + "settings.scanExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Suchlauf durchsucht die Ordner auf dem Server nach neuen Dateien. Das kann einige Minuten dauern." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "A scan looks through the folders on the server for new files. It can take a few minutes." + "en": { + "stringUnit": { + "state": "translated", + "value": "A scan looks through the folders on the server for new files. It can take a few minutes." } } } }, - "settings.seconds %@" : { - "comment" : "Ein Picker, das die Anzahl der Sekunden zeigt, die man zurückgehen kann.", - "isCommentAutoGenerated" : true + "settings.seconds %@": { + "comment": "Ein Picker, das die Anzahl der Sekunden zeigt, die man zurückgehen kann.", + "isCommentAutoGenerated": true }, - "settings.seconds %lld" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Sekunden" + "settings.seconds %lld": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Sekunden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld seconds" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld seconds" } } } }, - "settings.section.look" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Darstellung" + "settings.section.look": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Darstellung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Appearance" + "en": { + "stringUnit": { + "state": "translated", + "value": "Appearance" } } } }, - "settings.section.playing" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wiedergabe & Offline" + "settings.section.playing": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wiedergabe & Offline" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Playback & offline" + "en": { + "stringUnit": { + "state": "translated", + "value": "Playback & offline" } } } }, - "settings.server" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server" + "settings.server": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Server" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server" + "en": { + "stringUnit": { + "state": "translated", + "value": "Server" } } } }, - "settings.serverKind" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Art" + "settings.serverKind": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Art" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Type" + "en": { + "stringUnit": { + "state": "translated", + "value": "Type" } } } }, - "settings.serverName" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Name" + "settings.serverName": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Name" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Name" + "en": { + "stringUnit": { + "state": "translated", + "value": "Name" } } } }, - "settings.showBitrate" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bitrate im Qualitätsmenü zeigen" + "settings.showBitrate": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bitrate im Qualitätsmenü zeigen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Show the bitrate in the quality menu" + "en": { + "stringUnit": { + "state": "translated", + "value": "Show the bitrate in the quality menu" } } } }, - "settings.signOutConfirm" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wirklich abmelden?" + "settings.signOutConfirm": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wirklich abmelden?" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign out?" + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign out?" } } } }, - "settings.signOutExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das Token wird vom Gerät entfernt. Heruntergeladene Titel bleiben erhalten." + "settings.signOutExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Token wird vom Gerät entfernt. Heruntergeladene Titel bleiben erhalten." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The token will be removed from this device. Downloaded titles are kept." + "en": { + "stringUnit": { + "state": "translated", + "value": "The token will be removed from this device. Downloaded titles are kept." } } } }, - "settings.storageLimit" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Speicherlimit" + "settings.storageLimit": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Speicherlimit" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Storage limit" + "en": { + "stringUnit": { + "state": "translated", + "value": "Storage limit" } } } }, - "settings.storageUnlimited" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unbegrenzt" + "settings.storageUnlimited": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Unbegrenzt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unlimited" + "en": { + "stringUnit": { + "state": "translated", + "value": "Unlimited" } } } }, - "settings.version" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Version" + "settings.version": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Version" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Version" + "en": { + "stringUnit": { + "state": "translated", + "value": "Version" } } } }, - "sidebar.close" : { - "comment" : "Barrierefreiheit: abgedunkelte Fläche", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seitenleiste schließen" + "sidebar.close": { + "comment": "Barrierefreiheit: abgedunkelte Fläche", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitenleiste schließen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Close sidebar" + "en": { + "stringUnit": { + "state": "translated", + "value": "Close sidebar" } } } }, - "sidebar.downloads" : { - "comment" : "Eintrag in der Seitenleiste", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Downloads" + "sidebar.downloads": { + "comment": "Eintrag in der Seitenleiste", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Downloads" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Downloads" + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloads" } } } }, - "sidebar.edit" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bearbeiten" + "sidebar.edit": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bearbeiten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Edit" + "en": { + "stringUnit": { + "state": "translated", + "value": "Edit" } } } }, - "sidebar.editHint" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ziehen zum Umsortieren, Haken zum Ein- und Ausblenden." + "sidebar.editHint": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ziehen zum Umsortieren, Haken zum Ein- und Ausblenden." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Drag to reorder, tap the check to show or hide." + "en": { + "stringUnit": { + "state": "translated", + "value": "Drag to reorder, tap the check to show or hide." } } } }, - "sidebar.hide" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ausblenden" + "sidebar.hide": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ausblenden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hide" + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide" } } } }, - "sidebar.home" : { - "comment" : "Eintrag der Startseite in der Seitenleiste", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Start" + "sidebar.home": { + "comment": "Eintrag der Startseite in der Seitenleiste", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Start" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Home" + "en": { + "stringUnit": { + "state": "translated", + "value": "Home" } } } }, - "sidebar.libraries" : { - "comment" : "Überschrift über der Liste der Mediatheken", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mediatheken" + "sidebar.libraries": { + "comment": "Überschrift über der Liste der Mediatheken", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mediatheken" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Libraries" + "en": { + "stringUnit": { + "state": "translated", + "value": "Libraries" } } } }, - "sidebar.library" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bibliothek" + "sidebar.library": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bibliothek" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Library" + "en": { + "stringUnit": { + "state": "translated", + "value": "Library" } } } }, - "sidebar.liveTV" : { - "comment" : "Eintrag in der Seitenleiste", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Live-TV" + "sidebar.liveTV": { + "comment": "Eintrag in der Seitenleiste", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Live-TV" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Live TV" + "en": { + "stringUnit": { + "state": "translated", + "value": "Live TV" } } } }, - "sidebar.more" : { - "comment" : "Überschrift über Live-TV, Downloads und Einstellungen", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mehr" + "sidebar.more": { + "comment": "Überschrift über Live-TV, Downloads und Einstellungen", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mehr" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "More" + "en": { + "stringUnit": { + "state": "translated", + "value": "More" } } } }, - "sidebar.moveToSide" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Navigation zur Seite" + "sidebar.moveToSide": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Navigation zur Seite" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Move navigation to side" + "en": { + "stringUnit": { + "state": "translated", + "value": "Move navigation to side" } } } }, - "sidebar.moveToTop" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Navigation nach oben" + "sidebar.moveToTop": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Navigation nach oben" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Move navigation to top" + "en": { + "stringUnit": { + "state": "translated", + "value": "Move navigation to top" } } } }, - "sidebar.open" : { - "comment" : "Barrierefreiheit: Hamburger-Knopf", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seitenleiste öffnen" + "sidebar.open": { + "comment": "Barrierefreiheit: Hamburger-Knopf", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitenleiste öffnen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Open sidebar" + "en": { + "stringUnit": { + "state": "translated", + "value": "Open sidebar" } } } }, - "sidebar.search" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Suchen" + "sidebar.search": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Suchen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Search" + "en": { + "stringUnit": { + "state": "translated", + "value": "Search" } } } }, - "sidebar.settings" : { - "comment" : "Eintrag in der Seitenleiste", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Einstellungen" + "sidebar.settings": { + "comment": "Eintrag in der Seitenleiste", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Einstellungen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Settings" + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings" } } } }, - "sidebar.show" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Einblenden" + "sidebar.show": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Einblenden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Show" + "en": { + "stringUnit": { + "state": "translated", + "value": "Show" } } } }, - "signIn.cancelled" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Anmeldung abgebrochen." + "signIn.cancelled": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Anmeldung abgebrochen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign-in cancelled." + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign-in cancelled." } } } }, - "signIn.jellyfin.addressHint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schema und Port kannst du weglassen — die üblichen werden durchprobiert." + "signIn.jellyfin.addressHint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Schema und Port kannst du weglassen — die üblichen werden durchprobiert." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "You can leave out the scheme and port — the usual ones are tried in turn." + "en": { + "stringUnit": { + "state": "translated", + "value": "You can leave out the scheme and port — the usual ones are tried in turn." } } } }, - "signIn.jellyfin.addressMissing" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Trag bitte die Adresse deines Servers ein." + "signIn.jellyfin.addressMissing": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Trag bitte die Adresse deines Servers ein." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Please enter your server's address." + "en": { + "stringUnit": { + "state": "translated", + "value": "Please enter your server's address." } } } }, - "signIn.jellyfin.addressPlaceholder" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "192.168.1.50 oder medien.example.com" + "signIn.jellyfin.addressPlaceholder": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "192.168.1.50 oder medien.example.com" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "192.168.1.50 or media.example.com" + "en": { + "stringUnit": { + "state": "translated", + "value": "192.168.1.50 or media.example.com" } } } }, - "signIn.jellyfin.addressPrompt" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wo steht dein Server?" + "signIn.jellyfin.addressPrompt": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wo steht dein Server?" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Where is your server?" + "en": { + "stringUnit": { + "state": "translated", + "value": "Where is your server?" } } } }, - "signIn.jellyfin.codeExpired" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Code ist abgelaufen. Fordere einen neuen an oder melde dich mit Passwort an." + "signIn.jellyfin.codeExpired": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Code ist abgelaufen. Fordere einen neuen an oder melde dich mit Passwort an." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The code has expired. Request a new one or sign in with a password." + "en": { + "stringUnit": { + "state": "translated", + "value": "The code has expired. Request a new one or sign in with a password." } } } }, - "signIn.jellyfin.connect" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verbinden" + "signIn.jellyfin.connect": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verbinden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connect" + "en": { + "stringUnit": { + "state": "translated", + "value": "Connect" } } } }, - "signIn.jellyfin.foundServer" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gefundener Server" + "signIn.jellyfin.foundServer": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gefundener Server" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server found" + "en": { + "stringUnit": { + "state": "translated", + "value": "Server found" } } } }, - "signIn.jellyfin.notFound" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unter dieser Adresse antwortet kein Jellyfin-Server." + "signIn.jellyfin.notFound": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Unter dieser Adresse antwortet kein Jellyfin-Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No Jellyfin server answers at that address." + "en": { + "stringUnit": { + "state": "translated", + "value": "No Jellyfin server answers at that address." } } } }, - "signIn.jellyfin.otherAddress" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Andere Adresse eintragen" + "signIn.jellyfin.otherAddress": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Andere Adresse eintragen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Enter a different address" + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a different address" } } } }, - "signIn.jellyfin.password" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Passwort" + "signIn.jellyfin.password": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Passwort" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Password" + "en": { + "stringUnit": { + "state": "translated", + "value": "Password" } } } }, - "signIn.jellyfin.passwordNote" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das Passwort wird nur für diese eine Anfrage gehalten. Gespeichert wird allein der Zugangsschlüssel, und der im Schlüsselbund." + "signIn.jellyfin.passwordNote": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Passwort wird nur für diese eine Anfrage gehalten. Gespeichert wird allein der Zugangsschlüssel, und der im Schlüsselbund." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The password is held for this one request only. Only the access token is stored, and it goes into the keychain." + "en": { + "stringUnit": { + "state": "translated", + "value": "The password is held for this one request only. Only the access token is stored, and it goes into the keychain." } } } }, - "signIn.jellyfin.privacy" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "So erreicht dein Passwort diese App nie." + "signIn.jellyfin.privacy": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "So erreicht dein Passwort diese App nie." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This way your password never reaches this app." + "en": { + "stringUnit": { + "state": "translated", + "value": "This way your password never reaches this app." } } } }, - "signIn.jellyfin.quickConnectHint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Öffne Jellyfin auf einem Gerät, an dem du schon angemeldet bist, und gib diesen Code unter „Benutzer → Quick Connect“ ein." + "signIn.jellyfin.quickConnectHint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Öffne Jellyfin auf einem Gerät, an dem du schon angemeldet bist, und gib diesen Code unter „Benutzer → Quick Connect“ ein." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Open Jellyfin on a device you're already signed in on and enter this code under “User → Quick Connect”." + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Jellyfin on a device you're already signed in on and enter this code under “User → Quick Connect”." } } } }, - "signIn.jellyfin.searching" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server wird gesucht …" + "signIn.jellyfin.searching": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Server wird gesucht …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Looking for the server …" + "en": { + "stringUnit": { + "state": "translated", + "value": "Looking for the server …" } } } }, - "signIn.jellyfin.signingIn" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Anmeldung läuft …" + "signIn.jellyfin.signingIn": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Anmeldung läuft …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Signing in …" + "en": { + "stringUnit": { + "state": "translated", + "value": "Signing in …" } } } }, - "signIn.jellyfin.submit" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Anmelden" + "signIn.jellyfin.submit": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Anmelden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign in" + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in" } } } }, - "signIn.jellyfin.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Jellyfin-Server" + "signIn.jellyfin.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Jellyfin-Server" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Jellyfin server" + "en": { + "stringUnit": { + "state": "translated", + "value": "Jellyfin server" } } } }, - "signIn.jellyfin.useCode" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stattdessen einen Code bestätigen" + "signIn.jellyfin.useCode": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stattdessen einen Code bestätigen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Approve a code instead" + "en": { + "stringUnit": { + "state": "translated", + "value": "Approve a code instead" } } } }, - "signIn.jellyfin.usePassword" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stattdessen Benutzername und Passwort" + "signIn.jellyfin.usePassword": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stattdessen Benutzername und Passwort" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Use a username and password instead" + "en": { + "stringUnit": { + "state": "translated", + "value": "Use a username and password instead" } } } }, - "signIn.jellyfin.userName" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Benutzername" + "signIn.jellyfin.userName": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Benutzername" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Username" + "en": { + "stringUnit": { + "state": "translated", + "value": "Username" } } } }, - "signIn.jellyfin.waitingForApproval" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Warte auf die Bestätigung …" + "signIn.jellyfin.waitingForApproval": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Warte auf die Bestätigung …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Waiting for approval …" + "en": { + "stringUnit": { + "state": "translated", + "value": "Waiting for approval …" } } } }, - "signIn.manualHint" : { - "comment" : "Hinweis, wenn die Browser-Verknüpfung nicht greift", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hat das Fenster nicht verknüpft? Öffne plex.tv/link und gib diesen Code ein:" + "signIn.manualHint": { + "comment": "Hinweis, wenn die Browser-Verknüpfung nicht greift", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Hat das Fenster nicht verknüpft? Öffne plex.tv/link und gib diesen Code ein:" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Window didn't link? Open plex.tv/link and enter this code:" + "en": { + "stringUnit": { + "state": "translated", + "value": "Window didn't link? Open plex.tv/link and enter this code:" } } } }, - "signIn.privacy" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Anmeldung läuft über deinen Server. Dein Passwort erreicht diese App nie." + "signIn.privacy": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Anmeldung läuft über deinen Server. Dein Passwort erreicht diese App nie." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign-in happens on your server. Your password never reaches this app." + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign-in happens on your server. Your password never reaches this app." } } } }, - "signIn.tagline" : { - "comment" : "Untertitel auf dem Anmeldebildschirm", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Deine Mediathek, schöner." + "signIn.tagline": { + "comment": "Untertitel auf dem Anmeldebildschirm", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Deine Mediathek, schöner." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Your library, nicer." + "en": { + "stringUnit": { + "state": "translated", + "value": "Your library, nicer." } } } }, - "signIn.timedOut" : { - "comment" : "Die Anmeldung wurde nicht innerhalb der Gültigkeit des Codes abgeschlossen", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Anmeldung wurde nicht abgeschlossen. Versuch es noch einmal." + "signIn.timedOut": { + "comment": "Die Anmeldung wurde nicht innerhalb der Gültigkeit des Codes abgeschlossen", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Anmeldung wurde nicht abgeschlossen. Versuch es noch einmal." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign-in wasn't completed. Please try again." + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign-in wasn't completed. Please try again." } } } }, - "signIn.windowFailed" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das Anmeldefenster ließ sich nicht öffnen." + "signIn.windowFailed": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Anmeldefenster ließ sich nicht öffnen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The sign-in window couldn't be opened." + "en": { + "stringUnit": { + "state": "translated", + "value": "The sign-in window couldn't be opened." } } } }, - "signIn.withJellyfin" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mit Jellyfin anmelden" + "signIn.withJellyfin": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mit Jellyfin anmelden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign in with Jellyfin" + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in with Jellyfin" } } } }, - "signIn.withPlex" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mit Plex anmelden" + "signIn.withPlex": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mit Plex anmelden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign in with Plex" + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in with Plex" } } } }, - "sort.lastPlayed" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zuletzt gesehen" + "sort.lastPlayed": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zuletzt gesehen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Last watched" + "en": { + "stringUnit": { + "state": "translated", + "value": "Last watched" } } } }, - "sort.rating" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bewertung" + "sort.rating": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bewertung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rating" + "en": { + "stringUnit": { + "state": "translated", + "value": "Rating" } } } }, - "sort.recentlyAdded" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zuletzt hinzugefügt" + "sort.recentlyAdded": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zuletzt hinzugefügt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Recently added" + "en": { + "stringUnit": { + "state": "translated", + "value": "Recently added" } } } }, - "sort.releaseDate" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Erscheinungsjahr" + "sort.releaseDate": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Erscheinungsjahr" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Release date" + "en": { + "stringUnit": { + "state": "translated", + "value": "Release date" } } } }, - "sort.titleAscending" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titel (A–Z)" + "sort.titleAscending": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Titel (A–Z)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Title (A–Z)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Title (A–Z)" } } } }, - "sort.titleDescending" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titel (Z–A)" + "sort.titleDescending": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Titel (Z–A)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Title (Z–A)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Title (Z–A)" } } } }, - "subtitleStyle.headerExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Größe und Farbe der Untertitel im Player." + "subtitleStyle.headerExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Größe und Farbe der Untertitel im Player." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "How subtitles look in the player." + "en": { + "stringUnit": { + "state": "translated", + "value": "How subtitles look in the player." } } } }, - "subtitleStyle.palette" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Darstellung" + "subtitleStyle.palette": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Darstellung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Appearance" + "en": { + "stringUnit": { + "state": "translated", + "value": "Appearance" } } } }, - "subtitleStyle.palette.blackOnWhite" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schwarz auf Weiß" + "subtitleStyle.palette.blackOnWhite": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Schwarz auf Weiß" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Black on white" + "en": { + "stringUnit": { + "state": "translated", + "value": "Black on white" } } } }, - "subtitleStyle.palette.whiteOnBlack" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Weiß auf Schwarz" + "subtitleStyle.palette.whiteOnBlack": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Weiß auf Schwarz" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "White on black" + "en": { + "stringUnit": { + "state": "translated", + "value": "White on black" } } } }, - "subtitleStyle.palette.whiteOnClear" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Weiß" + "subtitleStyle.palette.whiteOnClear": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Weiß" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "White" + "en": { + "stringUnit": { + "state": "translated", + "value": "White" } } } }, - "subtitleStyle.palette.yellowOnBlack" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gelb auf Schwarz" + "subtitleStyle.palette.yellowOnBlack": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gelb auf Schwarz" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Yellow on black" + "en": { + "stringUnit": { + "state": "translated", + "value": "Yellow on black" } } } }, - "subtitleStyle.palette.yellowOnClear" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gelb" + "subtitleStyle.palette.yellowOnClear": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gelb" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Yellow" + "en": { + "stringUnit": { + "state": "translated", + "value": "Yellow" } } } }, - "subtitleStyle.paletteExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gilt für den Standardplayer und für Untertitel, die als Text vorliegen. Bilduntertitel von Blu-rays brennt der Server ins Bild — dort gibt es keinen Text zu gestalten, und diese Einstellungen bleiben ohne Wirkung." + "subtitleStyle.paletteExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gilt für den Standardplayer und für Untertitel, die als Text vorliegen. Bilduntertitel von Blu-rays brennt der Server ins Bild — dort gibt es keinen Text zu gestalten, und diese Einstellungen bleiben ohne Wirkung." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Applies to the standard player and to subtitles that come as text. Image subtitles from Blu-rays are burned into the picture by the server — there is no text to style, so these settings have no effect there." + "en": { + "stringUnit": { + "state": "translated", + "value": "Applies to the standard player and to subtitles that come as text. Image subtitles from Blu-rays are burned into the picture by the server — there is no text to style, so these settings have no effect there." } } } }, - "subtitleStyle.paletteHeader" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schrift und Hintergrund" + "subtitleStyle.paletteHeader": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Schrift und Hintergrund" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Text and background" + "en": { + "stringUnit": { + "state": "translated", + "value": "Text and background" } } } }, - "subtitleStyle.previewHeader" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vorschau" + "subtitleStyle.previewHeader": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorschau" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Preview" + "en": { + "stringUnit": { + "state": "translated", + "value": "Preview" } } } }, - "subtitleStyle.previewText" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "So sehen deine Untertitel aus." + "subtitleStyle.previewText": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "So sehen deine Untertitel aus." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This is how your subtitles look." + "en": { + "stringUnit": { + "state": "translated", + "value": "This is how your subtitles look." } } } }, - "subtitleStyle.size" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Größe" + "subtitleStyle.size": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Größe" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Size" + "en": { + "stringUnit": { + "state": "translated", + "value": "Size" } } } }, - "subtitleStyle.size.huge" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sehr groß" + "subtitleStyle.size.huge": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sehr groß" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Very large" + "en": { + "stringUnit": { + "state": "translated", + "value": "Very large" } } } }, - "subtitleStyle.size.large" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Groß" + "subtitleStyle.size.large": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Groß" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Large" + "en": { + "stringUnit": { + "state": "translated", + "value": "Large" } } } }, - "subtitleStyle.size.normal" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Normal" + "subtitleStyle.size.normal": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Normal" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Normal" + "en": { + "stringUnit": { + "state": "translated", + "value": "Normal" } } } }, - "subtitleStyle.size.small" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Klein" + "subtitleStyle.size.small": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Klein" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Small" + "en": { + "stringUnit": { + "state": "translated", + "value": "Small" } } } }, - "subtitleStyle.sizeExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bezogen auf die Vorgabe des Players, nicht auf eine feste Schriftgröße — die Untertitel bleiben damit auf jedem Bildschirm gleich gut lesbar." + "subtitleStyle.sizeExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bezogen auf die Vorgabe des Players, nicht auf eine feste Schriftgröße — die Untertitel bleiben damit auf jedem Bildschirm gleich gut lesbar." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Relative to the player’s own default rather than a fixed point size, so subtitles stay readable on any screen." + "en": { + "stringUnit": { + "state": "translated", + "value": "Relative to the player’s own default rather than a fixed point size, so subtitles stay readable on any screen." } } } }, - "subtitleStyle.sizeHeader" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schriftgröße" + "subtitleStyle.sizeHeader": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Schriftgröße" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Text size" + "en": { + "stringUnit": { + "state": "translated", + "value": "Text size" } } } }, - "subtitleStyle.title" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel" + "subtitleStyle.title": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles" + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles" } } } }, - "technical.audio" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tonspur" + "technical.audio": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Tonspur" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio" } } } }, - "technical.bitrate" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bitrate" + "technical.bitrate": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bitrate" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bitrate" + "en": { + "stringUnit": { + "state": "translated", + "value": "Bitrate" } } } }, - "technical.container" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Container" + "technical.container": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Container" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Container" + "en": { + "stringUnit": { + "state": "translated", + "value": "Container" } } } }, - "technical.directPlayNative" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Direkt (Systemplayer)" + "technical.directPlayNative": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Direkt (Systemplayer)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Direct (system player)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Direct (system player)" } } } }, - "technical.directPlayVLC" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Direkt (VLC)" + "technical.directPlayVLC": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Direkt (VLC)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Direct (VLC)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Direct (VLC)" } } } }, - "technical.file" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Datei auf dem Server" + "technical.file": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Datei auf dem Server" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "File on server" + "en": { + "stringUnit": { + "state": "translated", + "value": "File on server" } } } }, - "technical.playbackMethod" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wiedergabe" + "technical.playbackMethod": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wiedergabe" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Playback" + "en": { + "stringUnit": { + "state": "translated", + "value": "Playback" } } } }, - "technical.reason" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Grund" + "technical.reason": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Grund" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Reason" + "en": { + "stringUnit": { + "state": "translated", + "value": "Reason" } } } }, - "technical.reason.audioCodec %@" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "iOS kann %@-Ton nicht wiedergeben." + "technical.reason.audioCodec %@": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "iOS kann %@-Ton nicht wiedergeben." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "iOS can't play %@ audio." + "en": { + "stringUnit": { + "state": "translated", + "value": "iOS can't play %@ audio." } } } }, - "technical.reason.chosen" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Im Player wurde eine Qualitätsstufe gewählt." + "technical.reason.chosen": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Im Player wurde eine Qualitätsstufe gewählt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "A quality step was chosen in the player." + "en": { + "stringUnit": { + "state": "translated", + "value": "A quality step was chosen in the player." } } } }, - "technical.reason.container %@" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "iOS öffnet den Container %@ nicht." + "technical.reason.container %@": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "iOS öffnet den Container %@ nicht." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "iOS can't open the %@ container." + "en": { + "stringUnit": { + "state": "translated", + "value": "iOS can't open the %@ container." } } } }, - "technical.reason.unanalyzed" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Server hat diese Datei noch nicht analysiert." + "technical.reason.unanalyzed": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Server hat diese Datei noch nicht analysiert." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server hasn't analyzed this file yet." + "en": { + "stringUnit": { + "state": "translated", + "value": "The server hasn't analyzed this file yet." } } } }, - "technical.reason.videoCodec %@" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "iOS kann %@ nicht dekodieren." + "technical.reason.videoCodec %@": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "iOS kann %@ nicht dekodieren." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "iOS can't decode %@." + "en": { + "stringUnit": { + "state": "translated", + "value": "iOS can't decode %@." } } } }, - "technical.remux" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Umgepackt" + "technical.remux": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Umgepackt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remuxed" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remuxed" } } } }, - "technical.remuxAudio" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Umgepackt, Ton umgerechnet" + "technical.remuxAudio": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Umgepackt, Ton umgerechnet" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remuxed, audio converted" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remuxed, audio converted" } } } }, - "technical.resolution" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auflösung" + "technical.resolution": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auflösung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resolution" + "en": { + "stringUnit": { + "state": "translated", + "value": "Resolution" } } } }, - "technical.size" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dateigröße" + "technical.size": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Dateigröße" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "File size" + "en": { + "stringUnit": { + "state": "translated", + "value": "File size" } } } }, - "technical.subtitle" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel" + "technical.subtitle": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitle" + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitle" } } } }, - "technical.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Technische Angaben" + "technical.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Technische Angaben" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Technical details" + "en": { + "stringUnit": { + "state": "translated", + "value": "Technical details" } } } }, - "technical.transcode" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Umgerechnet vom Server" + "technical.transcode": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Umgerechnet vom Server" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Transcoded by server" + "en": { + "stringUnit": { + "state": "translated", + "value": "Transcoded by server" } } } }, - "technical.videoCodec" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bildcodec" + "technical.videoCodec": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bildcodec" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Video codec" + "en": { + "stringUnit": { + "state": "translated", + "value": "Video codec" } } } }, - "tile.play" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abspielen" + "tile.play": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abspielen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Play" + "en": { + "stringUnit": { + "state": "translated", + "value": "Play" } } } }, - "topShelf.noFile" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Noch nichts abgelegt." + "topShelf.noFile": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Noch nichts abgelegt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing stored yet." + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing stored yet." } } } }, - "topShelf.noGroup" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine App-Gruppe — das Regal kann nichts lesen." + "topShelf.noGroup": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine App-Gruppe — das Regal kann nichts lesen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No app group — the shelf cannot read anything." + "en": { + "stringUnit": { + "state": "translated", + "value": "No app group — the shelf cannot read anything." } } } }, - "topShelf.title" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Regal über der App" + "topShelf.title": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Regal über der App" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Top Shelf" + "en": { + "stringUnit": { + "state": "translated", + "value": "Top Shelf" } } } }, - "topShelf.unreadable" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Datei liegt da, lässt sich aber nicht lesen." + "topShelf.unreadable": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Datei liegt da, lässt sich aber nicht lesen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "File is there but cannot be read." + "en": { + "stringUnit": { + "state": "translated", + "value": "File is there but cannot be read." } } } }, - "topShelf.written %lld %lld %@" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Reihen, %lld Einträge, zuletzt %@" + "topShelf.written %lld %lld %@": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Reihen, %lld Einträge, zuletzt %@" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld rows, %lld entries, last %@" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld rows, %lld entries, last %@" } } } } }, - "version" : "1.1" -} \ No newline at end of file + "version": "1.1" +} diff --git a/Flixy/Session/DownloadService.swift b/Flixy/Session/DownloadService.swift index 72faf6b..c3560b8 100644 --- a/Flixy/Session/DownloadService.swift +++ b/Flixy/Session/DownloadService.swift @@ -139,6 +139,11 @@ struct DownloadService { durationMilliseconds: item.duration, originalBytes: part.size ?? 0 ), + // **Nur beim Original ist die Größe eine Tatsache.** Sie steht in + // den Metadaten des Servers. Die umgerechneten Stufen sind aus + // Bitrate und Dauer hochgerechnet — und was hochgerechnet ist, + // darf abweichen, ohne dass die Datei kaputt wäre. + expectedIsExact: quality == .original && part.size != nil, offlineMedia: offlineMedia(for: item, media: media, part: part) ) } diff --git a/Flixy/Views/Downloads/DownloadsView.swift b/Flixy/Views/Downloads/DownloadsView.swift index bf7c7b6..77defc6 100644 --- a/Flixy/Views/Downloads/DownloadsView.swift +++ b/Flixy/Views/Downloads/DownloadsView.swift @@ -222,14 +222,21 @@ private struct DownloadRow: View { .accessibilityLabel(Text("downloads.pause")) case .paused, .failed: + // **Fortsetzen und erneut versuchen sind nicht dasselbe**, und + // der Knopf hat beides gleich genannt. „Fortsetzen" stand auch + // dort, wo es nichts fortzusetzen gab — eine Zusage, die der Code + // nicht halten konnte: ein gescheiterter Download fing wieder bei + // null an. Jetzt entscheidet der Wiederaufnahme-Datensatz, und er + // entsteht genau dann, wenn eine Übertragung unterbrochen wurde. Button { Task { await downloads.resume(item) } } label: { - Image(systemName: "arrow.clockwise.circle").font(.title3) + Image(systemName: continues ? "play.circle" : "arrow.clockwise.circle") + .font(.title3) } .buttonStyle(.plain) .foregroundStyle(accent) - .accessibilityLabel(Text("downloads.resume")) + .accessibilityLabel(Text(continues ? "downloads.resume" : "action.retry")) case .completed: Image(systemName: "checkmark.circle.fill") @@ -260,12 +267,51 @@ private struct DownloadRow: View { } } + /// Ob ein Antippen dort weitermacht, wo es aufgehört hat. + /// + /// Ein angehaltener Download hat seinen Wiederaufnahme-Datensatz immer; + /// ein gescheiterter nur, wenn ihn das Netz unterbrochen hat und nicht der + /// Server abgelehnt. + private var continues: Bool { + item.state == .paused || item.resumeData != nil + } + + /// Wie schnell es gerade geht — `nil`, solange nichts gemessen ist. + private var speed: String? { + guard let perSecond = downloads.rate(forRatingKey: item.ratingKey)?.bytesPerSecond, + perSecond > 0 + else { return nil } + let menge = Int64(perSecond).formatted(.byteCount(style: .file)) + return String(localized: "downloads.perSecond \(menge)") + } + + /// Wie lange es bei diesem Tempo noch dauert. + private var remaining: String? { + guard let rate = downloads.rate(forRatingKey: item.ratingKey), + let rest = rate.remaining(received: item.receivedBytes, total: item.totalBytes) + else { return nil } + // Unter einer Minute keine Zahl: „noch 0 Min" wäre falsch und „noch + // 43 Sek" genauer, als die Schätzung ist. + guard rest >= .seconds(60) else { + return String(localized: "downloads.remaining.underMinute") + } + return String(localized: "downloads.remaining \(rest.runtimeLabel)") + } + private var statusLine: String { switch item.state { case .downloading where item.totalBytes > 0: - "\(item.receivedBytes.formatted(.byteCount(style: .file))) / \(item.totalBytes.formatted(.byteCount(style: .file)))" + [ + "\(item.receivedBytes.formatted(.byteCount(style: .file))) / \(item.totalBytes.formatted(.byteCount(style: .file)))", + speed, + remaining, + ].compactMap(\.self).joined(separator: " · ") case .downloading: - item.receivedBytes.formatted(.byteCount(style: .file)) + // Ohne bekannte Gesamtgröße gibt es keine Restzeit — das Tempo + // aber schon, und es ist genau dort die einzige Auskunft darüber, + // ob überhaupt etwas durchgeht. + [item.receivedBytes.formatted(.byteCount(style: .file)), speed] + .compactMap(\.self).joined(separator: " · ") case .paused: String(localized: "downloads.paused") case .queued, .waiting: diff --git a/Flixy/Views/Navigation/RootView.swift b/Flixy/Views/Navigation/RootView.swift index 7afc157..f9b0eeb 100644 --- a/Flixy/Views/Navigation/RootView.swift +++ b/Flixy/Views/Navigation/RootView.swift @@ -181,6 +181,19 @@ struct RootView: View { Task { await handOver(to: request) } } .task { await session.restore() } + // **Kommt das Netz zurück, geht es weiter.** + // + // Ein Download, den ein Funkloch unterbrochen hat, lag bisher da, bis + // ihn jemand von Hand antippte — auch dann, wenn das WLAN seit einer + // Stunde wieder stand. + // + // Hier und nicht im Koordinator: `FlixyDownloads` hängt bewusst nicht + // an `NetworkConditions`, es bekommt den Netzzustand als Wert gereicht + // (siehe `TransferHold`). Diese Ansicht kennt beide. + .onChange(of: network.isReachable) { _, erreichbar in + guard erreichbar else { return } + Task { await downloads.resumeInterrupted() } + } .onChange(of: session.phase) { _, newPhase in // Nach einer Neuanmeldung darf nicht eine Mediathek ausgewählt // bleiben, die es auf dem neuen Server gar nicht gibt. diff --git a/Packages/FlixyKit/Sources/FlixyDownloads/DownloadRate.swift b/Packages/FlixyKit/Sources/FlixyDownloads/DownloadRate.swift new file mode 100644 index 0000000..01cbf19 --- /dev/null +++ b/Packages/FlixyKit/Sources/FlixyDownloads/DownloadRate.swift @@ -0,0 +1,102 @@ +import Foundation + +/// Wie schnell ein Download gerade läuft — und wie lange er noch braucht. +/// +/// **Ein Wertetyp mit Uhr von außen.** Die Rückmeldungen der Übertragung +/// kommen mehrmals je Sekunde; was daraus eine Zahl macht, ist reine Rechnung +/// und gehört nicht in den Koordinator. Die Zeitpunkte kommen als Argument +/// herein, damit sich eine Übertragung ohne echte Uhr nachstellen lässt — eine +/// Rate, die man nur im Betrieb beurteilen kann, ist keine prüfbare Zahl. +/// +/// **Warum geglättet.** Zwischen zwei Rückmeldungen schwankt die rohe +/// Differenz um ein Vielfaches: ein Puffer läuft voll, der Funk hat ein Loch, +/// das System hält die Hintergrundsitzung kurz an. Ungeglättet stünde dort +/// eine Zahl, die zehnmal je Sekunde springt — und eine springende Zahl ist +/// keine Auskunft, sondern Unruhe. +public struct DownloadRate: Sendable, Equatable { + /// Wie stark ein neuer Messwert gegenüber dem bisherigen zählt. + /// + /// Ein Drittel: schnell genug, dass ein echter Einbruch binnen weniger + /// Sekunden ankommt, träge genug, dass einzelne Ausreißer untergehen. + private static let weight = 1.0 / 3.0 + + /// Kürzere Abstände werden nicht gemessen. + /// + /// Über fünfzig Millisekunden gerechnet entscheidet der Zufall, wann das + /// System den Puffer leert — nicht die Leitung. + static let minimumInterval: Duration = .milliseconds(500) + + /// Ab dieser Lücke beginnt die Messung von vorn. + /// + /// So lange ohne eine einzige Rückmeldung heißt nicht „langsam", sondern + /// „stand still": die App war angehalten, das Gerät gesperrt, das Netz + /// weg. Diese Pause in die Rate einzurechnen ergäbe eine Zahl nahe null + /// und eine Restzeit von Stunden. + static let maximumInterval: Duration = .seconds(10) + + /// Ein Messpunkt: wie viel war wann da. + /// + /// Als Typ und nicht als Tupel — ein Tupel kann `Equatable` nicht erben, + /// und ohne das wäre der ganze Wertetyp nicht vergleichbar. + private struct Anchor: Equatable { + var bytes: Int64 + var at: ContinuousClock.Instant + } + + /// Der letzte Messpunkt, gegen den gerechnet wird. + private var anchor: Anchor? + private var smoothed: Double? + + public init() {} + + /// Bytes je Sekunde — `nil`, solange nichts gemessen wurde. + /// + /// **Nicht null.** Null wäre eine Auskunft („es geht nichts durch"), und + /// die stimmt in der ersten halben Sekunde nie. + public var bytesPerSecond: Double? { smoothed } + + /// Nimmt den Stand der Übertragung entgegen. + public mutating func record(bytes: Int64, at now: ContinuousClock.Instant) { + guard let anchor else { + self.anchor = Anchor(bytes: bytes, at: now) + return + } + + let elapsed = now - anchor.at + guard elapsed >= Self.minimumInterval else { return } + defer { self.anchor = Anchor(bytes: bytes, at: now) } + + // Rückwärts heißt: eine neue Aufgabe fängt von vorn an. Dann ist jede + // bisherige Messung ungültig — und eine negative Rate wäre Unsinn. + guard bytes >= anchor.bytes, elapsed <= Self.maximumInterval else { + smoothed = nil + return + } + + let seconds = elapsed.inSeconds + guard seconds > 0 else { return } + + let gemessen = Double(bytes - anchor.bytes) / seconds + smoothed = smoothed.map { $0 + (gemessen - $0) * Self.weight } ?? gemessen + } + + /// Wie lange es bei dieser Rate noch dauert. + /// + /// `nil`, solange keine Rate feststeht oder die Gesamtgröße unbekannt ist. + /// Eine Restzeit ohne Rate wäre geraten, und geraten sieht man ihr nicht + /// an. + public func remaining(received: Int64, total: Int64) -> Duration? { + guard let smoothed, smoothed > 0, total > received else { return nil } + return .seconds(Double(total - received) / smoothed) + } +} + +extension Duration { + /// Die Dauer als Sekunden. + /// + /// `components` liefert ganze Sekunden und Attosekunden getrennt; für eine + /// Division braucht es beides in einer Zahl. + var inSeconds: Double { + Double(components.seconds) + Double(components.attoseconds) / 1e18 + } +} diff --git a/Packages/FlixyKit/Sources/FlixyDownloads/Engine/DownloadCoordinator.swift b/Packages/FlixyKit/Sources/FlixyDownloads/Engine/DownloadCoordinator.swift index 6a23a31..f4578e8 100644 --- a/Packages/FlixyKit/Sources/FlixyDownloads/Engine/DownloadCoordinator.swift +++ b/Packages/FlixyKit/Sources/FlixyDownloads/Engine/DownloadCoordinator.swift @@ -16,6 +16,8 @@ public struct DownloadPlan: Sendable { public var episodeIndex: Int? public var durationMilliseconds: Int? public var expectedBytes: Int64 + /// Ob `expectedBytes` die genaue Größe ist — siehe `DownloadItem`. + public var expectedIsExact: Bool public var offlineMedia: OfflineMedia? public init( @@ -30,6 +32,7 @@ public struct DownloadPlan: Sendable { episodeIndex: Int? = nil, durationMilliseconds: Int? = nil, expectedBytes: Int64 = 0, + expectedIsExact: Bool = false, offlineMedia: OfflineMedia? = nil ) { self.ratingKey = ratingKey @@ -43,6 +46,7 @@ public struct DownloadPlan: Sendable { self.episodeIndex = episodeIndex self.durationMilliseconds = durationMilliseconds self.expectedBytes = expectedBytes + self.expectedIsExact = expectedIsExact self.offlineMedia = offlineMedia } } @@ -80,6 +84,28 @@ public final class DownloadCoordinator { /// Wie viel die Downloads insgesamt belegen. public private(set) var usedBytes: Int64 = 0 + /// Das gemessene Tempo je laufender Übertragung. + /// + /// **Im Arbeitsspeicher, nicht in der Datenbank** — aus demselben Grund, + /// aus dem `handleProgress` kein `save()` ruft: die Messpunkte kommen + /// mehrmals je Sekunde, und jedes Schreiben wäre eine Plattenoperation. + /// Über einen Neustart hinweg wäre eine alte Rate ohnehin wertlos. + private var rates: [String: DownloadRate] = [:] + + /// Für welche Titel der Server die Größe **angekündigt** hat. + /// + /// Der Unterschied entscheidet, wann eine Datei als abgerissen gilt. Bei + /// einer angekündigten Größe ist jedes fehlende Byte ein Abriss; bei einer + /// hochgerechneten (Transcoder: Bitrate × Dauer) wäre dieselbe Strenge + /// falsch, denn die Schätzung weicht von Haus aus ab. + /// + /// Woran man es erkennt: `URLSession` meldet die erwartete Größe nur, wenn + /// die Antwort eine `Content-Length` trägt — sonst kommt −1. + /// + /// Ebenfalls im Arbeitsspeicher: nach einem Neustart wird die Übertragung + /// neu eingereiht, und dann meldet sich der Server wieder. + private var announcedSizes: Set = [] + /// Nur über WLAN übertragen. public var isWiFiOnly: Bool { didSet { defaults.set(isWiFiOnly, forKey: Key.wifiOnly) } @@ -279,7 +305,8 @@ public final class DownloadCoordinator { seasonIndex: plan.seasonIndex, episodeIndex: plan.episodeIndex, durationMilliseconds: plan.durationMilliseconds, - offlineMediaData: plan.offlineMedia.flatMap { try? JSONEncoder().encode($0) } + offlineMediaData: plan.offlineMedia.flatMap { try? JSONEncoder().encode($0) }, + expectedIsExact: plan.expectedIsExact ) context.insert(item) save() @@ -343,6 +370,7 @@ public final class DownloadCoordinator { task.cancel { continuation.resume(returning: $0) } } item.resumeData = data + forgetTransfer(item.ratingKey) item.state = .paused save() await startNextIfPossible() @@ -364,6 +392,7 @@ public final class DownloadCoordinator { /// Entfernt Eintrag und Datei. public func remove(_ item: DownloadItem) { + forgetTransfer(item.ratingKey) fileStore.remove(fileName: item.localFileName) fileStore.remove(fileName: item.artworkFileName) context.delete(item) @@ -462,13 +491,40 @@ public final class DownloadCoordinator { func handleProgress(ratingKey: String, received: Int64, expected: Int64) { guard let item = items.first(where: { $0.ratingKey == ratingKey }) else { return } item.receivedBytes = received - if expected > 0 { item.totalBytes = expected } + if expected > 0 { + item.totalBytes = expected + // Hier und nur hier ist belegt, dass die Größe vom Server kommt + // und nicht hochgerechnet ist. Siehe `announcedSizes`. + announcedSizes.insert(ratingKey) + } + + var rate = rates[ratingKey] ?? DownloadRate() + rate.record(bytes: received, at: .now) + rates[ratingKey] = rate + updateActivity() // Bewusst ohne `save()`: der Fortschritt kommt mehrmals je Sekunde, und // jedes Schreiben wäre eine Plattenoperation. Für die Anzeige reicht // die Beobachtung; gespeichert wird bei Zustandswechseln. } + /// Das gemessene Tempo einer laufenden Übertragung. + /// + /// `nil`, solange noch nichts gemessen wurde — und das ist eine ehrlichere + /// Antwort als „0 MB/s". + public func rate(forRatingKey key: String) -> DownloadRate? { + rates[key] + } + + /// Vergisst, was nur zu einer laufenden Übertragung gehört. + /// + /// Beim nächsten Anlauf wird neu gemessen: eine Rate von vorhin gälte + /// sonst für eine Leitung, die es inzwischen nicht mehr gibt. + private func forgetTransfer(_ ratingKey: String) { + rates[ratingKey] = nil + announcedSizes.remove(ratingKey) + } + func handleCompletion(ratingKey: String, temporaryFile: URL) { guard let item = items.first(where: { $0.ratingKey == ratingKey }), let sourceURL = URL(string: item.sourceURLString) @@ -483,13 +539,19 @@ public final class DownloadCoordinator { // Fehler verdeckt: kamen nur ein paar Kilobyte an, wurden sie als // empfangen *und* als erwartet notiert — und die Anzeige stand auf // hundert Prozent. - if let problem = Self.implausibility(written: written, expected: item.totalBytes) { + let problem = Self.implausibility( + written: written, + expected: item.totalBytes, + announced: item.expectedIsExact || announcedSizes.contains(ratingKey) + ) + if let problem { fileStore.remove(fileName: name) fail(item, message: problem) Task { await startNextIfPossible() } return } + forgetTransfer(ratingKey) item.localFileName = name item.receivedBytes = written if item.totalBytes <= 0 { item.totalBytes = written } @@ -508,10 +570,22 @@ public final class DownloadCoordinator { /// Prüft, ob die fertige Datei plausibel ist. /// /// Gibt eine Begründung zurück, wenn nicht. Zwei Fälle fallen darunter: - /// eine winzige Datei (fast immer eine Fehlerseite) und eine, die deutlich - /// unter der angekündigten Größe liegt (abgebrochene Übertragung, die als + /// eine winzige Datei (fast immer eine Fehlerseite) und eine, die unter + /// der erwarteten Größe liegt (abgebrochene Übertragung, die als /// erfolgreich gemeldet wurde). - static func implausibility(written: Int64, expected: Int64) -> String? { + /// + /// **`announced` entscheidet, wie streng gemessen wird** — und daran hing + /// ein Fehler, der Dateien kaputt machte und als Erfolg verbuchte. Hier + /// stand früher für jede Größe dieselbe Regel: unter der Hälfte ist es ein + /// Abriss, darüber in Ordnung. Eine bei sechzig Prozent abgerissene Datei + /// kam damit durch, bekam einen Haken — und brach beim Abspielen + /// mittendrin ab. + /// + /// Die Hälfte-Regel war nie für diesen Fall gedacht, sondern für die + /// **hochgerechnete** Größe der Transcoder-Stufen (Bitrate × Dauer). Dort + /// ist Abweichung normal. Hat der Server die Größe dagegen angekündigt, + /// ist jedes fehlende Byte ein Abriss, und zwar genau einer. + static func implausibility(written: Int64, expected: Int64, announced: Bool) -> String? { // Unter hundert Kilobyte ist keine Videodatei. Dieser Wert liegt // bewusst niedrig: eine sehr kurze Folge kann klein sein, eine // HTML-Fehlerseite ist es immer. @@ -521,9 +595,10 @@ public final class DownloadCoordinator { bundle: .module ) } - // Zehn Prozent Abweichung nach unten sind noch erklärbar — der - // Transcoder kennt die Endgröße vorab nicht genau. Die Hälfte nicht. - if expected > 0, Double(written) < Double(expected) * 0.5 { + // Angekündigt: auf das Byte genau. Hochgerechnet: die Hälfte, denn der + // Transcoder kennt die Endgröße vorab nicht. + let untergrenze = announced ? Double(expected) : Double(expected) * 0.5 + if expected > 0, Double(written) < untergrenze { return String( localized: "downloads.failed.truncated \(written.formatted(.byteCount(style: .file))) \(expected.formatted(.byteCount(style: .file)))", bundle: .module @@ -549,11 +624,49 @@ public final class DownloadCoordinator { return } + // **Den Wiederaufnahme-Datensatz retten.** + // + // Hier lag der zweite Teil des Fehlers. Es gab ihn nur beim Pausieren + // von Hand (`pause(_:)`); nach einem Netzabbruch war er weg, und + // „Fortsetzen" lud eine vierzig Gigabyte große Datei wieder ab Byte + // null — während der Knopf daneben „Fortsetzen" hieß. Eine + // Beschriftung, die der Code nicht halten konnte. + // + // iOS legt den Datensatz dem Fehler bei, wenn die Übertragung + // unterbrochen wurde und fortsetzbar ist. Fehlt er, war es keine + // Unterbrechung, sondern eine Ablehnung — und `resumeInterrupted()` + // unterscheidet die beiden genau daran. + item.resumeData = (error as NSError) + .userInfo[NSURLSessionDownloadTaskResumeData] as? Data + fail(item, message: error.localizedDescription) Task { await startNextIfPossible() } } + /// Nimmt wieder auf, was nur das Netz unterbrochen hat. + /// + /// **Weil niemand sonst merkt, dass das WLAN zurück ist.** Ein + /// gescheiterter Download lag bisher, bis man ihn von Hand antippte — auch + /// dann, wenn die Verbindung seit einer Stunde wieder stand. + /// + /// Nur Einträge mit Wiederaufnahme-Datensatz: den gibt es bei einer + /// unterbrochenen Übertragung, nicht bei einer abgelehnten. Ein Titel, den + /// der Server mit „nicht gefunden" beantwortet, versucht sich damit nicht + /// im Kreis. + public func resumeInterrupted() async { + let unterbrochen = items.filter { $0.state == .failed && $0.resumeData != nil } + guard !unterbrochen.isEmpty else { return } + + for item in unterbrochen { + item.state = .queued + item.failureMessage = nil + } + save() + await startNextIfPossible() + } + private func fail(_ item: DownloadItem, message: String) { + forgetTransfer(item.ratingKey) item.state = .failed item.failureMessage = message save() diff --git a/Packages/FlixyKit/Sources/FlixyDownloads/Model/DownloadItem.swift b/Packages/FlixyKit/Sources/FlixyDownloads/Model/DownloadItem.swift index afe93f1..ab0c071 100644 --- a/Packages/FlixyKit/Sources/FlixyDownloads/Model/DownloadItem.swift +++ b/Packages/FlixyKit/Sources/FlixyDownloads/Model/DownloadItem.swift @@ -29,6 +29,17 @@ public final class DownloadItem { public var totalBytes: Int64 public var receivedBytes: Int64 + /// Ob `totalBytes` die **genaue** Größe ist oder eine Schätzung. + /// + /// **Daran hängt, wann eine Datei als abgerissen gilt.** Beim Original + /// nennt der Server die Dateigröße in den Metadaten — da ist jedes + /// fehlende Byte ein Abriss. Bei den umgerechneten Stufen rechnet die App + /// die Größe aus Bitrate und Dauer hoch, und die weicht von Haus aus ab; + /// dort wäre dieselbe Strenge falsch. + /// + /// Ohne diese Unterscheidung galt für beides die Hälfte-Regel — und eine + /// bei sechzig Prozent abgerissene Datei bekam einen Haken. + public var expectedIsExact: Bool = false /// Dateiname im Download-Ordner. Nicht der volle Pfad: der Container der /// App bekommt bei jedem Systemupdate eine neue Kennung, ein gespeicherter @@ -69,7 +80,8 @@ public final class DownloadItem { seasonIndex: Int? = nil, episodeIndex: Int? = nil, durationMilliseconds: Int? = nil, - offlineMediaData: Data? = nil + offlineMediaData: Data? = nil, + expectedIsExact: Bool = false ) { self.ratingKey = ratingKey self.title = title @@ -85,6 +97,7 @@ public final class DownloadItem { self.episodeIndex = episodeIndex self.durationMilliseconds = durationMilliseconds self.offlineMediaData = offlineMediaData + self.expectedIsExact = expectedIsExact self.addedAt = Date() } diff --git a/Packages/FlixyKit/Tests/FlixyDownloadsTests/DownloadCoordinatorTests.swift b/Packages/FlixyKit/Tests/FlixyDownloadsTests/DownloadCoordinatorTests.swift index e83b17f..2da77d5 100644 --- a/Packages/FlixyKit/Tests/FlixyDownloadsTests/DownloadCoordinatorTests.swift +++ b/Packages/FlixyKit/Tests/FlixyDownloadsTests/DownloadCoordinatorTests.swift @@ -55,6 +55,22 @@ private func temporaryRoot() -> URL { @Suite("Download-Warteschlange") struct DownloadCoordinatorTests { + @Test("Ob die Größe genau ist, kommt am Eintrag an") + func carriesExactSizeFlag() throws { + let coordinator = try makeCoordinator(root: temporaryRoot()) + + var genau = plan("a", bytes: 20_000_000) + genau.expectedIsExact = true + let eins = try coordinator.enqueue(genau) + #expect(eins.expectedIsExact) + + // Die Voreinstellung ist „geschätzt" — die umgerechneten Stufen + // dürfen abweichen, und wer das Feld vergisst, bekommt die mildere + // Regel statt verworfener Downloads. + let zwei = try coordinator.enqueue(plan("b", bytes: 20_000_000)) + #expect(!zwei.expectedIsExact) + } + @Test("Ein Titel wird angenommen und steht in der Warteschlange") func acceptsFirstDownload() throws { let coordinator = try makeCoordinator(root: temporaryRoot()) diff --git a/Packages/FlixyKit/Tests/FlixyDownloadsTests/DownloadRateTests.swift b/Packages/FlixyKit/Tests/FlixyDownloadsTests/DownloadRateTests.swift new file mode 100644 index 0000000..a7d1259 --- /dev/null +++ b/Packages/FlixyKit/Tests/FlixyDownloadsTests/DownloadRateTests.swift @@ -0,0 +1,118 @@ +import Testing +@testable import FlixyDownloads + +@Suite("Downloadrate und Restzeit") +struct DownloadRateTests { + /// Ein fester Anfangszeitpunkt, von dem aus gerechnet wird. + /// + /// Die Uhr kommt von außen herein — deshalb braucht hier nichts zu warten, + /// und eine Übertragung über eine Viertelstunde ist in Mikrosekunden + /// nachgestellt. + private let start = ContinuousClock.now + + @Test("Vor dem ersten Abstand gibt es keine Zahl") + func noRateFromASinglePoint() { + var rate = DownloadRate() + rate.record(bytes: 0, at: start) + // Null wäre eine Auskunft („es geht nichts durch"), und die stimmt + // nicht — es ist nur noch nichts gemessen. + #expect(rate.bytesPerSecond == nil) + } + + @Test("Gleichmäßiger Zufluss ergibt eine ruhige Zahl") + func steadyFlowIsSteady() { + var rate = DownloadRate() + for sekunde in 0...5 { + rate.record(bytes: Int64(sekunde) * 1_000_000, at: start.advanced(by: .seconds(sekunde))) + } + // Auf das Byte genau: die Glättung zieht einen Messwert nur dann in + // eine Richtung, wenn er vom bisherigen abweicht. + #expect(rate.bytesPerSecond == 1_000_000) + } + + @Test("Ein Ausreißer verschiebt die Zahl, aber reißt sie nicht mit") + func outlierIsDamped() { + var rate = DownloadRate() + for sekunde in 0...5 { + rate.record(bytes: Int64(sekunde) * 1_000_000, at: start.advanced(by: .seconds(sekunde))) + } + // Eine einzelne Sekunde mit dem Zehnfachen. + rate.record(bytes: 15_000_000, at: start.advanced(by: .seconds(6))) + + let gemessen = try! #require(rate.bytesPerSecond) + // Roh wären es zehn Millionen. Geglättet liegt die Zahl dazwischen — + // sie folgt, statt zu springen. + #expect(gemessen > 1_000_000) + #expect(gemessen < 10_000_000) + } + + @Test("Messpunkte dichter als eine halbe Sekunde zählen nicht") + func samplesTooCloseAreIgnored() { + var rate = DownloadRate() + rate.record(bytes: 0, at: start) + rate.record(bytes: 50_000, at: start.advanced(by: .milliseconds(100))) + rate.record(bytes: 100_000, at: start.advanced(by: .milliseconds(200))) + #expect(rate.bytesPerSecond == nil) + + // Und der Bezugspunkt ist dabei nicht weitergerückt: gemessen wird + // über die volle Sekunde seit dem ersten Punkt, nicht über die letzten + // achthundert Millisekunden. + rate.record(bytes: 1_000_000, at: start.advanced(by: .seconds(1))) + #expect(rate.bytesPerSecond == 1_000_000) + } + + @Test("Eine lange Stille verfälscht die Rate nicht") + func longGapResetsTheMeasurement() { + var rate = DownloadRate() + rate.record(bytes: 0, at: start) + rate.record(bytes: 1_000_000, at: start.advanced(by: .seconds(1))) + #expect(rate.bytesPerSecond == 1_000_000) + + // Eine halbe Minute ohne Rückmeldung heißt nicht „langsam", sondern + // „stand still": App angehalten, Gerät gesperrt, Netz weg. Daraus eine + // Rate zu rechnen ergäbe dreißig Kilobyte je Sekunde und eine Restzeit + // von Stunden. + rate.record(bytes: 1_030_000, at: start.advanced(by: .seconds(31))) + #expect(rate.bytesPerSecond == nil) + } + + @Test("Fängt die Übertragung von vorn an, gilt keine alte Zahl") + func restartFromZeroDiscardsTheRate() { + var rate = DownloadRate() + rate.record(bytes: 0, at: start) + rate.record(bytes: 1_000_000, at: start.advanced(by: .seconds(1))) + #expect(rate.bytesPerSecond == 1_000_000) + + // Genau das passiert, wenn ein gescheiterter Download ohne + // Wiederaufnahme-Datensatz neu beginnt. + rate.record(bytes: 0, at: start.advanced(by: .seconds(2))) + #expect(rate.bytesPerSecond == nil) + } + + @Test("Die Restzeit folgt aus Rate und Rest") + func remainingFollowsFromRate() { + var rate = DownloadRate() + rate.record(bytes: 0, at: start) + rate.record(bytes: 1_000_000, at: start.advanced(by: .seconds(1))) + + let rest = try! #require(rate.remaining(received: 1_000_000, total: 11_000_000)) + #expect(rest == .seconds(10)) + } + + @Test("Ohne Rate keine Restzeit") + func noRemainingWithoutRate() { + var rate = DownloadRate() + rate.record(bytes: 0, at: start) + // Eine Restzeit ohne Messung wäre geraten — und geraten sieht man ihr + // nicht an. + #expect(rate.remaining(received: 0, total: 1_000_000) == nil) + } + + @Test("Ist alles da, gibt es nichts mehr zu schätzen") + func noRemainingWhenComplete() { + var rate = DownloadRate() + rate.record(bytes: 0, at: start) + rate.record(bytes: 1_000_000, at: start.advanced(by: .seconds(1))) + #expect(rate.remaining(received: 1_000_000, total: 1_000_000) == nil) + } +} diff --git a/Packages/FlixyKit/Tests/FlixyDownloadsTests/DownloadRulesTests.swift b/Packages/FlixyKit/Tests/FlixyDownloadsTests/DownloadRulesTests.swift index fd36040..1e2a71f 100644 --- a/Packages/FlixyKit/Tests/FlixyDownloadsTests/DownloadRulesTests.swift +++ b/Packages/FlixyKit/Tests/FlixyDownloadsTests/DownloadRulesTests.swift @@ -163,36 +163,78 @@ struct DownloadPlausibilityTests { // „fertig". Angekommen war eine HTML-Fehlerseite, und weil die // empfangene Größe auch als erwartete notiert wurde, stand die // Anzeige auf hundert Prozent. - #expect(DownloadCoordinator.implausibility(written: 2_048, expected: 873_000_000) != nil) + #expect(DownloadCoordinator.implausibility( + written: 2_048, expected: 873_000_000, announced: true + ) != nil) } @Test("Auch ohne angekündigte Größe fällt eine Fehlerseite auf") func tinyFileWithoutExpectation() { // Beim umgerechneten Download kennt der Server die Endgröße vorab // nicht — die Untergrenze muss deshalb allein tragen. - #expect(DownloadCoordinator.implausibility(written: 900, expected: 0) != nil) + #expect(DownloadCoordinator.implausibility( + written: 900, expected: 0, announced: false + ) != nil) } @Test("Eine abgebrochene Übertragung fällt auf") func truncatedTransferIsRejected() { - #expect(DownloadCoordinator.implausibility(written: 200_000_000, expected: 873_000_000) != nil) + #expect(DownloadCoordinator.implausibility( + written: 200_000_000, expected: 873_000_000, announced: false + ) != nil) + } + + @Test("Bei sechzig Prozent einer angekündigten Größe ist die Datei kaputt") + func truncatedAtSixtyPercentIsRejected() { + // **Der Fehler, der diese Unterscheidung nötig gemacht hat.** Hier + // galt dieselbe Hälfte-Regel wie für geschätzte Größen, und eine bei + // sechzig Prozent abgerissene Datei kam durch: Haken dran, und beim + // Abspielen brach sie mittendrin ab. Gemeldet wurde das als + // „plötzlich war er fertig". + #expect(DownloadCoordinator.implausibility( + written: 1_800_000_000, expected: 3_000_000_000, announced: true + ) != nil) + } + + @Test("Dieselben sechzig Prozent einer Schätzung gehen durch") + func truncatedAtSixtyPercentOfEstimatePasses() { + // Gleiche Zahlen, andere Herkunft: hochgerechnet aus Bitrate und + // Dauer. Dort ist eine Abweichung nach unten normal, und streng zu + // prüfen hieße, gültige Downloads zu verwerfen. + #expect(DownloadCoordinator.implausibility( + written: 1_800_000_000, expected: 3_000_000_000, announced: false + ) == nil) } @Test("Eine vollständige Datei geht durch") func completeFilePasses() { - #expect(DownloadCoordinator.implausibility(written: 873_000_000, expected: 873_000_000) == nil) + #expect(DownloadCoordinator.implausibility( + written: 873_000_000, expected: 873_000_000, announced: true + ) == nil) } - @Test("Kleinere Abweichungen sind erlaubt") + @Test("Kleinere Abweichungen sind erlaubt, solange die Größe geschätzt ist") func smallDeviationPasses() { // Der Transcoder arbeitet mit variabler Bitrate und trifft die - // geschätzte Größe nie genau. Zu streng zu prüfen hieße, gültige - // Downloads zu verwerfen. - #expect(DownloadCoordinator.implausibility(written: 800_000_000, expected: 873_000_000) == nil) + // geschätzte Größe nie genau. + #expect(DownloadCoordinator.implausibility( + written: 800_000_000, expected: 873_000_000, announced: false + ) == nil) + } + + @Test("Dieselbe Abweichung bei angekündigter Größe ist ein Abriss") + func smallDeviationOfAnnouncedSizeIsRejected() { + // Hat der Server die Größe genannt, gibt es keine erklärbare + // Abweichung: was fehlt, fehlt. + #expect(DownloadCoordinator.implausibility( + written: 800_000_000, expected: 873_000_000, announced: true + ) != nil) } @Test("Ohne Erwartung geht eine normale Datei durch") func noExpectationPasses() { - #expect(DownloadCoordinator.implausibility(written: 500_000_000, expected: 0) == nil) + #expect(DownloadCoordinator.implausibility( + written: 500_000_000, expected: 0, announced: false + ) == nil) } } diff --git a/Tools/JellyfinStub/server.py b/Tools/JellyfinStub/server.py index 9f40233..0c578b5 100644 --- a/Tools/JellyfinStub/server.py +++ b/Tools/JellyfinStub/server.py @@ -1,6 +1,6 @@ """Ein Jellyfin-Attrappenserver — gerade so viel, dass die App anmelden und stöbern kann. Nur zum Ansehen der Oberfläche; kein echter Server wird berührt.""" -import json, os, struct, zlib, hashlib +import json, os, struct, time, zlib, hashlib from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from urllib.parse import urlparse, parse_qs @@ -10,6 +10,28 @@ SAMPLE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sample.mp4") # Was an Wiedergabemeldungen hereinkam — damit sich prüfen lässt, ob die App # den Fortschritt tatsächlich meldet. REPORTS = [] +# Die kuenstliche grosse Datei — und was mit ihr passieren soll. +# +# **Ohne sie ist ein abgebrochener Download nicht nachstellbar**, und genau +# daran ist ein Fehler jahrelang vorbeigekommen: eine bei sechzig Prozent +# abgerissene Datei galt als fertig. Mit der Beispieldatei (28 KB) laesst sich +# das nicht zeigen — sie ist in einem Rutsch da. +# +# groesse wie gross die Datei sich gibt (Content-Length) +# abbruch_bei nach wie vielen Bytes die Verbindung geschlossen wird, 0 = nie +# tempo Bytes je Sekunde, 0 = so schnell die Leitung hergibt +# anfragen jede Anfrage mit ihrem Bereich — daran ist ablesbar, ob +# fortgesetzt oder von vorn begonnen wurde +# ohne_laenge keine Content-Length ankuendigen. Dann gilt fuer den Client +# das Ende der Verbindung als Ende der Datei — eine abgerissene +# Uebertragung sieht damit wie eine vollstaendige aus. Genau +# dieser Fall macht Dateien kaputt, ohne dass irgendwo ein +# Fehler auftaucht. +TRANSFER = {"groesse": 20_000_000, "abbruch_bei": 0, "tempo": 0, + "ohne_laenge": 0, "anfragen": []} +# Dieselbe Drossel fuer die Beispieldatei. Damit laesst sich der Player lange +# genug offen halten, um eine Geste zu messen. +VIDEO_TEMPO = {"bytes_je_sekunde": 0} # Die Benutzerdatenblaetter, je Titel. # # Ohne sie laesst sich „Aus Weiterschauen entfernen" gar nicht pruefen: der @@ -49,6 +71,8 @@ TITLES = [ # sieht ein wirkungsloser Filter wie ein wirksamer aus. ("x1", "Zwischen den Zeilen", "Movie", "Nur in Gemischtes zu finden."), ("x2", "Randnotiz", "Movie", "Ebenfalls nur in Gemischtes."), + # Die kuenstliche grosse Datei. Nicht zum Abspielen — darin stehen Nullen. + ("big1", "Die grosse Datei", "Movie", "Zum Pruefen von Abbruch und Fortsetzen."), ] # Welche Titel in welcher Mediathek liegen. Was hier nicht steht, liegt # ueberall — so war es vorher, und fuer die uebrigen Pruefungen reicht das. @@ -72,7 +96,10 @@ def item(key, name, kind, overview, *, resume=None, series=None): "MediaSources": [{ "Id": "src-" + key, "Container": "mp4", - "Size": 28135, + # Die angekuendigte Groesse. Sie landet als `expectedBytes` im + # Download und entscheidet mit darueber, ob eine Datei als + # abgerissen gilt. + "Size": TRANSFER["groesse"] if key == "big1" else 28135, "Bitrate": 1_200_000, "RunTimeTicks": 10 * 10_000_000, "SupportsDirectPlay": True, @@ -251,7 +278,11 @@ class Handler(BaseHTTPRequestHandler): # Sowohl `/stream` (abspielen) als auch `/stream.mp4` (herunterladen). # Umgerechnet wird hier natürlich nichts — es kommt dieselbe Datei. if p.startswith("/Videos/") and ("/stream" in p): + if p.split("/")[2] == "big1": + return self.serve_transfer() return self.serve_file(SAMPLE, "video/mp4") + if p == "/__transfer": + return self.reply(TRANSFER) if p.startswith(f"/Users/{USER_ID}/Items/") and p.count("/") == 4: key = p.rsplit("/", 1)[-1] match = next((t for t in TITLES if t[0] == key), None) @@ -290,6 +321,74 @@ class Handler(BaseHTTPRequestHandler): return self.reply(page([{"Id": "g1", "Name": "Drama", "Type": "Genre"}])) return self.reply(page([])) + def serve_transfer(self): + """Eine beliebig grosse Datei, die auf Wunsch mittendrin abreisst. + + Sie besteht aus Nullen — geprueft wird die Uebertragung, nicht der + Inhalt. Bereichsanfragen beantwortet sie wie ein echter Server, und + genau daran ist ablesbar, ob die App fortsetzt oder von vorn beginnt: + steht im Mitschnitt `bytes=12000000-`, hat sie fortgesetzt. + """ + gesamt = TRANSFER["groesse"] + bereich = self.headers.get("Range") + start, status = 0, 200 + if bereich and bereich.startswith("bytes="): + start = int(bereich[6:].split("-")[0] or 0) + status = 206 + + self.send_response(status) + self.send_header("Content-Type", "video/mp4") + self.send_header("Accept-Ranges", "bytes") + # **Ohne Pruefmarke kein Fortsetzen.** `URLSession` legt dem + # Wiederaufnahme-Versuch ein `If-Range` bei; fehlt dem Server eine + # Marke, mit der er das beantworten koennte, faengt die Uebertragung + # wieder bei null an — ganz gleich, wie gut die App ihren Datensatz + # aufbewahrt hat. Gemessen: ohne diese zwei Zeilen kam die zweite + # Anfrage ohne `Range` und holte alle 20 MB noch einmal. + self.send_header("ETag", f'"big1-{TRANSFER["groesse"]}"') + self.send_header("Last-Modified", "Mon, 01 Sep 2025 12:00:00 GMT") + if TRANSFER["ohne_laenge"]: + # Ohne Laenge und ohne Keep-Alive: das Ende der Verbindung ist das + # Ende der Datei. So verhaelt sich ein Transcoder, der die Endgroesse + # vorab nicht kennt. + self.protocol_version = "HTTP/1.0" + self.send_header("Connection", "close") + self.close_connection = True + else: + self.send_header("Content-Length", str(gesamt - start)) + if status == 206: + self.send_header("Content-Range", f"bytes {start}-{gesamt - 1}/{gesamt}") + self.end_headers() + + grenze = TRANSFER["abbruch_bei"] + tempo = TRANSFER["tempo"] + block = bytes(64 * 1024) + stelle, geliefert = start, 0 + while stelle < gesamt: + if grenze and stelle >= grenze: + break + rest = gesamt - stelle + if grenze: + rest = min(rest, grenze - stelle) + menge = min(len(block), rest) + try: + self.wfile.write(block[:menge]) + self.wfile.flush() + except (BrokenPipeError, ConnectionResetError): + break + stelle += menge + geliefert += menge + if tempo: + time.sleep(menge / tempo) + + TRANSFER["anfragen"].append( + {"bereich": bereich, "ab": start, "geliefert": geliefert, + "abgerissen": bool(grenze) and stelle < gesamt} + ) + # Angekuendigt war mehr, als gekommen ist — die Verbindung muss zu. + if stelle < gesamt: + self.close_connection = True + def serve_file(self, path, content_type): """Mit Bereichsanfragen — AVPlayer holt eine Datei nie am Stück.""" with open(path, "rb") as handle: @@ -311,7 +410,19 @@ class Handler(BaseHTTPRequestHandler): if status == 206: self.send_header("Content-Range", f"bytes {start}-{end}/{len(data)}") self.end_headers() - self.wfile.write(body) + + tempo = VIDEO_TEMPO["bytes_je_sekunde"] + if not tempo: + return self.wfile.write(body) + # Gedrosselt: nur so bleibt der Player lange genug offen, um eine + # Geste daran zu messen. Die Beispieldatei ist sonst sofort da. + for stelle in range(0, len(body), 4096): + try: + self.wfile.write(body[stelle:stelle + 4096]) + self.wfile.flush() + except (BrokenPipeError, ConnectionResetError): + return + time.sleep(4096 / tempo) def do_DELETE(self): REPORTS.append(("encoding-stopped", self.path)) @@ -330,6 +441,19 @@ class Handler(BaseHTTPRequestHandler): if url.path.startswith("/Sessions/Playing"): REPORTS.append((url.path, body.decode() if body else "")) return self.reply({}) + # Die Uebertragung steuern: Groesse, Abbruchstelle, Tempo — und die + # Drossel fuer die Beispieldatei. Alles zur Laufzeit, damit sich + # „abreissen lassen, dann fortsetzen" in einem Lauf pruefen laesst. + if url.path == "/__transfer": + neu = json.loads(body or b"{}") + for feld in ("groesse", "abbruch_bei", "tempo", "ohne_laenge"): + if feld in neu: + TRANSFER[feld] = int(neu[feld]) + if neu.get("vergessen"): + TRANSFER["anfragen"].clear() + if "video_tempo" in neu: + VIDEO_TEMPO["bytes_je_sekunde"] = int(neu["video_tempo"]) + return self.reply(TRANSFER) schluessel = userdata_key(url.path) if schluessel is not None: try: